diff --git a/libs/flecs/flecs.c b/libs/flecs/flecs.c index 4405eb6..b946f04 100644 --- a/libs/flecs/flecs.c +++ b/libs/flecs/flecs.c @@ -163,6 +163,10 @@ int32_t flecs_entity_index_not_alive_count( void flecs_entity_index_clear( ecs_entity_index_t *index); +/* Shrink entity index */ +void flecs_entity_index_shrink( + ecs_entity_index_t *index); + /* Return number of alive entities in index */ const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index); @@ -463,16 +467,27 @@ typedef struct ecs_table__t { uint64_t hash; /* Type hash */ int32_t lock; /* Prevents modifications */ int32_t traversable_count; /* Traversable relationship targets in table */ + uint16_t generation; /* Used for table cleanup */ int16_t record_count; /* Table record count including wildcards */ - - struct ecs_table_record_t *records; /* Array with table records */ - ecs_hashmap_t *name_index; /* Cached pointer to name index */ - ecs_bitset_t *bs_columns; /* Bitset columns */ int16_t bs_count; int16_t bs_offset; - int16_t ft_offset; + ecs_bitset_t *bs_columns; /* Bitset columns */ + + struct ecs_table_record_t *records; /* Array with table records */ + ecs_hashmap_t *name_index; /* Cached pointer to name index */ + +#ifdef FLECS_DEBUG_INFO + /* Fields used for debug visualization */ + struct { + ecs_world_t *world; + ecs_entity_t id; + } parent; /* Parent. Include world so it can be cast + * to a flecs::entity. */ + int16_t name_column; /* Column with entity name */ + int16_t doc_name_column; /* Column with entity doc name */ +#endif } ecs_table__t; /** Table column */ @@ -538,21 +553,11 @@ 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); - /* Add a new entry to the table for the specified entity */ int32_t flecs_table_append( ecs_world_t *world, @@ -674,6 +679,12 @@ extern const ecs_entity_t EcsFlag; #define ECS_MAX_JOBS_PER_WORKER (16) #define ECS_MAX_DEFER_STACK (8) +/* The bitmask used when determining the table version array index */ +#define ECS_TABLE_VERSION_ARRAY_BITMASK (0xff) + +/* The number of table versions to split tables across */ +#define ECS_TABLE_VERSION_ARRAY_SIZE (ECS_TABLE_VERSION_ARRAY_BITMASK + 1) + /* Magic number for a flecs object */ #define ECS_OBJECT_MAGIC (0x6563736f) @@ -742,6 +753,7 @@ typedef struct ecs_world_allocators_t { ecs_block_allocator_t graph_edge_lo; ecs_block_allocator_t graph_edge; ecs_block_allocator_t id_record; + ecs_block_allocator_t pair_id_record; ecs_block_allocator_t id_record_chunk; ecs_block_allocator_t table_diff; ecs_block_allocator_t sparse_chunk; @@ -762,6 +774,7 @@ typedef struct ecs_stage_allocators_t { /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { + EcsCmdNew, EcsCmdClone, EcsCmdBulkNew, EcsCmdAdd, @@ -803,7 +816,7 @@ typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ - ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ + ecs_component_record_t *cdr; /* component record (only for set/mut/emplace) */ ecs_cmd_entry_t *entry; ecs_entity_t entity; /* Entity id */ @@ -815,13 +828,6 @@ typedef struct ecs_cmd_t { ecs_entity_t system; /* System that enqueued the command */ } ecs_cmd_t; -/* Data structures that store the command queue */ -typedef struct ecs_commands_t { - ecs_vec_t queue; - ecs_stack_t stack; /* Temp memory used by deferred commands */ - ecs_sparse_t entries; /* - command batching */ -} ecs_commands_t; - /** Callback used to capture commands of a frame */ typedef void (*ecs_on_commands_action_t)( const ecs_stage_t *stage, @@ -897,7 +903,7 @@ typedef struct ecs_monitor_set_t { /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { - ecs_id_record_t *idr; + ecs_component_record_t *cdr; ecs_id_t id; ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; @@ -942,18 +948,18 @@ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ - ecs_id_record_t **id_index_lo; - ecs_map_t id_index_hi; /* map */ + ecs_component_record_t **id_index_lo; + ecs_map_t id_index_hi; /* map */ ecs_map_t type_info; /* map */ /* -- Cached handle to id records -- */ - ecs_id_record_t *idr_wildcard; - ecs_id_record_t *idr_wildcard_wildcard; - ecs_id_record_t *idr_any; - ecs_id_record_t *idr_isa_wildcard; - ecs_id_record_t *idr_childof_0; - ecs_id_record_t *idr_childof_wildcard; - ecs_id_record_t *idr_identifier_name; + ecs_component_record_t *idr_wildcard; + ecs_component_record_t *idr_wildcard_wildcard; + ecs_component_record_t *idr_any; + ecs_component_record_t *idr_isa_wildcard; + ecs_component_record_t *idr_childof_0; + ecs_component_record_t *idr_childof_wildcard; + ecs_component_record_t *idr_identifier_name; /* -- Mixins -- */ ecs_world_t *self; @@ -962,6 +968,10 @@ struct ecs_world_t { /* Unique id per generated event used to prevent duplicate notifications */ int32_t event_id; + /* Array of table versions used with component refs to determine if the + * cached pointer is still valid. */ + uint32_t table_version[ECS_TABLE_VERSION_ARRAY_SIZE]; + /* Is entity range checking enabled? */ bool range_check_enabled; @@ -1072,36 +1082,36 @@ void* ecs_table_cache_get( #define flecs_table_cache_count(cache) (cache)->tables.count bool flecs_table_cache_iter( - ecs_table_cache_t *cache, + const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, + const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_all_iter( - ecs_table_cache_t *cache, + const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); -ecs_table_cache_hdr_t* flecs_table_cache_next_( +const 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_CONST_CAST(T*, flecs_table_cache_next_(it))) #endif /** - * @file storage/id_index.h - * @brief Index for looking up tables by (component) id. + * @file storage/component_index.h + * @brief Index for (amongst others) looking up tables by component id. */ -#ifndef FLECS_ID_INDEX_H -#define FLECS_ID_INDEX_H +#ifndef FLECS_COMPONENT_INDEX_H +#define FLECS_COMPONENT_INDEX_H /* Linked list of id records */ typedef struct ecs_id_record_elem_t { - struct ecs_id_record_t *prev, *next; + struct ecs_component_record_t *prev, *next; } ecs_id_record_elem_t; typedef struct ecs_reachable_elem_t { @@ -1120,117 +1130,120 @@ typedef struct ecs_reachable_cache_t { ecs_vec_t ids; /* vec */ } ecs_reachable_cache_t; +/* Component index data that just applies to pairs */ +typedef struct ecs_pair_id_record_t { + /* Name lookup index (currently only used for ChildOf pairs) */ + ecs_hashmap_t *name_index; + + /* Lists for all id records that match a pair wildcard. The wildcard id + * record is at the head of the list. */ + ecs_id_record_elem_t first; /* (R, *) */ + ecs_id_record_elem_t second; /* (*, T) */ + ecs_id_record_elem_t trav; /* (*, T) with only traversable relationships */ + + /* Parent component record. For pair records the parent is the (R, *) record. */ + ecs_component_record_t *parent; + + /* Cache for finding components that are reachable through a relationship */ + ecs_reachable_cache_t reachable; +} ecs_pair_id_record_t; + /* Payload for id index which contains all data structures for an id. */ -struct ecs_id_record_t { +struct ecs_component_record_t { /* Cache with all tables that contain the id. Must be first member. */ ecs_table_cache_t cache; /* table_cache */ - /* Id of record */ + /* Component id of record */ ecs_id_t id; /* Flags for id */ ecs_flags32_t flags; +#ifdef FLECS_DEBUG_INFO + /* String representation of id (used for debug visualization) */ + char *str; +#endif + /* Cached pointer to type info for id, if id contains data. */ const ecs_type_info_t *type_info; - /* Name lookup index (currently only used for ChildOf pairs) */ - ecs_hashmap_t *name_index; - /* Storage for sparse components or union relationships */ void *sparse; - /* Lists for all id records that match a pair wildcard. The wildcard id - * record is at the head of the list. */ - ecs_id_record_elem_t first; /* (R, *) */ - ecs_id_record_elem_t second; /* (*, O) */ - ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ - - /* Parent id record. For pair records the parent is the (R, *) record. */ - ecs_id_record_t *parent; + /* Pair data */ + ecs_pair_id_record_t *pair; /* Refcount */ int32_t refcount; - /* Keep alive count. This count must be 0 when the id record is deleted. If + /* Keep alive count. This count must be 0 when the component record is deleted. If * it is not 0, an application attempted to delete an id that was still * queried for. */ int32_t keep_alive; - - /* Cache for finding components that are reachable through a relationship */ - ecs_reachable_cache_t reachable; }; -/* Get id record for id */ -ecs_id_record_t* flecs_id_record_get( - const ecs_world_t *world, - ecs_id_t id); +/* Bootstrap cached id records */ +void flecs_components_init( + ecs_world_t *world); -/* Ensure id record for id */ -ecs_id_record_t* flecs_id_record_ensure( +/* Cleanup all id records in world */ +void flecs_components_fini( + ecs_world_t *world); + +/* Ensure component record for id */ +ecs_component_record_t* flecs_components_ensure( ecs_world_t *world, ecs_id_t id); -/* Increase refcount of id record */ -void flecs_id_record_claim( +/* Increase refcount of component record */ +void flecs_component_claim( ecs_world_t *world, - ecs_id_record_t *idr); + ecs_component_record_t *cdr); -/* Decrease refcount of id record, delete if 0 */ -int32_t flecs_id_record_release( +/* Decrease refcount of component record, delete if 0 */ +int32_t flecs_component_release( ecs_world_t *world, - ecs_id_record_t *idr); + ecs_component_record_t *cdr); -/* Release all empty tables in id record */ -void flecs_id_record_release_tables( +/* Release all empty tables in component record */ +void flecs_component_release_tables( ecs_world_t *world, - ecs_id_record_t *idr); + ecs_component_record_t *cdr); -/* Set (component) type info for id record */ -bool flecs_id_record_set_type_info( +/* Set (component) type info for component record */ +bool flecs_component_set_type_info( ecs_world_t *world, - ecs_id_record_t *idr, + ecs_component_record_t *cdr, const ecs_type_info_t *ti); -/* Ensure id record has name index */ -ecs_hashmap_t* flecs_id_name_index_ensure( - ecs_world_t *world, - ecs_id_t id); +/* Return next (R, *) record */ +ecs_component_record_t* flecs_component_first_next( + ecs_component_record_t *cdr); -ecs_hashmap_t* flecs_id_record_name_index_ensure( - ecs_world_t *world, - ecs_id_record_t *idr); +/* Return next (*, T) record */ +ecs_component_record_t* flecs_component_second_next( + ecs_component_record_t *cdr); -/* Get name index for id record */ -ecs_hashmap_t* flecs_id_name_index_get( - const ecs_world_t *world, - ecs_id_t id); +/* Return next traversable (*, T) record */ +ecs_component_record_t* flecs_component_trav_next( + ecs_component_record_t *cdr); -/* Find table record for id */ -ecs_table_record_t* flecs_table_record_get( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id); +/* Ensure name index for component record */ +ecs_hashmap_t* flecs_component_name_index_ensure( + ecs_world_t *world, + ecs_component_record_t *cdr); -/* Find table record for id record */ -ecs_table_record_t* flecs_id_record_get_table( - const ecs_id_record_t *idr, - const ecs_table_t *table); +/* Get name index for component record */ +ecs_hashmap_t* flecs_component_name_index_get( + const ecs_world_t *world, + ecs_component_record_t *cdr); /* Init sparse storage */ -void flecs_id_record_init_sparse( +void flecs_component_init_sparse( ecs_world_t *world, - ecs_id_record_t *idr); - -/* Bootstrap cached id records */ -void flecs_init_id_records( - ecs_world_t *world); - -/* Cleanup all id records in world */ -void flecs_fini_id_records( - ecs_world_t *world); + ecs_component_record_t *cdr); -/* Return flags for matching id records */ +/* Return flags for matching component records */ ecs_flags32_t flecs_id_flags_get( ecs_world_t *world, ecs_id_t id); @@ -1291,9 +1304,9 @@ extern char *flecs_this_name_array; /* -- Instruction kinds -- */ typedef enum { + EcsQueryAll, /* Yield all tables */ EcsQueryAnd, /* And operator: find or match id against variable source */ EcsQueryAndAny, /* And operator with support for matching Any src/id */ - EcsQueryOnlyAny, /* Dedicated instruction for _ queries where the src is unknown */ EcsQueryTriv, /* Trivial search (batches multiple terms) */ EcsQueryCache, /* Cached search */ EcsQueryIsCache, /* Cached search for queries that are entirely cached */ @@ -1375,9 +1388,15 @@ typedef struct ecs_query_op_t { ecs_flags64_t written; /* Bitset with variables written by op */ } ecs_query_op_t; - /* And context */ +/* All context */ typedef struct { - ecs_id_record_t *idr; + int32_t cur; + ecs_table_record_t dummy_tr; +} ecs_query_all_ctx_t; + +/* And context */ +typedef struct { + ecs_component_record_t *cdr; ecs_table_cache_iter_t it; int16_t column; int16_t remaining; @@ -1385,7 +1404,7 @@ typedef struct { /* Union context */ typedef struct { - ecs_id_record_t *idr; + ecs_component_record_t *cdr; ecs_table_range_t range; ecs_map_iter_t tgt_iter; ecs_entity_t cur; @@ -1434,8 +1453,8 @@ typedef struct { ecs_entity_t trav; ecs_id_t with; ecs_id_t matched; - ecs_id_record_t *idr_with; - ecs_id_record_t *idr_trav; + ecs_component_record_t *idr_with; + ecs_component_record_t *idr_trav; ecs_trav_down_t *down; int32_t cache_elem; ecs_trav_up_cache_t cache; @@ -1445,13 +1464,13 @@ typedef struct { * traversal iterates and caches the entire tree. */ typedef struct { ecs_entity_t entity; - ecs_id_record_t *idr; + ecs_component_record_t *cdr; const ecs_table_record_t *tr; } ecs_trav_elem_t; typedef struct { ecs_id_t id; - ecs_id_record_t *idr; + ecs_component_record_t *cdr; ecs_vec_t entities; bool up; } ecs_trav_cache_t; @@ -1486,7 +1505,7 @@ typedef struct { /* Ids context */ typedef struct { - ecs_id_record_t *cur; + ecs_component_record_t *cur; } ecs_query_ids_ctx_t; /* Control flow context */ @@ -1532,6 +1551,7 @@ typedef struct { typedef struct ecs_query_op_ctx_t { union { + ecs_query_all_ctx_t all; ecs_query_and_ctx_t and; ecs_query_xfrom_ctx_t xfrom; ecs_query_up_ctx_t up; @@ -1611,6 +1631,10 @@ struct ecs_query_impl_t { char *tokens; /* Buffer with string tokens used by terms */ int32_t *monitor; /* Change monitor for fields with fixed src */ +#ifdef FLECS_DEBUG + ecs_termset_t final_terms; /* Terms that don't use component inheritance */ +#endif + /* Query cache */ struct ecs_query_cache_t *cache; /* Cache, if query contains cached terms */ @@ -1638,6 +1662,7 @@ struct ecs_query_cache_table_match_t { const ecs_table_record_t **trs; /* Information about where to find field in table */ ecs_id_t *ids; /* Resolved (component) ids for current table */ ecs_entity_t *sources; /* Subjects (sources) of ids */ + ecs_table_t **tables; /* Tables for fields with non-$this source */ ecs_termset_t set_fields; /* Fields that are set */ ecs_termset_t up_fields; /* Fields that are matched through traversal */ uint64_t group_id; /* Value used to organize tables in groups */ @@ -1649,7 +1674,7 @@ struct ecs_query_cache_table_match_t { /** Table record type for query table cache. A query only has one per table. */ typedef struct ecs_query_cache_table_t { - ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ + const ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ ecs_query_cache_table_match_t *first; /* List with matches for table */ ecs_query_cache_table_match_t *last; /* Last discovered match for table */ uint64_t table_id; @@ -1677,9 +1702,8 @@ typedef struct ecs_query_cache_event_t { /* Query level block allocators have sizes that depend on query field count */ typedef struct ecs_query_cache_allocators_t { - ecs_block_allocator_t trs; + ecs_block_allocator_t pointers; ecs_block_allocator_t ids; - ecs_block_allocator_t sources; ecs_block_allocator_t monitors; } ecs_query_cache_allocators_t; @@ -1958,7 +1982,7 @@ ecs_trav_down_t* flecs_query_get_down_cache( ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_with, bool self, bool empty); @@ -1973,8 +1997,8 @@ ecs_trav_up_t* flecs_query_get_up_cache( ecs_table_t *table, ecs_id_t with, ecs_entity_t trav, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav); + ecs_component_record_t *idr_with, + ecs_component_record_t *idr_trav); /* Free up traversal cache */ void flecs_query_up_cache_fini( @@ -2515,7 +2539,7 @@ void flecs_observer_fini( void flecs_emit( ecs_world_t *world, ecs_world_t *stage, - ecs_flags64_t set_mask, + ecs_flags64_t *set_mask, ecs_event_desc_t *desc); bool flecs_default_next_callback( @@ -2690,6 +2714,10 @@ bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage); +bool flecs_defer_new( + ecs_stage_t *stage, + ecs_entity_t entity); + bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, @@ -2864,37 +2892,13 @@ void flecs_delete_table( ecs_world_t *world, ecs_table_t *table); -/* Suspend/resume readonly state. To fully support implicit registration of - * components, it should be possible to register components while the world is - * in readonly mode. It is not uncommon that a component is used first from - * within a system, which are often ran while in readonly mode. - * - * Suspending readonly mode is only allowed when the world is not multithreaded. - * When a world is multithreaded, it is not safe to (even temporarily) leave - * readonly mode, so a multithreaded application should always explicitly - * register components in advance. - * - * These operations also suspend deferred mode. - */ -typedef struct ecs_suspend_readonly_state_t { - bool is_readonly; - bool is_deferred; - bool cmd_flushing; - int32_t defer_count; - ecs_entity_t scope; - ecs_entity_t with; - ecs_commands_t cmd_stack[2]; - ecs_commands_t *cmd; - ecs_stage_t *stage; -} ecs_suspend_readonly_state_t; +void flecs_increment_table_version( + ecs_world_t *world, + ecs_table_t *table); -ecs_world_t* flecs_suspend_readonly( +uint32_t flecs_get_table_version( const ecs_world_t *world, - ecs_suspend_readonly_state_t *state); - -void flecs_resume_readonly( - ecs_world_t *world, - ecs_suspend_readonly_state_t *state); + const uint64_t table_id); /* Convenience macro's for world allocator */ #define flecs_walloc(world, size)\ @@ -2924,6 +2928,82 @@ void flecs_resume_readonly( #endif +/** + * @file addons/journal.h + * @brief Journaling addon that logs API functions. + * + * The journaling addon traces API calls. The trace is formatted as runnable + * C code, which allows for (partially) reproducing the behavior of an app + * with the journaling trace. + * + * The journaling addon is disabled by default. Enabling it can have a + * significant impact on performance. + */ + +#ifdef FLECS_JOURNAL + +#ifndef FLECS_LOG +#define FLECS_LOG +#endif + +#ifndef FLECS_JOURNAL_H +#define FLECS_JOURNAL_H + +/** + * @defgroup c_addons_journal Journal + * @ingroup c_addons + * Journaling addon (disabled by default). + * + * + * @{ + */ + +/* Trace when log level is at or higher than level */ +#define FLECS_JOURNAL_LOG_LEVEL (0) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Journaling API, meant to be used by internals. */ + +typedef enum ecs_journal_kind_t { + EcsJournalNew, + EcsJournalMove, + EcsJournalClear, + EcsJournalDelete, + EcsJournalDeleteWith, + EcsJournalRemoveAll, + EcsJournalTableEvents +} ecs_journal_kind_t; + +FLECS_DBG_API +void flecs_journal_begin( + ecs_world_t *world, + ecs_journal_kind_t kind, + ecs_entity_t entity, + ecs_type_t *add, + ecs_type_t *remove); + +FLECS_DBG_API +void flecs_journal_end(void); + +#define flecs_journal(...)\ + flecs_journal_begin(__VA_ARGS__);\ + flecs_journal_end(); + +#ifdef __cplusplus +} +#endif // __cplusplus +/** @} */ +#endif // FLECS_JOURNAL_H +#else +#define flecs_journal_begin(...) +#define flecs_journal_end(...) +#define flecs_journal(...) + +#endif // FLECS_JOURNAL + /** * @file datastructures/name_index.h * @brief Data structure for resolving 64bit keys by string (name). @@ -3083,7 +3163,7 @@ void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, - ecs_id_record_t *table_index, + ecs_component_record_t *table_index, int32_t recur_depth); void flecs_invoke_hook( @@ -3244,7 +3324,7 @@ int32_t flecs_search_w_idr( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t *id_out, - ecs_id_record_t *idr); + ecs_component_record_t *cdr); int32_t flecs_search_relation_w_idr( const ecs_world_t *world, @@ -3256,12 +3336,12 @@ int32_t flecs_search_relation_w_idr( ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, - ecs_id_record_t *idr); + ecs_component_record_t *cdr); bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, - const ecs_id_record_t *idr, + const ecs_component_record_t *cdr, ecs_id_t id); int ecs_term_finalize( @@ -3323,10 +3403,11 @@ void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cdr = flecs_components_get(world, pair); if (evt == EcsOnSet) { - index = flecs_id_name_index_ensure(world, pair); + index = flecs_component_name_index_ensure(world, cdr); } else { - index = flecs_id_name_index_get(world, pair); + index = flecs_component_name_index_get(world, cdr); } } @@ -3457,13 +3538,13 @@ void flecs_assert_relation_unused( static bool flecs_set_id_flag( ecs_world_t *world, - ecs_id_record_t *idr, + ecs_component_record_t *cdr, ecs_flags32_t flag) { - if (!(idr->flags & flag)) { - idr->flags |= flag; + if (!(cdr->flags & flag)) { + cdr->flags |= flag; if (flag == EcsIdIsSparse) { - flecs_id_record_init_sparse(world, idr); + flecs_component_init_sparse(world, cdr); } return true; } @@ -3472,11 +3553,11 @@ bool flecs_set_id_flag( static bool flecs_unset_id_flag( - ecs_id_record_t *idr, + ecs_component_record_t *cdr, ecs_flags32_t flag) { - if ((idr->flags & flag)) { - idr->flags &= ~flag; + if ((cdr->flags & flag)) { + cdr->flags &= ~flag; return true; } return false; @@ -3499,27 +3580,27 @@ void flecs_register_id_flag_for_relation( bool changed = false; if (event == EcsOnAdd) { - ecs_id_record_t *idr; + ecs_component_record_t *cdr; if (!ecs_has_id(world, e, EcsRelationship) && !ecs_has_id(world, e, EcsTarget)) { - idr = flecs_id_record_ensure(world, e); - changed |= flecs_set_id_flag(world, idr, flag); + cdr = flecs_components_ensure(world, e); + changed |= flecs_set_id_flag(world, cdr, flag); } - idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); + cdr = flecs_components_ensure(world, ecs_pair(e, EcsWildcard)); do { - changed |= flecs_set_id_flag(world, idr, flag); - } while ((idr = idr->first.next)); + changed |= flecs_set_id_flag(world, cdr, flag); + } while ((cdr = flecs_component_first_next(cdr))); 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) { + ecs_component_record_t *cdr = flecs_components_get(world, e); + if (cdr) changed |= flecs_unset_id_flag(cdr, not_flag); + cdr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); + if (cdr) { do { - changed |= flecs_unset_id_flag(idr, not_flag); - } while ((idr = idr->first.next)); + changed |= flecs_unset_id_flag(cdr, not_flag); + } while ((cdr = flecs_component_first_next(cdr))); } } @@ -3536,7 +3617,7 @@ void flecs_register_final(ecs_iter_t *it) { 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) { + if (flecs_components_get(world, ecs_pair(EcsIsA, e)) != NULL) { char *e_str = ecs_get_path(world, e); ecs_throw(ECS_ID_IN_USE, "cannot change property 'Final' for '%s': already inherited from", @@ -3559,15 +3640,15 @@ void flecs_register_tag(ecs_iter_t *it) { ecs_entity_t e = it->entities[i]; if (it->event == EcsOnAdd) { - ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); do { - if (idr->type_info != NULL) { + if (cdr->type_info != NULL) { flecs_assert_relation_unused(world, e, EcsPairIsTag); } - idr->type_info = NULL; - } while ((idr = idr->first.next)); + cdr->type_info = NULL; + } while ((cdr = flecs_component_first_next(cdr))); } } } @@ -3812,6 +3893,9 @@ void flecs_bootstrap_builtin( ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_ensure(world, entity); + ecs_assert(record->table == &world->store.root, ECS_INTERNAL_ERROR, NULL); + flecs_table_delete( + world, &world->store.root, ECS_RECORD_TO_ROW(record->row), false); record->table = table; int32_t index = flecs_table_append(world, table, entity, false, false); @@ -3855,22 +3939,25 @@ ecs_table_t* flecs_bootstrap_component_table( { /* 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 | EcsIdOnInstantiateDontInherit | + ecs_component_record_t *cdr = flecs_components_ensure(world, EcsChildOf); + cdr->flags |= EcsIdOnDeleteObjectDelete | EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdTag; /* Initialize id records cached on world */ - world->idr_childof_wildcard = flecs_id_record_ensure(world, + world->idr_childof_wildcard = flecs_components_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete | EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive; - idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); - idr->flags |= EcsIdOnInstantiateDontInherit; + cdr = flecs_components_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); + cdr->flags |= EcsIdOnInstantiateDontInherit; world->idr_identifier_name = - flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); - world->idr_childof_0 = flecs_id_record_ensure(world, + flecs_components_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); + world->idr_childof_0 = flecs_components_ensure(world, ecs_pair(EcsChildOf, 0)); + /* Initialize root table */ + flecs_init_root_table(world); + ecs_id_t ids[] = { ecs_id(EcsComponent), EcsFinal, @@ -3924,6 +4011,34 @@ ecs_table_t* flecs_bootstrap_component_table( return result; } +/* Make entities alive before the root table is initialized */ +static +void flecs_bootstrap_make_alive( + ecs_world_t *world, + ecs_entity_t e) +{ + ecs_table_t *root = &world->store.root; + flecs_entities_make_alive(world, e); + + ecs_record_t *r = flecs_entities_ensure(world, e); + ecs_assert(r->table == NULL || r->table == root, + ECS_INTERNAL_ERROR, NULL); + + if (r->table == NULL) { + r->table = root; + r->row = flecs_ito(uint32_t, root->data.count); + + ecs_vec_t v_entities = ecs_vec_from_entities(root); + ecs_entity_t *array = ecs_vec_append_t(&world->allocator, + &v_entities, ecs_entity_t); + array[0] = e; + + root->data.entities = v_entities.array; + root->data.count = v_entities.count; + root->data.size = v_entities.size; + } +} + static void flecs_bootstrap_entity( ecs_world_t *world, @@ -3935,7 +4050,7 @@ void flecs_bootstrap_entity( ecs_os_strcpy(symbol, "flecs.core."); ecs_os_strcat(symbol, name); - ecs_make_alive(world, id); + flecs_bootstrap_make_alive(world, id); ecs_add_pair(world, id, EcsChildOf, parent); ecs_set_name(world, id, name); ecs_set_symbol(world, id, symbol); @@ -3948,6 +4063,50 @@ void flecs_bootstrap_entity( } } +static +void flecs_bootstrap_sanity_check( + ecs_world_t *world) +{ + (void)world; +#ifdef FLECS_DEBUG + int32_t i, e, count = flecs_sparse_count(&world->store.tables); + int32_t total_count = 0; + + for (i = -1; i < count; i ++) { + ecs_table_t *table; + if (i == -1) { + table = &world->store.root; + } else { + table = flecs_sparse_get_dense_t( + &world->store.tables, ecs_table_t, i); + } + for (e = 0; e < table->data.count; e ++) { + ecs_record_t *r = flecs_entities_get( + world, table->data.entities[e]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_RECORD_TO_ROW(r->row) == e, + ECS_INTERNAL_ERROR, NULL); + total_count ++; + } + } + + count = flecs_entities_count(world); + ecs_assert(count == total_count, ECS_INTERNAL_ERROR, NULL); + + for (i = 1; i < count; i ++) { + ecs_entity_t entity = flecs_entities_ids(world)[i]; + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->dense == (i + 1), ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table->data.entities[ECS_RECORD_TO_ROW(r->row)] == entity, + ECS_INTERNAL_ERROR, NULL); + } +#endif +} + void flecs_bootstrap( ecs_world_t *world) { @@ -3956,32 +4115,35 @@ void flecs_bootstrap( ecs_set_name_prefix(world, "Ecs"); /* Ensure builtin ids are alive */ - ecs_make_alive(world, ecs_id(EcsComponent)); - ecs_make_alive(world, EcsFinal); - ecs_make_alive(world, ecs_id(EcsIdentifier)); - ecs_make_alive(world, EcsName); - ecs_make_alive(world, EcsSymbol); - ecs_make_alive(world, EcsAlias); - ecs_make_alive(world, EcsChildOf); - ecs_make_alive(world, EcsFlecs); - ecs_make_alive(world, EcsFlecsCore); - ecs_make_alive(world, EcsFlecsInternals); - ecs_make_alive(world, EcsOnAdd); - ecs_make_alive(world, EcsOnRemove); - ecs_make_alive(world, EcsOnSet); - ecs_make_alive(world, EcsOnDelete); - ecs_make_alive(world, EcsPanic); - ecs_make_alive(world, EcsFlag); - ecs_make_alive(world, EcsIsA); - ecs_make_alive(world, EcsWildcard); - ecs_make_alive(world, EcsAny); - ecs_make_alive(world, EcsPairIsTag); - ecs_make_alive(world, EcsCanToggle); - ecs_make_alive(world, EcsTrait); - ecs_make_alive(world, EcsRelationship); - ecs_make_alive(world, EcsTarget); - ecs_make_alive(world, EcsSparse); - ecs_make_alive(world, EcsUnion); + flecs_bootstrap_make_alive(world, ecs_id(EcsComponent)); + flecs_bootstrap_make_alive(world, ecs_id(EcsIdentifier)); + flecs_bootstrap_make_alive(world, ecs_id(EcsPoly)); + flecs_bootstrap_make_alive(world, ecs_id(EcsDefaultChildComponent)); + flecs_bootstrap_make_alive(world, EcsFinal); + flecs_bootstrap_make_alive(world, EcsName); + flecs_bootstrap_make_alive(world, EcsSymbol); + flecs_bootstrap_make_alive(world, EcsAlias); + flecs_bootstrap_make_alive(world, EcsChildOf); + flecs_bootstrap_make_alive(world, EcsFlecs); + flecs_bootstrap_make_alive(world, EcsFlecsCore); + flecs_bootstrap_make_alive(world, EcsFlecsInternals); + flecs_bootstrap_make_alive(world, EcsOnAdd); + flecs_bootstrap_make_alive(world, EcsOnRemove); + flecs_bootstrap_make_alive(world, EcsOnSet); + flecs_bootstrap_make_alive(world, EcsOnDelete); + flecs_bootstrap_make_alive(world, EcsPanic); + flecs_bootstrap_make_alive(world, EcsFlag); + flecs_bootstrap_make_alive(world, EcsIsA); + flecs_bootstrap_make_alive(world, EcsWildcard); + flecs_bootstrap_make_alive(world, EcsAny); + flecs_bootstrap_make_alive(world, EcsCanToggle); + flecs_bootstrap_make_alive(world, EcsTrait); + flecs_bootstrap_make_alive(world, EcsRelationship); + flecs_bootstrap_make_alive(world, EcsTarget); + flecs_bootstrap_make_alive(world, EcsSparse); + flecs_bootstrap_make_alive(world, EcsUnion); + flecs_bootstrap_make_alive(world, EcsObserver); + flecs_bootstrap_make_alive(world, EcsPairIsTag); /* Register type information for builtin components */ flecs_type_info_init(world, EcsComponent, { @@ -4011,7 +4173,7 @@ void flecs_bootstrap( }); /* Create and cache often used id records on world */ - flecs_init_id_records(world); + flecs_components_init(world); /* Create table for builtin components. This table temporarily stores the * entities associated with builtin components, until they get moved to @@ -4091,6 +4253,7 @@ void flecs_bootstrap( flecs_bootstrap_trait(world, EcsReflexive); flecs_bootstrap_trait(world, EcsSymmetric); flecs_bootstrap_trait(world, EcsFinal); + flecs_bootstrap_trait(world, EcsInheritable); flecs_bootstrap_trait(world, EcsPairIsTag); flecs_bootstrap_trait(world, EcsExclusive); flecs_bootstrap_trait(world, EcsAcyclic); @@ -4147,7 +4310,7 @@ void flecs_bootstrap( ecs_set_scope(world, EcsFlecsInternals); /* Register observers for components/relationship properties. Most observers - * set flags on an id record when a property is added to a component, which + * set flags on an component record when a property is added to a component, which * allows for quick property testing in various operations. */ ecs_observer(world, { .query.terms = {{ .id = EcsFinal }}, @@ -4156,6 +4319,15 @@ void flecs_bootstrap( .callback = flecs_register_final }); + static ecs_on_trait_ctx_t inheritable_trait = { EcsIdIsInheritable, 0 }; + ecs_observer(world, { + .query.terms = {{ .id = EcsInheritable }}, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd}, + .callback = flecs_register_trait, + .ctx = &inheritable_trait + }); + ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard) } @@ -4361,12 +4533,23 @@ void flecs_bootstrap( /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_hierarchy(world); + /* Register constant tag */ + ecs_component(world, { + .entity = ecs_entity(world, { .id = EcsConstant, + .name = "constant", .symbol = "EcsConstant", + .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) + }) + }); + ecs_set_scope(world, 0); ecs_set_name_prefix(world, NULL); ecs_assert(world->idr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(world->idr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); + /* Verify that all entities are where they're supposed to be */ + flecs_bootstrap_sanity_check(world); + ecs_log_pop(); } @@ -4392,21 +4575,21 @@ ecs_iter_t ecs_each_id( .next = ecs_each_next }; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { return it; } ecs_each_iter_t *each_iter = &it.priv_.iter.each; each_iter->ids = id; each_iter->sizes = 0; - if (idr->type_info) { - each_iter->sizes = idr->type_info->size; + if (cdr->type_info) { + each_iter->sizes = cdr->type_info->size; } each_iter->sources = 0; each_iter->trs = NULL; - flecs_table_cache_iter((ecs_table_cache_t*)idr, &each_iter->it); + flecs_table_cache_iter((ecs_table_cache_t*)cdr, &each_iter->it); return it; error: @@ -4426,7 +4609,11 @@ bool ecs_each_next( it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); - it->ids = &table->type.array[next->index]; + if (next->index != -1) { + it->ids = &table->type.array[next->index]; + } else { + it->ids = NULL; + } it->trs = &each_iter->trs; it->sources = &each_iter->sources; it->sizes = &each_iter->sizes; @@ -4465,13970 +4652,12829 @@ bool ecs_children_next( #include -#ifdef FLECS_SCRIPT +#ifdef FLECS_QUERY_DSL /** - * @file addons/script/script.h - * @brief Flecs script implementation. + * @file addons/query_dsl/query_dsl.h + * @brief Query DSL parser addon. */ -#ifndef FLECS_SCRIPT_PRIVATE_H -#define FLECS_SCRIPT_PRIVATE_H - +#ifndef FLECS_QUERY_DSL_H +#define FLECS_QUERY_DSL_H -#ifdef FLECS_SCRIPT -#include +int flecs_terms_parse( + ecs_world_t *world, + const char *name, + const char *code, + char *token_buffer, + ecs_term_t *terms, + int32_t *term_count_out); -typedef struct ecs_script_scope_t ecs_script_scope_t; -typedef struct ecs_script_entity_t ecs_script_entity_t; +const char* flecs_term_parse( + ecs_world_t *world, + const char *name, + const char *expr, + char *token_buffer, + ecs_term_t *term); -typedef struct ecs_script_impl_t { - ecs_script_t pub; - ecs_allocator_t allocator; - ecs_script_scope_t *root; - ecs_expr_node_t *expr; /* Only set if script is just an expression */ - char *token_buffer; - char *token_remaining; /* Remaining space in token buffer */ - const char *next_token; /* First character after expression */ - int32_t token_buffer_size; - int32_t refcount; -} ecs_script_impl_t; +const char* flecs_id_parse( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_id_t *id); -typedef struct ecs_script_parser_t ecs_script_parser_t; +#endif -#define flecs_script_impl(script) ((ecs_script_impl_t*)script) +#endif -/** - * @file addons/script/tokenizer.h - * @brief Script tokenizer. - */ +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); -#ifndef FLECS_SCRIPT_TOKENIZER_H -#define FLECS_SCRIPT_TOKENIZER_H +typedef struct { + const ecs_type_info_t *ti; + void *ptr; +} flecs_component_ptr_t; -/* Tokenizer */ -typedef enum ecs_script_token_kind_t { - EcsTokEnd = '\0', - EcsTokUnknown, - EcsTokScopeOpen = '{', - EcsTokScopeClose = '}', - EcsTokParenOpen = '(', - EcsTokParenClose = ')', - EcsTokBracketOpen = '[', - EcsTokBracketClose = ']', - EcsTokMember = '.', - EcsTokComma = ',', - EcsTokSemiColon = ';', - EcsTokColon = ':', - EcsTokAssign = '=', - EcsTokAdd = '+', - EcsTokSub = '-', - EcsTokMul = '*', - EcsTokDiv = '/', - EcsTokMod = '%', - EcsTokBitwiseOr = '|', - EcsTokBitwiseAnd = '&', - EcsTokNot = '!', - EcsTokOptional = '?', - EcsTokAnnotation = '@', - EcsTokNewline = '\n', - EcsTokEq = 100, - EcsTokNeq = 101, - EcsTokGt = 102, - EcsTokGtEq = 103, - EcsTokLt = 104, - EcsTokLtEq = 105, - EcsTokAnd = 106, - EcsTokOr = 107, - EcsTokMatch = 108, - EcsTokRange = 109, - EcsTokShiftLeft = 110, - EcsTokShiftRight = 111, - EcsTokIdentifier = 112, - EcsTokString = 113, - EcsTokNumber = 114, - EcsTokKeywordModule = 115, - EcsTokKeywordUsing = 116, - EcsTokKeywordWith = 117, - EcsTokKeywordIf = 118, - EcsTokKeywordFor = 119, - EcsTokKeywordIn = 120, - EcsTokKeywordElse = 121, - EcsTokKeywordTemplate = 122, - EcsTokKeywordProp = 130, - EcsTokKeywordConst = 131, - EcsTokKeywordMatch = 132, - EcsTokAddAssign = 133, - EcsTokMulAssign = 134, -} ecs_script_token_kind_t; +static +flecs_component_ptr_t flecs_table_get_component( + ecs_table_t *table, + int32_t column_index, + int32_t row) +{ + ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); + ecs_column_t *column = &table->data.columns[column_index]; + return (flecs_component_ptr_t){ + .ti = column->ti, + .ptr = ECS_ELEM(column->data, column->ti->size, row) + }; +error: + return (flecs_component_ptr_t){0}; +} -typedef struct ecs_script_token_t { - const char *value; - ecs_script_token_kind_t kind; -} ecs_script_token_t; +static +flecs_component_ptr_t flecs_get_component_ptr( + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cdr) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -typedef struct ecs_script_tokens_t { - int32_t count; - ecs_script_token_t tokens[256]; -} ecs_script_tokens_t; + if (!cdr) { + return (flecs_component_ptr_t){0}; + } -typedef struct ecs_script_tokenizer_t { - ecs_script_tokens_t stack; - ecs_script_token_t *tokens; -} ecs_script_tokenizer_t; + if (cdr->flags & EcsIdIsSparse) { + ecs_entity_t entity = ecs_table_entities(table)[row]; + return (flecs_component_ptr_t){ + .ti = cdr->type_info, + .ptr = flecs_sparse_get_any(cdr->sparse, 0, entity) + }; + } -const char* flecs_script_until( - ecs_script_parser_t *parser, - const char *ptr, - ecs_script_token_t *out, - char until); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr || (tr->column == -1)) { + return (flecs_component_ptr_t){0}; + } -const char* flecs_script_token_kind_str( - ecs_script_token_kind_t kind); + return flecs_table_get_component(table, tr->column, row); +} -const char* flecs_script_token_str( - ecs_script_token_kind_t kind); +static +void* flecs_get_component( + ecs_table_t *table, + int32_t row, + ecs_component_record_t *cdr) +{ + return flecs_get_component_ptr(table, row, cdr).ptr; +} -const char* flecs_script_token( - ecs_script_parser_t *parser, - const char *ptr, - ecs_script_token_t *out, - bool is_lookahead); +void* flecs_get_base_component( + const ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_component_record_t *cdr, + int32_t recur_depth) +{ + ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, + "cycle detected in IsA relationship"); -const char* flecs_scan_whitespace( - ecs_script_parser_t *parser, - const char *pos); + /* Table (and thus entity) does not have component, look for base */ + if (!(table->flags & EcsTableHasIsA)) { + return NULL; + } -const char* flecs_script_identifier( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out); + if (!(cdr->flags & EcsIdOnInstantiateInherit)) { + return NULL; + } -#endif + /* Exclude Name */ + if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { + return NULL; + } + /* Table should always be in the table index for (IsA, *), otherwise the + * HasBase flag should not have been set */ + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->idr_isa_wildcard, table); + ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); -struct ecs_script_parser_t { - ecs_script_impl_t *script; - ecs_script_scope_t *scope; - const char *pos; - char *token_cur; - char *token_keep; - bool significant_newline; - bool merge_variable_members; + 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; - /* For term parser */ - ecs_term_t *term; - ecs_oper_kind_t extra_oper; - ecs_term_ref_t *extra_args; -}; + do { + ecs_id_t pair = ids[i ++]; + ecs_entity_t base = ecs_pair_second(world, pair); -typedef struct ecs_function_calldata_t { - ecs_entity_t function; - ecs_function_callback_t callback; - void *ctx; -} ecs_function_calldata_t; + ecs_record_t *r = flecs_entities_get(world, base); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); -/** - * @file addons/script/ast.h - * @brief Script AST. - */ + table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -#ifndef FLECS_SCRIPT_AST_H -#define FLECS_SCRIPT_AST_H + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr) { + ptr = flecs_get_base_component(world, table, id, cdr, + recur_depth + 1); + } else { + if (cdr->flags & EcsIdIsSparse) { + return flecs_sparse_get_any(cdr->sparse, 0, base); + } else { + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_table_get_component(table, tr->column, row).ptr; + } + } + } while (!ptr && (i < end)); -typedef enum ecs_script_node_kind_t { - EcsAstScope, - EcsAstTag, - EcsAstComponent, - EcsAstDefaultComponent, - EcsAstVarComponent, - EcsAstWithVar, - EcsAstWithTag, - EcsAstWithComponent, - EcsAstWith, - EcsAstUsing, - EcsAstModule, - EcsAstAnnotation, - EcsAstTemplate, - EcsAstProp, - EcsAstConst, - EcsAstEntity, - EcsAstPairScope, - EcsAstIf, - EcsAstFor -} ecs_script_node_kind_t; + return ptr; +error: + return NULL; +} -typedef struct ecs_script_node_t { - ecs_script_node_kind_t kind; - const char *pos; -} ecs_script_node_t; +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_path(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; + } -struct ecs_script_scope_t { - ecs_script_node_t node; - ecs_vec_t stmts; - ecs_script_scope_t *parent; - ecs_id_t default_component_eval; + /* 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); + } - /* Array with component ids that are added in scope. Used to limit - * archetype moves. */ - ecs_vec_t components; /* vec */ -}; + if (slot == 0) { + char *slot_of_str = ecs_get_path(world, slot_of); + char *slot_str = ecs_get_path(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); + } -typedef struct ecs_script_id_t { - const char *first; - const char *second; - ecs_id_t flag; - ecs_id_t eval; + ecs_add_pair(world, parent, slot, child); + break; + } - /* If first or second refer to a variable, these are the cached variable - * stack pointers so we don't have to lookup variables by name. */ - int32_t first_sp; - int32_t second_sp; - - /* If true, the lookup result for this id cannot be cached. This is the case - * for entities that are defined inside of templates, which have different - * values for each instantiation. */ - bool dynamic; -} ecs_script_id_t; - -typedef struct ecs_script_tag_t { - ecs_script_node_t node; - ecs_script_id_t id; -} ecs_script_tag_t; + depth ++; + } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); + + if (parent == 0) { + char *slot_of_str = ecs_get_path(world, slot_of); + char *slot_str = ecs_get_path(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); + } + } -typedef struct ecs_script_component_t { - ecs_script_node_t node; - ecs_script_id_t id; - ecs_expr_node_t *expr; - ecs_value_t eval; - bool is_collection; -} ecs_script_component_t; +error: + return; +} -typedef struct ecs_script_default_component_t { - ecs_script_node_t node; - ecs_expr_node_t *expr; - ecs_value_t eval; -} ecs_script_default_component_t; +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; +} -typedef struct ecs_script_var_component_t { - ecs_script_node_t node; - const char *name; - int32_t sp; -} ecs_script_var_component_t; +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; +} -struct ecs_script_entity_t { - ecs_script_node_t node; - const char *kind; - const char *name; - bool name_is_var; - bool kind_w_expr; - ecs_script_scope_t *scope; - ecs_expr_node_t *name_expr; +static +ecs_entity_t flecs_new_id( + const ecs_world_t *world) +{ + flecs_poly_assert(world, ecs_world_t); - /* Populated during eval */ - ecs_script_entity_t *parent; - ecs_entity_t eval; - ecs_entity_t eval_kind; -}; + /* 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*, world); -typedef struct ecs_script_with_t { - ecs_script_node_t node; - ecs_script_scope_t *expressions; - ecs_script_scope_t *scope; -} ecs_script_with_t; + ecs_entity_t entity; + if (unsafe_world->flags & EcsWorldMultiThreaded) { + /* When world is in multithreading mode, make sure OS API has threading + * functions initialized */ + ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, + "thread safe id creation unavailable: threading API not available"); -typedef struct ecs_script_inherit_t { - ecs_script_node_t node; - ecs_script_scope_t *base_list; -} ecs_script_inherit_t; + /* Can't atomically increase number above max int */ + ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, + ECS_INVALID_OPERATION, "thread safe ids exhausted"); + entity = (ecs_entity_t)ecs_os_ainc( + (int32_t*)&flecs_entities_max_id(unsafe_world)); + } else { + entity = flecs_entities_new_id(unsafe_world); + } -typedef struct ecs_script_pair_scope_t { - ecs_script_node_t node; - ecs_script_id_t id; - ecs_script_scope_t *scope; -} ecs_script_pair_scope_t; + ecs_assert(!unsafe_world->info.max_id || + ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, + ECS_OUT_OF_RANGE, NULL); -typedef struct ecs_script_using_t { - ecs_script_node_t node; - const char *name; -} ecs_script_using_t; + flecs_journal(unsafe_world, EcsJournalNew, entity, 0, 0); -typedef struct ecs_script_module_t { - ecs_script_node_t node; - const char *name; -} ecs_script_module_t; + return entity; +} -typedef struct ecs_script_annot_t { - ecs_script_node_t node; - const char *name; - const char *expr; -} ecs_script_annot_t; +static +int32_t flecs_child_type_insert( + ecs_type_t *type, + void **component_data, + ecs_id_t id) +{ + int32_t i, count = type->count; + for (i = 0; i < count; i ++) { + ecs_id_t cur = type->array[i]; + if (cur == id) { + /* Id is already part of type */ + return -1; + } -typedef struct ecs_script_template_node_t { - ecs_script_node_t node; - const char *name; - ecs_script_scope_t* scope; -} ecs_script_template_node_t; + if (cur > id) { + /* A larger id was found so id can't be part of the type. */ + break; + } + } -typedef struct ecs_script_var_node_t { - ecs_script_node_t node; - const char *name; - const char *type; - ecs_expr_node_t *expr; -} ecs_script_var_node_t; + /* Assumes that the array has enough memory to store the new element. */ + int32_t to_move = type->count - i; + if (to_move) { + ecs_os_memmove(&type->array[i + 1], + &type->array[i], to_move * ECS_SIZEOF(ecs_id_t)); + ecs_os_memmove(&component_data[i + 1], + &component_data[i], to_move * ECS_SIZEOF(void*)); + } -typedef struct ecs_script_if_t { - ecs_script_node_t node; - ecs_script_scope_t *if_true; - ecs_script_scope_t *if_false; - ecs_expr_node_t *expr; -} ecs_script_if_t; + component_data[i] = NULL; + type->array[i] = id; + type->count ++; -typedef struct ecs_script_for_range_t { - ecs_script_node_t node; - const char *loop_var; - ecs_expr_node_t *from; - ecs_expr_node_t *to; - ecs_script_scope_t *scope; -} ecs_script_for_range_t; + return i; +} -#define ecs_script_node(kind, node)\ - ((ecs_script_##kind##_t*)node) +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, + const ecs_instantiate_ctx_t *ctx) +{ + if (!ecs_table_count(child_table)) { + return; + } -bool flecs_scope_is_empty( - ecs_script_scope_t *scope); + ecs_type_t type = child_table->type; + ecs_data_t *child_data = &child_table->data; -ecs_script_scope_t* flecs_script_insert_scope( - ecs_script_parser_t *parser); + ecs_entity_t slot_of = 0; + ecs_entity_t *ids = type.array; + int32_t type_count = type.count; -ecs_script_entity_t* flecs_script_insert_entity( - ecs_script_parser_t *parser, - const char *name, - bool name_is_expr); + /* Instantiate child table for each instance */ -ecs_script_pair_scope_t* flecs_script_insert_pair_scope( - ecs_script_parser_t *parser, - const char *first, - const char *second); + /* Create component array for creating the table */ + ecs_table_diff_t diff = { .added = {0}}; + diff.added.array = ecs_os_alloca_n(ecs_entity_t, type_count + 1); + void **component_data = ecs_os_alloca_n(void*, type_count + 1); -ecs_script_with_t* flecs_script_insert_with( - ecs_script_parser_t *parser); + /* 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; + for (i = 0; i < type_count; i ++) { + ecs_id_t id = ids[i]; -ecs_script_using_t* flecs_script_insert_using( - ecs_script_parser_t *parser, - const char *name); + /* 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) + { + ecs_table_record_t *tr = &child_table->_->records[i]; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + if (cdr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } + } -ecs_script_module_t* flecs_script_insert_module( - ecs_script_parser_t *parser, - const char *name); + /* 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; + } + } -ecs_script_template_node_t* flecs_script_insert_template( - ecs_script_parser_t *parser, - const char *name); + /* Keep track of the element that creates the ChildOf relationship with + * the prefab parent. We need to replace this element to make sure the + * created children point to the instance and not the prefab */ + if (ECS_HAS_RELATION(id, EcsChildOf) && + (ECS_PAIR_SECOND(id) == (uint32_t)base)) { + childof_base_index = diff.added.count; + } -ecs_script_annot_t* flecs_script_insert_annot( - ecs_script_parser_t *parser, - const char *name, - const char *expr); + /* If this is a pure override, make sure we have a concrete version of the + * component. This relies on the fact that overrides always come after + * concrete components in the table type so we can check the components + * that have already been added to the child table type. */ + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + ecs_id_t concreteId = id & ~ECS_AUTO_OVERRIDE; + flecs_child_type_insert(&diff.added, component_data, concreteId); + continue; + } -ecs_script_var_node_t* flecs_script_insert_var( - ecs_script_parser_t *parser, - const char *name); + int32_t storage_index = ecs_table_type_to_column_index(child_table, i); + if (storage_index != -1) { + component_data[diff.added.count] = + child_data->columns[storage_index].data; + } else { + component_data[diff.added.count] = NULL; + } -ecs_script_tag_t* flecs_script_insert_tag( - ecs_script_parser_t *parser, - const char *name); + diff.added.array[diff.added.count] = id; + diff.added.count ++; + diff.added_flags |= flecs_id_flags_get(world, id); + } -ecs_script_tag_t* flecs_script_insert_pair_tag( - ecs_script_parser_t *parser, - const char *first, - const char *second); + /* Table must contain children of base */ + ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); -ecs_script_component_t* flecs_script_insert_component( - ecs_script_parser_t *parser, - const char *name); + /* If children are added to a prefab, make sure they are prefabs too */ + if (table->flags & EcsTableIsPrefab) { + if (flecs_child_type_insert( + &diff.added, component_data, EcsPrefab) != -1) + { + childof_base_index ++; + } + } -ecs_script_component_t* flecs_script_insert_pair_component( - ecs_script_parser_t *parser, - const char *first, - const char *second); + /* Instantiate the prefab child table for each new instance */ + const ecs_entity_t *instances = ecs_table_entities(table); + int32_t child_count = ecs_table_count(child_table); + ecs_entity_t *child_ids = flecs_walloc_n(world, ecs_entity_t, child_count); -ecs_script_default_component_t* flecs_script_insert_default_component( - ecs_script_parser_t *parser); + for (i = row; i < count + row; i ++) { + ecs_entity_t instance = instances[i]; + ecs_table_t *i_table = NULL; + + /* Replace ChildOf element in the component array with instance id */ + diff.added.array[childof_base_index] = ecs_pair(EcsChildOf, instance); -ecs_script_var_component_t* flecs_script_insert_var_component( - ecs_script_parser_t *parser, - const char *name); + /* Find or create table */ + i_table = flecs_table_find_or_create(world, &diff.added); -ecs_script_if_t* flecs_script_insert_if( - ecs_script_parser_t *parser); + ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(i_table->type.count == diff.added.count, + ECS_INTERNAL_ERROR, NULL); -ecs_script_for_range_t* flecs_script_insert_for_range( - ecs_script_parser_t *parser); + /* 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. */ + const ecs_entity_t *children = ecs_table_entities(child_table); +#ifdef FLECS_DEBUG + for (j = 0; j < child_count; j ++) { + ecs_entity_t child = children[j]; + ecs_check(child != instance, ECS_INVALID_PARAMETER, + "cycle detected in IsA relationship"); + } +#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 -/** - * @file addons/script/expr/expr.h - * @brief Script expression support. - */ - -#ifndef FLECS_EXPR_SCRIPT_H -#define FLECS_EXPR_SCRIPT_H - -/** - * @file addons/script/expr/stack.h - * @brief Script expression AST. - */ - -#ifndef FLECS_SCRIPT_EXPR_STACK_H -#define FLECS_SCRIPT_EXPR_STACK_H + /* Attempt to reserve ids for children that have the same offset from + * the instance as from the base prefab. This ensures stable ids for + * instance children, even across networked applications. */ + ecs_instantiate_ctx_t ctx_cur = {base, instance}; + if (ctx) { + ctx_cur = *ctx; + } -#define FLECS_EXPR_STACK_MAX (256) -#define FLECS_EXPR_SMALL_DATA_SIZE (24) + for (j = 0; j < child_count; j ++) { + if ((uint32_t)children[j] < (uint32_t)ctx_cur.root_prefab) { + /* Child id is smaller than root prefab id, can't use offset */ + child_ids[j] = flecs_new_id(world); + continue; + } + /* Get prefab offset, ignore lifecycle generation count */ + ecs_entity_t prefab_offset = + (uint32_t)children[j] - (uint32_t)ctx_cur.root_prefab; + ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL); -typedef union ecs_expr_small_value_t { - bool bool_; - char char_; - ecs_byte_t byte_; - int8_t i8; - int16_t i16; - int32_t i32; - int64_t i64; - intptr_t iptr; - uint8_t u8; - uint16_t u16; - uint32_t u32; - uint64_t u64; - uintptr_t uptr; - double f32; - double f64; - char *string; - ecs_entity_t entity; - ecs_id_t id; + /* First check if any entity with the desired id exists */ + ecs_entity_t instance_child = (uint32_t)ctx_cur.root_instance + prefab_offset; + ecs_entity_t alive_id = flecs_entities_get_alive(world, instance_child); + if (alive_id && flecs_entities_is_alive(world, alive_id)) { + /* Alive entity with requested id exists, can't use offset id */ + child_ids[j] = flecs_new_id(world); + continue; + } - /* Avoid allocations for small trivial types */ - char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; -} ecs_expr_small_value_t; + /* Id is not in use. Make it alive & match the generation of the instance. */ + instance_child = ctx_cur.root_instance + prefab_offset; + flecs_entities_make_alive(world, instance_child); + flecs_entities_ensure(world, instance_child); + ecs_assert(ecs_is_alive(world, instance_child), ECS_INTERNAL_ERROR, NULL); + child_ids[j] = instance_child; + } -typedef struct ecs_expr_value_t { - ecs_value_t value; - const ecs_type_info_t *type_info; - bool owned; /* Is value owned by the runtime */ -} ecs_expr_value_t; + /* Create children */ + int32_t child_row; + const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids, + &diff.added, child_count, component_data, false, &child_row, &diff); -typedef struct ecs_expr_stack_frame_t { - ecs_stack_cursor_t *cur; - int32_t sp; -} ecs_expr_stack_frame_t; + /* 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); + } + } -typedef struct ecs_expr_stack_t { - ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; - ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; - ecs_stack_t stack; - int32_t frame; -} ecs_expr_stack_t; + /* 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, &ctx_cur); + } + } -void flecs_expr_stack_init( - ecs_expr_stack_t *stack); + flecs_wfree_n(world, ecs_entity_t, child_count, child_ids); +error: + return; +} -void flecs_expr_stack_fini( - ecs_expr_stack_t *stack); +void flecs_instantiate( + ecs_world_t *world, + ecs_entity_t base, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_instantiate_ctx_t *ctx) +{ + ecs_record_t *record = flecs_entities_get_any(world, base); + ecs_table_t *base_table = record->table; + ecs_assert(base_table != NULL, ECS_INTERNAL_ERROR, NULL); -ecs_expr_value_t* flecs_expr_stack_alloc( - ecs_expr_stack_t *stack, - const ecs_type_info_t *ti); + /* If prefab has union relationships, also set them on instance */ + if (base_table->flags & EcsTableHasUnion) { + const ecs_entity_t *entities = ecs_table_entities(table); + ecs_component_record_t *union_idr = flecs_components_get(world, + ecs_pair(EcsWildcard, EcsUnion)); + ecs_assert(union_idr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = flecs_component_get_table( + union_idr, base_table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = 0, j, union_count = 0; + do { + ecs_id_t id = base_table->type.array[i]; + if (ECS_PAIR_SECOND(id) == EcsUnion) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ecs_get_target(world, base, rel, 0); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cdr = + (ecs_component_record_t*)base_table->_->records[i].hdr.cache; -ecs_expr_value_t* flecs_expr_stack_result( - ecs_expr_stack_t *stack, - ecs_expr_node_t *node); + for (j = row; j < (row + count); j ++) { + flecs_switch_set(cdr->sparse, (uint32_t)entities[j], tgt); + } -void flecs_expr_stack_push( - ecs_expr_stack_t *stack); + union_count ++; + } -void flecs_expr_stack_pop( - ecs_expr_stack_t *stack); + i ++; + } while (union_count < tr->count); + } -#endif + if (!(base_table->flags & EcsTableIsPrefab)) { + /* Don't instantiate children from base entities that aren't prefabs */ + return; + } -/** - * @file addons/script/expr_ast.h - * @brief Script expression AST. - */ + ecs_component_record_t *cdr = flecs_components_get(world, ecs_childof(base)); + ecs_table_cache_iter_t it; + if (cdr && flecs_table_cache_all_iter((ecs_table_cache_t*)cdr, &it)) { + ecs_os_perf_trace_push("flecs.instantiate"); + 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, ctx); + } + ecs_os_perf_trace_pop("flecs.instantiate"); + } +} -#ifndef FLECS_SCRIPT_EXPR_AST_H -#define FLECS_SCRIPT_EXPR_AST_H +static +void flecs_sparse_on_add( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *added, + bool construct) +{ + int32_t i, j; + for (i = 0; i < added->count; i ++) { + ecs_id_t id = added->array[i]; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr && cdr->flags & EcsIdIsSparse) { + const ecs_type_info_t *ti = cdr->type_info; + ecs_xtor_t ctor = ti->hooks.ctor; + ecs_iter_action_t on_add = ti->hooks.on_add; + const ecs_entity_t *entities = ecs_table_entities(table); + for (j = 0; j < count; j ++) { + ecs_entity_t e = entities[row + j]; + void *ptr = flecs_sparse_ensure(cdr->sparse, 0, e); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (construct && ctor) { + ctor(ptr, 1, ti); + } -#define FLECS_EXPR_SMALL_DATA_SIZE (24) + if (on_add) { + const ecs_table_record_t *tr = + flecs_component_get_table(cdr, table); + flecs_invoke_hook(world, table, tr, count, row, + &entities[row + j],id, ti, EcsOnAdd, on_add); + } + } + } + } +} -typedef enum ecs_expr_node_kind_t { - EcsExprValue, - EcsExprInterpolatedString, - EcsExprInitializer, - EcsExprEmptyInitializer, - EcsExprUnary, - EcsExprBinary, - EcsExprIdentifier, - EcsExprVariable, - EcsExprGlobalVariable, - EcsExprFunction, - EcsExprMethod, - EcsExprMember, - EcsExprElement, - EcsExprComponent, - EcsExprCast, - EcsExprCastNumber, - EcsExprMatch -} ecs_expr_node_kind_t; +static +void flecs_sparse_on_remove( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *removed) +{ + int32_t i, j; + for (i = 0; i < removed->count; i ++) { + ecs_id_t id = removed->array[i]; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr && cdr->flags & EcsIdIsSparse) { + const ecs_type_info_t *ti = cdr->type_info; + const ecs_table_record_t *tr = + flecs_component_get_table(cdr, table); + ecs_xtor_t dtor = ti->hooks.dtor; + ecs_iter_action_t on_remove = ti->hooks.on_remove; + const ecs_entity_t *entities = ecs_table_entities(table); + for (j = 0; j < count; j ++) { + ecs_entity_t e = entities[row + j]; + if (on_remove) { + flecs_invoke_hook(world, table, tr, count, row, + &entities[row + j], id, ti, EcsOnRemove, on_remove); + } + void *ptr = flecs_sparse_remove_fast(cdr->sparse, 0, e); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + if (dtor) { + dtor(ptr, 1, ti); + } + } + } + } +} -struct ecs_expr_node_t { - ecs_expr_node_kind_t kind; - ecs_entity_t type; - const ecs_type_info_t *type_info; - const char *pos; -}; +static +void flecs_union_on_add( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *added) +{ + int32_t i, j; + for (i = 0; i < added->count; i ++) { + ecs_id_t id = added->array[i]; + if (ECS_IS_PAIR(id)) { + ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); + ecs_component_record_t *cdr = flecs_components_get(world, wc); + if (cdr && cdr->flags & EcsIdIsUnion) { + const ecs_entity_t *entities = ecs_table_entities(table); + for (j = 0; j < count; j ++) { + ecs_entity_t e = entities[row + j]; + flecs_switch_set( + cdr->sparse, (uint32_t)e, ecs_pair_second(world, id)); + } + } + } + } +} -typedef struct ecs_expr_value_node_t { - ecs_expr_node_t node; - void *ptr; - ecs_expr_small_value_t storage; -} ecs_expr_value_node_t; +static +void flecs_union_on_remove( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + const ecs_type_t *removed) +{ + int32_t i, j; + for (i = 0; i < removed->count; i ++) { + ecs_id_t id = removed->array[i]; + if (ECS_IS_PAIR(id)) { + ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); + ecs_component_record_t *cdr = flecs_components_get(world, wc); + if (cdr && cdr->flags & EcsIdIsUnion) { + const ecs_entity_t *entities = ecs_table_entities(table); + for (j = 0; j < count; j ++) { + ecs_entity_t e = entities[row + j]; + flecs_switch_reset(cdr->sparse, (uint32_t)e); + } + } + } + } +} -typedef struct ecs_expr_interpolated_string_t { - ecs_expr_node_t node; - char *value; /* modified by parser */ - char *buffer; /* for storing expr tokens */ - ecs_size_t buffer_size; - ecs_vec_t fragments; /* vec */ - ecs_vec_t expressions; /* vec */ -} ecs_expr_interpolated_string_t; +static +void flecs_notify_on_add( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff, + ecs_flags32_t flags, + ecs_flags64_t *set_mask, + bool construct, + bool sparse) +{ + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_t *added = &diff->added; -typedef struct ecs_expr_initializer_element_t { - const char *member; - ecs_expr_node_t *value; - uintptr_t offset; - ecs_script_token_kind_t operator; -} ecs_expr_initializer_element_t; + if (added->count) { + ecs_flags32_t diff_flags = + diff->added_flags|(table->flags & EcsTableHasTraversable); + if (!diff_flags) { + return; + } -typedef struct ecs_expr_initializer_t { - ecs_expr_node_t node; - ecs_vec_t elements; - const ecs_type_info_t *type_info; - bool is_collection; - bool is_dynamic; -} ecs_expr_initializer_t; + if (sparse && (diff_flags & EcsTableHasSparse)) { + flecs_sparse_on_add(world, table, row, count, added, construct); + } -typedef struct ecs_expr_variable_t { - ecs_expr_node_t node; - const char *name; - ecs_value_t global_value; /* Only set for global variables */ - int32_t sp; /* For fast variable lookups */ -} ecs_expr_variable_t; + if (diff_flags & EcsTableHasUnion) { + flecs_union_on_add(world, table, row, count, added); + } -typedef struct ecs_expr_identifier_t { - ecs_expr_node_t node; - const char *value; - ecs_expr_node_t *expr; -} ecs_expr_identifier_t; + if (diff_flags & (EcsTableHasOnAdd|EcsTableHasTraversable)) { + flecs_emit(world, world, set_mask, &(ecs_event_desc_t){ + .event = EcsOnAdd, + .ids = added, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world, + .flags = flags + }); + } + } +} -typedef struct ecs_expr_unary_t { - ecs_expr_node_t node; - ecs_expr_node_t *expr; - ecs_script_token_kind_t operator; -} ecs_expr_unary_t; +void flecs_notify_on_remove( + ecs_world_t *world, + ecs_table_t *table, + ecs_table_t *other_table, + int32_t row, + int32_t count, + const ecs_table_diff_t *diff) +{ + ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_t *removed = &diff->removed; + ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); -typedef struct ecs_expr_binary_t { - ecs_expr_node_t node; - ecs_expr_node_t *left; - ecs_expr_node_t *right; - ecs_script_token_kind_t operator; -} ecs_expr_binary_t; + if (removed->count) { + ecs_flags32_t diff_flags = + diff->removed_flags|(table->flags & EcsTableHasTraversable); + if (!diff_flags) { + return; + } -typedef struct ecs_expr_member_t { - ecs_expr_node_t node; - ecs_expr_node_t *left; - const char *member_name; - uintptr_t offset; -} ecs_expr_member_t; + if (diff_flags & EcsTableHasUnion) { + flecs_union_on_remove(world, table, row, count, removed); + } -typedef struct ecs_expr_function_t { - ecs_expr_node_t node; - ecs_expr_node_t *left; - ecs_expr_initializer_t *args; - const char *function_name; - ecs_function_calldata_t calldata; -} ecs_expr_function_t; + if (diff_flags & (EcsTableHasOnRemove|EcsTableHasTraversable)) { + flecs_emit(world, world, 0, &(ecs_event_desc_t) { + .event = EcsOnRemove, + .ids = removed, + .table = table, + .other_table = other_table, + .offset = row, + .count = count, + .observable = world + }); + } -typedef struct ecs_expr_element_t { - ecs_expr_node_t node; - ecs_expr_node_t *left; - ecs_expr_node_t *index; - ecs_size_t elem_size; -} ecs_expr_element_t; + if (diff_flags & EcsTableHasSparse) { + flecs_sparse_on_remove(world, table, row, count, removed); + } + } +} -typedef struct ecs_expr_component_t { - ecs_expr_node_t node; - ecs_expr_node_t *expr; - ecs_id_t component; -} ecs_expr_component_t; +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; + } -typedef struct ecs_expr_cast_t { - ecs_expr_node_t node; - ecs_expr_node_t *expr; -} ecs_expr_cast_t; + 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; + } -typedef struct ecs_expr_match_element_t { - ecs_expr_node_t *compare; - ecs_expr_node_t *expr; -} ecs_expr_match_element_t; + EcsIdentifier *names = ecs_table_get_pair(world, + dst, EcsIdentifier, EcsName, offset); + ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); -typedef struct ecs_expr_match_t { - ecs_expr_node_t node; - ecs_expr_node_t *expr; - ecs_vec_t elements; - ecs_expr_match_element_t any; -} ecs_expr_match_t; + int32_t i; + const ecs_entity_t *entities = &ecs_table_entities(dst)[offset]; + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + EcsIdentifier *name = &names[i]; -ecs_expr_value_node_t* flecs_expr_value_from( - ecs_script_t *script, - ecs_expr_node_t *node, - ecs_entity_t type); + 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); -ecs_expr_variable_t* flecs_expr_variable_from( - ecs_script_t *script, - ecs_expr_node_t *node, - const char *name); + flecs_name_index_ensure( + dst_index, e, name_str, name->length, name->hash); + name->index = dst_index; + } + } +} -ecs_expr_value_node_t* flecs_expr_bool( - ecs_script_parser_t *parser, - bool value); +static +ecs_record_t* flecs_new_entity( + ecs_world_t *world, + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + ecs_table_diff_t *diff, + bool ctor, + ecs_flags32_t evt_flags) +{ + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t row = flecs_table_append(world, table, entity, ctor, true); + record->table = table; + record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); -ecs_expr_value_node_t* flecs_expr_int( - ecs_script_parser_t *parser, - int64_t value); + ecs_assert(ecs_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); + flecs_notify_on_add( + world, table, NULL, row, 1, diff, evt_flags, 0, ctor, true); + ecs_assert(table == record->table, ECS_INTERNAL_ERROR, NULL); -ecs_expr_value_node_t* flecs_expr_uint( - ecs_script_parser_t *parser, - uint64_t value); + return record; +} -ecs_expr_value_node_t* flecs_expr_float( - ecs_script_parser_t *parser, - double value); +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_expr_value_node_t* flecs_expr_string( - ecs_script_parser_t *parser, - const char *value); + 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_table_count(src_table) > 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_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); -ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( - ecs_script_parser_t *parser, - const char *value); + /* Append new row to destination table */ + int32_t dst_row = flecs_table_append(world, dst_table, entity, + false, false); -ecs_expr_value_node_t* flecs_expr_entity( - ecs_script_parser_t *parser, - ecs_entity_t value); + /* Invoke remove actions for removed components */ + flecs_notify_on_remove(world, src_table, dst_table, src_row, 1, diff); -ecs_expr_initializer_t* flecs_expr_initializer( - ecs_script_parser_t *parser); + /* Copy entity & components from src_table to dst_table */ + flecs_table_move(world, entity, entity, dst_table, dst_row, + src_table, src_row, ctor); + ecs_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); -ecs_expr_identifier_t* flecs_expr_identifier( - ecs_script_parser_t *parser, - const char *value); + /* 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, + evt_flags, 0, ctor, true); -ecs_expr_variable_t* flecs_expr_variable( - ecs_script_parser_t *parser, - const char *value); + flecs_update_name_index(world, src_table, dst_table, dst_row, 1); -ecs_expr_unary_t* flecs_expr_unary( - ecs_script_parser_t *parser); + ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); +error: + return; +} -ecs_expr_binary_t* flecs_expr_binary( - ecs_script_parser_t *parser); +/* 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; + } -ecs_expr_member_t* flecs_expr_member( - ecs_script_parser_t *parser); + 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)); + } -ecs_expr_function_t* flecs_expr_function( - ecs_script_parser_t *parser); + flecs_monitor_mark_dirty(world, id); + } +} -ecs_expr_element_t* flecs_expr_element( - ecs_script_parser_t *parser); +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); +} -ecs_expr_match_t* flecs_expr_match( - ecs_script_parser_t *parser); +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_expr_cast_t* flecs_expr_cast( - ecs_script_t *script, - ecs_expr_node_t *node, - ecs_entity_t type); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *src_table = record->table; + int is_trav = (record->row & EcsEntityIsTraversable) != 0; + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); -#endif + 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->flags & EcsTableHasUnion) { + diff->added_flags |= EcsIdIsUnion; + flecs_notify_on_add(world, src_table, src_table, + ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, 0, + construct, true); + } + flecs_journal_end(); + return; + } -/** - * @file addons/script/exor_visit.h - * @brief Script AST visitor utilities. - */ + ecs_os_perf_trace_push("flecs.commit"); -#ifndef FLECS_EXPR_SCRIPT_VISIT_H -#define FLECS_EXPR_SCRIPT_VISIT_H - -#define flecs_expr_visit_error(script, node, ...) \ - ecs_parser_error( \ - script->name, script->code, \ - ((const ecs_expr_node_t*)node)->pos - script->code, \ - __VA_ARGS__); - -int flecs_expr_visit_type( - ecs_script_t *script, - ecs_expr_node_t *node, - const ecs_expr_eval_desc_t *desc); - -int flecs_expr_visit_fold( - ecs_script_t *script, - ecs_expr_node_t **node, - const ecs_expr_eval_desc_t *desc); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(dst_table, is_trav); -int flecs_expr_visit_eval( - const ecs_script_t *script, - ecs_expr_node_t *node, - const ecs_expr_eval_desc_t *desc, - ecs_value_t *out); + flecs_move_entity(world, entity, record, dst_table, diff, + construct, evt_flags); -void flecs_expr_visit_free( - ecs_script_t *script, - ecs_expr_node_t *node); + flecs_table_traversable_add(src_table, -is_trav); -ecs_script_var_t flecs_expr_find_var( - ecs_script_t *script, - const char *name); + /* 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); + } -#endif + if (!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_os_perf_trace_pop("flecs.commit"); -int flecs_value_copy_to( - ecs_world_t *world, - ecs_value_t *dst, - const ecs_expr_value_t *src); +error: + flecs_journal_end(); + return; +} -int flecs_value_move_to( +static +const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, - ecs_value_t *dst, - ecs_value_t *src); - -int flecs_value_binary( - const ecs_script_t *script, - const ecs_value_t *left, - const ecs_value_t *right, - ecs_value_t *out, - ecs_script_token_kind_t operator); + 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); + } -int flecs_value_unary( - const ecs_script_t *script, - const ecs_value_t *expr, - ecs_value_t *out, - ecs_script_token_kind_t operator); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_kind_t left_oper, - ecs_expr_node_t **out); + int32_t row = flecs_table_appendn(world, table, count, entities); -const char* flecs_script_parse_initializer( - ecs_script_parser_t *parser, - const char *pos, - char until, - ecs_expr_initializer_t **node_out); + /* Update entity index. */ + int i; + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + r->table = table; + r->row = ECS_ROW_TO_RECORD(row + i, 0); + } -void flecs_expr_to_str_buf( - const ecs_world_t *world, - const ecs_expr_node_t *expr, - ecs_strbuf_t *buf, - bool colors); + ecs_type_t type = table->type; + if (!type.count) { + return entities; + } -bool flecs_string_is_interpolated( - const char *str); + ecs_type_t component_array = { 0 }; + if (!component_ids) { + component_ids = &component_array; + component_array.array = type.array; + component_array.count = type.count; + } -char* flecs_string_escape( - char *str); + flecs_defer_begin(world, world->stages[0]); -bool flecs_value_is_0( - const ecs_value_t *value); + flecs_notify_on_add(world, table, NULL, row, count, diff, + (component_data == NULL) ? 0 : EcsEventNoOnSet, 0, true, true); -bool flecs_expr_is_type_integer( - ecs_entity_t type); + 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; + } -bool flecs_expr_is_type_number( - ecs_entity_t type); + /* Find component in storage type */ + ecs_entity_t id = component_ids->array[c_i]; + ecs_component_record_t *cdr = flecs_components_get(world, id); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + 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"); -#endif + int32_t index = tr->column; + ecs_column_t *column = &table->data.columns[index]; + ecs_type_info_t *ti = column->ti; + int32_t size = ti->size; + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *ptr = ECS_ELEM(column->data, size, row); -/** - * @file addons/script/visit.h - * @brief Script AST visitor utilities. - */ + 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); + } + }; -#ifndef FLECS_SCRIPT_VISIT_H -#define FLECS_SCRIPT_VISIT_H + int32_t j, storage_count = table->column_count; + for (j = 0; j < storage_count; j ++) { + ecs_id_t id = flecs_column_id(table, j); + ecs_type_t set_type = { + .array = &id, + .count = 1 + }; -typedef struct ecs_script_visit_t ecs_script_visit_t; + flecs_notify_on_set(world, table, row, count, &set_type, true); + } + } -typedef int (*ecs_visit_action_t)( - ecs_script_visit_t *visitor, - ecs_script_node_t *node); + flecs_defer_end(world, world->stages[0]); -struct ecs_script_visit_t { - ecs_script_impl_t *script; - ecs_visit_action_t visit; - ecs_script_node_t* nodes[256]; - ecs_script_node_t *prev, *next; - int32_t depth; -}; + if (row_out) { + *row_out = row; + } -int ecs_script_visit_( - ecs_script_visit_t *visitor, - ecs_visit_action_t visit, - ecs_script_impl_t *script); + if (sparse_count) { + entities = flecs_entities_ids(world); + return &entities[sparse_count]; + } else { + return entities; + } +} -#define ecs_script_visit(script, visitor, visit) \ - ecs_script_visit_((ecs_script_visit_t*)visitor,\ - (ecs_visit_action_t)visit,\ - script) +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); -int ecs_script_visit_node_( - ecs_script_visit_t *v, - ecs_script_node_t *node); + 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. */ +} -#define ecs_script_visit_node(visitor, node) \ - ecs_script_visit_node_((ecs_script_visit_t*)visitor, \ - (ecs_script_node_t*)node) +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; + } -int ecs_script_visit_scope_( - ecs_script_visit_t *v, - ecs_script_scope_t *node); + 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); -#define ecs_script_visit_scope(visitor, node) \ - ecs_script_visit_scope_((ecs_script_visit_t*)visitor, node) + flecs_commit(world, entity, r, dst_table, &diff, true, 0); -ecs_script_node_t* ecs_script_parent_node_( - ecs_script_visit_t *v); + flecs_defer_end(world, stage); +} -#define ecs_script_parent_node(visitor) \ - ecs_script_parent_node_((ecs_script_visit_t*)visitor) +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_script_scope_t* ecs_script_current_scope_( - ecs_script_visit_t *v); + 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); -#define ecs_script_current_scope(visitor) \ - ecs_script_current_scope_((ecs_script_visit_t*)visitor) + flecs_commit(world, entity, r, dst_table, &diff, true, 0); -ecs_script_node_t* ecs_script_parent_( - ecs_script_visit_t *v, - ecs_script_node_t *node); + flecs_defer_end(world, stage); +} -#define ecs_script_parent(visitor, node) \ - ecs_script_parent_((ecs_script_visit_t*)visitor, (ecs_script_node_t*)node) +void flecs_add_ids( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t *ids, + int32_t count) +{ + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); -ecs_script_node_t* ecs_script_next_node_( - ecs_script_visit_t *v); + ecs_table_t *table = ecs_get_table(world, entity); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + table = flecs_find_table_add(world, table, id, &diff); + } -#define ecs_script_next_node(visitor) \ - ecs_script_next_node_((ecs_script_visit_t*)visitor) + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_commit(world, entity, r, table, &table_diff, true, 0); + flecs_table_diff_builder_fini(world, &diff); +} -int32_t ecs_script_node_line_number_( - ecs_script_impl_t *script, - ecs_script_node_t *node); +static +flecs_component_ptr_t flecs_ensure( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t id, + ecs_record_t *r) +{ + flecs_component_ptr_t dst = {0}; -#define ecs_script_node_line_number(script, node) \ - ecs_script_node_line_number_(script, (ecs_script_node_t*)node) + flecs_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, + "invalid component id specified for ensure"); -#endif + ecs_component_record_t *cdr = NULL; + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); -/** - * @file addons/script/visit_eval.h - * @brief Script evaluation visitor. - */ + if (id < FLECS_HI_COMPONENT_ID) { + int16_t column_index = table->component_map[id]; + if (column_index > 0) { + ecs_column_t *column = &table->data.columns[column_index - 1]; + ecs_type_info_t *ti = column->ti; + dst.ptr = ECS_ELEM(column->data, ti->size, + ECS_RECORD_TO_ROW(r->row)); + dst.ti = ti; + return dst; + } else if (column_index < 0) { + column_index = flecs_ito(int16_t, -column_index - 1); + const ecs_table_record_t *tr = &table->_->records[column_index]; + cdr = (ecs_component_record_t*)tr->hdr.cache; + if (cdr->flags & EcsIdIsSparse) { + dst.ptr = flecs_sparse_get_any(cdr->sparse, 0, entity); + dst.ti = cdr->type_info; + return dst; + } + } + } else { + cdr = flecs_components_get(world, id); + dst = flecs_get_component_ptr(table, ECS_RECORD_TO_ROW(r->row), cdr); + if (dst.ptr) { + return dst; + } + } -#ifndef FLECS_SCRIPT_VISIT_EVAL_H -#define FLECS_SCRIPT_VISIT_EVAL_H + /* If entity didn't have component yet, add it */ + flecs_add_id_w_record(world, entity, r, id, true); -typedef struct ecs_script_eval_visitor_t { - ecs_script_visit_t base; - ecs_world_t *world; - ecs_script_runtime_t *r; - ecs_script_template_t *template; /* Set when creating template */ - ecs_entity_t template_entity; /* Set when creating template instance */ - ecs_entity_t module; - ecs_entity_t parent; - ecs_script_entity_t *entity; - ecs_entity_t with_relationship; - int32_t with_relationship_sp; - bool is_with_scope; - bool dynamic_variable_binding; - ecs_script_vars_t *vars; -} ecs_script_eval_visitor_t; + /* Flush commands so the pointer we're fetching is stable */ + flecs_defer_end(world, world->stages[0]); + flecs_defer_begin(world, world->stages[0]); -void flecs_script_eval_error_( - ecs_script_eval_visitor_t *v, - ecs_script_node_t *node, - const char *fmt, - ...); + if (!cdr) { + cdr = flecs_components_get(world, id); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + } -#define flecs_script_eval_error(v, node, ...)\ - flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + dst = flecs_get_component_ptr(r->table, ECS_RECORD_TO_ROW(r->row), cdr); +error: + return dst; +} -int flecs_script_find_entity( - ecs_script_eval_visitor_t *v, - ecs_entity_t from, - const char *path, - int32_t *frame_offset, - ecs_entity_t *out); +void flecs_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + const ecs_table_record_t *tr, + int32_t count, + int32_t row, + const ecs_entity_t *entities, + ecs_id_t id, + const ecs_type_info_t *ti, + ecs_entity_t event, + ecs_iter_action_t hook) +{ + int32_t defer = world->stages[0]->defer; + if (defer < 0) { + world->stages[0]->defer *= -1; + } -ecs_script_var_t* flecs_script_find_var( - const ecs_script_vars_t *vars, - const char *name, - int32_t *frame_offset); + ecs_iter_t it = { .field_count = 1}; + it.entities = entities; -ecs_entity_t flecs_script_create_entity( - ecs_script_eval_visitor_t *v, - const char *name); + flecs_iter_init(world, &it, flecs_iter_cache_all); + it.world = world; + it.real_world = world; + it.table = table; + it.trs[0] = tr; + it.row_fields = !!(((ecs_component_record_t*)tr->hdr.cache)->flags & EcsIdIsSparse); + it.ref_fields = it.row_fields; + 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.callback_ctx = ti->hooks.binding_ctx; + it.count = count; + it.offset = row; + it.flags = EcsIterIsValid; -const ecs_type_info_t* flecs_script_get_type_info( - ecs_script_eval_visitor_t *v, - void *node, - ecs_id_t id); + hook(&it); + ecs_iter_fini(&it); -int flecs_script_eval_expr( - ecs_script_eval_visitor_t *v, - ecs_expr_node_t **expr_ptr, - ecs_value_t *value); + world->stages[0]->defer = defer; +} -void flecs_script_eval_visit_init( - const ecs_script_impl_t *script, - ecs_script_eval_visitor_t *v, - const ecs_script_eval_desc_t *desc); +void flecs_notify_on_set( + ecs_world_t *world, + ecs_table_t *table, + int32_t row, + int32_t count, + ecs_type_t *ids, + bool owned) +{ + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = &ecs_table_entities(table)[row]; + ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert((row + count) <= ecs_table_count(table), + ECS_INTERNAL_ERROR, NULL); -void flecs_script_eval_visit_fini( - ecs_script_eval_visitor_t *v, - const ecs_script_eval_desc_t *desc); + if (owned) { + int i; + for (i = 0; i < ids->count; i ++) { + ecs_id_t id = ids->array[i]; + ecs_component_record_t *cdr = flecs_components_get(world, id); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = cdr->type_info; + ecs_iter_action_t on_set = ti->hooks.on_set; + if (!on_set) { + continue; + } -int flecs_script_eval_node( - ecs_script_eval_visitor_t *v, - ecs_script_node_t *node); + const ecs_table_record_t *tr = + flecs_component_get_table(cdr, table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); -int flecs_script_check_node( - ecs_script_eval_visitor_t *v, - ecs_script_node_t *node); + if (cdr->flags & EcsIdIsSparse) { + int32_t j; + for (j = 0; j < count; j ++) { + flecs_invoke_hook(world, table, tr, 1, row, &entities[j], + id, ti, EcsOnSet, on_set); + } + } else { + ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); + if (on_set) { + flecs_invoke_hook(world, table, tr, count, row, entities, + id, ti, EcsOnSet, on_set); + } + } + } + } -int flecs_script_check_scope( - ecs_script_eval_visitor_t *v, - ecs_script_scope_t *node); + /* Run OnSet notifications */ + if (table->flags & EcsTableHasOnSet && ids->count) { + flecs_emit(world, world, 0, &(ecs_event_desc_t) { + .event = EcsOnSet, + .ids = ids, + .table = table, + .offset = row, + .count = count, + .observable = world + }); + } +} -/* Functions shared between check and eval visitor */ +void flecs_record_add_flag( + ecs_record_t *record, + uint32_t flag) +{ + if (flag == EcsEntityIsTraversable) { + if (!(record->row & flag)) { + ecs_table_t *table = record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_table_traversable_add(table, 1); + } + } + record->row |= flag; +} -int flecs_script_eval_scope( - ecs_script_eval_visitor_t *v, - ecs_script_scope_t *node); - -int flecs_script_eval_id( - ecs_script_eval_visitor_t *v, - void *node, - ecs_script_id_t *id); - -int flecs_script_eval_using( - ecs_script_eval_visitor_t *v, - ecs_script_using_t *node); - -int flecs_script_eval_const( - ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node); - -ecs_entity_t flecs_script_find_entity_action( - const ecs_world_t *world, - const char *path, - void *ctx); - -#endif - -/** - * @file addons/script/template.h - * @brief Script template implementation. - */ - -#ifndef FLECS_SCRIPT_TEMPLATE_H -#define FLECS_SCRIPT_TEMPLATE_H - -extern ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); - -struct ecs_script_template_t { - /* Template handle */ - ecs_entity_t entity; - - /* Template AST node */ - ecs_script_template_node_t *node; - - /* Hoisted using statements */ - ecs_vec_t using_; - - /* Hoisted variables */ - ecs_script_vars_t *vars; - - /* Default values for props */ - ecs_vec_t prop_defaults; - - /* Type info for template component */ - const ecs_type_info_t *type_info; -}; - -#define ECS_TEMPLATE_SMALL_SIZE (36) - -/* Event used for deferring template instantiation */ -typedef struct EcsScriptTemplateSetEvent { - ecs_entity_t template_entity; - ecs_entity_t *entities; - void *data; - int32_t count; - - /* Storage for small template types */ - int64_t _align; /* Align data storage to 8 bytes */ - char data_storage[ECS_TEMPLATE_SMALL_SIZE]; - ecs_entity_t entity_storage; -} EcsScriptTemplateSetEvent; - -int flecs_script_eval_template( - ecs_script_eval_visitor_t *v, - ecs_script_template_node_t *template); - -ecs_script_template_t* flecs_script_template_init( - ecs_script_impl_t *script); - -void flecs_script_template_fini( - ecs_script_impl_t *script, - ecs_script_template_t *template); - -void flecs_script_template_import( - ecs_world_t *world); - -#endif - - -struct ecs_script_runtime_t { - ecs_allocator_t allocator; - ecs_expr_stack_t expr_stack; - ecs_stack_t stack; - ecs_vec_t using; - ecs_vec_t with; - ecs_vec_t with_type_info; - ecs_vec_t annot; -}; - -ecs_script_t* flecs_script_new( - ecs_world_t *world); - -ecs_script_scope_t* flecs_script_scope_new( - ecs_script_parser_t *parser); - -int flecs_script_visit_free( - ecs_script_t *script); - -ecs_script_vars_t* flecs_script_vars_push( - ecs_script_vars_t *parent, - ecs_stack_t *stack, - ecs_allocator_t *allocator); - -int flecs_terms_parse( - ecs_script_t *script, - ecs_term_t *terms, - int32_t *term_count_out); +void flecs_add_flag( + ecs_world_t *world, + ecs_entity_t entity, + uint32_t flag) +{ + ecs_record_t *record = flecs_entities_get_any(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_record_add_flag(record, flag); +} -const char* flecs_id_parse( - const ecs_world_t *world, - const char *name, - const char *expr, - ecs_id_t *id); +/* -- Public functions -- */ -const char* flecs_term_parse( +bool ecs_commit( ecs_world_t *world, - const char *name, - const char *expr, - ecs_term_t *term, - char *token_buffer); - -ecs_script_runtime_t* flecs_script_runtime_get( - ecs_world_t *world); + ecs_entity_t entity, + ecs_record_t *record, + ecs_table_t *table, + const ecs_type_t *added, + const ecs_type_t *removed) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, + "commit cannot be called on stage or while world is deferred"); -void flecs_script_register_builtin_functions( - ecs_world_t *world); + ecs_table_t *src_table = NULL; + if (!record) { + record = flecs_entities_get(world, entity); + src_table = record->table; + } -void flecs_function_import( - ecs_world_t *world); + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; -int flecs_script_check( - const ecs_script_t *script, - const ecs_script_eval_desc_t *desc); + if (added) { + diff.added = *added; + diff.added_flags = table->flags & EcsTableAddEdgeFlags; + } + if (removed) { + diff.removed = *removed; + if (src_table) { + diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; + } + } -#endif // FLECS_SCRIPT -#endif // FLECS_SCRIPT_PRIVATE_H + ecs_defer_begin(world); + flecs_commit(world, entity, record, table, &diff, true, 0); + ecs_defer_end(world); -#endif + return src_table != table; +error: + return false; +} -static -const ecs_entity_t* flecs_bulk_new( +ecs_entity_t ecs_set_with( ecs_world_t *world, - ecs_table_t *table, - const ecs_entity_t *entities, - ecs_type_t *component_ids, - int32_t count, - void **c_info, - bool move, - int32_t *row_out, - ecs_table_diff_t *diff); - -typedef struct { - const ecs_type_info_t *ti; - void *ptr; -} flecs_component_ptr_t; + 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; +} -static -flecs_component_ptr_t flecs_table_get_component( - ecs_table_t *table, - int32_t column_index, - int32_t row) +ecs_id_t ecs_get_with( + const ecs_world_t *world) { - 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_ELEM(column->data, column->ti->size, row) - }; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + return stage->with; error: - return (flecs_component_ptr_t){0}; + return 0; } static -flecs_component_ptr_t flecs_get_component_ptr( - ecs_table_t *table, - int32_t row, - ecs_id_record_t *idr) +void flecs_add_to_root_table( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t e) { - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - - if (!idr) { - return (flecs_component_ptr_t){0}; - } + flecs_poly_assert(world, ecs_world_t); - if (idr->flags & EcsIdIsSparse) { - ecs_entity_t entity = ecs_table_entities(table)[row]; - return (flecs_component_ptr_t){ - .ti = idr->type_info, - .ptr = flecs_sparse_get_any(idr->sparse, 0, entity) - }; + if (world->flags & EcsWorldMultiThreaded) { + if (flecs_defer_new(stage, e)) { + return; + } + ecs_abort(ECS_INTERNAL_ERROR, NULL); /* Can't happen */ } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr || (tr->column == -1)) { - return (flecs_component_ptr_t){0}; - } + ecs_record_t *r = flecs_entities_get(world, e); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); - return flecs_table_get_component(table, tr->column, row); + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + flecs_new_entity(world, e, r, &world->store.root, &diff, false, 0); + ecs_assert(r->table == &world->store.root, ECS_INTERNAL_ERROR, NULL); } -static -void* flecs_get_component( - ecs_table_t *table, - int32_t row, - ecs_id_record_t *idr) +ecs_entity_t ecs_new( + ecs_world_t *world) { - return flecs_get_component_ptr(table, row, idr).ptr; + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_entity_t e = flecs_new_id(world); + flecs_add_to_root_table(world, stage, e); + return e; } -void* flecs_get_base_component( - const ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_id_record_t *idr, - int32_t recur_depth) +ecs_entity_t ecs_new_low_id( + ecs_world_t *world) { - ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, - "cycle detected in IsA relationship"); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - /* Table (and thus entity) does not have component, look for base */ - if (!(table->flags & EcsTableHasIsA)) { - return NULL; + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (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 (!(idr->flags & EcsIdOnInstantiateInherit)) { - return NULL; + ecs_entity_t e = 0; + if (world->info.last_component_id < FLECS_HI_COMPONENT_ID) { + do { + e = world->info.last_component_id ++; + } while (ecs_exists(world, e) && e <= FLECS_HI_COMPONENT_ID); } - /* Exclude Name */ - if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { - return NULL; + if (!e || e >= FLECS_HI_COMPONENT_ID) { + /* If the low component ids are depleted, return a regular entity id */ + e = ecs_new(world); + } else { + flecs_entities_ensure(world, e); + flecs_add_to_root_table(world, stage, e); } - /* 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); + return e; +error: + return 0; +} - 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; +ecs_entity_t ecs_new_w_id( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_entity_t entity = ecs_new(world); + ecs_add_id(world, entity, id); + return entity; +error: + return 0; +} - do { - ecs_id_t pair = ids[i ++]; - ecs_entity_t base = ecs_pair_second(world, pair); +ecs_entity_t ecs_new_w_table( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_record_t *r = flecs_entities_get(world, base); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_stage_from_world(&world); + ecs_entity_t entity = flecs_new_id(world); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; + if (table->flags & EcsTableHasIsA) { + flags |= EcsTableHasOnAdd; + } - table = r->table; - if (!table) { - continue; - } + ecs_table_diff_t table_diff = { + .added = table->type, + .added_flags = flags + }; - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - ptr = flecs_get_base_component(world, table, id, idr, - recur_depth + 1); - } else { - if (idr->flags & EcsIdIsSparse) { - return flecs_sparse_get_any(idr->sparse, 0, base); - } else { - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_table_get_component(table, tr->column, row).ptr; - } - } - } while (!ptr && (i < end)); + flecs_new_entity(world, entity, r, table, &table_diff, true, 0); - return ptr; + return entity; error: - return NULL; + return 0; } static -void flecs_instantiate_slot( +void flecs_copy_id( 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_record_t *r, + ecs_id_t id, + size_t size, + void *dst_ptr, + void *src_ptr, + const ecs_type_info_t *ti) { - 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_path(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); - } + ecs_check(dst_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - if (slot == 0) { - char *slot_of_str = ecs_get_path(world, slot_of); - char *slot_str = ecs_get_path(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_copy_t copy = ti->hooks.copy; + if (copy) { + copy(dst_ptr, src_ptr, 1, ti); + } else { + ecs_os_memcpy(dst_ptr, src_ptr, flecs_utosize(size)); + } - ecs_add_pair(world, parent, slot, child); - break; - } + flecs_table_mark_dirty(world, r->table, id); - depth ++; - } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); - - if (parent == 0) { - char *slot_of_str = ecs_get_path(world, slot_of); - char *slot_str = ecs_get_path(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_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + 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); } - error: return; } +/* Traverse table graph by either adding or removing identifiers parsed from the + * passed in expression. */ static -ecs_table_t* flecs_find_table_add( +int flecs_traverse_from_expr( ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_table_diff_builder_t *diff) + const char *name, + const char *expr, + ecs_vec_t *ids) { - 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; -} +#ifdef FLECS_QUERY_DSL + const char *ptr = expr; + if (ptr) { + ecs_id_t id = 0; + while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { + if (!id) { + break; + } -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 (!ecs_id_is_valid(world, id)) { + char *idstr = ecs_id_str(world, id); + ecs_parser_error(name, expr, (ptr - expr), + "id %s is invalid for add expression", idstr); + ecs_os_free(idstr); + goto error; + } -static -int32_t flecs_child_type_insert( - ecs_type_t *type, - void **component_data, - ecs_id_t id) -{ - int32_t i, count = type->count; - for (i = 0; i < count; i ++) { - ecs_id_t cur = type->array[i]; - if (cur == id) { - /* Id is already part of type */ - return -1; + ecs_vec_append_t(&world->allocator, ids, ecs_id_t)[0] = id; } - if (cur > id) { - /* A larger id was found so id can't be part of the type. */ - break; + if (!ptr) { + goto error; } } + return 0; +#else + (void)world; + (void)name; + (void)expr; + (void)ids; + ecs_err("cannot parse component expression: script addon required"); + goto error; +#endif +error: + return -1; +} - /* Assumes that the array has enough memory to store the new element. */ - int32_t to_move = type->count - i; - if (to_move) { - ecs_os_memmove(&type->array[i + 1], - &type->array[i], to_move * ECS_SIZEOF(ecs_id_t)); - ecs_os_memmove(&component_data[i + 1], - &component_data[i], to_move * ECS_SIZEOF(void*)); - } - - component_data[i] = NULL; - type->array[i] = id; - type->count ++; - - return i; -} - +/* 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_instantiate_children( +void flecs_defer_from_expr( ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_table_t *child_table, - const ecs_instantiate_ctx_t *ctx) + ecs_entity_t entity, + const char *name, + const char *expr) { - if (!ecs_table_count(child_table)) { - return; - } - - ecs_type_t type = child_table->type; - ecs_data_t *child_data = &child_table->data; - - ecs_entity_t slot_of = 0; - ecs_entity_t *ids = type.array; - int32_t type_count = type.count; - - /* Instantiate child table for each instance */ - - /* Create component array for creating the table */ - ecs_table_diff_t diff = { .added = {0}}; - diff.added.array = ecs_os_alloca_n(ecs_entity_t, type_count + 1); - void **component_data = ecs_os_alloca_n(void*, type_count + 1); - - /* Copy in component identifiers. Find the base index in the component - * array, since we'll need this to replace the base with the instance id */ - int j, i, childof_base_index = -1; - for (i = 0; i < type_count; i ++) { - ecs_id_t id = ids[i]; - - /* If id has DontInherit flag don't inherit it, except for the name - * and ChildOf pairs. The name is preserved so applications can lookup - * the instantiated children by name. The ChildOf pair is replaced later - * with the instance parent. */ - if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && - ECS_PAIR_FIRST(id) != EcsChildOf) - { - ecs_table_record_t *tr = &child_table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - } - - /* If child is a slot, keep track of which parent to add it to, but - * don't add slot relationship to child of instance. If this is a child - * of a prefab, keep the SlotOf relationship intact. */ - if (!(table->flags & EcsTableIsPrefab)) { - if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { - ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); - slot_of = ecs_pair_second(world, id); - continue; +#ifdef FLECS_QUERY_DSL + const char *ptr = expr; + if (ptr) { + ecs_id_t id = 0; + while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { + if (!id) { + break; } + ecs_add_id(world, entity, id); } + } +#else + (void)world; + (void)entity; + (void)name; + (void)expr; + ecs_err("cannot parse component expression: script addon required"); +#endif +} - /* Keep track of the element that creates the ChildOf relationship with - * the prefab parent. We need to replace this element to make sure the - * created children point to the instance and not the prefab */ - if (ECS_HAS_RELATION(id, EcsChildOf) && - (ECS_PAIR_SECOND(id) == (uint32_t)base)) { - childof_base_index = diff.added.count; - } +/* If operation is not deferred, add components by finding the target + * table and moving the entity towards it. */ +static +int flecs_traverse_add( + ecs_world_t *world, + ecs_entity_t result, + const char *name, + const ecs_entity_desc_t *desc, + ecs_entity_t scope, + ecs_id_t with, + bool new_entity, + bool name_assigned) +{ + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_vec_t ids; - /* If this is a pure override, make sure we have a concrete version of the - * component. This relies on the fact that overrides always come after - * concrete components in the table type so we can check the components - * that have already been added to the child table type. */ - if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { - ecs_id_t concreteId = id & ~ECS_AUTO_OVERRIDE; - flecs_child_type_insert(&diff.added, component_data, concreteId); - continue; + /* Add components from the 'add_expr' expression. Look up before naming + * entity, so that expression can't resolve to self. */ + ecs_vec_init_t(&world->allocator, &ids, ecs_id_t, 0); + if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { + if (flecs_traverse_from_expr(world, name, desc->add_expr, &ids)) { + goto error; } + } - int32_t storage_index = ecs_table_type_to_column_index(child_table, i); - if (storage_index != -1) { - component_data[diff.added.count] = - child_data->columns[storage_index].data; + /* Set symbol */ + 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, "%s (provided) vs. %s (existing)", + desc->symbol, sym); } else { - component_data[diff.added.count] = NULL; + ecs_set_symbol(world, result, desc->symbol); } - - diff.added.array[diff.added.count] = id; - diff.added.count ++; - diff.added_flags |= flecs_id_flags_get(world, id); } - /* 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) { - if (flecs_child_type_insert( - &diff.added, component_data, EcsPrefab) != -1) - { - childof_base_index ++; + /* If a name is provided but not yet assigned, add the Name component */ + if (name && !name_assigned) { + if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { + if (name[0] == '#') { + /* Numerical ids should always return, unless it's invalid */ + goto error; + } } + } else if (new_entity && scope) { + ecs_add_pair(world, result, EcsChildOf, scope); } - /* Instantiate the prefab child table for each new instance */ - const ecs_entity_t *instances = ecs_table_entities(table); - int32_t child_count = ecs_table_count(child_table); - ecs_entity_t *child_ids = flecs_walloc_n(world, ecs_entity_t, child_count); + /* Find existing table */ + ecs_table_t *src_table = NULL, *table = NULL; + ecs_record_t *r = flecs_entities_get(world, result); + table = r->table; - for (i = row; i < count + row; i ++) { - ecs_entity_t instance = instances[i]; - ecs_table_t *i_table = NULL; - - /* Replace ChildOf element in the component array with instance id */ - diff.added.array[childof_base_index] = ecs_pair(EcsChildOf, instance); + /* Add components from the 'add' array */ + if (desc->add) { + int32_t i = 0; + ecs_id_t id; - /* Find or create table */ - i_table = flecs_table_find_or_create(world, &diff.added); + while ((id = desc->add[i ++])) { + table = flecs_find_table_add(world, table, id, &diff); + } + } - ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(i_table->type.count == diff.added.count, - ECS_INTERNAL_ERROR, NULL); + /* Add components from the 'set' array */ + if (desc->set) { + int32_t i = 0; + ecs_id_t id; - /* 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. */ - const ecs_entity_t *children = ecs_table_entities(child_table); + while ((id = desc->set[i ++].type)) { + table = flecs_find_table_add(world, table, id, &diff); + } + } -#ifdef FLECS_DEBUG - for (j = 0; j < child_count; j ++) { - ecs_entity_t child = children[j]; - ecs_check(child != instance, ECS_INVALID_PARAMETER, - "cycle detected in IsA relationship"); + /* Add ids from .expr */ + { + int32_t i, count = ecs_vec_count(&ids); + ecs_id_t *expr_ids = ecs_vec_first(&ids); + for (i = 0; i < count; i ++) { + table = flecs_find_table_add(world, table, expr_ids[i], &diff); } -#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 + } - /* Attempt to reserve ids for children that have the same offset from - * the instance as from the base prefab. This ensures stable ids for - * instance children, even across networked applications. */ - ecs_instantiate_ctx_t ctx_cur = {base, instance}; - if (ctx) { - ctx_cur = *ctx; + /* 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 (new_entity) { + if (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); + } + } - for (j = 0; j < child_count; j ++) { - if ((uint32_t)children[j] < (uint32_t)ctx_cur.root_prefab) { - /* Child id is smaller than root prefab id, can't use offset */ - child_ids[j] = ecs_new(world); - continue; - } + /* 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]); + } - /* Get prefab offset, ignore lifecycle generation count */ - ecs_entity_t prefab_offset = - (uint32_t)children[j] - (uint32_t)ctx_cur.root_prefab; - ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL); + /* Set component values */ + if (desc->set) { + table = r->table; + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i = 0, row = ECS_RECORD_TO_ROW(r->row); + const ecs_value_t *v; + + flecs_defer_begin(world, world->stages[0]); - /* First check if any entity with the desired id exists */ - ecs_entity_t instance_child = (uint32_t)ctx_cur.root_instance + prefab_offset; - ecs_entity_t alive_id = flecs_entities_get_alive(world, instance_child); - if (alive_id && flecs_entities_is_alive(world, alive_id)) { - /* Alive entity with requested id exists, can't use offset id */ - child_ids[j] = ecs_new(world); + while ((void)(v = &desc->set[i ++]), v->type) { + if (!v->ptr) { continue; } - - /* Id is not in use. Make it alive & match the generation of the instance. */ - instance_child = ctx_cur.root_instance + prefab_offset; - flecs_entities_make_alive(world, instance_child); - flecs_entities_ensure(world, instance_child); - ecs_assert(ecs_is_alive(world, instance_child), ECS_INTERNAL_ERROR, NULL); - child_ids[j] = instance_child; - } - - /* Create children */ - int32_t child_row; - const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids, - &diff.added, child_count, component_data, false, &child_row, &diff); - - /* 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_assert(ECS_RECORD_TO_ROW(r->row) == row, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cdr = flecs_components_get(world, v->type); + flecs_component_ptr_t ptr = flecs_get_component_ptr(table, row, cdr); + ecs_check(ptr.ptr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = cdr->type_info; + flecs_copy_id(world, r, v->type, + flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } - /* 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, &ctx_cur); - } + flecs_defer_end(world, world->stages[0]); } - flecs_wfree_n(world, ecs_entity_t, child_count, child_ids); + flecs_table_diff_builder_fini(world, &diff); + ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); + return 0; error: - return; + flecs_table_diff_builder_fini(world, &diff); + ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); + return -1; } -void flecs_instantiate( +/* 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 base, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_instantiate_ctx_t *ctx) + 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_record_t *record = flecs_entities_get_any(world, base); - ecs_table_t *base_table = record->table; - if (!base_table) { - return; + const char *sep = desc->sep; + const char *root_sep = desc->root_sep; + + /* If this is a new entity without a name, add the scope. If a name is + * provided, the scope will be added by the add_path_w_sep function */ + if (flecs_new_entity) { + if (flecs_new_entity && scope && !name && !name_assigned) { + ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); + } + + if (with) { + ecs_add_id(world, entity, with); + } } - /* If prefab has union relationships, also set them on instance */ - if (base_table->flags & EcsTableHasUnion) { - const ecs_entity_t *entities = ecs_table_entities(table); - ecs_id_record_t *union_idr = flecs_id_record_get(world, - ecs_pair(EcsWildcard, EcsUnion)); - ecs_assert(union_idr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = flecs_id_record_get_table( - union_idr, base_table); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i = 0, j, union_count = 0; - do { - ecs_id_t id = base_table->type.array[i]; - if (ECS_PAIR_SECOND(id) == EcsUnion) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t tgt = ecs_get_target(world, base, rel, 0); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + /* Add components from the 'add' id array */ + if (desc->add) { + int32_t i = 0; + ecs_id_t id; - for (j = row; j < (row + count); j ++) { - ecs_add_pair(world, entities[j], rel, tgt); + while ((id = desc->add[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; } - - union_count ++; } - - i ++; - } while (union_count < tr->count); + if (defer) { + ecs_add_id(world, entity, id); + } + } } - if (!(base_table->flags & EcsTableIsPrefab)) { - /* Don't instantiate children from base entities that aren't prefabs */ - return; + /* Set component values */ + if (desc->set) { + int32_t i = 0; + const ecs_value_t *v; + while ((void)(v = &desc->set[i ++]), v->type) { + if (v->ptr) { + ecs_set_id(world, entity, v->type, 0, v->ptr); + } else { + ecs_add_id(world, entity, v->type); + } + } } - 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)) { - ecs_os_perf_trace_push("flecs.instantiate"); - 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, ctx); - } - ecs_os_perf_trace_pop("flecs.instantiate"); + /* Add components from the 'add_expr' expression */ + if (desc->add_expr) { + flecs_defer_from_expr(world, entity, name, desc->add_expr); } -} -static -void flecs_sparse_on_add( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *added, - bool construct) -{ - int32_t i, j; - for (i = 0; i < added->count; i ++) { - ecs_id_t id = added->array[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr && idr->flags & EcsIdIsSparse) { - const ecs_type_info_t *ti = idr->type_info; - ecs_xtor_t ctor = ti->hooks.ctor; - ecs_iter_action_t on_add = ti->hooks.on_add; - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - void *ptr = flecs_sparse_ensure(idr->sparse, 0, e); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (construct && ctor) { - ctor(ptr, 1, ti); - } + int32_t thread_count = ecs_get_stage_count(world); - if (on_add) { - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, table); - flecs_invoke_hook(world, table, tr, count, row, - &entities[row + j],id, ti, EcsOnAdd, on_add); - } + /* 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); } } } -} -static -void flecs_sparse_on_remove( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *removed) -{ - int32_t i, j; - for (i = 0; i < removed->count; i ++) { - ecs_id_t id = removed->array[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr && idr->flags & EcsIdIsSparse) { - const ecs_type_info_t *ti = idr->type_info; - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, table); - ecs_xtor_t dtor = ti->hooks.dtor; - ecs_iter_action_t on_remove = ti->hooks.on_remove; - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - if (on_remove) { - flecs_invoke_hook(world, table, tr, count, row, - &entities[row + j], id, ti, EcsOnRemove, on_remove); - } - void *ptr = flecs_sparse_remove_fast(idr->sparse, 0, e); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - if (dtor) { - dtor(ptr, 1, ti); - } - } - } + /* Set name */ + if (name && !name_assigned) { + ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } } -static -void flecs_union_on_add( +ecs_entity_t ecs_entity_init( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *added) + const ecs_entity_desc_t *desc) { - int32_t i, j; - for (i = 0; i < added->count; i ++) { - ecs_id_t id = added->array[i]; - if (ECS_IS_PAIR(id)) { - ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); - ecs_id_record_t *idr = flecs_id_record_get(world, wc); - if (idr && idr->flags & EcsIdIsUnion) { - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - flecs_switch_set( - idr->sparse, (uint32_t)e, ecs_pair_second(world, id)); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_entity_desc_t was not initialized to zero"); + + 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; + +#ifdef FLECS_DEBUG + if (desc->add) { + ecs_id_t id; + int32_t i = 0; + while ((id = desc->add[i ++])) { + if (ECS_HAS_ID_FLAG(id, PAIR) && + (ECS_PAIR_FIRST(id) == EcsChildOf)) + { + if (desc->name) { + ecs_check(false, ECS_INVALID_PARAMETER, "%s: cannot set parent in " + "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent", + desc->name); + } else { + ecs_check(false, ECS_INVALID_PARAMETER, "cannot set parent in " + "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent"); } } } } -} +#endif -static -void flecs_union_on_remove( - ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - const ecs_type_t *removed) -{ - int32_t i, j; - for (i = 0; i < removed->count; i ++) { - ecs_id_t id = removed->array[i]; - if (ECS_IS_PAIR(id)) { - ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); - ecs_id_record_t *idr = flecs_id_record_get(world, wc); - if (idr && idr->flags & EcsIdIsUnion) { - const ecs_entity_t *entities = ecs_table_entities(table); - for (j = 0; j < count; j ++) { - ecs_entity_t e = entities[row + j]; - flecs_switch_reset(idr->sparse, (uint32_t)e); - } + const char *name = desc->name; + const char *sep = desc->sep; + if (!sep) { + sep = "."; + } + + if (name) { + if (!name[0]) { + name = NULL; + } else if (flecs_name_is_id(name)){ + ecs_entity_t id = flecs_name_to_id(name); + if (!id) { + return 0; } + if (result && (id != result)) { + ecs_err("name id conflicts with provided id"); + return 0; + } + name = NULL; + result = id; } } -} -static -void flecs_notify_on_add( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - const ecs_table_diff_t *diff, - ecs_flags32_t flags, - ecs_flags64_t set_mask, - bool construct, - bool sparse) -{ - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_t *added = &diff->added; + const char *root_sep = desc->root_sep; + bool flecs_new_entity = false; + bool name_assigned = false; - if (added->count) { - ecs_flags32_t diff_flags = - diff->added_flags|(table->flags & EcsTableHasTraversable); - if (!diff_flags) { - return; + /* 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; + } } + } - if (sparse && (diff_flags & EcsTableHasSparse)) { - flecs_sparse_on_add(world, table, row, count, added, construct); + /* Parent field takes precedence over scope */ + if (desc->parent) { + scope = desc->parent; + ecs_check(ecs_is_valid(world, desc->parent), + ECS_INVALID_PARAMETER, "ecs_entity_desc_t::parent is not valid"); + } + + /* Find or create entity */ + if (!result) { + if (name) { + /* If add array contains a ChildOf pair, use it as scope instead */ + result = ecs_lookup_path_w_sep( + world, scope, name, sep, root_sep, false); + if (result) { + name_assigned = true; + } } - if (diff_flags & EcsTableHasUnion) { - flecs_union_on_add(world, table, row, count, added); + if (!result) { + if (desc->use_low_id) { + result = ecs_new_low_id(world); + } else { + result = ecs_new(world); + } + flecs_new_entity = true; + ecs_assert(ecs_get_type(world, result) != NULL, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_get_type(world, result)->count == 0, + ECS_INTERNAL_ERROR, NULL); } + } else { + /* Make sure provided id is either alive or revivable */ + ecs_make_alive(world, result); - if (diff_flags & (EcsTableHasOnAdd|EcsTableHasTraversable)) { - flecs_emit(world, world, set_mask, &(ecs_event_desc_t){ - .event = EcsOnAdd, - .ids = added, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world, - .flags = flags - }); + name_assigned = ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName); + if (name && name_assigned) { + /* If entity has name, verify that name matches. The name provided + * to the function could either have been relative to the current + * scope, or fully qualified. */ + char *path; + ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; + if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { + /* Fully qualified name was provided, so make sure to + * compare with fully qualified name */ + path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); + } else { + /* Relative name was provided, so make sure to compare with + * relative name */ + if (!sep || sep[0]) { + path = ecs_get_path_w_sep(world, scope, result, sep, ""); + } else { + /* Safe, only freed when sep is valid */ + path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); + } + } + if (path) { + if (ecs_os_strcmp(path, name)) { + /* Mismatching name */ + ecs_err("existing entity '%s' is initialized with " + "conflicting name '%s'", path, name); + if (!sep || sep[0]) { + ecs_os_free(path); + } + return 0; + } + if (!sep || sep[0]) { + ecs_os_free(path); + } + } + } + } + + ecs_assert(name_assigned == ecs_has_pair( + world, result, ecs_id(EcsIdentifier), EcsName), + ECS_INTERNAL_ERROR, NULL); + + if (ecs_is_deferred(world)) { + flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, + scope, with, flecs_new_entity, name_assigned); + } else { + if (flecs_traverse_add(world, result, name, desc, + scope, with, flecs_new_entity, name_assigned)) + { + return 0; } } + + return result; +error: + return 0; } -void flecs_notify_on_remove( +const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - const ecs_table_diff_t *diff) + const ecs_bulk_desc_t *desc) { - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_t *removed = &diff->removed; - ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); + flecs_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, + "ecs_bulk_desc_t was not initialized to zero"); - if (removed->count) { - ecs_flags32_t diff_flags = - diff->removed_flags|(table->flags & EcsTableHasTraversable); - if (!diff_flags) { - return; - } + const ecs_entity_t *entities = desc->entities; + int32_t count = desc->count; - if (diff_flags & EcsTableHasUnion) { - flecs_union_on_remove(world, table, row, count, removed); + 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 ++) { + flecs_entities_ensure(world, entities[i]); } + } - if (diff_flags & (EcsTableHasOnRemove|EcsTableHasTraversable)) { - flecs_emit(world, world, 0, &(ecs_event_desc_t) { - .event = EcsOnRemove, - .ids = removed, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world - }); - } + ecs_type_t ids; + ecs_table_t *table = desc->table; + if (!table) { + table = &world->store.root; + } - if (diff_flags & EcsTableHasSparse) { - flecs_sparse_on_remove(world, table, row, count, removed); + if (!table->type.count) { + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + int32_t i = 0; + ecs_id_t id; + while ((id = desc->ids[i])) { + table = flecs_find_table_add(world, table, id, &diff); + i ++; } + + ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); + ids.count = i; + + ecs_table_diff_t table_diff; + flecs_table_diff_build_noalloc(&diff, &table_diff); + flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, + &table_diff); + flecs_table_diff_builder_fini(world, &diff); + } else { + ecs_table_diff_t diff = { + .added.array = table->type.array, + .added.count = table->type.count + }; + ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; + flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, + &diff); } + + if (!sparse_count) { + return entities; + } else { + /* Refetch entity ids, in case the underlying array was reallocated */ + entities = flecs_entities_ids(world); + return &entities[sparse_count]; + } +error: + return NULL; } static -void flecs_update_name_index( +void flecs_check_component( ecs_world_t *world, - ecs_table_t *src, - ecs_table_t *dst, - int32_t offset, - int32_t count) + ecs_entity_t result, + const EcsComponent *ptr, + ecs_size_t size, + ecs_size_t alignment) { - 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; + if (ptr->size != size) { + char *path = ecs_get_path(world, result); + ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); + ecs_os_free(path); + } + if (ptr->alignment != alignment) { + char *path = ecs_get_path(world, result); + ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); + ecs_os_free(path); } +} - 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; +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, + "ecs_component_desc_t was not initialized to 0"); + + /* 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; + } } - EcsIdentifier *names = ecs_table_get_pair(world, - dst, EcsIdentifier, EcsName, offset); - ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_suspend_readonly_state_t readonly_state; + world = flecs_suspend_readonly(world, &readonly_state); - int32_t i; - const ecs_entity_t *entities = &ecs_table_entities(dst)[offset]; - for (i = 0; i < count; i ++) { - ecs_entity_t e = entities[i]; - EcsIdentifier *name = &names[i]; + bool new_component = true; + if (!result) { + result = ecs_new_low_id(world); + } else { + ecs_make_alive(world, result); + new_component = ecs_has(world, result, EcsComponent); + } - uint64_t index_hash = name->index_hash; - if (index_hash) { - flecs_name_index_remove(src_index, e, index_hash); + EcsComponent *ptr = ecs_ensure(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 registered", + ecs_get_name(world, result)); + } else { + ecs_trace("#[green]component#[reset] %s registered", + ecs_get_name(world, result)); + } } - const char *name_str = name->value; - if (name_str) { - ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); + } else { + flecs_check_component(world, result, ptr, + desc->type.size, desc->type.alignment); + } - flecs_name_index_ensure( - dst_index, e, name_str, name->length, name->hash); - name->index = dst_index; - } + if (desc->type.name && new_component) { + ecs_entity(world, { .id = result, .name = desc->type.name }); + } + + ecs_modified(world, result, EcsComponent); + + if (desc->type.size && + !ecs_id_in_use(world, result) && + !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) + { + ecs_set_hooks_id(world, result, &desc->type.hooks); + } + + if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { + world->info.last_component_id = result + 1; } + + flecs_resume_readonly(world, &readonly_state); + + ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + + return result; +error: + return 0; } -static -ecs_record_t* flecs_new_entity( +const ecs_entity_t* ecs_bulk_new_w_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, + int32_t count) { - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t row = flecs_table_append(world, table, entity, ctor, true); - record->table = table; - record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_assert(ecs_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); - flecs_notify_on_add( - world, table, NULL, row, 1, diff, evt_flags, 0, ctor, true); - ecs_assert(table == record->table, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *ids; + if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { + return ids; + } - return record; -} + ecs_table_t *table = &world->store.root; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + + if (id) { + table = flecs_find_table_add(world, table, id, &diff); + } + + ecs_table_diff_t td; + flecs_table_diff_build_noalloc(&diff, &td); + ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); + flecs_table_diff_builder_fini(world, &diff); + flecs_defer_end(world, stage); -static int commit_indent = 0; + return ids; +error: + return NULL; +} -static -void flecs_move_entity( +void ecs_clear( 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_entity_t entity) { - 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_table_count(src_table) > 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_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); - - /* Append new row to destination table */ - int32_t dst_row = flecs_table_append(world, dst_table, entity, - false, false); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - /* Invoke remove actions for removed components */ - flecs_notify_on_remove(world, src_table, dst_table, src_row, 1, diff); + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_clear(stage, entity)) { + return; + } - /* Copy entity & components from src_table to dst_table */ - flecs_table_move(world, entity, entity, dst_table, dst_row, - src_table, src_row, ctor); - ecs_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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, - evt_flags, 0, ctor, true); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (table->type.count) { + ecs_table_diff_t diff = { + .removed = table->type, + .removed_flags = table->flags & EcsTableRemoveEdgeFlags + }; - flecs_update_name_index(world, src_table, dst_table, dst_row, 1); + flecs_commit(world, entity, r, &world->store.root, &diff, false, 0); + } - ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); + flecs_defer_end(world, stage); error: return; } static -void flecs_delete_entity( +void flecs_throw_invalid_delete( 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); - flecs_table_delete(world, table, row, true); + ecs_id_t id) +{ + char *id_str = NULL; + if (!(world->flags & EcsWorldQuit)) { + id_str = ecs_id_str(world, id); + ecs_err("(OnDelete, Panic) constraint violated while deleting %s", + id_str); + ecs_os_free(id_str); + #ifndef FLECS_SOFT_ASSERT + ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); + #endif + } } -/* 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( +void flecs_marked_id_push( ecs_world_t *world, - ecs_type_t *ids) + ecs_component_record_t* cdr, + ecs_entity_t action, + bool delete_id) { - if (!ids) { - return; - } + ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, + &world->store.marked_ids, ecs_marked_id_t); - 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)); - } + m->cdr = cdr; + m->id = cdr->id; + m->action = action; + m->delete_id = delete_id; - flecs_monitor_mark_dirty(world, id); - } + flecs_component_claim(world, cdr); } static -void flecs_update_component_monitors( +void flecs_id_mark_for_delete( 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); -} + ecs_component_record_t *cdr, + ecs_entity_t action, + bool delete_id); static -void flecs_commit( +void flecs_targets_mark_for_delete( 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_table_t *table) { - 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 && src_table->flags & EcsTableHasUnion) { - diff->added_flags |= EcsIdIsUnion; - flecs_notify_on_add(world, src_table, src_table, - ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, 0, - construct, true); + ecs_component_record_t *cdr; + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t i, count = ecs_table_count(table); + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (!r) { + continue; } - flecs_journal_end(); - return; - } - commit_indent += 2; - - ecs_os_perf_trace_push("flecs.commit"); - - 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; + /* 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; } - 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_entity_t e = entities[i]; + if (flags & EcsEntityIsId) { + if ((cdr = flecs_components_get(world, e))) { + flecs_id_mark_for_delete(world, cdr, + ECS_ID_ON_DELETE(cdr->flags), true); + } + if ((cdr = flecs_components_get(world, ecs_pair(e, EcsWildcard)))) { + flecs_id_mark_for_delete(world, cdr, + ECS_ID_ON_DELETE(cdr->flags), true); + } + } + if (flags & EcsEntityIsTarget) { + if ((cdr = flecs_components_get(world, ecs_pair(EcsWildcard, e)))) { + flecs_id_mark_for_delete(world, cdr, + ECS_ID_ON_DELETE_TARGET(cdr->flags), true); + } + if ((cdr = flecs_components_get(world, ecs_pair(EcsFlag, e)))) { + flecs_id_mark_for_delete(world, cdr, + ECS_ID_ON_DELETE_TARGET(cdr->flags), true); + } } } +} - /* 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); +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; } - - commit_indent -=2 ; - - ecs_os_perf_trace_pop("flecs.commit"); - -error: - flecs_journal_end(); - return; + return false; } static -const ecs_entity_t* flecs_bulk_new( - ecs_world_t *world, +ecs_entity_t flecs_get_delete_action( 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) + const ecs_table_record_t *tr, + ecs_entity_t action, + bool delete_target) { - int32_t sparse_count = 0; - if (!entities) { - sparse_count = flecs_entities_count(world); - entities = flecs_entities_new_ids(world, count); - } + ecs_entity_t result = action; + if (!result && delete_target) { + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + ecs_id_t id = cdr->id; - if (!table) { - return entities; - } + /* 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_component_record_t *idrr = (ecs_component_record_t*)trr->hdr.cache; + result = ECS_ID_ON_DELETE_TARGET(idrr->flags); + if (result == EcsDelete) { + /* Delete takes precedence over Remove */ + break; + } - ecs_type_t type = table->type; - if (!type.count) { - return entities; - } + 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; + } + } - ecs_type_t component_array = { 0 }; - if (!component_ids) { - component_ids = &component_array; - component_array.array = type.array; - component_array.count = type.count; + /* We should always have as many matching ids as tr->count */ + ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); + } + } while (--count); } - int32_t row = flecs_table_appendn(world, table, count, entities); + return result; +} - /* Update entity index. */ - int i; - for (i = 0; i < count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - r->table = table; - r->row = ECS_ROW_TO_RECORD(row + i, 0); +static +void flecs_update_monitors_for_delete( + ecs_world_t *world, + ecs_id_t id) +{ + flecs_update_component_monitors(world, NULL, &(ecs_type_t){ + .array = (ecs_id_t[]){id}, + .count = 1 + }); +} + +static +void flecs_id_mark_for_delete( + ecs_world_t *world, + ecs_component_record_t *cdr, + ecs_entity_t action, + bool delete_id) +{ + if (cdr->flags & EcsIdMarkedForDelete) { + return; } - flecs_defer_begin(world, world->stages[0]); + cdr->flags |= EcsIdMarkedForDelete; + flecs_marked_id_push(world, cdr, action, delete_id); - flecs_notify_on_add(world, table, NULL, row, count, diff, - (component_data == NULL) ? 0 : EcsEventNoOnSet, 0, true, true); + ecs_id_t id = cdr->id; - 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) { + 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(&cdr->cache, &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->flags & EcsTableMarkedForDelete) { continue; } - /* Find component in storage type */ - ecs_entity_t id = component_ids->array[c_i]; - const ecs_table_record_t *tr = flecs_table_record_get( - world, table, id); - ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, - "id is not a component"); - ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, - "id is not a component"); - ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, - "ids cannot be wildcards"); - - int32_t index = tr->column; - ecs_column_t *column = &table->data.columns[index]; - ecs_type_info_t *ti = column->ti; - int32_t size = ti->size; - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *ptr = ECS_ELEM(column->data, size, row); - - ecs_copy_t copy; - ecs_move_t move; - if (is_move && (move = ti->hooks.move)) { - move(ptr, src_ptr, count, ti); - } else if (!is_move && (copy = ti->hooks.copy)) { - copy(ptr, src_ptr, count, ti); - } else { - ecs_os_memcpy(ptr, src_ptr, size * count); - } - }; - - int32_t j, storage_count = table->column_count; - for (j = 0; j < storage_count; j ++) { - ecs_id_t id = flecs_column_id(table, j); - ecs_type_t set_type = { - .array = &id, - .count = 1 - }; + ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, + delete_target); - flecs_notify_on_set(world, table, row, count, &set_type, true); + /* 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); + } } } - flecs_defer_end(world, world->stages[0]); - - if (row_out) { - *row_out = row; + /* Same for empty tables */ + if (flecs_table_cache_empty_iter(&cdr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + tr->hdr.table->flags |= EcsTableMarkedForDelete; + } } - if (sparse_count) { - entities = flecs_entities_ids(world); - return &entities[sparse_count]; - } else { - return entities; + /* 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_component_record_t *cur = cdr; + if (ECS_PAIR_SECOND(id) == EcsWildcard) { + while ((cur = flecs_component_first_next(cur))) { + flecs_update_monitors_for_delete(world, cur->id); + } + } else { + ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + while ((cur = flecs_component_second_next(cur))) { + flecs_update_monitors_for_delete(world, cur->id); + } + } } } static -void flecs_add_id_w_record( +bool flecs_on_delete_mark( ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, ecs_id_t id, - bool construct) + ecs_entity_t action, + bool delete_id) { - 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. */ -} + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + /* If there's no component record, there's nothing to delete */ + return false; + } -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; + 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(cdr->flags); + } } - 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); + if (action == EcsPanic) { + /* This id is protected from deletion */ + flecs_throw_invalid_delete(world, id); + return false; + } - flecs_commit(world, entity, r, dst_table, &diff, true, 0); + flecs_id_mark_for_delete(world, cdr, action, delete_id); - flecs_defer_end(world, stage); + return true; } static -void flecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +void flecs_remove_from_table( + ecs_world_t *world, + ecs_table_t *table) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_remove(stage, entity, id)) { - return; - } + ecs_table_diff_t temp_diff = { .added = {0} }; + ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; + flecs_table_diff_builder_init(world, &diff); + ecs_table_t *dst_table = table; - 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); + /* 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_component_record_t *cdr = ids[i].cdr; - flecs_commit(world, entity, r, dst_table, &diff, true, 0); + if (!(tr = flecs_component_get_table(cdr, dst_table))) { + continue; + } - flecs_defer_end(world, stage); -} + t = tr->index; -void flecs_add_ids( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t *ids, - int32_t count) -{ - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; - flecs_table_diff_builder_init(world, &diff); + do { + ecs_id_t id = dst_table->type.array[t]; + ecs_table_t *tgt_table = flecs_table_traverse_remove( + world, dst_table, &id, &temp_diff); + ecs_assert(tgt_table != dst_table, ECS_INTERNAL_ERROR, NULL); + dst_table = tgt_table; + flecs_table_diff_build_append_table(world, &diff, &temp_diff); + } while (dst_table->type.count && (t = ecs_search_offset( + world, dst_table, t, cdr->id, NULL)) != -1); + } - ecs_table_t *table = ecs_get_table(world, entity); - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - table = flecs_find_table_add(world, table, id, &diff); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + + 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); + ecs_log_pop_3(); + } + + flecs_table_merge(world, dst_table, table); } - ecs_table_diff_t table_diff; - flecs_table_diff_build_noalloc(&diff, &table_diff); - flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); } static -flecs_component_ptr_t flecs_ensure( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t id, - ecs_record_t *r) +bool flecs_on_delete_clear_tables( + ecs_world_t *world) { - flecs_component_ptr_t dst = {0}; + /* 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_component_record_t *cdr = ids[i].cdr; + ecs_entity_t action = ids[i].action; - flecs_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, - "invalid component id specified for ensure"); + /* Empty all tables for id */ + { + ecs_table_cache_iter_t it; + if (flecs_table_cache_iter(&cdr->cache, &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; - ecs_id_record_t *idr = NULL; - ecs_table_t *table; - if ((table = r->table)) { - if (id < FLECS_HI_COMPONENT_ID) { - int16_t column_index = table->component_map[id]; - if (column_index > 0) { - ecs_column_t *column = &table->data.columns[column_index - 1]; - ecs_type_info_t *ti = column->ti; - dst.ptr = ECS_ELEM(column->data, ti->size, - ECS_RECORD_TO_ROW(r->row)); - dst.ti = ti; - return dst; - } else if (column_index < 0) { - column_index = flecs_ito(int16_t, -column_index - 1); - const ecs_table_record_t *tr = &table->_->records[column_index]; - idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdIsSparse) { - dst.ptr = flecs_sparse_get_any(idr->sparse, 0, entity); - dst.ti = idr->type_info; - return dst; + 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); + } + } } } - } else { - idr = flecs_id_record_get(world, id); - dst = flecs_get_component_ptr(table, ECS_RECORD_TO_ROW(r->row), idr); - if (dst.ptr) { - return dst; - } - } - } - /* If entity didn't have component yet, add it */ - flecs_add_id_w_record(world, entity, r, id, true); + /* 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]); + } - /* Flush commands so the pointer we're fetching is stable */ - flecs_defer_end(world, world->stages[0]); - flecs_defer_begin(world, world->stages[0]); + /* User code (from triggers) could have enqueued more ids to delete, + * reobtain the array in case it got reallocated */ + ids = ecs_vec_first(&world->store.marked_ids); + } - if (!idr) { - idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, 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; + } + } while (true); - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - dst = flecs_get_component_ptr(r->table, ECS_RECORD_TO_ROW(r->row), idr); -error: - return dst; + return true; } -void flecs_invoke_hook( - ecs_world_t *world, - ecs_table_t *table, - const ecs_table_record_t *tr, - int32_t count, - int32_t row, - const ecs_entity_t *entities, - ecs_id_t id, - const ecs_type_info_t *ti, - ecs_entity_t event, - ecs_iter_action_t hook) +static +bool flecs_on_delete_clear_ids( + ecs_world_t *world) { - int32_t defer = world->stages[0]->defer; - if (defer < 0) { - world->stages[0]->defer *= -1; - } + 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_iter_t it = { .field_count = 1}; - it.entities = entities; + 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; + } + } - flecs_iter_init(world, &it, flecs_iter_cache_all); - it.world = world; - it.real_world = world; - it.table = table; - it.trs[0] = tr; - it.row_fields = !!(((ecs_id_record_t*)tr->hdr.cache)->flags & EcsIdIsSparse); - it.ref_fields = it.row_fields; - 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.callback_ctx = ti->hooks.binding_ctx; - it.count = count; - it.offset = row; - it.flags = EcsIterIsValid; + ecs_component_record_t *cdr = ids[i].cdr; + bool delete_id = ids[i].delete_id; - hook(&it); - ecs_iter_fini(&it); + flecs_component_release_tables(world, cdr); - world->stages[0]->defer = defer; + /* Release the claim taken by flecs_marked_id_push. This may delete the + * component record as all other claims may have been released. */ + int32_t rc = flecs_component_release(world, cdr); + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); + (void)rc; + + /* If rc is 0, the id was likely deleted by a nested delete_with call + * made by an on_remove handler/OnRemove observer */ + if (rc) { + if (delete_id) { + /* If id should be deleted, release initial claim. This happens when + * a component, tag, or part of a pair is deleted. */ + flecs_component_release(world, cdr); + } else { + /* If id should not be deleted, unmark component record for deletion. This + * happens when all instances *of* an id are deleted, for example + * when calling ecs_remove_all or ecs_delete_with. */ + cdr->flags &= ~EcsIdMarkedForDelete; + } + } + } + } while (-- twice); + + return true; } -void flecs_notify_on_set( +static +void flecs_on_delete( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - int32_t count, - ecs_type_t *ids, - bool owned) + ecs_id_t id, + ecs_entity_t action, + bool delete_id) { - ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_entity_t *entities = &ecs_table_entities(table)[row]; - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert((row + count) <= ecs_table_count(table), - ECS_INTERNAL_ERROR, NULL); + /* 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 i, count = ecs_vec_count(&world->store.marked_ids); - if (owned) { - int i; - for (i = 0; i < ids->count; i ++) { - ecs_id_t id = ids->array[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = idr->type_info; - ecs_iter_action_t on_set = ti->hooks.on_set; - if (!on_set) { - 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); + /* 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(); - if (idr->flags & EcsIdIsSparse) { - int32_t j; - for (j = 0; j < count; j ++) { - flecs_invoke_hook(world, table, tr, 1, row, &entities[j], - id, ti, EcsOnSet, on_set); - } - } else { - ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); - if (on_set) { - flecs_invoke_hook(world, table, tr, count, row, entities, - id, ti, EcsOnSet, on_set); - } - } + /* Empty tables with all the to be deleted ids */ + flecs_on_delete_clear_tables(world); + + /* 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); + 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); - /* Run OnSet notifications */ - if (table->flags & EcsTableHasOnSet && ids->count) { - flecs_emit(world, world, 0, &(ecs_event_desc_t) { - .event = EcsOnSet, - .ids = ids, - .table = table, - .offset = row, - .count = count, - .observable = world - }); + /* Ids are deleted, clear stack */ + ecs_vec_clear(&world->store.marked_ids); + + /* If any components got deleted, cleanup type info. Delaying this + * ensures that type info remains available during cleanup. */ + count = ecs_vec_count(&world->store.deleted_components); + ecs_entity_t *comps = ecs_vec_first(&world->store.deleted_components); + for (i = 0; i < count; i ++) { + flecs_type_info_free(world, comps[i]); + } + + ecs_vec_clear(&world->store.deleted_components); + + ecs_log_pop_2(); } } -void flecs_record_add_flag( - ecs_record_t *record, - uint32_t flag) +void ecs_delete_with( + ecs_world_t *world, + 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); - } - } + 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; } - record->row |= flag; + + flecs_on_delete(world, id, EcsDelete, false); + flecs_defer_end(world, stage); + + flecs_journal_end(); } -void flecs_add_flag( +void ecs_remove_all( ecs_world_t *world, - ecs_entity_t entity, - uint32_t flag) + 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); -} + flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); -/* -- Public functions -- */ + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { + return; + } -bool ecs_commit( + flecs_on_delete(world, id, EcsRemove, false); + flecs_defer_end(world, stage); + + flecs_journal_end(); +} + +void ecs_delete( ecs_world_t *world, - ecs_entity_t entity, - ecs_record_t *record, - ecs_table_t *table, - const ecs_type_t *added, - const ecs_type_t *removed) + ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, - "commit cannot be called on stage or while world is deferred"); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_table_t *src_table = NULL; - if (!record) { - record = flecs_entities_get(world, entity); - src_table = record->table; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_delete(stage, entity)) { + return; } - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + ecs_os_perf_trace_push("flecs.delete"); - if (added) { - diff.added = *added; - diff.added_flags = table->flags & EcsTableAddEdgeFlags; - } - if (removed) { - diff.removed = *removed; - if (src_table) { - diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; + ecs_record_t *r = flecs_entities_try(world, entity); + if (r) { + flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); + + ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); + ecs_table_t *table; + if (row_flags) { + if (row_flags & EcsEntityIsTarget) { + flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); + flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); + r->cdr = 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) { + flecs_table_traversable_add(r->table, -1); + } + + /* Merge operations before deleting entity */ + flecs_defer_end(world, stage); + flecs_defer_begin(world, stage); } - } - ecs_defer_begin(world); - flecs_commit(world, entity, record, table, &diff, true, 0); - ecs_defer_end(world); + table = r->table; - return src_table != table; + if (table) { /* NULL if entity got cleaned up as result of cycle */ + ecs_table_diff_t diff = { + .removed = table->type, + .removed_flags = table->flags & EcsTableRemoveEdgeFlags + }; + + int32_t row = ECS_RECORD_TO_ROW(r->row); + flecs_notify_on_remove( + world, table, &world->store.root, row, 1, &diff); + flecs_table_delete(world, table, row, true); + } + + flecs_entities_remove(world, entity); + + flecs_journal_end(); + } + + flecs_defer_end(world, stage); error: - return false; + ecs_os_perf_trace_pop("flecs.delete"); + return; } -ecs_entity_t ecs_set_with( +void ecs_add_id( ecs_world_t *world, + ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_id_t prev = stage->with; - stage->with = id; - return prev; + 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 0; + return; } -ecs_id_t ecs_get_with( - const ecs_world_t *world) +void ecs_remove_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->with; + 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 0; + return; } -ecs_entity_t ecs_new( - ecs_world_t *world) +void ecs_auto_override_id( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | id); +error: + return; +} + +ecs_entity_t ecs_clone( + ecs_world_t *world, + ecs_entity_t dst, + ecs_entity_t src, + bool copy_value) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); + ecs_check(!dst || (ecs_get_table(world, dst)->type.count == 0), + ECS_INVALID_PARAMETER, NULL); - /* It is possible that the world passed to this function is a stage, so - * make sure we have the actual world. Cast away const since this is one of - * the few functions that may modify the world while it is in readonly mode, - * 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_stage_t *stage = flecs_stage_from_world(&world); + if (!dst) { + dst = ecs_new(world); + } - ecs_entity_t entity; - if (unsafe_world->flags & EcsWorldMultiThreaded) { - /* When world is in multithreading mode, make sure OS API has threading - * functions initialized */ - ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, - "thread safe id creation unavailable: threading API not available"); + if (flecs_defer_clone(stage, dst, src, copy_value)) { + return dst; + } - /* Can't atomically increase number above max int */ - ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, - ECS_INVALID_OPERATION, "thread safe ids exhausted"); - entity = (ecs_entity_t)ecs_os_ainc( - (int32_t*)&flecs_entities_max_id(unsafe_world)); - } else { - entity = flecs_entities_new_id(unsafe_world); + 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; + ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *dst_table = src_table; + if (src_table->flags & EcsTableHasName) { + dst_table = ecs_table_remove_id(world, src_table, + ecs_pair_t(EcsIdentifier, EcsName)); } - ecs_assert(!unsafe_world->info.max_id || - ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, - ECS_OUT_OF_RANGE, NULL); + ecs_type_t dst_type = dst_table->type; + ecs_table_diff_t diff = { + .added = dst_type, + .added_flags = dst_table->flags & EcsTableAddEdgeFlags + }; - flecs_journal(world, EcsJournalNew, entity, 0, 0); + ecs_record_t *dst_r = flecs_entities_get(world, dst); + /* Note 'ctor' parameter below is set to 'false' if the value will be + * copied, since flecs_table_move will call a copy constructor. */ + if (dst_table != dst_r->table) { + flecs_move_entity(world, dst, dst_r, dst_table, &diff, !copy_value, 0); + } + int32_t row = ECS_RECORD_TO_ROW(dst_r->row); - return entity; + if (copy_value) { + flecs_table_move(world, dst, src, dst_table, + row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); + int32_t i, count = dst_table->column_count; + for (i = 0; i < count; i ++) { + ecs_id_t id = flecs_column_id(dst_table, i); + ecs_type_t type = { + .array = &id, + .count = 1 + }; + flecs_notify_on_set(world, dst_table, row, 1, &type, true); + } + } + + flecs_defer_end(world, stage); + return dst; error: return 0; } -ecs_entity_t ecs_new_low_id( - ecs_world_t *world) +#define ecs_get_low_id(table, r, id)\ + ecs_assert(table->component_map != NULL, ECS_INTERNAL_ERROR, NULL);\ + int16_t column_index = table->component_map[id];\ + if (column_index > 0) {\ + ecs_column_t *column = &table->data.columns[column_index - 1];\ + return ECS_ELEM(column->data, column->ti->size, \ + ECS_RECORD_TO_ROW(r->row));\ + } else if (column_index < 0) {\ + column_index = flecs_ito(int16_t, -column_index - 1);\ + const ecs_table_record_t *tr = &table->_->records[column_index];\ + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache;\ + if (cdr->flags & EcsIdIsSparse) {\ + return flecs_sparse_get_any(cdr->sparse, 0, entity);\ + }\ + } + +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); - /* 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); + 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; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (id < FLECS_HI_COMPONENT_ID) { + ecs_get_low_id(table, r, id); + if (!(table->flags & EcsTableHasIsA)) { + return NULL; + } } - ecs_entity_t id = 0; - if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { - do { - id = unsafe_world->info.last_component_id ++; - } while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + return NULL; } - if (!id || id >= FLECS_HI_COMPONENT_ID) { - /* If the low component ids are depleted, return a regular entity id */ - id = ecs_new(unsafe_world); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr) { + return flecs_get_base_component(world, table, id, cdr, 0); } else { - flecs_entities_ensure(world, id); + if (cdr->flags & EcsIdIsSparse) { + return flecs_sparse_get_any(cdr->sparse, 0, entity); + } + ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); } - ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); - - return id; -error: - return 0; + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_table_get_component(table, tr->column, row).ptr; +error: + return NULL; } -ecs_entity_t ecs_new_w_id( - ecs_world_t *world, +void* ecs_get_mut_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_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_entity_t entity = ecs_new(world); + world = ecs_get_world(world); - if (flecs_defer_add(stage, entity, id)) { - return entity; - } + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_table_diff_builder_t diff_builder = ECS_TABLE_DIFF_INIT; - flecs_table_diff_builder_init(world, &diff_builder); - ecs_table_t *table = flecs_find_table_add( - world, &world->store.root, id, &diff_builder); + ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_diff_t diff; - flecs_table_diff_build_noalloc(&diff_builder, &diff); - ecs_record_t *r = flecs_entities_get(world, entity); - flecs_new_entity(world, entity, r, table, &diff, true, 0); - flecs_table_diff_builder_fini(world, &diff_builder); - - flecs_defer_end(world, stage); + if (id < FLECS_HI_COMPONENT_ID) { + ecs_get_low_id(table, r, id); + return NULL; + } - return entity; + ecs_component_record_t *cdr = flecs_components_get(world, id); + int32_t row = ECS_RECORD_TO_ROW(r->row); + return flecs_get_component_ptr(table, row, cdr).ptr; error: - return 0; + return NULL; } -ecs_entity_t ecs_new_w_table( +void* ecs_ensure_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(table != 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_stage_from_world(&world); - ecs_entity_t entity = ecs_new(world); - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; - if (table->flags & EcsTableHasIsA) { - flags |= EcsTableHasOnAdd; + ecs_stage_t *stage = flecs_stage_from_world(&world); + if (flecs_defer_cmd(stage)) { + return flecs_defer_set( + world, stage, EcsCmdEnsure, entity, id, 0, NULL, NULL); } - ecs_table_diff_t table_diff = { - .added = table->type, - .added_flags = flags - }; - - flecs_new_entity(world, entity, r, table, &table_diff, true, 0); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + void *result = flecs_ensure(world, entity, id, r).ptr; + ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); - return entity; + flecs_defer_end(world, stage); + return result; error: - return 0; + return NULL; } -static -void flecs_copy_id( +void* ecs_ensure_modified_id( ecs_world_t *world, - ecs_record_t *r, - ecs_id_t id, - size_t size, - void *dst_ptr, - void *src_ptr, - const ecs_type_info_t *ti) + ecs_entity_t entity, + ecs_id_t id) { - ecs_check(dst_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(src_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - copy(dst_ptr, src_ptr, 1, ti); - } else { - ecs_os_memcpy(dst_ptr, src_ptr, flecs_utosize(size)); - } + 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_table_mark_dirty(world, r->table, id); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); - 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 flecs_defer_set(world, stage, EcsCmdSet, entity, id, 0, NULL, NULL); error: - return; + return NULL; } -/* Traverse table graph by either adding or removing identifiers parsed from the - * passed in expression. */ static -int flecs_traverse_from_expr( - ecs_world_t *world, - const char *name, - const char *expr, - ecs_vec_t *ids) +ecs_record_t* flecs_access_begin( + ecs_world_t *stage, + ecs_entity_t entity, + bool write) { -#ifdef FLECS_SCRIPT - const char *ptr = expr; - if (ptr) { - ecs_id_t id = 0; - while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { - if (!id) { - break; - } + ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); - if (!ecs_id_is_valid(world, id)) { - char *idstr = ecs_id_str(world, id); - ecs_parser_error(name, expr, (ptr - expr), - "id %s is invalid for add expression", idstr); - ecs_os_free(idstr); - goto error; - } + 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_vec_append_t(&world->allocator, ids, ecs_id_t)[0] = id; - } + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (!ptr) { - goto error; - } + int32_t count = ecs_os_ainc(&table->_->lock); + (void)count; + if (write) { + ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); } - return 0; -#else - (void)world; - (void)name; - (void)expr; - (void)ids; - ecs_err("cannot parse component expression: script addon required"); - goto error; -#endif + + return r; error: - return -1; + return NULL; } -/* Add/remove components based on the parsed expression. This operation is - * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ static -void flecs_defer_from_expr( - ecs_world_t *world, - ecs_entity_t entity, - const char *name, - const char *expr) +void flecs_access_end( + const ecs_record_t *r, + bool write) { -#ifdef FLECS_SCRIPT - const char *ptr = expr; - if (ptr) { - ecs_id_t id = 0; - while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { - if (!id) { - break; - } - ecs_add_id(world, entity, id); - } + 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); } -#else - (void)world; - (void)entity; - (void)name; - (void)expr; - ecs_err("cannot parse component expression: script addon required"); -#endif + ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); + +error: + return; } -/* If operation is not deferred, add components by finding the target - * table and moving the entity towards it. */ -static -int flecs_traverse_add( +ecs_record_t* ecs_write_begin( 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 new_entity, - bool name_assigned) + ecs_entity_t entity) { - 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); - ecs_vec_t ids; + return flecs_access_begin(world, entity, true); +} - /* Add components from the 'add_expr' expression. Look up before naming - * entity, so that expression can't resolve to self. */ - ecs_vec_init_t(&world->allocator, &ids, ecs_id_t, 0); - if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { - if (flecs_traverse_from_expr(world, name, desc->add_expr, &ids)) { - goto error; - } - } +void ecs_write_end( + ecs_record_t *r) +{ + flecs_access_end(r, true); +} - /* Set symbol */ - 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, "%s (provided) vs. %s (existing)", - desc->symbol, sym); - } else { - ecs_set_symbol(world, result, desc->symbol); - } - } +const ecs_record_t* ecs_read_begin( + ecs_world_t *world, + ecs_entity_t entity) +{ + return flecs_access_begin(world, entity, false); +} - /* If a name is provided but not yet assigned, add the Name component */ - if (name && !name_assigned) { - if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { - if (name[0] == '#') { - /* Numerical ids should always return, unless it's invalid */ - goto error; - } - } - } else if (new_entity && scope) { - ecs_add_pair(world, result, EcsChildOf, scope); - } +void ecs_read_end( + const ecs_record_t *r) +{ + flecs_access_end(r, false); +} - /* Find existing table */ - ecs_table_t *src_table = NULL, *table = NULL; - ecs_record_t *r = flecs_entities_get(world, result); - table = r->table; +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; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.entities[ECS_RECORD_TO_ROW(record->row)]; +error: + return 0; +} - /* Add components from the 'add' array */ - if (desc->add) { - int32_t i = 0; - ecs_id_t id; +const void* ecs_record_get_id( + const ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + ecs_component_record_t *cdr = flecs_components_get(world, id); + return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), cdr); +} - while ((id = desc->add[i ++])) { - table = flecs_find_table_add(world, table, id, &diff); - } +bool ecs_record_has_id( + ecs_world_t *stage, + const ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + if (r->table) { + return ecs_table_has_id(world, r->table, id); } + return false; +} - /* Add components from the 'set' array */ - if (desc->set) { - int32_t i = 0; - ecs_id_t id; +void* ecs_record_ensure_id( + ecs_world_t *stage, + ecs_record_t *r, + ecs_id_t id) +{ + const ecs_world_t *world = ecs_get_world(stage); + ecs_component_record_t *cdr = flecs_components_get(world, id); + return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), cdr); +} - while ((id = desc->set[i ++].type)) { - table = flecs_find_table_add(world, table, id, &diff); - } - } +ecs_ref_t ecs_ref_init_id( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id) +{ + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); - /* Add ids from .expr */ - { - int32_t i, count = ecs_vec_count(&ids); - ecs_id_t *expr_ids = ecs_vec_first(&ids); - for (i = 0; i < count; i ++) { - table = flecs_find_table_add(world, table, expr_ids[i], &diff); - } - } + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_check(record != NULL, ECS_INVALID_PARAMETER, + "cannot create ref for empty entity"); - /* 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 (new_entity) { - if (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); - } + ecs_ref_t result = { + .entity = entity, + .id = id, + .record = record + }; + + ecs_table_t *table = record->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + result.table_id = table->id; + result.table_version = flecs_get_table_version(world, result.table_id); + result.ptr = flecs_get_component(table, ECS_RECORD_TO_ROW(record->row), + flecs_components_get(world, id)); + + return result; +error: + return (ecs_ref_t){0}; +} + +void ecs_ref_update( + const ecs_world_t *world, + ecs_ref_t *ref) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); + + if (ref->table_version == flecs_get_table_version(world, ref->table_id)) { + return; } - /* 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]); + ecs_record_t *r = ref->record; + ecs_table_t *table = r->table; + if (!table) { /* Table can be NULL, entity could have been deleted */ + ref->table_id = 0; + ref->table_version = 0; + ref->ptr = NULL; + return; } - /* Set component values */ - if (desc->set) { - table = r->table; - ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - int32_t i = 0, row = ECS_RECORD_TO_ROW(r->row); - const ecs_value_t *v; - - flecs_defer_begin(world, world->stages[0]); + ref->table_id = table->id; + ref->table_version = flecs_get_table_version(world, ref->table_id); + ref->ptr = flecs_get_component(table, ECS_RECORD_TO_ROW(r->row), + flecs_components_get(world, ref->id)); - while ((void)(v = &desc->set[i ++]), v->type) { - if (!v->ptr) { - continue; - } - ecs_assert(ECS_RECORD_TO_ROW(r->row) == row, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *idr = flecs_id_record_get(world, v->type); - flecs_component_ptr_t ptr = flecs_get_component_ptr(table, row, idr); - ecs_check(ptr.ptr != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = idr->type_info; - flecs_copy_id(world, r, v->type, - flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); - } +error: + return; +} - flecs_defer_end(world, world->stages[0]); - } +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, "ref not initialized"); + ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, "ref not initialized"); + ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, "ref not initialized"); + ecs_check(id == ref->id, ECS_INVALID_PARAMETER, "id does not match ref"); - flecs_table_diff_builder_fini(world, &diff); - ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); - return 0; + (void)id; + + ecs_ref_update(world, ref); + + return ref->ptr; error: - flecs_table_diff_builder_fini(world, &diff); - ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); - return -1; + return NULL; } -/* When in deferred mode, we need to add/remove components one by one using - * the regular operations. */ -static -void flecs_deferred_add_remove( +void* ecs_emplace_id( 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, + bool *is_new) { - 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(ecs_id_is_valid(world, id), 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)); - } + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (with) { - ecs_add_id(world, entity, with); - } + if (flecs_defer_cmd(stage)) { + return flecs_defer_set( + world, stage, EcsCmdEmplace, entity, id, 0, NULL, is_new); } - /* Add components from the 'add' id array */ - if (desc->add) { - int32_t i = 0; - ecs_id_t id; + ecs_check(is_new || !ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, + "cannot emplace a component the entity already has"); - while ((id = desc->add[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_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); + flecs_defer_end(world, stage); - /* Set component values */ - if (desc->set) { - int32_t i = 0; - const ecs_value_t *v; - while ((void)(v = &desc->set[i ++]), v->type) { - if (v->ptr) { - ecs_set_id(world, entity, v->type, 0, v->ptr); - } else { - ecs_add_id(world, entity, v->type); - } - } - } + ecs_component_record_t *cdr = flecs_components_get(world, id); + void *ptr = flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), cdr); + ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, + "emplaced component was removed during operation, make sure to not " + "remove component T in on_add(T) hook/OnAdd(T) observer"); - /* Add components from the 'add_expr' expression */ - if (desc->add_expr) { - flecs_defer_from_expr(world, entity, name, desc->add_expr); + if (is_new) { + *is_new = table != r->table; } - int32_t thread_count = ecs_get_stage_count(world); + return ptr; +error: + return NULL; +} - /* 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); - } - } +static +void flecs_modified_id_if( + ecs_world_t *world, + ecs_entity_t entity, + ecs_id_t id, + bool owned) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + + if (flecs_defer_modified(stage, entity, id)) { + return; } - /* Set name */ - if (name && !name_assigned) { - ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr || !flecs_component_get_table(cdr, table)) { + flecs_defer_end(world, stage); + return; } + + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); + + flecs_table_mark_dirty(world, table, id); + flecs_defer_end(world, stage); +error: + return; } -ecs_entity_t ecs_entity_init( +void ecs_modified_id( ecs_world_t *world, - const ecs_entity_desc_t *desc) + ecs_entity_t entity, + ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_entity_desc_t was not initialized to zero"); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_entity_t scope = stage->scope; - ecs_id_t with = ecs_get_with(world); - ecs_entity_t result = desc->id; -#ifdef FLECS_DEBUG - if (desc->add) { - ecs_id_t id; - int32_t i = 0; - while ((id = desc->add[i ++])) { - if (ECS_HAS_ID_FLAG(id, PAIR) && - (ECS_PAIR_FIRST(id) == EcsChildOf)) - { - if (desc->name) { - ecs_check(false, ECS_INVALID_PARAMETER, "%s: cannot set parent in " - "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent", - desc->name); - } else { - ecs_check(false, ECS_INVALID_PARAMETER, "cannot set parent in " - "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent"); - } - } - } + if (flecs_defer_modified(stage, entity, id)) { + return; } -#endif - const char *name = desc->name; - const char *sep = desc->sep; - if (!sep) { - sep = "."; - } + /* 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); - if (name) { - if (!name[0]) { - name = NULL; - } else if (flecs_name_is_id(name)){ - ecs_entity_t id = flecs_name_to_id(name); - if (!id) { - return 0; - } - if (result && (id != result)) { - ecs_err("name id conflicts with provided id"); - return 0; - } - name = NULL; - result = id; - } - } + 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); - const char *root_sep = desc->root_sep; - bool flecs_new_entity = false; - bool name_assigned = false; + flecs_table_mark_dirty(world, table, id); + flecs_defer_end(world, stage); +error: + return; +} - /* 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; - } - } +static +void flecs_set_id_copy( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr) +{ + if (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, EcsCmdSet, entity, id, + flecs_utosize(size), ptr, NULL); + return; } - /* Parent field takes precedence over scope */ - if (desc->parent) { - scope = desc->parent; - ecs_check(ecs_is_valid(world, desc->parent), - ECS_INVALID_PARAMETER, "ecs_entity_desc_t::parent is not valid"); - } + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); - /* Find or create entity */ - if (!result) { - if (name) { - /* If add array contains a ChildOf pair, use it as scope instead */ - result = ecs_lookup_path_w_sep( - world, scope, name, sep, root_sep, false); - if (result) { - name_assigned = true; - } - } + flecs_copy_id(world, r, id, size, dst.ptr, ptr, dst.ti); - if (!result) { - if (desc->use_low_id) { - result = ecs_new_low_id(world); - } else { - result = ecs_new(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_make_alive(world, result); + flecs_defer_end(world, stage); +} - 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); - } - } - } +static +void flecs_set_id_move( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + size_t size, + void *ptr, + ecs_cmd_kind_t cmd_kind) +{ + if (flecs_defer_cmd(stage)) { + flecs_defer_set(world, stage, cmd_kind, entity, id, + flecs_utosize(size), ptr, NULL); + return; } - ecs_assert(name_assigned == ecs_has_pair( - world, result, ecs_id(EcsIdentifier), EcsName), - ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = flecs_entities_get(world, entity); + flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); + ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); - if (ecs_is_deferred(world)) { - flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, - scope, with, flecs_new_entity, name_assigned); + const ecs_type_info_t *ti = dst.ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_move_t move; + if (cmd_kind != EcsCmdEmplace) { + /* ctor will have happened by ensure */ + move = ti->hooks.move_dtor; } else { - if (flecs_traverse_add(world, result, name, desc, - scope, with, flecs_new_entity, name_assigned)) - { - return 0; + move = ti->hooks.ctor_move_dtor; + } + if (move) { + move(dst.ptr, ptr, 1, ti); + } else { + ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); + } + + flecs_table_mark_dirty(world, r->table, id); + + if (cmd_kind == EcsCmdSet) { + ecs_table_t *table = r->table; + if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { + ecs_type_t ids = { .array = &id, .count = 1 }; + flecs_notify_on_set( + world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } - return result; + flecs_defer_end(world, stage); error: - return 0; + return; } -const ecs_entity_t* ecs_bulk_init( +void ecs_set_id( ecs_world_t *world, - const ecs_bulk_desc_t *desc) + ecs_entity_t entity, + ecs_id_t id, + size_t size, + const void *ptr) { - flecs_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, - "ecs_bulk_desc_t was not initialized to zero"); - - const ecs_entity_t *entities = desc->entities; - int32_t count = desc->count; - - int32_t sparse_count = 0; - if (!entities) { - sparse_count = flecs_entities_count(world); - entities = flecs_entities_new_ids(world, count); - ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - int i; - for (i = 0; i < count; i ++) { - ecs_make_alive(world, entities[i]); - } - } - - ecs_type_t ids; - ecs_table_t *table = desc->table; - if (!table) { - ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; - flecs_table_diff_builder_init(world, &diff); - - int32_t i = 0; - ecs_id_t id; - while ((id = desc->ids[i])) { - table = flecs_find_table_add(world, table, id, &diff); - i ++; - } - - ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); - ids.count = i; + ecs_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_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); - } + ecs_stage_t *stage = flecs_stage_from_world(&world); - 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]; - } + /* Safe to cast away const: function won't modify if move arg is false */ + flecs_set_id_copy(world, stage, entity, id, size, + ECS_CONST_CAST(void*, ptr)); error: - return NULL; + return; } +#if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) static -void flecs_check_component( +bool flecs_can_toggle( ecs_world_t *world, - ecs_entity_t result, - const EcsComponent *ptr, - ecs_size_t size, - ecs_size_t alignment) + ecs_id_t id) { - if (ptr->size != size) { - char *path = ecs_get_path(world, result); - ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); - ecs_os_free(path); - } - if (ptr->alignment != alignment) { - char *path = ecs_get_path(world, result); - ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); - ecs_os_free(path); + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + return false; } + + return (cdr->flags & EcsIdCanToggle) != 0; } +#endif -ecs_entity_t ecs_component_init( +void ecs_enable_id( ecs_world_t *world, - const ecs_component_desc_t *desc) + ecs_entity_t entity, + ecs_id_t id, + bool enable) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_component_desc_t was not initialized to 0"); - - /* If existing entity is provided, check if it is already registered as a - * component and matches the size/alignment. This can prevent having to - * suspend readonly mode, and increases the number of scenarios in which - * this function can be called in multithreaded mode. */ - ecs_entity_t result = desc->entity; - if (result && ecs_is_alive(world, result)) { - const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); - if (const_ptr) { - flecs_check_component(world, result, const_ptr, - desc->type.size, desc->type.alignment); - return result; - } - } - - ecs_suspend_readonly_state_t readonly_state; - world = flecs_suspend_readonly(world, &readonly_state); + ecs_stage_t *stage = flecs_stage_from_world(&world); - bool new_component = true; - if (!result) { - result = ecs_new_low_id(world); - } else { - ecs_make_alive(world, result); - new_component = ecs_has(world, result, EcsComponent); - } + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); + ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, + "add CanToggle trait to component"); - EcsComponent *ptr = ecs_ensure(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_entity_t bs_id = id | ECS_TOGGLE; + ecs_add_id(world, entity, bs_id); - if (desc->type.name && new_component) { - ecs_entity(world, { .id = result, .name = desc->type.name }); + if (flecs_defer_enable(stage, entity, id, enable)) { + return; } - ecs_modified(world, result, EcsComponent); - - if (desc->type.size && - !ecs_id_in_use(world, result) && - !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) - { - ecs_set_hooks_id(world, result, &desc->type.hooks); - } + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_table_t *table = r->table; + int32_t index = ecs_table_get_type_index(world, table, bs_id); + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { - world->info.last_component_id = result + 1; - } + ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); + index -= table->_->bs_offset; + ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - flecs_resume_readonly(world, &readonly_state); + /* 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); - ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); + flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); - return result; + flecs_defer_end(world, stage); error: - return 0; + return; } -const ecs_entity_t* ecs_bulk_new_w_id( - ecs_world_t *world, - ecs_id_t id, - int32_t count) +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_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - const ecs_entity_t *ids; - if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { - return ids; - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - 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_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_entity_t bs_id = id | ECS_TOGGLE; + int32_t index = ecs_table_get_type_index(world, table, bs_id); + if (index == -1) { + /* If table does not have TOGGLE column for component, component is + * always enabled, if the entity has it */ + return ecs_has_id(world, entity, id); } - ecs_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); + 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 ids; + return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: - return NULL; + return false; } -void ecs_clear( - ecs_world_t *world, - ecs_entity_t entity) +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_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_clear(stage, entity)) { - return; - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - ecs_record_t *r = flecs_entities_get(world, entity); + 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) { - ecs_table_diff_t diff = { - .removed = table->type, - .removed_flags = table->flags & EcsTableRemoveEdgeFlags - }; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (id < FLECS_HI_COMPONENT_ID) { + if (table->component_map[id] != 0) { + return true; + } + } else { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (tr) { + return true; + } + } + } - flecs_delete_entity(world, r, &diff); - r->table = NULL; + if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { + return false; + } - if (r->row & EcsEntityIsTraversable) { - flecs_table_traversable_add(table, -1); + if (ECS_IS_PAIR(id) && (table->flags & EcsTableHasUnion)) { + ecs_component_record_t *u_idr = flecs_components_get(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsUnion)); + if (u_idr && u_idr->flags & EcsIdIsUnion) { + uint64_t cur = flecs_switch_get(u_idr->sparse, (uint32_t)entity); + return (uint32_t)cur == ECS_PAIR_SECOND(id); } - } + } - flecs_defer_end(world, stage); + ecs_table_record_t *tr; + int32_t column = ecs_search_relation(world, table, 0, id, + EcsIsA, 0, 0, 0, &tr); + if (column == -1) { + return false; + } + + return true; error: - return; + return false; } -static -void flecs_throw_invalid_delete( - ecs_world_t *world, +bool ecs_owns_id( + const ecs_world_t *world, + ecs_entity_t entity, ecs_id_t id) { - char *id_str = NULL; - if (!(world->flags & EcsWorldQuit)) { - id_str = ecs_id_str(world, id); - ecs_err("(OnDelete, Panic) constraint violated while deleting %s", - id_str); - ecs_os_free(id_str); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } -} - -static -void flecs_marked_id_push( - ecs_world_t *world, - ecs_id_record_t* idr, - ecs_entity_t action, - bool delete_id) -{ - ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, - &world->store.marked_ids, ecs_marked_id_t); - - m->idr = idr; - m->id = idr->id; - m->action = action; - m->delete_id = delete_id; - - flecs_id_record_claim(world, idr); -} - -static -void flecs_id_mark_for_delete( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action, - bool delete_id); + 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); -static -void flecs_targets_mark_for_delete( - ecs_world_t *world, - ecs_table_t *table) -{ - ecs_id_record_t *idr; - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t i, count = ecs_table_count(table); - for (i = 0; i < count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - if (!r) { - continue; - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - /* 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_record_t *r = flecs_entities_get_any(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - 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); + if (id < FLECS_HI_COMPONENT_ID) { + return table->component_map[id] != 0; + } else { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (tr) { + return true; } } } + +error: + return false; } -static -bool flecs_id_is_delete_target( - ecs_id_t id, - ecs_entity_t action) +ecs_entity_t ecs_get_target( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel, + int32_t index) { - 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; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); + + ecs_record_t *r = flecs_entities_get(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_id_t wc = ecs_pair(rel, EcsWildcard); + ecs_component_record_t *cdr = flecs_components_get(world, wc); + const ecs_table_record_t *tr = NULL; + if (cdr) { + tr = flecs_component_get_table(cdr, table); + } + if (!tr) { + if (!cdr || (cdr->flags & EcsIdOnInstantiateInherit)) { + goto look_in_base; + } else { + return 0; + } } - return false; -} -static -ecs_entity_t flecs_get_delete_action( - ecs_table_t *table, - ecs_table_record_t *tr, - ecs_entity_t action, - bool delete_target) -{ - ecs_entity_t result = action; - if (!result && delete_target) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_id_t id = idr->id; + if (index >= tr->count) { + index -= tr->count; + goto look_in_base; + } - /* 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_entity_t result = + ecs_pair_second(world, table->type.array[tr->index + index]); - 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; - } - } + if (result == EcsUnion) { + wc = ecs_pair(rel, EcsUnion); + ecs_component_record_t *wc_idr = flecs_components_get(world, wc); + ecs_assert(wc_idr != NULL, ECS_INTERNAL_ERROR, NULL); + result = flecs_switch_get(wc_idr->sparse, (uint32_t)entity); + } - /* We should always have as many matching ids as tr->count */ - ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); + return result; +look_in_base: + if (table->flags & EcsTableHasIsA) { + const ecs_table_record_t *tr_isa = flecs_component_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; } - } while (--count); + } } - return result; +error: + return 0; } -static -void flecs_update_monitors_for_delete( - ecs_world_t *world, - ecs_id_t id) +ecs_entity_t ecs_get_parent( + const ecs_world_t *world, + ecs_entity_t entity) { - flecs_update_component_monitors(world, NULL, &(ecs_type_t){ - .array = (ecs_id_t[]){id}, - .count = 1 - }); + return ecs_get_target(world, entity, EcsChildOf, 0); } -static -void flecs_id_mark_for_delete( - ecs_world_t *world, - ecs_id_record_t *idr, - ecs_entity_t action, - bool delete_id) +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) { - if (idr->flags & EcsIdMarkedForDelete) { - return; + 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); } - idr->flags |= EcsIdMarkedForDelete; - flecs_marked_id_push(world, idr, action, delete_id); + world = ecs_get_world(world); - ecs_id_t id = idr->id; + ecs_table_t *table = ecs_get_table(world, entity); + ecs_entity_t subject = 0; - bool delete_target = flecs_id_is_delete_target(id, action); + 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 */ - /* 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; - } + if (table) { + ecs_id_t *ids = table->type.array; + int32_t i, count = table->type.count; - ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, - delete_target); + 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 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); + if (ecs_has_id(world, ent, id)) { + subject = ent; + break; + } } } } - /* 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); - } - } + if (subject == 0) { + return entity; + } else { + return subject; } +error: + return 0; } -static -bool flecs_on_delete_mark( - ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action, - bool delete_id) +int32_t ecs_get_depth( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t rel) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If there's no id record, there's nothing to delete */ - return false; - } - - if (!action) { - /* If no explicit action is provided, derive it */ - if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { - /* Delete actions are determined by the component, or in the case - * of a pair by the relationship. */ - action = ECS_ID_ON_DELETE(idr->flags); - } - } + ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, + "cannot safely determine depth for relationship that is not acyclic " + "(add Acyclic property to relationship)"); - if (action == EcsPanic) { - /* This id is protected from deletion */ - flecs_throw_invalid_delete(world, id); - return false; + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return ecs_table_get_depth(world, table, rel); } - flecs_id_mark_for_delete(world, idr, action, delete_id); - - return true; + return 0; +error: + return -1; } static -void flecs_remove_from_table( - ecs_world_t *world, - ecs_table_t *table) +const char* flecs_get_identifier( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) { - ecs_table_diff_t temp_diff = { .added = {0} }; - ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; - flecs_table_diff_builder_init(world, &diff); - ecs_table_t *dst_table = table; - - /* To find the dst table, remove all ids that are marked for deletion */ - int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); - ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); - const ecs_table_record_t *tr; - for (i = 0; i < count; i ++) { - const ecs_id_record_t *idr = ids[i].idr; - - if (!(tr = flecs_id_record_get_table(idr, dst_table))) { - continue; - } - - t = tr->index; - - do { - ecs_id_t id = dst_table->type.array[t]; - ecs_table_t *tgt_table = flecs_table_traverse_remove( - world, dst_table, &id, &temp_diff); - ecs_assert(tgt_table != dst_table, ECS_INTERNAL_ERROR, NULL); - dst_table = tgt_table; - flecs_table_diff_build_append_table(world, &diff, &temp_diff); - } while (dst_table->type.count && (t = ecs_search_offset( - world, dst_table, t, idr->id, NULL)) != -1); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + const EcsIdentifier *ptr = ecs_get_pair( + world, entity, EcsIdentifier, tag); - if (!dst_table->type.count) { - /* If this removes all components, clear table */ - flecs_table_clear_entities(world, table); + if (ptr) { + return ptr->value; } 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); - ecs_log_pop_3(); - } - - flecs_table_merge(world, dst_table, table); - } + return NULL; } - - flecs_table_diff_builder_fini(world, &diff); +error: + return NULL; } -static -bool flecs_on_delete_clear_tables( - ecs_world_t *world) +const char* ecs_get_name( + 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; - - if ((action == EcsRemove) || - !(table->flags & EcsTableMarkedForDelete)) - { - flecs_remove_from_table(world, table); - } else { - ecs_dbg_3( - "#[red]delete#[reset] entities from table %u", - (uint32_t)table->id); - flecs_table_delete_entities(world, table); - } - } - } - } - - /* Run commands so children get notified before parent is deleted */ - if (world->stages[0]->defer) { - flecs_defer_end(world, world->stages[0]); - flecs_defer_begin(world, world->stages[0]); - } - - /* User code (from triggers) could have enqueued more ids to delete, - * reobtain the array in case it got reallocated */ - ids = ecs_vec_first(&world->store.marked_ids); - } - - /* Check if new ids were marked since we started */ - int32_t new_last = ecs_vec_count(&world->store.marked_ids); - if (new_last != last) { - /* Iterate remaining ids */ - ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); - first = last; - last = new_last; - } else { - break; - } - } while (true); - - return true; + return flecs_get_identifier(world, entity, EcsName); } -static -bool flecs_on_delete_clear_ids( - ecs_world_t *world) +const char* ecs_get_symbol( + const 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; - - do { - for (i = 0; i < count; i ++) { - /* Release normal ids before wildcard ids */ - if (ecs_id_is_wildcard(ids[i].id)) { - if (twice == 2) { - continue; - } - } else { - if (twice == 1) { - continue; - } - } - - ecs_id_record_t *idr = ids[i].idr; - bool delete_id = ids[i].delete_id; - - flecs_id_record_release_tables(world, idr); - - /* Release the claim taken by flecs_marked_id_push. This may delete the - * id record as all other claims may have been released. */ - int32_t rc = flecs_id_record_release(world, idr); - ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); - (void)rc; - - /* If rc is 0, the id was likely deleted by a nested delete_with call - * made by an on_remove handler/OnRemove observer */ - if (rc) { - if (delete_id) { - /* If id should be deleted, release initial claim. This happens when - * a component, tag, or part of a pair is deleted. */ - flecs_id_record_release(world, idr); - } else { - /* If id should not be deleted, unmark id record for deletion. This - * happens when all instances *of* an id are deleted, for example - * when calling ecs_remove_all or ecs_delete_with. */ - idr->flags &= ~EcsIdMarkedForDelete; - } - } - } - } while (-- twice); - - return true; + 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_on_delete( +ecs_entity_t flecs_set_identifier( ecs_world_t *world, - ecs_id_t id, - ecs_entity_t action, - bool delete_id) + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_entity_t tag, + const char *name) { - /* 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 i, count = ecs_vec_count(&world->store.marked_ids); - - /* 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); - - /* 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); - 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); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); - /* Ids are deleted, clear stack */ - ecs_vec_clear(&world->store.marked_ids); + if (!entity) { + entity = ecs_new(world); + } - /* If any components got deleted, cleanup type info. Delaying this - * ensures that type info remains available during cleanup. */ - count = ecs_vec_count(&world->store.deleted_components); - ecs_entity_t *comps = ecs_vec_first(&world->store.deleted_components); - for (i = 0; i < count; i ++) { - flecs_type_info_free(world, comps[i]); - } + if (!name) { + ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); + return entity; + } - ecs_vec_clear(&world->store.deleted_components); + EcsIdentifier *ptr = ecs_ensure_pair(world, entity, EcsIdentifier, tag); + ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_log_pop_2(); + if (tag == EcsName) { + /* Insert command after ensure, but before the name is potentially + * freed. Even though the name is a const char*, it is possible that the + * application passed in the existing name of the entity which could + * still cause it to be freed. */ + flecs_defer_path(stage, 0, entity, name); } + + ecs_os_strset(&ptr->value, name); + ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); + + return entity; +error: + return 0; } -void ecs_delete_with( +ecs_entity_t ecs_set_name( ecs_world_t *world, - ecs_id_t id) + ecs_entity_t entity, + const char *name) { - 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; + if (!entity) { + return ecs_entity(world, { + .name = name + }); } - flecs_on_delete(world, id, EcsDelete, false); - flecs_defer_end(world, stage); + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_set_identifier(world, stage, entity, EcsName, name); - flecs_journal_end(); + return entity; } -void ecs_remove_all( +ecs_entity_t ecs_set_symbol( ecs_world_t *world, - ecs_id_t id) + ecs_entity_t entity, + const char *name) { - flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { - return; - } + return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); +} - flecs_on_delete(world, id, EcsRemove, false); - flecs_defer_end(world, stage); +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *name) +{ + flecs_set_identifier(world, NULL, entity, EcsAlias, name); +} - flecs_journal_end(); +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); } -void ecs_delete( - ecs_world_t *world, +bool ecs_is_valid( + 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); - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_delete(stage, entity)) { - return; + /* 0 is not a valid entity id */ + if (!entity) { + return false; + } + + /* Entity identifiers should not contain flag bits */ + if (entity & ECS_ID_FLAGS_MASK) { + return false; } - ecs_os_perf_trace_push("flecs.delete"); - - ecs_record_t *r = flecs_entities_try(world, entity); - if (r) { - flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); - - ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); - ecs_table_t *table; - if (row_flags) { - if (row_flags & EcsEntityIsTarget) { - flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); - flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); - r->idr = NULL; - } - if (row_flags & EcsEntityIsId) { - flecs_on_delete(world, entity, 0, true); - flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); - } - if (row_flags & EcsEntityIsTraversable) { - table = r->table; - if (table) { - flecs_table_traversable_add(table, -1); - } - } - /* Merge operations before deleting entity */ - flecs_defer_end(world, stage); - flecs_defer_begin(world, stage); - } - - table = r->table; - - if (table) { - ecs_table_diff_t diff = { - .removed = table->type, - .removed_flags = table->flags & EcsTableRemoveEdgeFlags - }; - - flecs_delete_entity(world, r, &diff); - - r->row = 0; - r->table = NULL; - } - - flecs_entities_remove(world, entity); - - flecs_journal_end(); + /* Entities should not contain data in dead zone bits */ + if (entity & ~0xFF00FFFFFFFFFFFF) { + return false; } - flecs_defer_end(world, stage); + /* If id exists, it must be alive (the generation count must match) */ + return ecs_is_alive(world, entity); error: - ecs_os_perf_trace_pop("flecs.delete"); - return; + return false; } -void ecs_add_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +ecs_id_t ecs_strip_generation( + ecs_entity_t e) { - 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; + /* If this is not a pair, erase the generation bits */ + if (!(e & ECS_ID_FLAGS_MASK)) { + e &= ~ECS_GENERATION_MASK; + } + + return e; } -void ecs_remove_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +bool ecs_is_alive( + const ecs_world_t *world, + ecs_entity_t entity) { 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; -} + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); -void ecs_auto_override_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | id); + world = ecs_get_world(world); + + return flecs_entities_is_alive(world, entity); error: - return; + return false; } -ecs_entity_t ecs_clone( - ecs_world_t *world, - ecs_entity_t dst, - ecs_entity_t src, - bool copy_value) +ecs_entity_t ecs_get_alive( + const ecs_world_t *world, + ecs_entity_t entity) { 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(world); + + if (!entity) { + return 0; } - if (flecs_defer_clone(stage, dst, src, copy_value)) { - return dst; - } + /* Make sure we're not working with a stage */ + world = ecs_get_world(world); - 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; + if (flecs_entities_is_alive(world, entity)) { + return entity; } - ecs_table_t *dst_table = src_table; - if (src_table->flags & EcsTableHasName) { - dst_table = ecs_table_remove_id(world, src_table, - ecs_pair_t(EcsIdentifier, EcsName)); + /* Make sure id does not have generation. This guards against accidentally + * "upcasting" a not alive identifier to an alive one. */ + if ((uint32_t)entity != entity) { + return 0; } - ecs_type_t dst_type = dst_table->type; - ecs_table_diff_t diff = { - .added = dst_type, - .added_flags = dst_table->flags & EcsTableAddEdgeFlags - }; - ecs_record_t *dst_r = flecs_entities_get(world, dst); - /* Note 'ctor' parameter below is set to 'false' if the value will be copied, since flecs_table_move - will call a copy constructor */ - flecs_new_entity(world, dst, dst_r, dst_table, &diff, !copy_value, 0); - int32_t row = ECS_RECORD_TO_ROW(dst_r->row); - - if (copy_value) { - flecs_table_move(world, dst, src, dst_table, - row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); - int32_t i, count = dst_table->column_count; - for (i = 0; i < count; i ++) { - ecs_id_t id = flecs_column_id(dst_table, i); - ecs_type_t type = { - .array = &id, - .count = 1 - }; - flecs_notify_on_set(world, dst_table, row, 1, &type, true); - } + ecs_entity_t current = flecs_entities_get_alive(world, entity); + if (!current || !flecs_entities_is_alive(world, current)) { + return 0; } -done: - flecs_defer_end(world, stage); - return dst; + return current; error: return 0; } -#define ecs_get_low_id(table, r, id)\ - ecs_assert(table->component_map != NULL, ECS_INTERNAL_ERROR, NULL);\ - int16_t column_index = table->component_map[id];\ - if (column_index > 0) {\ - ecs_column_t *column = &table->data.columns[column_index - 1];\ - return ECS_ELEM(column->data, column->ti->size, \ - ECS_RECORD_TO_ROW(r->row));\ - } else if (column_index < 0) {\ - column_index = flecs_ito(int16_t, -column_index - 1);\ - const ecs_table_record_t *tr = &table->_->records[column_index];\ - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;\ - if (idr->flags & EcsIdIsSparse) {\ - return flecs_sparse_get_any(idr->sparse, 0, entity);\ - }\ - } - -const void* ecs_get_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +void ecs_make_alive( + ecs_world_t *world, + ecs_entity_t entity) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - world = ecs_get_world(world); + /* Const cast is safe, function checks for threading */ + world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + /* 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, + "cannot make entity alive while world is in multithreaded mode"); - ecs_table_t *table = r->table; - if (!table) { - return NULL; + /* Check if a version of the provided id is alive */ + ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); + if (any == entity) { + /* If alive and equal to the argument, there's nothing left to do */ + return; } - if (id < FLECS_HI_COMPONENT_ID) { - ecs_get_low_id(table, r, id); - if (!(table->flags & EcsTableHasIsA)) { - return NULL; - } - } + /* If the id is currently alive but did not match the argument, fail */ + ecs_check(!any, ECS_INVALID_PARAMETER, + "entity is alive with different generation"); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } + /* Set generation if not alive. The sparse set checks if the provided + * id matches its own generation which is necessary for alive ids. This + * check would cause ecs_ensure to fail if the generation of the 'entity' + * argument doesn't match with its generation. + * + * While this could've been addressed in the sparse set, this is a rare + * scenario that can only be triggered by ecs_ensure. Implementing it here + * allows the sparse set to not do this check, which is more efficient. */ + flecs_entities_make_alive(world, entity); - 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 { - if (idr->flags & EcsIdIsSparse) { - return flecs_sparse_get_any(idr->sparse, 0, entity); - } - ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); - } + /* Ensure id exists. The underlying data structure will verify that the + * generation count matches the provided one. */ + ecs_record_t *r = flecs_entities_ensure(world, entity); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_table_get_component(table, tr->column, row).ptr; + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + flecs_new_entity(world, entity, r, &world->store.root, &diff, false, 0); + ecs_assert(r->table == &world->store.root, ECS_INTERNAL_ERROR, NULL); error: - return NULL; + return; } -void* ecs_get_mut_id( - const ecs_world_t *world, - ecs_entity_t entity, +void ecs_make_alive_id( + 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); - - world = ecs_get_world(world); + flecs_poly_assert(world, ecs_world_t); + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_entity_t o = ECS_PAIR_SECOND(id); - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); - ecs_table_t *table = r->table; - if (!table) { - return NULL; + if (flecs_entities_get_alive(world, r) == 0) { + ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, + "first element of pair is not alive"); + ecs_make_alive(world, r); + } + if (flecs_entities_get_alive(world, o) == 0) { + ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, + "second element of pair is not alive"); + ecs_make_alive(world, o); + } + } else { + ecs_make_alive(world, id & ECS_COMPONENT_MASK); } +error: + return; +} - if (id < FLECS_HI_COMPONENT_ID) { - ecs_get_low_id(table, r, id); - return NULL; - } +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); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - int32_t row = ECS_RECORD_TO_ROW(r->row); - return flecs_get_component_ptr(table, row, idr).ptr; + world = ecs_get_world(world); + + return flecs_entities_exists(world, entity); error: - return NULL; + return false; } -void* ecs_ensure_id( +void ecs_set_version( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) + ecs_entity_t entity_with_generation) { - 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_poly_assert(world, ecs_world_t); + ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, + "cannot change entity generation when world is in readonly mode"); + ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, + "cannot change entity generation while world is deferred"); - ecs_stage_t *stage = flecs_stage_from_world(&world); - if (flecs_defer_cmd(stage)) { - return flecs_defer_set( - world, stage, EcsCmdEnsure, entity, id, 0, NULL, NULL); + flecs_entities_make_alive(world, entity_with_generation); + + if (flecs_entities_is_alive(world, entity_with_generation)) { + ecs_record_t *r = flecs_entities_get(world, entity_with_generation); + if (r && r->table) { + int32_t row = ECS_RECORD_TO_ROW(r->row); + ecs_entity_t *entities = r->table->data.entities; + entities[row] = entity_with_generation; + } } +} - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - void *result = flecs_ensure(world, entity, id, r).ptr; - ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); +ecs_table_t* ecs_get_table( + const ecs_world_t *world, + ecs_entity_t entity) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); + + world = ecs_get_world(world); - flecs_defer_end(world, stage); - return result; + ecs_record_t *record = flecs_entities_get(world, entity); + ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); + return record->table; error: return NULL; } -void* ecs_ensure_modified_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +const ecs_type_t* ecs_get_type( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); + ecs_table_t *table = ecs_get_table(world, entity); + if (table) { + return &table->type; + } - return flecs_defer_set(world, stage, EcsCmdSet, entity, id, 0, NULL, NULL); -error: return NULL; } -static -ecs_record_t* flecs_access_begin( - ecs_world_t *stage, - ecs_entity_t entity, - bool write) +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id) { - ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id != 0, 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); + world = ecs_get_world(world); - ecs_table_t *table; - if (!(table = r->table)) { - return NULL; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr && ECS_IS_PAIR(id)) { + cdr = flecs_components_get(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + if (!cdr || !cdr->type_info) { + cdr = NULL; + } + if (!cdr) { + ecs_entity_t first = ecs_pair_first(world, id); + if (!first || !ecs_has_id(world, first, EcsPairIsTag)) { + cdr = flecs_components_get(world, + ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); + if (!cdr || !cdr->type_info) { + cdr = NULL; + } + } + } } - int32_t count = ecs_os_ainc(&table->_->lock); - (void)count; - if (write) { - ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); + if (cdr) { + return cdr->type_info; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_type_info_get(world, id); } - - return r; error: return NULL; } -static -void flecs_access_end( - const ecs_record_t *r, - bool write) +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id) { - 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(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_type_info_t *ti = ecs_get_type_info(world, id); + if (ti) { + ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); + return ti->component; } - ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); - error: - return; -} - -ecs_record_t* ecs_write_begin( - ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_access_begin(world, entity, true); + return 0; } -void ecs_write_end( - ecs_record_t *r) +bool ecs_id_is_tag( + const ecs_world_t *world, + ecs_id_t id) { - flecs_access_end(r, true); -} + 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, EcsPairIsTag)) { + 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; + } + } -const ecs_record_t* ecs_read_begin( - ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_access_begin(world, entity, false); + return false; } -void ecs_read_end( - const ecs_record_t *r) +int32_t ecs_count_id( + const ecs_world_t *world, + ecs_entity_t id) { - flecs_access_end(r, false); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); -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) { + if (!id) { return 0; } - return table->data.entities[ECS_RECORD_TO_ROW(record->row)]; + int32_t count = 0; + ecs_iter_t it = ecs_each_id(world, id); + while (ecs_each_next(&it)) { + count += it.count; + } + + return count; error: return 0; } -const void* ecs_record_get_id( - const ecs_world_t *stage, - const ecs_record_t *r, - ecs_id_t id) +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled) { - const ecs_world_t *world = ecs_get_world(stage); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); + 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) & EcsIdOnInstantiateDontInherit)){ + ecs_enable(world, id, enabled); + } + } + } else { + if (enabled) { + ecs_remove_id(world, entity, EcsDisabled); + } else { + ecs_add_id(world, entity, EcsDisabled); + } + } } -bool ecs_record_has_id( - ecs_world_t *stage, - const ecs_record_t *r, - ecs_id_t id) +bool ecs_defer_begin( + ecs_world_t *world) { - const ecs_world_t *world = ecs_get_world(stage); - if (r->table) { - return ecs_table_has_id(world, r->table, id); - } + 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; } -void* ecs_record_ensure_id( - ecs_world_t *stage, - ecs_record_t *r, - ecs_id_t id) +bool ecs_defer_end( + ecs_world_t *world) { - const ecs_world_t *world = ecs_get_world(stage); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); + 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; } -ecs_ref_t ecs_ref_init_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +void ecs_defer_suspend( + ecs_world_t *world) { - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_check(record != NULL, ECS_INVALID_PARAMETER, - "cannot create ref for empty entity"); - - ecs_ref_t result = { - .entity = entity, - .id = id, - .record = record - }; - - ecs_table_t *table = record->table; - if (table) { - result.tr = flecs_table_record_get(world, table, id); - result.table_id = table->id; - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, + "world/stage must be deferred before it can be suspended"); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, + "world/stage is already suspended"); + stage->defer = -stage->defer; +error: + return; +} - return result; +void ecs_defer_resume( + ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, + "world/stage must be suspended before it can be resumed"); + stage->defer = -stage->defer; error: - return (ecs_ref_t){0}; + return; } -static -bool flecs_ref_needs_sync( - ecs_ref_t *ref, - ecs_table_record_t *tr, - const ecs_table_t *table) +const char* ecs_id_flag_str( + ecs_entity_t entity) { - ecs_assert(ref != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return !tr || ref->table_id != table->id || tr->hdr.table != table; + 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, AUTO_OVERRIDE)) { + return "AUTO_OVERRIDE"; + } else { + return "UNKNOWN"; + } } -void ecs_ref_update( +void ecs_id_str_buf( const ecs_world_t *world, - ecs_ref_t *ref) + ecs_id_t id, + ecs_strbuf_t *buf) { 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; + world = ecs_get_world(world); + + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); + ecs_strbuf_appendch(buf, '|'); } - ecs_table_record_t *tr = ref->tr; - if (flecs_ref_needs_sync(ref, tr, table)) { - tr = ref->tr = flecs_table_record_get(world, table, ref->id); - if (!tr) { - return; + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AUTO_OVERRIDE)); + ecs_strbuf_appendch(buf, '|'); + } + + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t obj = ECS_PAIR_SECOND(id); + + ecs_entity_t e; + if ((e = ecs_get_alive(world, rel))) { + rel = e; + } + if ((e = ecs_get_alive(world, obj))) { + obj = e; } - ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); - ref->table_id = table->id; + ecs_strbuf_appendch(buf, '('); + ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf, false); + ecs_strbuf_appendch(buf, ','); + ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf, false); + 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, false); } + error: return; } -void* ecs_ref_get_id( +char* ecs_id_str( 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, "ref not initialized"); - ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, "ref not initialized"); - ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, "ref not initialized"); - ecs_check(id == ref->id, ECS_INVALID_PARAMETER, "ref not initialized"); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_id_str_buf(world, id, &buf); + return ecs_strbuf_get(&buf); +} - ecs_record_t *r = ref->record; - ecs_table_t *table = r->table; - if (!table) { - return NULL; - } +static +void ecs_type_str_buf( + const ecs_world_t *world, + const ecs_type_t *type, + ecs_strbuf_t *buf) +{ + ecs_entity_t *ids = type->array; + int32_t i, count = type->count; - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < count; i ++) { + ecs_entity_t id = ids[i]; - ecs_table_record_t *tr = ref->tr; - if (flecs_ref_needs_sync(ref, tr, table)) { - tr = ref->tr = flecs_table_record_get(world, table, id); - if (!tr) { - return NULL; + if (i) { + ecs_strbuf_appendch(buf, ','); + ecs_strbuf_appendch(buf, ' '); } - ref->table_id = table->id; - ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); - } - - int32_t column = tr->column; - if (column == -1) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdIsSparse) { - return flecs_sparse_get_any(idr->sparse, 0, ref->entity); + if (id == 1) { + ecs_strbuf_appendlit(buf, "Component"); + } else { + ecs_id_str_buf(world, id, buf); } } - return flecs_table_get_component(table, column, row).ptr; -error: - return NULL; } -void* ecs_emplace_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool *is_new) +char* ecs_type_str( + const ecs_world_t *world, + const ecs_type_t *type) { - 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, EcsCmdEmplace, entity, id, 0, NULL, is_new); - } - - ecs_check(is_new || !ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, - "cannot emplace a component the entity already has"); - - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = r->table; - flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); - flecs_defer_end(world, stage); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - void *ptr = flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); - ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, - "emplaced component was removed during operation, make sure to not " - "remove component T in on_add(T) hook/OnAdd(T) observer"); - - if (is_new) { - *is_new = table != r->table; + if (!type) { + return ecs_os_strdup(""); } - return ptr; -error: - return NULL; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + ecs_type_str_buf(world, type, &buf); + return ecs_strbuf_get(&buf); } -static -void flecs_modified_id_if( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - bool owned) +char* ecs_table_str( + const ecs_world_t *world, + const ecs_table_t *table) { - 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 (!table || !flecs_table_record_get(world, table, id)) { - flecs_defer_end(world, stage); - return; + if (table) { + return ecs_type_str(world, &table->type); + } else { + return NULL; } - - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); - - flecs_table_mark_dirty(world, table, id); - flecs_defer_end(world, stage); -error: - return; } -void ecs_modified_id( - ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) +char* ecs_entity_str( + const ecs_world_t *world, + ecs_entity_t entity) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_t buf = ECS_STRBUF_INIT; 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_get_path_w_sep_buf(world, 0, entity, 0, "", &buf, false); + + 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, ']'); - /* 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); + return ecs_strbuf_get(&buf); error: - return; + return NULL; } static -void flecs_set_id_copy( +void flecs_flush_bulk_new( ecs_world_t *world, ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - void *ptr) + ecs_cmd_t *cmd) { - if (flecs_defer_cmd(stage)) { - flecs_defer_set(world, stage, EcsCmdSet, entity, id, - flecs_utosize(size), ptr, NULL); - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); + ecs_entity_t *entities = cmd->is._n.entities; - flecs_copy_id(world, r, id, size, dst.ptr, ptr, dst.ti); + if (cmd->id) { + int i, count = cmd->is._n.count; + for (i = 0; i < count; i ++) { + ecs_record_t *r = flecs_entities_ensure(world, entities[i]); + if (!r->table) { + flecs_add_to_root_table(world, stage, entities[i]); + } + flecs_add_id(world, entities[i], cmd->id); + } + } - flecs_defer_end(world, stage); + ecs_os_free(entities); } static -void flecs_set_id_move( +void flecs_dtor_value( ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, ecs_id_t id, - size_t size, - void *ptr, - ecs_cmd_kind_t cmd_kind) + void *value, + int32_t count) { - if (flecs_defer_cmd(stage)) { - flecs_defer_set(world, stage, cmd_kind, entity, id, - flecs_utosize(size), ptr, NULL); - return; - } - - ecs_record_t *r = flecs_entities_get(world, entity); - flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); - ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_type_info_t *ti = dst.ti; + const ecs_type_info_t *ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_move_t move; - if (cmd_kind != EcsCmdEmplace) { - /* ctor will have happened by ensure */ - move = ti->hooks.move_dtor; - } else { - move = ti->hooks.ctor_move_dtor; - } - if (move) { - move(dst.ptr, ptr, 1, ti); - } else { - ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); - } - - flecs_table_mark_dirty(world, r->table, id); - - if (cmd_kind == EcsCmdSet) { - ecs_table_t *table = r->table; - if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { - ecs_type_t ids = { .array = &id, .count = 1 }; - flecs_notify_on_set( - world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); + 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); } } - - flecs_defer_end(world, stage); -error: - return; } -void ecs_set_id( +static +void flecs_free_cmd_event( ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id, - size_t size, - const void *ptr) + ecs_event_desc_t *desc) { - 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); + if (desc->ids) { + flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); + } - ecs_stage_t *stage = flecs_stage_from_world(&world); + /* Safe const cast, command makes a copy of type object */ + flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), + ecs_type_t); - /* Safe to cast away const: function won't modify if move arg is false */ - flecs_set_id_copy(world, stage, entity, id, size, - ECS_CONST_CAST(void*, ptr)); -error: - return; + if (desc->param) { + flecs_dtor_value(world, desc->event, + /* Safe const cast, command makes copy of value */ + ECS_CONST_CAST(void*, desc->param), 1); + } } -#if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) static -bool flecs_can_toggle( +void flecs_discard_cmd( ecs_world_t *world, - ecs_id_t id) + ecs_cmd_t *cmd) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return false; + if (cmd->kind == EcsCmdBulkNew) { + ecs_os_free(cmd->is._n.entities); + } else if (cmd->kind == EcsCmdEvent) { + flecs_free_cmd_event(world, cmd->is._1.value); + } else { + ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); + void *value = cmd->is._1.value; + if (value) { + flecs_dtor_value(world, cmd->id, value, 1); + flecs_stack_free(value, cmd->is._1.size); + } } - - return (idr->flags & EcsIdCanToggle) != 0; } -#endif -void ecs_enable_id( +static +bool flecs_remove_invalid( ecs_world_t *world, - ecs_entity_t entity, ecs_id_t id, - bool enable) + ecs_id_t *id_out) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, - "add CanToggle trait to component"); - - ecs_entity_t bs_id = id | ECS_TOGGLE; - ecs_add_id(world, entity, bs_id); - - if (flecs_defer_enable(stage, entity, id, enable)) { - return; + 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_component_record_t *cdr = flecs_components_get(world, + ecs_pair(rel, EcsWildcard)); + if (cdr) { + ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(cdr->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_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = r->table; - int32_t index = ecs_table_get_type_index(world, table, bs_id); - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); - - 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); - - flecs_defer_end(world, stage); -error: - return; + return true; } -bool ecs_is_enabled_id( - const ecs_world_t *world, +static +void flecs_cmd_batch_for_entity( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, ecs_entity_t entity, - ecs_id_t id) + ecs_cmd_t *cmds, + int32_t start) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); - - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); - ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; - if (!table) { - return false; - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t bs_id = id | ECS_TOGGLE; - int32_t index = ecs_table_get_type_index(world, table, bs_id); - if (index == -1) { - /* If table does not have TOGGLE column for component, component is - * always enabled, if the entity has it */ - return ecs_has_id(world, entity, id); - } - - ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); - index -= table->_->bs_offset; - ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_bitset_t *bs = &table->_->bs_columns[index]; - - return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); -error: - return false; -} - -bool ecs_has_id( - const ecs_world_t *world, - ecs_entity_t entity, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + world->info.cmd.batched_entity_count ++; - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + 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; - 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; - } + /* Mask to let observable implementation know which components were set. + * This prevents the code from overwriting components with an override if + * the batch also contains an IsA pair. + * Use 4 elements, so the implementation supports up to 256 components added + * in a single batch. */ + ecs_flags64_t set_mask[4] = {0}; - if (id < FLECS_HI_COMPONENT_ID) { - if (table->component_map[id] != 0) { - return true; - } - } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (tr) { - return true; - } + 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 (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { - return false; - } - if (ECS_IS_PAIR(id) && (table->flags & EcsTableHasUnion)) { - ecs_id_record_t *u_idr = flecs_id_record_get(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsUnion)); - if (u_idr && u_idr->flags & EcsIdIsUnion) { - uint64_t cur = flecs_switch_get(u_idr->sparse, (uint32_t)entity); - return (uint32_t)cur == ECS_PAIR_SECOND(id); + /* Check if added id is still valid (like is the parent of a ChildOf + * pair still alive), if not run cleanup actions for entity */ + if (id) { + if (flecs_remove_invalid(world, id, &id)) { + if (!id) { + /* Entity should remain alive but id should not be added */ + cmd->kind = EcsCmdSkip; + continue; + } + /* Entity should remain alive and id is still valid */ + } else { + /* Id was no longer valid and had a Delete policy */ + cmd->kind = EcsCmdSkip; + ecs_delete(world, entity); + flecs_table_diff_builder_clear(diff); + return; + } } - } - ecs_table_record_t *tr; - int32_t column = ecs_search_relation(world, table, 0, id, - EcsIsA, 0, 0, 0, &tr); - if (column == -1) { - return false; - } + ecs_cmd_kind_t kind = cmd->kind; + switch(kind) { + case EcsCmdNew: + break; + case EcsCmdAddModified: + /* Add is batched, but keep Modified */ + cmd->kind = EcsCmdModified; - return true; -error: - return false; -} + /* fall through */ + case EcsCmdAdd: + table = flecs_find_table_add(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsCmdSet: + case EcsCmdEnsure: { + ecs_id_t *ids = diff->added.array; -bool ecs_owns_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_table_t *next = flecs_find_table_add(world, table, id, diff); + if (next != table) { + table = next; + } - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + /* Find id in added array so we can set the bit in the set_mask */ + int32_t i; + for (i = 0; i < diff->added.count; i ++) { + if (ids[i] == id) { + break; + } + } - 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_assert(i < 256, ECS_UNSUPPORTED, + "cannot add more than 256 components in a single operation"); + set_mask[i >> 6] |= 1llu << (i & 63); - if (id < FLECS_HI_COMPONENT_ID) { - return table->component_map[id] != 0; - } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (tr) { - return true; + world->info.cmd.batched_command_count ++; + break; + } + case EcsCmdEmplace: + /* Don't add for emplace, as this requires a special call to ensure + * the constructor is not invoked for the component */ + break; + case EcsCmdRemove: + table = flecs_find_table_remove(world, table, id, diff); + world->info.cmd.batched_command_count ++; + break; + case EcsCmdClear: + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + if (table->type.count) { + 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); + diff->removed_flags |= table->flags & EcsTableRemoveEdgeFlags; } + table = &world->store.root; + world->info.cmd.batched_command_count ++; + break; + case EcsCmdClone: + case EcsCmdBulkNew: + case EcsCmdPath: + case EcsCmdDelete: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdDisable: + case EcsCmdEvent: + case EcsCmdSkip: + case EcsCmdModifiedNoHook: + case EcsCmdModified: + break; } - } -error: - return false; -} + /* Add, remove and clear operations can be skipped since they have no + * side effects besides adding/removing components */ + if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { + cmd->kind = EcsCmdSkip; + } + } while ((cur = next_for_entity)); -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); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + /* Invoke OnAdd handlers after commit. This ensures that observers with + * mixed OnAdd/OnSet events won't get called with uninitialized values for + * an OnSet field. */ + ecs_type_t added = { diff->added.array, diff->added.count }; + diff->added.array = NULL; + diff->added.count = 0; - 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; - } + /* 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]); - 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 (!idr || (idr->flags & EcsIdOnInstantiateInherit)) { - goto look_in_base; - } else { - return 0; - } + /* If destination table has new sparse components, make sure they're created + * for the entity. */ + if ((table->flags & EcsTableHasSparse) && added.count) { + flecs_sparse_on_add( + world, table, ECS_RECORD_TO_ROW(r->row), 1, &added, true); } - if (index >= tr->count) { - index -= tr->count; - goto look_in_base; - } + /* If the batch contains set commands, copy the component value from the + * temporary command storage to the actual component storage before OnSet + * observers are invoked. This ensures that for multi-component OnSet + * observers all components are assigned a valid value before the observer + * is invoked. + * This only happens for entities that didn't have the assigned component + * yet, as for entities that did have the component already the value will + * have been assigned directly to the component storage. */ + if (set_mask[0] | set_mask[1] | set_mask[2] | set_mask[3]) { + cur = start; + do { + cmd = &cmds[cur]; + next_for_entity = cmd->next_for_entity; + if (next_for_entity < 0) { + next_for_entity *= -1; + } - ecs_entity_t result = - ecs_pair_second(world, table->type.array[tr->index + index]); + switch(cmd->kind) { + case EcsCmdSet: + case EcsCmdEnsure: { + flecs_component_ptr_t ptr = {0}; + if (r->table) { + ecs_component_record_t *cdr = flecs_components_get(world, cmd->id); + ptr = flecs_get_component_ptr( + r->table, ECS_RECORD_TO_ROW(r->row), cdr); + } - if (result == EcsUnion) { - wc = ecs_pair(rel, EcsUnion); - ecs_id_record_t *wc_idr = flecs_id_record_get(world, wc); - ecs_assert(wc_idr != NULL, ECS_INTERNAL_ERROR, NULL); - result = flecs_switch_get(wc_idr->sparse, (uint32_t)entity); - } + /* It's possible that even though the component was set, the + * command queue also contained a remove command, so before we + * do anything ensure the entity actually has the component. */ + if (ptr.ptr) { + const ecs_type_info_t *ti = ptr.ti; + ecs_move_t move = ti->hooks.move; + if (move) { + move(ptr.ptr, cmd->is._1.value, 1, ti); + ecs_xtor_t dtor = ti->hooks.dtor; + if (dtor) { + dtor(cmd->is._1.value, 1, ti); + } + } else { + ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); + } - return result; -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); + flecs_stack_free(cmd->is._1.value, cmd->is._1.size); + cmd->is._1.value = 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; + if (cmd->kind == EcsCmdSet) { + /* A set operation is add + copy + modified. We just did + * the add and copy, so the only thing that's left is a + * modified command, which will call the OnSet + * observers. */ + cmd->kind = EcsCmdModified; + } else { + /* If this was an ensure, nothing's left to be done */ + cmd->kind = EcsCmdSkip; + } + } else { + /* The entity no longer has the component which means that + * there was a remove command for the component in the + * command queue. In that case skip the command. */ + cmd->kind = EcsCmdSkip; + } + break; + } + case EcsCmdNew: + case EcsCmdClone: + case EcsCmdBulkNew: + case EcsCmdAdd: + case EcsCmdRemove: + case EcsCmdEmplace: + case EcsCmdModified: + case EcsCmdModifiedNoHook: + case EcsCmdAddModified: + case EcsCmdPath: + case EcsCmdDelete: + case EcsCmdClear: + case EcsCmdOnDeleteAction: + case EcsCmdEnable: + case EcsCmdDisable: + case EcsCmdEvent: + case EcsCmdSkip: + break; } + } while ((cur = next_for_entity)); + } + + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + + if (added.count) { + ecs_table_diff_t add_diff = ECS_TABLE_DIFF_INIT; + add_diff.added = added; + add_diff.added_flags = diff->added_flags; + + flecs_defer_begin(world, world->stages[0]); + flecs_notify_on_add(world, r->table, start_table, + ECS_RECORD_TO_ROW(r->row), 1, &add_diff, 0, set_mask, true, false); + flecs_defer_end(world, world->stages[0]); + if (r->row & EcsEntityIsTraversable) { + /* Update monitors since we didn't do this in flecs_commit. */ + flecs_update_component_monitors(world, &added, NULL); } } -not_found: -error: - return 0; -} + diff->added.array = added.array; + diff->added.count = added.count; -ecs_entity_t ecs_get_parent( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return ecs_get_target(world, entity, EcsChildOf, 0); + flecs_table_diff_builder_clear(diff); } -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) +/* 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); + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); - if (!id) { - return ecs_get_target(world, entity, rel, 0); + if (stage->defer < 0) { + /* Defer suspending makes it possible to do operations on the storage + * without flushing the commands in the queue */ + return false; } - world = ecs_get_world(world); + ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - ecs_table_t *table = ecs_get_table(world, entity); - ecs_entity_t subject = 0; + if (!--stage->defer && !stage->cmd_flushing) { + ecs_os_perf_trace_push("flecs.commands.merge"); - if (rel) { - int32_t column = ecs_search_relation( - world, table, 0, id, rel, 0, &subject, 0, 0); - if (column == -1) { - return 0; + /* Test whether we're flushing to another queue or whether we're + * flushing to the storage */ + bool merge_to_world = false; + if (flecs_poly_is(world, ecs_world_t)) { + merge_to_world = world->stages[0]->defer == 0; } - } else { - entity = 0; /* Don't return entity if id was not found */ - if (table) { - ecs_id_t *ids = table->type.array; - int32_t i, count = table->type.count; + do { + ecs_stage_t *dst_stage = flecs_stage_from_world(&world); + ecs_commands_t *commands = stage->cmd; + ecs_vec_t *queue = &commands->queue; - 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 (stage->cmd == &stage->cmd_stack[0]) { + stage->cmd = &stage->cmd_stack[1]; + } else { + stage->cmd = &stage->cmd_stack[0]; + } - if (ecs_has_id(world, ent, id)) { - subject = ent; - break; - } + if (!ecs_vec_count(queue)) { + break; } - } - } - if (subject == 0) { - return entity; - } else { - return subject; - } -error: - return 0; -} + stage->cmd_flushing = true; -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, - "cannot safely determine depth for relationship that is not acyclic " - "(add Acyclic property to relationship)"); + /* Internal callback for capturing commands */ + if (world->on_commands_active) { + world->on_commands_active(stage, queue, + world->on_commands_ctx_active); + } - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return ecs_table_get_depth(world, table, rel); - } + ecs_cmd_t *cmds = ecs_vec_first(queue); + int32_t i, count = ecs_vec_count(queue); - return 0; -error: - return -1; -} + ecs_table_diff_builder_t diff; + flecs_table_diff_builder_init(world, &diff); -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); + 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); - const EcsIdentifier *ptr = ecs_get_pair( - world, entity, EcsIdentifier, tag); + /* 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 ++; + } + } - if (ptr) { - return ptr->value; - } else { - return NULL; - } -error: - return NULL; -} + /* Invalidate entry */ + if (cmd->entry) { + cmd->entry->first = -1; + } -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity) -{ - return flecs_get_identifier(world, entity, EcsName); -} + /* If entity is no longer alive, this could be because the queue + * contained both a delete and a subsequent add/remove/set which + * should be ignored. */ + ecs_cmd_kind_t kind = cmd->kind; + if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { + world->info.cmd.discard_count ++; + flecs_discard_cmd(world, cmd); + continue; + } -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; - } -} + ecs_id_t id = cmd->id; -static -ecs_entity_t flecs_set_identifier( - ecs_world_t *world, - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t tag, - const char *name) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); + switch(kind) { + case EcsCmdNew: + flecs_add_to_root_table(world, stage, id); + break; + case EcsCmdAdd: + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + if (flecs_remove_invalid(world, id, &id)) { + if (id) { + world->info.cmd.add_count ++; + flecs_add_id(world, e, id); + } else { + world->info.cmd.discard_count ++; + } + } else { + world->info.cmd.discard_count ++; + ecs_delete(world, e); + } + break; + case EcsCmdRemove: + flecs_remove_id(world, e, id); + world->info.cmd.remove_count ++; + break; + case EcsCmdClone: + ecs_clone(world, e, id, cmd->is._1.clone_value); + world->info.cmd.other_count ++; + break; + case EcsCmdSet: + flecs_set_id_move(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.set_count ++; + break; + case EcsCmdEmplace: + if (merge_to_world) { + bool is_new; + ecs_emplace_id(world, e, id, &is_new); + if (!is_new) { + kind = EcsCmdEnsure; + } + } + flecs_set_id_move(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.ensure_count ++; + break; + case EcsCmdEnsure: + flecs_set_id_move(world, dst_stage, e, + cmd->id, flecs_itosize(cmd->is._1.size), + cmd->is._1.value, kind); + world->info.cmd.ensure_count ++; + break; + case EcsCmdModified: + flecs_modified_id_if(world, e, id, true); + world->info.cmd.modified_count ++; + break; + case EcsCmdModifiedNoHook: + flecs_modified_id_if(world, e, id, false); + world->info.cmd.modified_count ++; + break; + case EcsCmdAddModified: + flecs_add_id(world, e, id); + flecs_modified_id_if(world, e, id, true); + world->info.cmd.set_count ++; + break; + case EcsCmdDelete: { + ecs_delete(world, e); + world->info.cmd.delete_count ++; + break; + } + case EcsCmdClear: + ecs_clear(world, e); + world->info.cmd.clear_count ++; + break; + case EcsCmdOnDeleteAction: + ecs_defer_begin(world); + flecs_on_delete(world, id, e, false); + ecs_defer_end(world); + world->info.cmd.other_count ++; + break; + case EcsCmdEnable: + ecs_enable_id(world, e, id, true); + world->info.cmd.other_count ++; + break; + case EcsCmdDisable: + ecs_enable_id(world, e, id, false); + world->info.cmd.other_count ++; + break; + case EcsCmdBulkNew: + flecs_flush_bulk_new(world, stage, cmd); + world->info.cmd.other_count ++; + continue; + case EcsCmdPath: { + bool keep_alive = true; + ecs_make_alive(world, e); + if (cmd->id) { + if (ecs_is_alive(world, cmd->id)) { + ecs_add_pair(world, e, EcsChildOf, cmd->id); + } else { + ecs_delete(world, e); + keep_alive = false; + } + } + if (keep_alive) { + ecs_set_name(world, e, cmd->is._1.value); + } + ecs_os_free(cmd->is._1.value); + cmd->is._1.value = NULL; + world->info.cmd.other_count ++; + break; + } + case EcsCmdEvent: { + ecs_event_desc_t *desc = cmd->is._1.value; + ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_emit((ecs_world_t*)stage, desc); + flecs_free_cmd_event(world, desc); + world->info.cmd.event_count ++; + break; + } + case EcsCmdSkip: + break; + } - if (!entity) { - entity = ecs_new(world); - } + if (cmd->is._1.value) { + flecs_stack_free(cmd->is._1.value, cmd->is._1.size); + } + } - if (!name) { - ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); - return entity; - } + stage->cmd_flushing = false; - EcsIdentifier *ptr = ecs_ensure_pair(world, entity, EcsIdentifier, tag); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + flecs_stack_reset(&commands->stack); + ecs_vec_clear(queue); + flecs_table_diff_builder_fini(world, &diff); - if (tag == EcsName) { - /* Insert command after ensure, 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); - } + /* Internal callback for capturing commands, signal queue is done */ + if (world->on_commands_active) { + world->on_commands_active(stage, NULL, + world->on_commands_ctx_active); + } + } while (true); - ecs_os_strset(&ptr->value, name); - ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); - - return entity; -error: - return 0; -} + ecs_os_perf_trace_pop("flecs.commands.merge"); -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 - }); + return true; } - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_set_identifier(world, stage, entity, EcsName, name); - - return entity; + return false; } -ecs_entity_t ecs_set_symbol( +/* Delete operations from queue without executing them. */ +bool flecs_defer_purge( ecs_world_t *world, - ecs_entity_t entity, - const char *name) + ecs_stage_t *stage) { - return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); -void ecs_set_alias( - ecs_world_t *world, - ecs_entity_t entity, - const char *name) -{ - flecs_set_identifier(world, NULL, entity, EcsAlias, name); -} + if (!--stage->defer) { + ecs_vec_t commands = stage->cmd->queue; -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); -} + 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]); + } -bool ecs_is_valid( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); - /* 0 is not a valid entity id */ - if (!entity) { - return false; - } - - /* Entity identifiers should not contain flag bits */ - if (entity & ECS_ID_FLAGS_MASK) { - return false; - } + ecs_vec_clear(&commands); + flecs_stack_reset(&stage->cmd->stack); + flecs_sparse_clear(&stage->cmd->entries); + } - /* Entities should not contain data in dead zone bits */ - if (entity & ~0xFF00FFFFFFFFFFFF) { - return false; + return true; } - /* If id exists, it must be alive (the generation count must match) */ - return ecs_is_alive(world, entity); error: return false; } -ecs_id_t ecs_strip_generation( - ecs_entity_t e) -{ - /* If this is not a pair, erase the generation bits */ - if (!(e & ECS_ID_FLAGS_MASK)) { - e &= ~ECS_GENERATION_MASK; - } +/** + * @file entity_name.c + * @brief Functions for working with named entities. + */ - return e; -} +#include -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); +#define ECS_NAME_BUFFER_LENGTH (64) - world = ecs_get_world(world); +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 escape) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); - return flecs_entities_is_alive(world, entity); -error: - return false; -} + ecs_entity_t cur = 0; + const char *name = NULL; + ecs_size_t name_len = 0; -ecs_entity_t ecs_get_alive( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!entity) { - return 0; - } + if (child && ecs_is_alive(world, child)) { + ecs_record_t *r = flecs_entities_get(world, child); + ecs_assert(r != NULL, ECS_INVALID_OPERATION, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + bool hasName = r->table->flags & EcsTableHasName; - /* Make sure we're not working with a stage */ - world = ecs_get_world(world); + if (hasName) { + 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, escape); + 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); + } + } - if (flecs_entities_is_alive(world, entity)) { - return entity; + const EcsIdentifier *id = ecs_get_pair( + world, child, EcsIdentifier, EcsName); + if (id) { + name = id->value; + name_len = id->length; + } + } } - /* Make sure id does not have generation. This guards against accidentally - * "upcasting" a not alive identifier to an alive one. */ - if ((uint32_t)entity != entity) { - return 0; - } + if (name) { + /* Check if we need to escape separator character */ + const char *sep_in_name = NULL; + if (!sep[1]) { + sep_in_name = strchr(name, sep[0]); + } - ecs_entity_t current = flecs_entities_get_alive(world, entity); - if (!current || !flecs_entities_is_alive(world, current)) { - return 0; + if (sep_in_name || escape) { + const char *name_ptr; + char ch; + for (name_ptr = name; (ch = name_ptr[0]); name_ptr ++) { + char esc[3]; + if (ch != sep[0]) { + if (escape) { + flecs_chresc(esc, ch, '\"'); + ecs_strbuf_appendch(buf, esc[0]); + if (esc[1]) { + ecs_strbuf_appendch(buf, esc[1]); + } + } else { + ecs_strbuf_appendch(buf, ch); + } + } else { + if (!escape) { + ecs_strbuf_appendch(buf, '\\'); + ecs_strbuf_appendch(buf, sep[0]); + } else { + ecs_strbuf_appendlit(buf, "\\\\"); + flecs_chresc(esc, ch, '\"'); + ecs_strbuf_appendch(buf, esc[0]); + if (esc[1]) { + ecs_strbuf_appendch(buf, esc[1]); + } + } + } + } + } else { + ecs_strbuf_appendstrn(buf, name, name_len); + } + } else { + ecs_strbuf_appendch(buf, '#'); + ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); } - return current; -error: - return 0; + return cur != 0; } -void ecs_make_alive( - ecs_world_t *world, - ecs_entity_t entity) +bool flecs_name_is_id( + const char *name) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - - /* Const cast is safe, function checks for threading */ - world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); - - /* The entity index can be mutated while in staged/readonly mode, as long as - * the world is not multithreaded. */ - ecs_assert(!(world->flags & EcsWorldMultiThreaded), - ECS_INVALID_OPERATION, - "cannot make entity alive while world is in multithreaded mode"); - - /* 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_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + if (name[0] == '#') { + /* If name is not just digits it's not an id */ + const char *ptr; + char ch; + for (ptr = name + 1; (ch = ptr[0]); ptr ++) { + if (!isdigit(ch)) { + return false; + } + } + return true; } - - /* If the id is currently alive but did not match the argument, fail */ - ecs_check(!any, ECS_INVALID_PARAMETER, - "entity is alive with different generation"); - - /* 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_make_alive(world, entity); - - /* Ensure id exists. The underlying data structure will verify that the - * generation count matches the provided one. */ - flecs_entities_ensure(world, entity); -error: - return; + return false; } -void ecs_make_alive_id( - ecs_world_t *world, - ecs_id_t id) +ecs_entity_t flecs_name_to_id( + const char *name) { - flecs_poly_assert(world, ecs_world_t); - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); - - ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); - - if (flecs_entities_get_alive(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_alive(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); + ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); + ecs_entity_t res = flecs_ito(uint64_t, atoll(name + 1)); + if (res >= UINT32_MAX) { + return 0; /* Invalid id */ } -error: - return; + return res; } -bool ecs_exists( - const ecs_world_t *world, - ecs_entity_t entity) +static +ecs_entity_t flecs_get_builtin( + const char *name) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); + if (name[0] == '.' && name[1] == '\0') { + return EcsThis; + } else if (name[0] == '*' && name[1] == '\0') { + return EcsWildcard; + } else if (name[0] == '_' && name[1] == '\0') { + return EcsAny; + } else if (name[0] == '$' && name[1] == '\0') { + return EcsVariable; + } - return flecs_entities_exists(world, entity); -error: - return false; + return 0; } -void ecs_set_version( - ecs_world_t *world, - ecs_entity_t entity_with_generation) +static +bool flecs_is_sep( + const char **ptr, + const char *sep) { - flecs_poly_assert(world, ecs_world_t); - ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, - "cannot change entity generation when world is in readonly mode"); - ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, - "cannot change entity generation while world is deferred"); - - flecs_entities_make_alive(world, entity_with_generation); + ecs_size_t len = ecs_os_strlen(sep); - if (flecs_entities_is_alive(world, entity_with_generation)) { - ecs_record_t *r = flecs_entities_get(world, entity_with_generation); - if (r && r->table) { - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_entity_t *entities = r->table->data.entities; - entities[row] = entity_with_generation; - } + if (!ecs_os_strncmp(*ptr, sep, len)) { + *ptr += len; + return true; + } else { + return false; } } -ecs_table_t* ecs_get_table( - const ecs_world_t *world, - ecs_entity_t entity) +static +const char* flecs_path_elem( + const char *path, + const char *sep, + char **buffer_out, + ecs_size_t *size_out) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); - - world = ecs_get_world(world); - - ecs_record_t *record = flecs_entities_get(world, entity); - ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); - return record->table; -error: - return NULL; -} - -const ecs_type_t* ecs_get_type( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return &table->type; + char *buffer = NULL; + if (buffer_out) { + buffer = *buffer_out; } - return NULL; -} + const char *ptr; + char ch; + int32_t template_nesting = 0; + int32_t pos = 0; + ecs_size_t size = size_out ? *size_out : 0; -const ecs_type_info_t* ecs_get_type_info( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); + for (ptr = path; (ch = *ptr); ptr ++) { + if (ch == '<') { + template_nesting ++; + } else if (ch == '>') { + template_nesting --; + } else if (ch == '\\') { + ptr ++; + if (buffer) { + buffer[pos] = ptr[0]; + } + pos ++; + continue; + } - world = ecs_get_world(world); + ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); - 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 (!template_nesting && flecs_is_sep(&ptr, sep)) { + break; } - if (!idr) { - ecs_entity_t first = ecs_pair_first(world, id); - if (!first || !ecs_has_id(world, first, EcsPairIsTag)) { - idr = flecs_id_record_get(world, - ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); - if (!idr || !idr->type_info) { - idr = NULL; + + if (buffer) { + if (pos >= (size - 1)) { + if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ + char *new_buffer = ecs_os_malloc(size * 2 + 1); + ecs_os_memcpy(new_buffer, buffer, size); + buffer = new_buffer; + } else { /* heap buffer */ + buffer = ecs_os_realloc(buffer, size * 2 + 1); } + size *= 2; } + + buffer[pos] = ch; } + + pos ++; } - if (idr) { - return idr->type_info; - } else if (!(id & ECS_ID_FLAGS_MASK)) { - return flecs_type_info_get(world, id); + if (buffer) { + buffer[pos] = '\0'; + *buffer_out = buffer; + *size_out = size; } -error: - return NULL; -} -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, - ecs_id_t id) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_type_info_t *ti = ecs_get_type_info(world, id); - if (ti) { - ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); - return ti->component; + if (pos || ptr[0]) { + return ptr; + } else { + return NULL; } error: - return 0; + return NULL; } -bool ecs_id_is_tag( - const ecs_world_t *world, - ecs_id_t id) +static +bool flecs_is_root_path( + const char *path, + const char *prefix) { - 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, EcsPairIsTag)) { - 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 */ - } - } + if (prefix) { + return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); } else { - if (ecs_get_typeid(world, id) == 0) { - return true; - } + return false; } - - return false; } -int32_t ecs_count_id( +static +ecs_entity_t flecs_get_parent_from_path( const ecs_world_t *world, - ecs_entity_t id) + ecs_entity_t parent, + const char **path_ptr, + const char *sep, + const char *prefix, + bool new_entity, + bool *error) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); - if (!id) { - return 0; - } + bool start_from_root = false; + const char *path = *path_ptr; - int32_t count = 0; - ecs_iter_t it = ecs_each_id(world, id); - while (ecs_each_next(&it)) { - count += it.count; + if (flecs_is_root_path(path, prefix)) { + path += ecs_os_strlen(prefix); + parent = 0; + start_from_root = true; } - return count; -error: - return 0; -} + if (path[0] == '#') { + parent = flecs_name_to_id(path); + if (!parent && ecs_os_strncmp(path, "#0", 2)) { + *error = true; + return 0; + } -void ecs_enable( - ecs_world_t *world, - ecs_entity_t entity, - bool enabled) -{ - if (ecs_has_id(world, entity, EcsPrefab)) { - /* If entity is a type, enable/disable all entities in the type */ - const ecs_type_t *type = ecs_get_type(world, entity); - ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t *ids = type->array; - int32_t i, count = type->count; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - if (!(ecs_id_get_flags(world, id) & EcsIdOnInstantiateDontInherit)){ - ecs_enable(world, id, enabled); - } + path ++; + while (path[0] && isdigit(path[0])) { + path ++; /* Skip id part of path */ } - } else { - if (enabled) { - ecs_remove_id(world, entity, EcsDisabled); - } else { - ecs_add_id(world, entity, EcsDisabled); + + /* Skip next separator so that the returned path points to the next + * name element. */ + ecs_size_t sep_len = ecs_os_strlen(sep); + if (!ecs_os_strncmp(path, sep, ecs_os_strlen(sep))) { + path += sep_len; } + + start_from_root = true; + } + + if (!start_from_root && !parent && new_entity) { + parent = ecs_get_scope(world); } -} -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; -} + *path_ptr = path; -bool ecs_defer_end( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - return flecs_defer_end(world, stage); -error: - return false; + return parent; } -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, - "world/stage must be deferred before it can be suspended"); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, - "world/stage is already suspended"); - stage->defer = -stage->defer; -error: - return; -} +static +void flecs_on_set_symbol(ecs_iter_t *it) { + EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); + ecs_world_t *world = it->real_world; -void ecs_defer_resume( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, - "world/stage must be suspended before it can be resumed"); - stage->defer = -stage->defer; -error: - return; + 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); + } } -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, AUTO_OVERRIDE)) { - return "AUTO_OVERRIDE"; - } else { - return "UNKNOWN"; - } +void flecs_bootstrap_hierarchy(ecs_world_t *world) { + ecs_observer(world, { + .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), + .query.terms[0] = { + .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol) + }, + .callback = flecs_on_set_symbol, + .events = {EcsOnSet}, + .yield_existing = true + }); } -void ecs_id_str_buf( + +/* Public functions */ + +void ecs_get_path_w_sep_buf( const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf) + ecs_entity_t parent, + ecs_entity_t child, + const char *sep, + const char *prefix, + ecs_strbuf_t *buf, + bool escape) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); - if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); - ecs_strbuf_appendch(buf, '|'); + if (child == EcsWildcard) { + ecs_strbuf_appendch(buf, '*'); + return; } - - if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AUTO_OVERRIDE)); - ecs_strbuf_appendch(buf, '|'); + if (child == EcsAny) { + ecs_strbuf_appendch(buf, '_'); + return; } - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - ecs_entity_t obj = ECS_PAIR_SECOND(id); - - ecs_entity_t e; - if ((e = ecs_get_alive(world, rel))) { - rel = e; - } - if ((e = ecs_get_alive(world, obj))) { - obj = e; - } + if (!sep) { + sep = "."; + } - ecs_strbuf_appendch(buf, '('); - ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf, false); - ecs_strbuf_appendch(buf, ','); - ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf, false); - ecs_strbuf_appendch(buf, ')'); + if (!child || parent != child) { + flecs_path_append(world, parent, child, sep, prefix, buf, escape); } else { - ecs_entity_t e = id & ECS_COMPONENT_MASK; - ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf, false); + 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, false); 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(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_component_record_t *cdr = flecs_components_get(world, pair); + ecs_hashmap_t *index = NULL; + if (cdr) { + index = flecs_component_name_index_get(world, cdr); + } + 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(""); - } - - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_type_str_buf(world, type, &buf); - return ecs_strbuf_get(&buf); + const char *path) +{ + return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); } -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; } -} -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_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + world = ecs_get_world(world); - ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf, false); - - ecs_strbuf_appendlit(&buf, " ["); - const ecs_type_t *type = ecs_get_type(world, entity); - if (type) { - ecs_type_str_buf(world, type, &buf); + ecs_entity_t e = 0; + if (lookup_as_path) { + e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); } - ecs_strbuf_appendch(&buf, ']'); - return ecs_strbuf_get(&buf); + if (!e) { + e = flecs_name_index_find(&world->symbols, name, 0, 0); + } + + return e; error: - return NULL; + return 0; } -static -void flecs_flush_bulk_new( - ecs_world_t *world, - ecs_cmd_t *cmd) +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) { - ecs_entity_t *entities = cmd->is._n.entities; - - if (cmd->id) { - int i, count = cmd->is._n.count; - for (i = 0; i < count; i ++) { - flecs_entities_ensure(world, entities[i]); - flecs_add_id(world, entities[i], cmd->id); - } + if (!path) { + return 0; } - ecs_os_free(entities); -} + ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(!parent || ecs_is_valid(world, parent), + ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *stage = world; + world = ecs_get_world(world); -static -void flecs_dtor_value( - ecs_world_t *world, - ecs_id_t id, - void *value, - int32_t count) -{ - const ecs_type_info_t *ti = ecs_get_type_info(world, id); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - ecs_size_t size = ti->size; - void *ptr; - int i; - for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { - dtor(ptr, 1, ti); - } + ecs_entity_t e = flecs_get_builtin(path); + if (e) { + return e; } -} -static -void flecs_free_cmd_event( - ecs_world_t *world, - ecs_event_desc_t *desc) -{ - if (desc->ids) { - flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); + e = flecs_name_index_find(&world->aliases, path, 0, 0); + if (e) { + return e; } - /* Safe const cast, command makes a copy of type object */ - flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), - ecs_type_t); + char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; + const char *ptr; + int32_t size = ECS_NAME_BUFFER_LENGTH; + ecs_entity_t cur; + bool lookup_path_search = false; - if (desc->param) { - flecs_dtor_value(world, desc->event, - /* Safe const cast, command makes copy of value */ - ECS_CONST_CAST(void*, desc->param), 1); + 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 ++; } -} -static -void flecs_discard_cmd( - ecs_world_t *world, - ecs_cmd_t *cmd) -{ - if (cmd->kind == EcsCmdBulkNew) { - ecs_os_free(cmd->is._n.entities); - } else if (cmd->kind == EcsCmdEvent) { - flecs_free_cmd_event(world, cmd->is._1.value); - } else { - ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); - void *value = cmd->is._1.value; - if (value) { - flecs_dtor_value(world, cmd->id, value, 1); - flecs_stack_free(value, cmd->is._1.size); + if (!sep) { + sep = "."; + } + + bool error = false; + parent = flecs_get_parent_from_path( + stage, parent, &path, sep, prefix, true, &error); + if (error) { + return 0; + } + + if (parent && !(parent = ecs_get_alive(world, parent))) { + return 0; + } + + if (!path[0]) { + return parent; + } + + if (!sep[0]) { + return ecs_lookup_child(world, parent, path); + } + +retry: + cur = parent; + ptr = path; + + while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { + cur = ecs_lookup_child(world, cur, elem); + if (!cur) { + goto tail; } } -} -static -bool flecs_remove_invalid( - ecs_world_t *world, - ecs_id_t id, - ecs_id_t *id_out) -{ - if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ECS_PAIR_FIRST(id); - if (!flecs_entities_is_valid(world, rel)) { - /* After relationship is deleted we can no longer see what its - * delete action was, so pretend this never happened */ - *id_out = 0; - return true; - } else { - ecs_entity_t obj = ECS_PAIR_SECOND(id); - if (!flecs_entities_is_valid(world, obj)) { - /* Check the relationship's policy for deleted objects */ - ecs_id_record_t *idr = flecs_id_record_get(world, - ecs_pair(rel, EcsWildcard)); - if (idr) { - ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(idr->flags); - if (action == EcsDelete) { - /* Entity should be deleted, don't bother checking - * other ids */ - return false; - } else if (action == EcsPanic) { - /* If policy is throw this object should not have - * been deleted */ - flecs_throw_invalid_delete(world, id); - } else { - *id_out = 0; - return true; - } - } else { - *id_out = 0; - return true; - } +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; } } - } 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; + + if (lookup_path_search) { + if (lookup_path_cur != lookup_path) { + lookup_path_cur --; + parent = lookup_path_cur[0]; + goto retry; + } } } - return true; + if (elem != buff) { + ecs_os_free(elem); + } + + return cur; +error: + return 0; } -static -ecs_table_t* flecs_cmd_batch_add_diff( +ecs_entity_t ecs_set_scope( ecs_world_t *world, - ecs_table_t *dst, - ecs_table_t *cur, - ecs_table_t *prev) + ecs_entity_t scope) { - int32_t p = 0, p_count = prev->type.count; - int32_t c = 0, c_count = cur->type.count; - ecs_id_t *p_ids = prev->type.array; - ecs_id_t *c_ids = cur->type.array; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - for (; c < c_count;) { - ecs_id_t c_id = c_ids[c]; - ecs_id_t p_id = p_ids[p]; + ecs_entity_t cur = stage->scope; + stage->scope = scope; - if (p_id < c_id) { - /* Previous id no longer exists in new table, so it was removed */ - dst = ecs_table_remove_id(world, dst, p_id); - } - if (c_id < p_id) { - /* Current id didn't exist in previous table, so it was added */ - dst = ecs_table_add_id(world, dst, c_id); - } + return cur; +error: + return 0; +} - c += c_id <= p_id; - p += p_id <= c_id; - if (p == p_count) { - break; - } - } +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; +} - /* Remainder */ - for (; p < p_count; p ++) { - ecs_id_t p_id = p_ids[p]; - dst = ecs_table_remove_id(world, dst, p_id); - } - for (; c < c_count; c ++) { - ecs_id_t c_id = c_ids[c]; - dst = ecs_table_add_id(world, dst, c_id); - } +ecs_entity_t* ecs_set_lookup_path( + ecs_world_t *world, + const ecs_entity_t *lookup_path) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_stage_t *stage = flecs_stage_from_world(&world); - return dst; + /* Safe: application owns lookup path */ + ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); + stage->lookup_path = lookup_path; + + return cur; +error: + return NULL; +} + +ecs_entity_t* ecs_get_lookup_path( + const ecs_world_t *world) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); + /* Safe: application owns lookup path */ + return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); +error: + return NULL; +} + +const char* ecs_set_name_prefix( + ecs_world_t *world, + const char *prefix) +{ + flecs_poly_assert(world, ecs_world_t); + const char *old_prefix = world->info.name_prefix; + world->info.name_prefix = prefix; + return old_prefix; } static -void flecs_cmd_batch_for_entity( +void flecs_add_path( ecs_world_t *world, - ecs_table_diff_builder_t *diff, + bool defer_suspend, + ecs_entity_t parent, ecs_entity_t entity, - ecs_cmd_t *cmds, - int32_t start) + const char *name) { - ecs_record_t *r = flecs_entities_get(world, entity); - ecs_table_t *table = NULL; - if (r) { - table = r->table; + 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); } - world->info.cmd.batched_entity_count ++; - - ecs_table_t *start_table = table; - ecs_cmd_t *cmd; - int32_t next_for_entity; - ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ - int32_t cur = start; - ecs_id_t id; - - /* Mask to let observable implementation know which components were set. - * This prevents the code from overwriting components with an override if - * the batch also contains an IsA pair. */ - ecs_flags64_t set_mask = 0; - - 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, parent); + } - /* Check if added id is still valid (like is the parent of a ChildOf - * pair still alive), if not run cleanup actions for entity */ - if (id) { - if (flecs_remove_invalid(world, id, &id)) { - if (!id) { - /* Entity should remain alive but id should not be added */ - cmd->kind = EcsCmdSkip; - continue; - } - /* Entity should remain alive and id is still valid */ - } else { - /* Id was no longer valid and had a Delete policy */ - cmd->kind = EcsCmdSkip; - ecs_delete(world, entity); - flecs_table_diff_builder_clear(diff); - return; - } - } + ecs_assert(name[0] != '#', ECS_INVALID_PARAMETER, + "path should not contain identifier with #"); - ecs_cmd_kind_t kind = cmd->kind; - switch(kind) { - case EcsCmdAddModified: - /* Add is batched, but keep Modified */ - cmd->kind = EcsCmdModified; + ecs_set_name(world, entity, name); - /* fall through */ - case EcsCmdAdd: - table = flecs_find_table_add(world, table, id, diff); - world->info.cmd.batched_command_count ++; - break; - case EcsCmdModified: - if (start_table) { - /* If a modified was inserted for an existing component, the value - * of the component could have been changed. If this is the case, - * call on_set hooks before the OnAdd/OnRemove observers are invoked - * when moving the entity to a different table. - * This ensures that if OnAdd/OnRemove observers access the modified - * component value, the on_set hook has had the opportunity to - * run first to set any computed values of the component. */ - int32_t row = ECS_RECORD_TO_ROW(r->row); - ecs_id_record_t *idr = flecs_id_record_get(world, cmd->id); - const ecs_table_record_t *tr = - flecs_id_record_get_table(idr, start_table); - if (tr) { - const ecs_type_info_t *ti = idr->type_info; - ecs_iter_action_t on_set; - if ((on_set = ti->hooks.on_set)) { - ecs_table_t *prev_table = r->table; - ecs_defer_begin(world); - flecs_invoke_hook(world, start_table, tr, 1, row, - &entity,cmd->id, ti, EcsOnSet, on_set); - ecs_defer_end(world); - - /* Don't run on_set hook twice, but make sure to still - * invoke observers. */ - cmd->kind = EcsCmdModifiedNoHook; - - /* If entity changed tables in hook, add difference to - * destination table, so we don't lose the side effects - * of the hook. */ - if (r->table != prev_table) { - table = flecs_cmd_batch_add_diff( - world, table, r->table, prev_table); - } - } - } - } - break; - case EcsCmdSet: - case EcsCmdEnsure: { - ecs_id_t *ids = diff->added.array; - uint8_t added_index = flecs_ito(uint8_t, diff->added.count); + if (defer_suspend) { + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_resume_readonly(real_world, &srs); + flecs_defer_path(stage, parent, entity, name); + } +} - ecs_table_t *next = flecs_find_table_add(world, table, id, diff); - if (next != table) { - table = next; - if (diff->added.count == (added_index + 1)) { - /* Single id was added, must be at the end of the array */ - ecs_assert(ids[added_index] == id, ECS_INTERNAL_ERROR, NULL); - set_mask |= (1llu << added_index); - } else { - /* Id was already added or multiple ids got added. Do a linear - * search to find the index we need to set the set_mask. */ - int32_t i; - for (i = 0; i < diff->added.count; i ++) { - if (ids[i] == id) { - break; - } - } +ecs_entity_t ecs_add_path_w_sep( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t parent, + const char *path, + const char *sep, + const char *prefix) +{ + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + const ecs_world_t *real_world = world; + if (flecs_poly_is(world, ecs_stage_t)) { + real_world = ecs_get_world(world); + } - ecs_assert(i != diff->added.count, ECS_INTERNAL_ERROR, NULL); - set_mask |= (1llu << i); - } - } + if (!sep) { + sep = "."; + } - world->info.cmd.batched_command_count ++; - break; - } - case EcsCmdEmplace: - /* Don't add for emplace, as this requires a special call to ensure - * the constructor is not invoked for the component */ - break; - case EcsCmdRemove: - table = flecs_find_table_remove(world, table, id, diff); - world->info.cmd.batched_command_count ++; - break; - case EcsCmdClear: - if (table) { - ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, - &diff->removed, ecs_id_t, table->type.count); - ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, - table->type.count); - diff->removed_flags |= table->flags & EcsTableRemoveEdgeFlags; - } - table = &world->store.root; - world->info.cmd.batched_command_count ++; - break; - case EcsCmdClone: - case EcsCmdBulkNew: - case EcsCmdPath: - case EcsCmdDelete: - case EcsCmdOnDeleteAction: - case EcsCmdEnable: - case EcsCmdDisable: - case EcsCmdEvent: - case EcsCmdSkip: - case EcsCmdModifiedNoHook: - break; + if (!path) { + if (!entity) { + entity = ecs_new(world); } - /* Add, remove and clear operations can be skipped since they have no - * side effects besides adding/removing components */ - if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { - cmd->kind = EcsCmdSkip; + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, entity); } - } while ((cur = next_for_entity)); - - /* Invoke OnAdd handlers after commit. This ensures that observers with - * mixed OnAdd/OnSet events won't get called with uninitialized values for - * an OnSet field. */ - ecs_type_t added = { diff->added.array, diff->added.count }; - diff->added.array = NULL; - diff->added.count = 0; - /* 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]); + return entity; + } - /* If destination table has new sparse components, make sure they're created - * for the entity. */ - if (table && (table->flags & EcsTableHasSparse) && added.count) { - flecs_sparse_on_add( - world, table, ECS_RECORD_TO_ROW(r->row), 1, &added, true); + bool root_path = flecs_is_root_path(path, prefix); + bool error = false; + parent = flecs_get_parent_from_path( + world, parent, &path, sep, prefix, !entity, &error); + if (error) { + /* Invalid id */ + ecs_err("invalid identifier: '%s'", path); + return 0; } - /* If the batch contains set commands, copy the component value from the - * temporary command storage to the actual component storage before OnSet - * observers are invoked. This ensures that for multi-component OnSet - * observers all components are assigned a valid value before the observer - * is invoked. - * This only happens for entities that didn't have the assigned component - * yet, as for entities that did have the component already the value will - * have been assigned directly to the component storage. */ - if (set_mask) { - cur = start; - do { - cmd = &cmds[cur]; - next_for_entity = cmd->next_for_entity; - if (next_for_entity < 0) { - next_for_entity *= -1; - } + char buff[ECS_NAME_BUFFER_LENGTH]; + const char *ptr = path; + char *elem = buff; + int32_t 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_is_deferred(world) && + !(real_world->flags & EcsWorldMultiThreaded); + + ecs_entity_t cur = parent; + char *name = NULL; - switch(cmd->kind) { - case EcsCmdSet: - case EcsCmdEnsure: { - flecs_component_ptr_t ptr = {0}; - if (r->table) { - ecs_id_record_t *idr = flecs_id_record_get(world, cmd->id); - ptr = flecs_get_component_ptr( - r->table, ECS_RECORD_TO_ROW(r->row), idr); + if (sep[0]) { + while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); } - /* It's possible that even though the component was set, the - * command queue also contained a remove command, so before we - * do anything ensure the entity actually has the component. */ - if (ptr.ptr) { - const ecs_type_info_t *ti = ptr.ti; - ecs_move_t move = ti->hooks.move; - if (move) { - move(ptr.ptr, cmd->is._1.value, 1, ti); - ecs_xtor_t dtor = ti->hooks.dtor; - if (dtor) { - dtor(cmd->is._1.value, 1, ti); - } - } else { - ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); - } + name = ecs_os_strdup(elem); - flecs_stack_free(cmd->is._1.value, cmd->is._1.size); - cmd->is._1.value = NULL; + /* If this is the last entity in the path, use the provided id */ + bool last_elem = false; + if (!flecs_path_elem(ptr, sep, NULL, NULL)) { + e = entity; + last_elem = true; + } - if (cmd->kind == EcsCmdSet) { - /* A set operation is add + copy + modified. We just did - * the add and copy, so the only thing that's left is a - * modified command, which will call the OnSet - * observers. */ - cmd->kind = EcsCmdModified; + if (!e) { + if (last_elem) { + ecs_entity_t prev = ecs_set_scope(world, 0); + e = ecs_entity(world, {0}); + ecs_set_scope(world, prev); } else { - /* If this was an ensure, nothing's left to be done */ - cmd->kind = EcsCmdSkip; + e = ecs_new(world); } - } else { - /* The entity no longer has the component which means that - * there was a remove command for the component in the - * command queue. In that case skip the command. */ - cmd->kind = EcsCmdSkip; } - break; - } - case EcsCmdClone: - case EcsCmdBulkNew: - case EcsCmdAdd: - case EcsCmdRemove: - case EcsCmdEmplace: - case EcsCmdModified: - case EcsCmdModifiedNoHook: - case EcsCmdAddModified: - case EcsCmdPath: - case EcsCmdDelete: - case EcsCmdClear: - case EcsCmdOnDeleteAction: - case EcsCmdEnable: - case EcsCmdDisable: - case EcsCmdEvent: - case EcsCmdSkip: - break; + + if (!cur && last_elem && root_path) { + ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); + } + + flecs_add_path(world, suspend_defer, cur, e, name); } - } while ((cur = next_for_entity)); - } - if (added.count && r->table) { - ecs_table_diff_t add_diff = ECS_TABLE_DIFF_INIT; - add_diff.added = added; - add_diff.added_flags = diff->added_flags; + cur = e; + } - flecs_defer_begin(world, world->stages[0]); - flecs_notify_on_add(world, r->table, start_table, - ECS_RECORD_TO_ROW(r->row), 1, &add_diff, 0, set_mask, true, false); - flecs_defer_end(world, world->stages[0]); - if (r->row & EcsEntityIsTraversable) { - /* Update monitors since we didn't do this in flecs_commit. */ - flecs_update_component_monitors(world, &added, NULL); + if (entity && (cur != entity)) { + ecs_throw(ECS_ALREADY_DEFINED, name); } - } - diff->added.array = added.array; - diff->added.count = added.count; + if (name) { + ecs_os_free(name); + } - flecs_table_diff_builder_clear(diff); + if (elem != buff) { + ecs_os_free(elem); + } + } else { + flecs_add_path(world, suspend_defer, parent, entity, path); + } + + return cur; +error: + return 0; } -/* Leave safe section. Run all deferred commands. */ -bool flecs_defer_end( +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) { - flecs_poly_assert(world, ecs_world_t); - flecs_poly_assert(stage, ecs_stage_t); + return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); +} - if (stage->defer < 0) { - /* Defer suspending makes it possible to do operations on the storage - * without flushing the commands in the queue */ - return false; - } +/** + * @file id.c + * @brief Id utilities. + */ - ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); - - if (!--stage->defer && !stage->cmd_flushing) { - ecs_os_perf_trace_push("flecs.commands.merge"); - /* Test whether we're flushing to another queue or whether we're - * flushing to the storage */ - bool merge_to_world = false; - if (flecs_poly_is(world, ecs_world_t)) { - merge_to_world = world->stages[0]->defer == 0; - } +#ifdef FLECS_QUERY_DSL +#endif - do { - ecs_stage_t *dst_stage = flecs_stage_from_world(&world); - ecs_commands_t *commands = stage->cmd; - ecs_vec_t *queue = &commands->queue; +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern) +{ + if (id == pattern) { + return true; + } - if (stage->cmd == &stage->cmd_stack[0]) { - stage->cmd = &stage->cmd_stack[1]; - } else { - stage->cmd = &stage->cmd_stack[0]; - } + if (ECS_HAS_ID_FLAG(pattern, PAIR)) { + if (!ECS_HAS_ID_FLAG(id, PAIR)) { + return false; + } - if (!ecs_vec_count(queue)) { - break; - } + ecs_entity_t id_first = ECS_PAIR_FIRST(id); + ecs_entity_t id_second = ECS_PAIR_SECOND(id); + ecs_entity_t pattern_first = ECS_PAIR_FIRST(pattern); + ecs_entity_t pattern_second = ECS_PAIR_SECOND(pattern); - stage->cmd_flushing = true; + ecs_check(id_first != 0, ECS_INVALID_PARAMETER, + "first element of pair cannot be 0"); + ecs_check(id_second != 0, ECS_INVALID_PARAMETER, + "second element of pair cannot be 0"); - /* Internal callback for capturing commands */ - if (world->on_commands_active) { - world->on_commands_active(stage, queue, - world->on_commands_ctx_active); + ecs_check(pattern_first != 0, ECS_INVALID_PARAMETER, + "first element of pair cannot be 0"); + ecs_check(pattern_second != 0, ECS_INVALID_PARAMETER, + "second element of pair cannot be 0"); + + if (pattern_first == EcsWildcard) { + if (pattern_second == EcsWildcard || pattern_second == id_second) { + return true; } - - ecs_cmd_t *cmds = ecs_vec_first(queue); - int32_t i, count = ecs_vec_count(queue); - - ecs_table_diff_builder_t diff; - flecs_table_diff_builder_init(world, &diff); - - for (i = 0; i < count; i ++) { - ecs_cmd_t *cmd = &cmds[i]; - ecs_entity_t e = cmd->entity; - bool is_alive = flecs_entities_is_alive(world, e); - - /* A negative index indicates the first command for an entity */ - if (merge_to_world && (cmd->next_for_entity < 0)) { - /* Batch commands for entity to limit archetype moves */ - if (is_alive) { - flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); - } else { - world->info.cmd.discard_count ++; - } - } - - /* Invalidate entry */ - if (cmd->entry) { - cmd->entry->first = -1; - } - - /* If entity is no longer alive, this could be because the queue - * contained both a delete and a subsequent add/remove/set which - * should be ignored. */ - ecs_cmd_kind_t kind = cmd->kind; - if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { - world->info.cmd.discard_count ++; - flecs_discard_cmd(world, cmd); - continue; - } - - ecs_id_t id = cmd->id; - - switch(kind) { - case EcsCmdAdd: - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - if (flecs_remove_invalid(world, id, &id)) { - if (id) { - world->info.cmd.add_count ++; - flecs_add_id(world, e, id); - } else { - world->info.cmd.discard_count ++; - } - } else { - world->info.cmd.discard_count ++; - ecs_delete(world, e); - } - break; - case EcsCmdRemove: - flecs_remove_id(world, e, id); - world->info.cmd.remove_count ++; - break; - case EcsCmdClone: - ecs_clone(world, e, id, cmd->is._1.clone_value); - world->info.cmd.other_count ++; - break; - case EcsCmdSet: - flecs_set_id_move(world, dst_stage, e, - cmd->id, flecs_itosize(cmd->is._1.size), - cmd->is._1.value, kind); - world->info.cmd.set_count ++; - break; - case EcsCmdEmplace: - if (merge_to_world) { - bool is_new; - ecs_emplace_id(world, e, id, &is_new); - if (!is_new) { - kind = EcsCmdEnsure; - } - } - flecs_set_id_move(world, dst_stage, e, - cmd->id, flecs_itosize(cmd->is._1.size), - cmd->is._1.value, kind); - world->info.cmd.ensure_count ++; - break; - case EcsCmdEnsure: - flecs_set_id_move(world, dst_stage, e, - cmd->id, flecs_itosize(cmd->is._1.size), - cmd->is._1.value, kind); - world->info.cmd.ensure_count ++; - break; - case EcsCmdModified: - flecs_modified_id_if(world, e, id, true); - world->info.cmd.modified_count ++; - break; - case EcsCmdModifiedNoHook: - flecs_modified_id_if(world, e, id, false); - world->info.cmd.modified_count ++; - break; - case EcsCmdAddModified: - flecs_add_id(world, e, id); - flecs_modified_id_if(world, e, id, true); - world->info.cmd.set_count ++; - break; - case EcsCmdDelete: { - ecs_delete(world, e); - world->info.cmd.delete_count ++; - break; - } - case EcsCmdClear: - ecs_clear(world, e); - world->info.cmd.clear_count ++; - break; - case EcsCmdOnDeleteAction: - ecs_defer_begin(world); - flecs_on_delete(world, id, e, false); - ecs_defer_end(world); - world->info.cmd.other_count ++; - break; - case EcsCmdEnable: - ecs_enable_id(world, e, id, true); - world->info.cmd.other_count ++; - break; - case EcsCmdDisable: - ecs_enable_id(world, e, id, false); - world->info.cmd.other_count ++; - break; - case EcsCmdBulkNew: - flecs_flush_bulk_new(world, cmd); - world->info.cmd.other_count ++; - continue; - case EcsCmdPath: { - bool keep_alive = true; - ecs_make_alive(world, e); - if (cmd->id) { - if (ecs_is_alive(world, cmd->id)) { - ecs_add_pair(world, e, EcsChildOf, cmd->id); - } else { - ecs_delete(world, e); - keep_alive = false; - } - } - if (keep_alive) { - ecs_set_name(world, e, cmd->is._1.value); - } - ecs_os_free(cmd->is._1.value); - cmd->is._1.value = NULL; - world->info.cmd.other_count ++; - break; - } - case EcsCmdEvent: { - ecs_event_desc_t *desc = cmd->is._1.value; - ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_emit((ecs_world_t*)stage, desc); - flecs_free_cmd_event(world, desc); - world->info.cmd.event_count ++; - break; - } - case EcsCmdSkip: - break; + } else if (pattern_first == 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_second) { + return true; } - - if (cmd->is._1.value) { - flecs_stack_free(cmd->is._1.value, cmd->is._1.size); + if (ECS_PAIR_SECOND(id) == pattern_second) { + return true; } } - - stage->cmd_flushing = false; - - flecs_stack_reset(&commands->stack); - ecs_vec_clear(queue); - flecs_table_diff_builder_fini(world, &diff); - - /* Internal callback for capturing commands, signal queue is done */ - if (world->on_commands_active) { - world->on_commands_active(stage, NULL, - world->on_commands_ctx_active); + } else if (pattern_second == EcsWildcard) { + if (pattern_first == id_first) { + return true; } - } while (true); - - ecs_os_perf_trace_pop("flecs.commands.merge"); + } + } else { + if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { + return false; + } - return true; + if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { + return true; + } } +error: return false; } -/* Delete operations from queue without executing them. */ -bool flecs_defer_purge( - ecs_world_t *world, - ecs_stage_t *stage) +bool ecs_id_is_pair( + ecs_id_t id) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); - - if (!--stage->defer) { - ecs_vec_t commands = stage->cmd->queue; - - if (ecs_vec_count(&commands)) { - ecs_cmd_t *cmds = ecs_vec_first(&commands); - int32_t i, count = ecs_vec_count(&commands); - for (i = 0; i < count; i ++) { - flecs_discard_cmd(world, &cmds[i]); - } - - ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); - - ecs_vec_clear(&commands); - flecs_stack_reset(&stage->cmd->stack); - flecs_sparse_clear(&stage->cmd->entries); - } + return ECS_HAS_ID_FLAG(id, PAIR); +} +bool ecs_id_is_wildcard( + ecs_id_t id) +{ + if ((id == EcsWildcard) || (id == EcsAny)) { return true; } -error: - return false; -} - -/** - * @file entity_name.c - * @brief Functions for working with named entities. - */ + bool is_pair = ECS_IS_PAIR(id); + if (!is_pair) { + return false; + } -#include + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); -#define ECS_NAME_BUFFER_LENGTH (64) + return (first == EcsWildcard) || (second == EcsWildcard) || + (first == EcsAny) || (second == EcsAny); +} -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 escape) +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id) { - flecs_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)) { - ecs_record_t *r = flecs_entities_get(world, child); - bool hasName = false; - if (r && r->table) { - hasName = r->table->flags & EcsTableHasName; - } - - if (hasName) { - 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, escape); - if (!sep[1]) { - ecs_strbuf_appendch(buf, sep[0]); - } else { - ecs_strbuf_appendstr(buf, sep); - } - } - } else if (prefix && prefix[0]) { - if (!prefix[1]) { - ecs_strbuf_appendch(buf, prefix[0]); - } else { - ecs_strbuf_appendstr(buf, prefix); - } - } - - const EcsIdentifier *id = ecs_get_pair( - world, child, EcsIdentifier, EcsName); - if (id) { - name = id->value; - name_len = id->length; - } - } + if (!id) { + return false; + } + if (ecs_id_is_wildcard(id)) { + return false; } - if (name) { - /* Check if we need to escape separator character */ - const char *sep_in_name = NULL; - if (!sep[1]) { - sep_in_name = strchr(name, sep[0]); + if (ECS_HAS_ID_FLAG(id, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + return false; } - - if (sep_in_name || escape) { - const char *name_ptr; - char ch; - for (name_ptr = name; (ch = name_ptr[0]); name_ptr ++) { - char esc[3]; - if (ch != sep[0]) { - if (escape) { - flecs_chresc(esc, ch, '\"'); - ecs_strbuf_appendch(buf, esc[0]); - if (esc[1]) { - ecs_strbuf_appendch(buf, esc[1]); - } - } else { - ecs_strbuf_appendch(buf, ch); - } - } else { - if (!escape) { - ecs_strbuf_appendch(buf, '\\'); - ecs_strbuf_appendch(buf, sep[0]); - } else { - ecs_strbuf_appendlit(buf, "\\\\"); - flecs_chresc(esc, ch, '\"'); - ecs_strbuf_appendch(buf, esc[0]); - if (esc[1]) { - ecs_strbuf_appendch(buf, esc[1]); - } - } - } - } - } else { - ecs_strbuf_appendstrn(buf, name, name_len); + 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; } - } else { - ecs_strbuf_appendch(buf, '#'); - ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); } - return cur != 0; + return true; } -bool flecs_name_is_id( - const char *name) +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t id) { - ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); - if (name[0] == '#') { - /* If name is not just digits it's not an id */ - const char *ptr; - char ch; - for (ptr = name + 1; (ch = ptr[0]); ptr ++) { - if (!isdigit(ch)) { - return false; - } - } - return true; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + return cdr->flags; + } else { + return 0; } - return false; } -ecs_entity_t flecs_name_to_id( - const char *name) +ecs_id_t ecs_id_from_str( + const ecs_world_t *world, + const char *expr) { - ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); - ecs_entity_t res = flecs_ito(uint64_t, atoll(name + 1)); - if (res >= UINT32_MAX) { - return 0; /* Invalid id */ +#ifdef FLECS_QUERY_DSL + ecs_id_t result; + + /* Temporarily disable parser logging */ + int prev_level = ecs_log_set_level(-3); + if (!flecs_id_parse(world, NULL, expr, &result)) { + /* Invalid expression */ + ecs_log_set_level(prev_level); + return 0; } - return res; + ecs_log_set_level(prev_level); + return result; +#else + (void)world; + (void)expr; + ecs_abort(ECS_UNSUPPORTED, + "ecs_id_from_str requires FLECS_QUERY_DSL addon"); +#endif } -static -ecs_entity_t flecs_get_builtin( - const char *name) -{ - if (name[0] == '.' && name[1] == '\0') { - return EcsThis; - } else if (name[0] == '*' && name[1] == '\0') { - return EcsWildcard; - } else if (name[0] == '_' && name[1] == '\0') { - return EcsAny; - } else if (name[0] == '$' && name[1] == '\0') { - return EcsVariable; - } +/** + * @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. + */ - return 0; -} +#include -static -bool flecs_is_sep( - const char **ptr, - const char *sep) -{ - ecs_size_t len = ecs_os_strlen(sep); +/* Utility macros to enforce consistency when initializing iterator fields */ - if (!ecs_os_strncmp(*ptr, sep, len)) { - *ptr += len; - return true; - } else { - return false; +/* 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;\ } -} -static -const char* flecs_path_elem( - const char *path, - const char *sep, - char **buffer_out, - ecs_size_t *size_out) -{ - char *buffer = NULL; - if (buffer_out) { - buffer = *buffer_out; +/* 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);\ } - const char *ptr; - char ch; - int32_t template_nesting = 0; - int32_t pos = 0; - ecs_size_t size = size_out ? *size_out : 0; +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); +} - for (ptr = path; (ch = *ptr); ptr ++) { - if (ch == '<') { - template_nesting ++; - } else if (ch == '>') { - template_nesting --; - } else if (ch == '\\') { - ptr ++; - if (buffer) { - buffer[pos] = ptr[0]; - } - pos ++; - continue; - } +void flecs_iter_free( + void *ptr, + ecs_size_t size) +{ + flecs_stack_free(ptr, size); +} - ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); +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); - if (!template_nesting && flecs_is_sep(&ptr, sep)) { - break; - } + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + ecs_stack_t *stack = &stage->allocators.iter_stack; - if (buffer) { - if (pos >= (size - 1)) { - if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ - char *new_buffer = ecs_os_malloc(size * 2 + 1); - ecs_os_memcpy(new_buffer, buffer, size); - buffer = new_buffer; - } else { /* heap buffer */ - buffer = ecs_os_realloc(buffer, size * 2 + 1); - } - size *= 2; - } + it->priv_.cache.used = 0; + it->priv_.cache.allocated = 0; + it->priv_.cache.stack_cursor = flecs_stack_get_cursor(stack); - buffer[pos] = ch; - } + 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, trs, ecs_table_record_t*, it->field_count); + INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); +} - pos ++; - } +void ecs_iter_fini( + ecs_iter_t *it) +{ + ECS_BIT_CLEAR(it->flags, EcsIterIsValid); - if (buffer) { - buffer[pos] = '\0'; - *buffer_out = buffer; - *size_out = size; + if (it->fini) { + it->fini(it); } - if (pos || ptr[0]) { - return ptr; - } else { - return NULL; + ecs_world_t *world = it->world; + if (!world) { + return; } -error: - return NULL; -} -static -bool flecs_is_root_path( - const char *path, - const char *prefix) -{ - if (prefix) { - return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); - } else { - return false; - } + FINI_CACHE(it, ids, ecs_id_t, it->field_count); + FINI_CACHE(it, sources, ecs_entity_t, it->field_count); + FINI_CACHE(it, trs, ecs_table_record_t*, it->field_count); + FINI_CACHE(it, variables, ecs_var_t, it->variable_count); + + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_stack_restore_cursor(&stage->allocators.iter_stack, + it->priv_.cache.stack_cursor); } -static -ecs_entity_t flecs_get_parent_from_path( - const ecs_world_t *world, - ecs_entity_t parent, - const char **path_ptr, - const char *sep, - const char *prefix, - bool new_entity, - bool *error) +/* --- Public API --- */ + +void* ecs_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index) { - ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + ecs_check(!size || ecs_field_size(it, index) == size || + !ecs_field_size(it, index), + ECS_INVALID_PARAMETER, "mismatching size for field %d", index); + (void)size; - bool start_from_root = false; - const char *path = *path_ptr; + const ecs_table_record_t *tr = it->trs[index]; + if (!tr) { + ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); + return NULL; + } - if (flecs_is_root_path(path, prefix)) { - path += ecs_os_strlen(prefix); - parent = 0; - start_from_root = true; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + ecs_assert(!(cdr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, + "use ecs_field_at to access fields for sparse components"); + (void)cdr; + + ecs_entity_t src = it->sources[index]; + ecs_table_t *table; + int32_t row; + if (!src) { + table = it->table; + row = it->offset; + } else { + ecs_record_t *r = flecs_entities_get(it->real_world, src); + table = r->table; + row = ECS_RECORD_TO_ROW(r->row); } - if (path[0] == '#') { - parent = flecs_name_to_id(path); - if (!parent && ecs_os_strncmp(path, "#0", 2)) { - *error = true; - return 0; - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - path ++; - while (path[0] && isdigit(path[0])) { - path ++; /* Skip id part of path */ - } + int32_t column_index = tr->column; + ecs_assert(column_index != -1, ECS_NOT_A_COMPONENT, + "only components can be fetched with fields"); + ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); - /* Skip next separator so that the returned path points to the next - * name element. */ - ecs_size_t sep_len = ecs_os_strlen(sep); - if (!ecs_os_strncmp(path, sep, ecs_os_strlen(sep))) { - path += sep_len; - } + ecs_column_t *column = &table->data.columns[column_index]; + ecs_assert((row < table->data.count) || + (it->query && (it->query->flags & EcsQueryMatchEmptyTables)), + ECS_INTERNAL_ERROR, NULL); - start_from_root = true; - } - - if (!start_from_root && !parent && new_entity) { - parent = ecs_get_scope(world); + if (!size) { + size = (size_t)column->ti->size; } - *path_ptr = path; - - return parent; + return ECS_ELEM(column->data, (ecs_size_t)size, row); +error: + return NULL; } -static -void flecs_on_set_symbol(ecs_iter_t *it) { - EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); - ecs_world_t *world = it->real_world; +void* ecs_field_at_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index, + int32_t row) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + ecs_check(!size || ecs_field_size(it, index) == size || + !ecs_field_size(it, index), + ECS_INVALID_PARAMETER, "mismatching size for field %d", index); - 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); + const ecs_table_record_t *tr = it->trs[index]; + if (!tr) { + ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); + return NULL; } -} -void flecs_bootstrap_hierarchy(ecs_world_t *world) { - ecs_observer(world, { - .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), - .query.terms[0] = { - .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol) - }, - .callback = flecs_on_set_symbol, - .events = {EcsOnSet}, - .yield_existing = true - }); -} + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + ecs_assert((cdr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, + "use ecs_field to access fields for non-sparse components"); + ecs_assert(it->row_fields & (1ull << index), ECS_INTERNAL_ERROR, NULL); + ecs_entity_t src = it->sources[index]; + if (!src) { + src = ecs_table_entities(it->table)[row + it->offset]; + } -/* Public functions */ + return flecs_sparse_get_any(cdr->sparse, flecs_uto(int32_t, size), src); +error: + return NULL; +} -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, - bool escape) +bool ecs_field_is_readonly( + const ecs_iter_t *it, + int8_t index) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, + "operation only valid for query iterators"); + ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + const ecs_term_t *term = &it->query->terms[index]; - world = ecs_get_world(world); + if (term->inout == EcsIn) { + return true; + } else if (term->inout == EcsInOutDefault) { + if (!ecs_term_match_this(term)) { + return true; + } - if (child == EcsWildcard) { - ecs_strbuf_appendch(buf, '*'); - return; - } - if (child == EcsAny) { - ecs_strbuf_appendch(buf, '_'); - return; + const ecs_term_ref_t *src = &term->src; + if (!(src->id & EcsSelf)) { + return true; + } } +error: + return false; +} - if (!sep) { - sep = "."; - } +bool ecs_field_is_writeonly( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, + "operation only valid for query iterators"); + ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); - if (!child || parent != child) { - flecs_path_append(world, parent, child, sep, prefix, buf, escape); - } else { - ecs_strbuf_appendstrn(buf, "", 0); - } + const ecs_term_t *term = &it->query->terms[index]; + return term->inout == EcsOut; +error: + return false; +} + +bool ecs_field_is_set( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + return it->set_fields & (1llu << (index)); error: - return; + return false; } -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) +bool ecs_field_is_self( + const ecs_iter_t *it, + int8_t index) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; - ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf, false); - return ecs_strbuf_get(&buf); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + return it->sources == NULL || it->sources[index] == 0; +error: + return false; } -ecs_entity_t ecs_lookup_child( - const ecs_world_t *world, - ecs_entity_t parent, - const char *name) +ecs_id_t ecs_field_id( + const ecs_iter_t *it, + int8_t index) { - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); - if (flecs_name_is_id(name)) { - ecs_entity_t result = flecs_name_to_id(name); - if (result && ecs_is_alive(world, result)) { - if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { - return 0; - } - return result; - } - } + return it->ids[index]; +error: + return 0; +} - 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); +int32_t ecs_field_column( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + const ecs_table_record_t *tr = it->trs[index]; + if (tr) { + return tr->index; } else { - return 0; + return -1; } error: return 0; } -ecs_entity_t ecs_lookup( - const ecs_world_t *world, - const char *path) -{ - return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); -} +ecs_entity_t ecs_field_src( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); -ecs_entity_t ecs_lookup_symbol( - const ecs_world_t *world, - const char *name, - bool lookup_as_path, - bool recursive) -{ - if (!name) { + if (it->sources) { + return it->sources[index]; + } else { return 0; } - - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - world = ecs_get_world(world); - - ecs_entity_t e = 0; - if (lookup_as_path) { - e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); - } - - if (!e) { - e = flecs_name_index_find(&world->symbols, name, 0, 0); - } - - return e; error: return 0; } -ecs_entity_t ecs_lookup_path_w_sep( - const ecs_world_t *world, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix, - bool recursive) +size_t ecs_field_size( + const ecs_iter_t *it, + int8_t index) { - if (!path) { - return 0; - } + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_check(!parent || ecs_is_valid(world, parent), - ECS_INVALID_PARAMETER, NULL); - const ecs_world_t *stage = world; - world = ecs_get_world(world); + return (size_t)it->sizes[index]; +error: + return 0; +} - ecs_entity_t e = flecs_get_builtin(path); - if (e) { - return e; +char* ecs_iter_str( + const ecs_iter_t *it) +{ + if (!(it->flags & EcsIterIsValid)) { + return NULL; } - e = flecs_name_index_find(&world->aliases, path, 0, 0); - if (e) { - return e; - } + ecs_world_t *world = it->world; + ecs_strbuf_t buf = ECS_STRBUF_INIT; + int8_t i; - char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; - const char *ptr; - int32_t size = ECS_NAME_BUFFER_LENGTH; - ecs_entity_t cur; - bool lookup_path_search = false; + 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); + char *str = ecs_id_str(world, id); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); - 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 ++; - } + ecs_strbuf_list_push(&buf, "src: ", ","); + for (i = 0; i < it->field_count; i ++) { + ecs_entity_t subj = ecs_field_src(it, i); + char *str = ecs_get_path(world, subj); + ecs_strbuf_list_appendstr(&buf, str); + ecs_os_free(str); + } + ecs_strbuf_list_pop(&buf, "\n"); - if (!sep) { - sep = "."; + ecs_strbuf_list_push(&buf, "set: ", ","); + for (i = 0; i < it->field_count; i ++) { + if (ecs_field_is_set(it, i)) { + ecs_strbuf_list_appendlit(&buf, "true"); + } else { + ecs_strbuf_list_appendlit(&buf, "false"); + } + } + ecs_strbuf_list_pop(&buf, "\n"); } - bool error = false; - parent = flecs_get_parent_from_path( - stage, parent, &path, sep, prefix, true, &error); - if (error) { - return 0; - } - - if (parent && !(parent = ecs_get_alive(world, parent))) { - return 0; - } + 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; + } - if (!path[0]) { - return parent; - } + ecs_var_t var = it->variables[i]; + if (!var.entity) { + /* Skip table variables */ + continue; + } - if (!sep[0]) { - return ecs_lookup_child(world, parent, path); - } + if (!actual_count) { + ecs_strbuf_list_push(&buf, "var: ", ","); + } -retry: - cur = parent; - ptr = path; + char *str = ecs_get_path(world, var.entity); + ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); + ecs_os_free(str); - while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { - cur = ecs_lookup_child(world, cur, elem); - if (!cur) { - goto tail; + actual_count ++; } - } - -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 (actual_count) { + ecs_strbuf_list_pop(&buf, "\n"); } + } - if (lookup_path_search) { - if (lookup_path_cur != lookup_path) { - lookup_path_cur --; - parent = lookup_path_cur[0]; - goto retry; - } + 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_path(world, e); + ecs_strbuf_appendlit(&buf, " - "); + ecs_strbuf_appendstr(&buf, str); + ecs_strbuf_appendch(&buf, '\n'); + ecs_os_free(str); } } - if (elem != buff) { - ecs_os_free(elem); - } + return ecs_strbuf_get(&buf); +} - return cur; +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 0; + return false; } -ecs_entity_t ecs_set_scope( - ecs_world_t *world, - ecs_entity_t scope) +int32_t ecs_iter_count( + ecs_iter_t *it) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t cur = stage->scope; - stage->scope = scope; + ECS_BIT_SET(it->flags, EcsIterNoData); - return cur; + int32_t count = 0; + while (ecs_iter_next(it)) { + count += it->count; + } + return count; error: return 0; } -ecs_entity_t ecs_get_scope( - const ecs_world_t *world) +ecs_entity_t ecs_iter_first( + ecs_iter_t *it) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); - return stage->scope; + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->flags, EcsIterNoData); + + ecs_entity_t result = 0; + if (ecs_iter_next(it)) { + result = it->entities[0]; + ecs_iter_fini(it); + } + + return result; error: return 0; } -ecs_entity_t* ecs_set_lookup_path( - ecs_world_t *world, - const ecs_entity_t *lookup_path) +bool ecs_iter_is_true( + ecs_iter_t *it) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - /* Safe: application owns lookup path */ - ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); - stage->lookup_path = lookup_path; + ECS_BIT_SET(it->flags, EcsIterNoData); - return cur; + bool result = ecs_iter_next(it); + if (result) { + ecs_iter_fini(it); + } + return result; error: - return NULL; + return false; } -ecs_entity_t* ecs_get_lookup_path( - const ecs_world_t *world) +ecs_entity_t ecs_iter_get_var( + ecs_iter_t *it, + int32_t var_id) { - 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); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_var_t *var = &it->variables[var_id]; + ecs_entity_t e = var->entity; + if (!e) { + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + if (table) { + if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { + ecs_assert(ecs_table_count(table) > var->range.offset, + ECS_INTERNAL_ERROR, NULL); + e = ecs_table_entities(table)[var->range.offset]; + } + } + } else { + ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); + } + + return e; error: - return NULL; + return 0; } -const char* ecs_set_name_prefix( - ecs_world_t *world, - const char *prefix) +ecs_table_t* ecs_iter_get_var_as_table( + ecs_iter_t *it, + int32_t var_id) { - flecs_poly_assert(world, ecs_world_t); - const char *old_prefix = world->info.name_prefix; - world->info.name_prefix = prefix; - return old_prefix; -} + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); -static -void flecs_add_path( - ecs_world_t *world, - bool defer_suspend, - ecs_entity_t parent, - ecs_entity_t entity, - const char *name) -{ - ecs_suspend_readonly_state_t srs; - ecs_world_t *real_world = NULL; - if (defer_suspend) { - real_world = flecs_suspend_readonly(world, &srs); - ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; } - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, parent); + 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; + } + } + } } - ecs_assert(name[0] != '#', ECS_INVALID_PARAMETER, - "path should not contain identifier with #"); - - ecs_set_name(world, entity, name); + if (table) { + if (var->range.offset) { + /* Don't return whole table if only partial table is matched */ + return NULL; + } - if (defer_suspend) { - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_resume_readonly(real_world, &srs); - flecs_defer_path(stage, parent, entity, name); + if (!var->range.count || ecs_table_count(table) == var->range.count) { + /* Return table if count matches */ + return table; + } } + +error: + return NULL; } -ecs_entity_t ecs_add_path_w_sep( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t parent, - const char *path, - const char *sep, - const char *prefix) +ecs_table_range_t ecs_iter_get_var_as_range( + ecs_iter_t *it, + int32_t var_id) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_world_t *real_world = world; - if (flecs_poly_is(world, ecs_stage_t)) { - real_world = ecs_get_world(world); - } + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - if (!sep) { - sep = "."; - } + ecs_table_range_t result = { 0 }; - if (!path) { - if (!entity) { - entity = ecs_new(world); + ecs_var_t *var = &it->variables[var_id]; + ecs_table_t *table = var->range.table; + if (!table && !var_id) { + table = it->table; + } + + if (!table) { + ecs_entity_t e = var->entity; + if (e) { + ecs_record_t *r = flecs_entities_get(it->real_world, e); + if (r) { + result.table = r->table; + result.offset = ECS_RECORD_TO_ROW(r->row); + result.count = 1; + } } - - if (parent) { - ecs_add_pair(world, entity, EcsChildOf, entity); + } else { + result.table = table; + result.offset = var->range.offset; + result.count = var->range.count; + if (!result.count) { + result.count = ecs_table_count(table); } - - return entity; } - bool root_path = flecs_is_root_path(path, prefix); - bool error = false; - parent = flecs_get_parent_from_path( - world, parent, &path, sep, prefix, !entity, &error); - if (error) { - /* Invalid id */ - ecs_err("invalid identifier: '%s'", path); - return 0; - } + return result; +error: + return (ecs_table_range_t){0}; +} - char buff[ECS_NAME_BUFFER_LENGTH]; - const char *ptr = path; - char *elem = buff; - int32_t 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_is_deferred(world) && - !(real_world->flags & EcsWorldMultiThreaded); - - ecs_entity_t cur = parent; - char *name = NULL; +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, + "invalid variable index %d", var_id); + ecs_check(var_id < FLECS_QUERY_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, + "cannot constrain variable while iterating"); + ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); - if (sep[0]) { - while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { - ecs_entity_t e = ecs_lookup_child(world, cur, elem); - if (!e) { - if (name) { - ecs_os_free(name); - } + ecs_var_t *var = &it->variables[var_id]; + var->entity = entity; - name = ecs_os_strdup(elem); + 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; + } - /* If this is the last entity in the path, use the provided id */ - bool last_elem = false; - if (!flecs_path_elem(ptr, sep, NULL, NULL)) { - e = entity; - last_elem = true; - } + it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); - if (!e) { - if (last_elem) { - ecs_entity_t prev = ecs_set_scope(world, 0); - e = ecs_entity(world, {0}); - ecs_set_scope(world, prev); - } else { - e = ecs_new(world); - } - } + /* Update iterator for constrained iterator */ + flecs_query_iter_constrain(it); - if (!cur && last_elem && root_path) { - ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); - } +error: + return; +} - flecs_add_path(world, suspend_defer, cur, e, name); - } +void ecs_iter_set_var_as_table( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_t *table) +{ + ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; + ecs_iter_set_var_as_range(it, var_id, &range); +} - cur = e; - } +void ecs_iter_set_var_as_range( + ecs_iter_t *it, + int32_t var_id, + const ecs_table_range_t *range) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, + "invalid variable index %d", var_id); + ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, + "variable index %d out of bounds", var_id); + 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); - if (entity && (cur != entity)) { - ecs_throw(ECS_ALREADY_DEFINED, name); - } + ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, + "cannot set query variables while iterating"); - if (name) { - ecs_os_free(name); - } + ecs_var_t *var = &it->variables[var_id]; + var->range = *range; - if (elem != buff) { - ecs_os_free(elem); - } + if (range->count == 1) { + ecs_table_t *table = range->table; + var->entity = ecs_table_entities(table)[range->offset]; } else { - flecs_add_path(world, suspend_defer, parent, entity, path); + var->entity = 0; } - return cur; + it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + + /* Update iterator for constrained iterator */ + flecs_query_iter_constrain(it); + error: - return 0; + return; } -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) +bool ecs_iter_var_is_constrained( + ecs_iter_t *it, + int32_t var_id) { - return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); + return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; } -/** - * @file id.c - * @brief Id utilities. - */ +static +void ecs_chained_iter_fini( + ecs_iter_t *it) +{ + ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_iter_fini(it->chain_it); -#ifdef FLECS_SCRIPT -#endif + it->chain_it = NULL; +} -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern) +ecs_iter_t ecs_page_iter( + const ecs_iter_t *it, + int32_t offset, + int32_t limit) { - if (id == pattern) { - return true; - } + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); - if (ECS_HAS_ID_FLAG(pattern, PAIR)) { - if (!ECS_HAS_ID_FLAG(id, PAIR)) { - return false; - } + ecs_iter_t result = *it; + result.priv_.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ - ecs_entity_t id_first = ECS_PAIR_FIRST(id); - ecs_entity_t id_second = ECS_PAIR_SECOND(id); - ecs_entity_t pattern_first = ECS_PAIR_FIRST(pattern); - ecs_entity_t pattern_second = ECS_PAIR_SECOND(pattern); + 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); - ecs_check(id_first != 0, ECS_INVALID_PARAMETER, - "first element of pair cannot be 0"); - ecs_check(id_second != 0, ECS_INVALID_PARAMETER, - "second element of pair cannot be 0"); + return result; +error: + return (ecs_iter_t){ 0 }; +} - ecs_check(pattern_first != 0, ECS_INVALID_PARAMETER, - "first element of pair cannot be 0"); - ecs_check(pattern_second != 0, ECS_INVALID_PARAMETER, - "second element of pair cannot be 0"); - - if (pattern_first == EcsWildcard) { - if (pattern_second == EcsWildcard || pattern_second == id_second) { - return true; - } - } else if (pattern_first == 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_second) { - return true; - } - if (ECS_PAIR_SECOND(id) == pattern_second) { - return true; - } - } - } else if (pattern_second == EcsWildcard) { - if (pattern_first == id_first) { - return true; +bool ecs_page_next( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + + ecs_iter_t *chain_it = it->chain_it; + + do { + if (!ecs_iter_next(chain_it)) { + goto depleted; + } + + ecs_page_iter_t *iter = &it->priv_.iter.page; + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); + + if (!chain_it->table) { + goto yield; /* Task query */ + } + + int32_t offset = iter->offset; + int32_t limit = iter->limit; + if (!(offset || limit)) { + if (it->count) { + goto yield; + } else { + goto depleted; } } - } else { - if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { - return false; + + int32_t count = it->count; + int32_t remaining = iter->remaining; + + if (offset) { + if (offset > count) { + /* No entities to iterate in current table */ + iter->offset -= count; + it->count = 0; + continue; + } else { + iter->offset = 0; + it->offset = offset; + count = it->count -= offset; + it->entities = + &(ecs_table_entities(it->table)[it->offset]); + } } - if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { - return true; + if (remaining) { + if (remaining > count) { + iter->remaining -= count; + } else { + it->count = remaining; + iter->remaining = 0; + } + } else if (limit) { + /* Limit hit: no more entities left to iterate */ + goto done; } - } + } while (it->count == 0); +yield: + return true; + +done: + /* Cleanup iterator resources if it wasn't yet depleted */ + ecs_iter_fini(chain_it); + +depleted: error: return false; } -bool ecs_id_is_pair( - ecs_id_t id) -{ - return ECS_HAS_ID_FLAG(id, PAIR); -} - -bool ecs_id_is_wildcard( - ecs_id_t id) +ecs_iter_t ecs_worker_iter( + const ecs_iter_t *it, + int32_t index, + int32_t count) { - if ((id == EcsWildcard) || (id == EcsAny)) { - return true; - } - - bool is_pair = ECS_IS_PAIR(id); - if (!is_pair) { - return false; - } + 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, + "invalid field index %d", index); + ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); + 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 (first == EcsWildcard) || (second == EcsWildcard) || - (first == EcsAny) || (second == EcsAny); + return result; +error: + return (ecs_iter_t){ 0 }; } -bool ecs_id_is_valid( - const ecs_world_t *world, - ecs_id_t id) +bool ecs_worker_next( + ecs_iter_t *it) { - if (!id) { - return false; - } - if (ecs_id_is_wildcard(id)) { - return false; - } + 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); - if (ECS_HAS_ID_FLAG(id, PAIR)) { - if (!ECS_PAIR_FIRST(id)) { + 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, first; + + do { + if (!ecs_iter_next(chain_it)) { return false; } - if (!ECS_PAIR_SECOND(id)) { - return false; + + /* Copy everything up to the private iterator data */ + ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); + + int32_t count = it->count; + per_worker = 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; + } } - } else if (id & ECS_ID_FLAGS_MASK) { - if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { - return false; + + 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->frame_offset += first; + it->count = per_worker; + it->offset += first; + + it->entities = &(ecs_table_entities(it->table)[it->offset]); return true; +error: + return false; } -ecs_flags32_t ecs_id_get_flags( - const ecs_world_t *world, - ecs_id_t id) +/** + * @file misc.c + * @brief Miscellaneous functions. + */ + +#include +#include + +#ifndef FLECS_NDEBUG +static int64_t flecs_s_min[] = { + [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; +static int64_t flecs_s_max[] = { + [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; +static uint64_t flecs_u_max[] = { + [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; + +uint64_t flecs_ito_( + size_t size, + bool is_signed, + bool lt_zero, + uint64_t u, + const char *err) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - return idr->flags; + 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 { - return 0; + ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); + ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); } + + return u; } +#endif -ecs_id_t ecs_id_from_str( - const ecs_world_t *world, - const char *expr) +int32_t flecs_next_pow_of_2( + int32_t n) { -#ifdef FLECS_SCRIPT - ecs_id_t result; + n --; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n ++; - /* Temporarily disable parser logging */ - int prev_level = ecs_log_set_level(-3); - if (!flecs_id_parse(world, NULL, expr, &result)) { - /* Invalid expression */ - ecs_log_set_level(prev_level); - return 0; - } - ecs_log_set_level(prev_level); - return result; -#else - (void)world; - (void)expr; - ecs_abort(ECS_UNSUPPORTED, "ecs_id_from_str requires FLECS_SCRIPT addon"); -#endif + return n; } -/** - * @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 +/** 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; +} -/* Utility macros to enforce consistency when initializing iterator fields */ +ecs_time_t ecs_time_sub( + ecs_time_t t1, + ecs_time_t t2) +{ + ecs_time_t result; -/* If term count is smaller than cache size, initialize with inline array, - * otherwise allocate. */ -#define INIT_CACHE(it, stack, fields, f, T, count)\ - if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ - it->f = flecs_stack_calloc_n(stack, T, count);\ - it->priv_.cache.used |= flecs_iter_cache_##f;\ + if (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; } -/* 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);\ - } + return result; +} -void* flecs_iter_calloc( - ecs_iter_t *it, - ecs_size_t size, - ecs_size_t align) +void ecs_sleepf( + double t) { - 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 (t > 0) { + int sec = (int)t; + int nsec = (int)((t - sec) * 1000000000); + ecs_os_sleep(sec, nsec); + } } -void flecs_iter_free( - void *ptr, - ecs_size_t size) +double ecs_time_measure( + ecs_time_t *start) { - flecs_stack_free(ptr, size); + 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 flecs_iter_init( - const ecs_world_t *world, - ecs_iter_t *it, - ecs_flags8_t fields) +void* ecs_os_memdup( + const void *src, + ecs_size_t size) { - ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), - ECS_INTERNAL_ERROR, NULL); + 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; +} - ecs_stage_t *stage = flecs_stage_from_world( - ECS_CONST_CAST(ecs_world_t**, &world)); - ecs_stack_t *stack = &stage->allocators.iter_stack; +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); +} - it->priv_.cache.used = 0; - it->priv_.cache.allocated = 0; - it->priv_.cache.stack_cursor = flecs_stack_get_cursor(stack); +int flecs_id_qsort_cmp(const void *a, const void *b) { + ecs_id_t id_a = *(const ecs_id_t*)a; + ecs_id_t id_b = *(const ecs_id_t*)b; + return (id_a > id_b) - (id_a < id_b); +} - 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, trs, ecs_table_record_t*, it->field_count); - INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); +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; } -void ecs_iter_fini( - ecs_iter_t *it) +char* flecs_vasprintf( + const char *fmt, + va_list args) { - ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + ecs_size_t size = 0; + char *result = NULL; + va_list tmpa; - if (it->fini) { - it->fini(it); + va_copy(tmpa, args); + + size = vsnprintf(result, 0, fmt, tmpa); + + va_end(tmpa); + + if ((int32_t)size < 0) { + return NULL; } - ecs_world_t *world = it->world; - if (!world) { - return; + result = (char *) ecs_os_malloc(size + 1); + + if (!result) { + return NULL; } - FINI_CACHE(it, ids, ecs_id_t, it->field_count); - FINI_CACHE(it, sources, ecs_entity_t, it->field_count); - FINI_CACHE(it, trs, ecs_table_record_t*, it->field_count); - FINI_CACHE(it, variables, ecs_var_t, it->variable_count); + ecs_os_vsnprintf(result, size + 1, fmt, args); - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_stack_restore_cursor(&stage->allocators.iter_stack, - it->priv_.cache.stack_cursor); + return result; } -/* --- Public API --- */ - -void* ecs_field_w_size( - const ecs_iter_t *it, - size_t size, - int8_t index) +char* flecs_asprintf( + const char *fmt, + ...) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, - "operation invalid before calling next()"); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); - ecs_check(!size || ecs_field_size(it, index) == size || - !ecs_field_size(it, index), - ECS_INVALID_PARAMETER, "mismatching size for field %d", index); - (void)size; + va_list args; + va_start(args, fmt); + char *result = flecs_vasprintf(fmt, args); + va_end(args); + return result; +} - const ecs_table_record_t *tr = it->trs[index]; - if (!tr) { - ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); - return NULL; - } +char* flecs_to_snake_case(const char *str) { + int32_t upper_count = 0, len = 1; + const char *ptr = str; + char ch, *out, *out_ptr; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(!(idr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, - "use ecs_field_at to access fields for sparse components"); - (void)idr; + for (ptr = &str[1]; (ch = *ptr); ptr ++) { + if (isupper(ch)) { + upper_count ++; + } + len ++; + } - ecs_entity_t src = it->sources[index]; - ecs_table_t *table; - int32_t row; - if (!src) { - table = it->table; - row = it->offset; - } else { - ecs_record_t *r = flecs_entities_get(it->real_world, src); - table = r->table; - row = ECS_RECORD_TO_ROW(r->row); + 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_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + out_ptr[0] = '\0'; - int32_t column_index = tr->column; - ecs_assert(column_index != -1, ECS_NOT_A_COMPONENT, - "only components can be fetched with fields"); - ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); + return out; +} - ecs_column_t *column = &table->data.columns[column_index]; - ecs_assert((row < table->data.count) || - (it->query && (it->query->flags & EcsQueryMatchEmptyTables)), - ECS_INTERNAL_ERROR, NULL); +char* flecs_load_from_file( + const char *filename) +{ + FILE* file; + char* content = NULL; + int32_t bytes; + size_t size; - if (!size) { - size = (size_t)column->ti->size; + /* Open file for reading */ + ecs_os_fopen(&file, filename, "r"); + if (!file) { + ecs_err("%s (%s)", ecs_os_strerror(errno), filename); + goto error; } - return ECS_ELEM(column->data, (ecs_size_t)size, row); -error: - return NULL; -} - -void* ecs_field_at_w_size( - const ecs_iter_t *it, - size_t size, - int8_t index, - int32_t row) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, - "operation invalid before calling next()"); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); - ecs_check(!size || ecs_field_size(it, index) == size || - !ecs_field_size(it, index), - ECS_INVALID_PARAMETER, "mismatching size for field %d", index); - - const ecs_table_record_t *tr = it->trs[index]; - if (!tr) { - ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); - return NULL; + /* Determine file size */ + fseek(file, 0, SEEK_END); + bytes = (int32_t)ftell(file); + if (bytes == -1) { + goto error; } + fseek(file, 0, SEEK_SET); - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert((idr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, - "use ecs_field to access fields for non-sparse components"); - ecs_assert(it->row_fields & (1ull << index), ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t src = it->sources[index]; - if (!src) { - src = ecs_table_entities(it->table)[row + it->offset]; + /* 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'; } - return flecs_sparse_get_any(idr->sparse, flecs_uto(int32_t, size), src); + fclose(file); + + return content; error: + if (file) { + fclose(file); + } + ecs_os_free(content); return NULL; } -bool ecs_field_is_readonly( - const ecs_iter_t *it, - int8_t index) +char* flecs_chresc( + char *out, + char in, + char delimiter) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, - "operation invalid before calling next()"); - ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, - "operation only valid for query iterators"); - ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); - const ecs_term_t *term = &it->query->terms[index]; - - if (term->inout == EcsIn) { - return true; - } else if (term->inout == EcsInOutDefault) { - if (!ecs_term_match_this(term)) { - return true; - } - - const ecs_term_ref_t *src = &term->src; - if (!(src->id & EcsSelf)) { - return true; + char *bptr = out; + switch(in) { + case '\a': + *bptr++ = '\\'; + *bptr = 'a'; + break; + case '\b': + *bptr++ = '\\'; + *bptr = 'b'; + break; + case '\f': + *bptr++ = '\\'; + *bptr = 'f'; + break; + case '\n': + *bptr++ = '\\'; + *bptr = 'n'; + break; + case '\r': + *bptr++ = '\\'; + *bptr = 'r'; + break; + case '\t': + *bptr++ = '\\'; + *bptr = 't'; + break; + case '\v': + *bptr++ = '\\'; + *bptr = 'v'; + break; + case '\\': + *bptr++ = '\\'; + *bptr = '\\'; + break; + case '\033': + *bptr = '['; /* Used for terminal colors */ + break; + default: + if (in == delimiter) { + *bptr++ = '\\'; + *bptr = delimiter; + } else { + *bptr = in; } + break; } -error: - return false; -} -bool ecs_field_is_writeonly( - const ecs_iter_t *it, - int8_t index) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, - "operation invalid before calling next()"); - ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, - "operation only valid for query iterators"); - ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); + *(++bptr) = '\0'; - const ecs_term_t *term = &it->query->terms[index]; - return term->inout == EcsOut; -error: - return false; + return bptr; } -bool ecs_field_is_set( - const ecs_iter_t *it, - int8_t index) +const char* flecs_chrparse( + const char *in, + char *out) { - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, - "operation invalid before calling next()"); - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); - - return it->set_fields & (1llu << (index)); -error: - return false; -} + const char *result = in + 1; + char ch; -bool ecs_field_is_self( - const ecs_iter_t *it, - int8_t index) -{ - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); + if (in[0] == '\\') { + result ++; - return it->sources == NULL || it->sources[index] == 0; -error: - return false; -} + 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_id_t ecs_field_id( - const ecs_iter_t *it, - int8_t index) -{ - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); + if (out) { + *out = ch; + } - return it->ids[index]; + return result; error: - return 0; + return NULL; } -int32_t ecs_field_column( - const ecs_iter_t *it, - int8_t index) +ecs_size_t flecs_stresc( + char *out, + ecs_size_t n, + char delimiter, + const char *in) { - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); - - const ecs_table_record_t *tr = it->trs[index]; - if (tr) { - return tr->index; - } else { - return -1; + const char *ptr = in; + char ch, *bptr = out, buff[3]; + ecs_size_t written = 0; + while ((ch = *ptr++)) { + if ((written += (ecs_size_t)(flecs_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++; + } + } } -error: - return 0; -} - -ecs_entity_t ecs_field_src( - const ecs_iter_t *it, - int8_t index) -{ - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); - if (it->sources) { - return it->sources[index]; - } else { - return 0; + if (bptr) { + while (written < n) { + *bptr = '\0'; + bptr++; + written++; + } } + return written; error: return 0; } -size_t ecs_field_size( - const ecs_iter_t *it, - int8_t index) +char* flecs_astresc( + char delimiter, + const char *in) { - ecs_check(index >= 0, ECS_INVALID_PARAMETER, - "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, - "field index %d out of bounds", index); + if (!in) { + return NULL; + } - return (size_t)it->sizes[index]; -error: - return 0; + ecs_size_t len = flecs_stresc(NULL, 0, delimiter, in); + char *out = ecs_os_malloc_n(char, len + 1); + flecs_stresc(out, len, delimiter, in); + out[len] = '\0'; + return out; } -char* ecs_iter_str( - const ecs_iter_t *it) +const char* flecs_parse_digit( + const char *ptr, + char *token) { - if (!(it->flags & EcsIterIsValid)) { + char *tptr = token; + char ch = ptr[0]; + + if (!isdigit(ch) && ch != '-') { + ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); return NULL; } - ecs_world_t *world = it->world; - ecs_strbuf_t buf = ECS_STRBUF_INIT; - int8_t 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); - char *str = ecs_id_str(world, id); - ecs_strbuf_list_appendstr(&buf, str); - ecs_os_free(str); - } - ecs_strbuf_list_pop(&buf, "\n"); + tptr[0] = ch; + tptr ++; + ptr ++; - ecs_strbuf_list_push(&buf, "src: ", ","); - for (i = 0; i < it->field_count; i ++) { - ecs_entity_t subj = ecs_field_src(it, i); - char *str = ecs_get_path(world, subj); - ecs_strbuf_list_appendstr(&buf, str); - ecs_os_free(str); + for (; (ch = *ptr); ptr ++) { + if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { + break; } - ecs_strbuf_list_pop(&buf, "\n"); - ecs_strbuf_list_push(&buf, "set: ", ","); - for (i = 0; i < it->field_count; i ++) { - if (ecs_field_is_set(it, i)) { - ecs_strbuf_list_appendlit(&buf, "true"); - } else { - ecs_strbuf_list_appendlit(&buf, "false"); - } - } - ecs_strbuf_list_pop(&buf, "\n"); + tptr[0] = ch; + tptr ++; } - if (it->variable_count && it->variable_names) { - int32_t actual_count = 0; - for (i = 0; i < it->variable_count; i ++) { - const char *var_name = it->variable_names[i]; - if (!var_name || var_name[0] == '_' || !strcmp(var_name, "this")) { - /* Skip anonymous variables */ - continue; - } - - ecs_var_t var = it->variables[i]; - if (!var.entity) { - /* Skip table variables */ - continue; - } - - if (!actual_count) { - ecs_strbuf_list_push(&buf, "var: ", ","); - } - - char *str = ecs_get_path(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"); - } - } + tptr[0] = '\0'; + + return ptr; +} - 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_path(world, e); - ecs_strbuf_appendlit(&buf, " - "); - ecs_strbuf_appendstr(&buf, str); - ecs_strbuf_appendch(&buf, '\n'); - ecs_os_free(str); - } +const char* flecs_parse_ws_eol( + const char *ptr) +{ + while (isspace(*ptr)) { + ptr ++; } - return ecs_strbuf_get(&buf); + return ptr; } -bool ecs_iter_next( - ecs_iter_t *iter) +/** + * @file observable.c + * @brief Observable implementation. + * + * The observable implementation contains functions that find the set of + * observers to invoke for an event. The code also contains the implementation + * of a reachable id cache, which is used to speedup event propagation when + * relationships are added/removed to/from entities. + */ + + +void flecs_observable_init( + ecs_observable_t *observable) { - ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); - return iter->next(iter); -error: - return false; + 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; } -int32_t ecs_iter_count( - ecs_iter_t *it) +void flecs_observable_fini( + ecs_observable_t *observable) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, 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_BIT_SET(it->flags, EcsIterNoData); + 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; - int32_t count = 0; - while (ecs_iter_next(it)) { - count += it->count; + /* All observers should've unregistered by now */ + ecs_assert(!ecs_map_is_init(&er->event_ids), + ECS_INTERNAL_ERROR, NULL); } - return count; -error: - return 0; + + flecs_sparse_fini(&observable->events); } -ecs_entity_t ecs_iter_first( - ecs_iter_t *it) +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->flags, EcsIterNoData); - - ecs_entity_t result = 0; - if (ecs_iter_next(it)) { - result = it->entities[0]; - ecs_iter_fini(it); - } + 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 == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); - return result; -error: - return 0; + /* User events */ + return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); } -bool ecs_iter_is_true( - ecs_iter_t *it) +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - - ECS_BIT_SET(it->flags, EcsIterNoData); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - bool result = ecs_iter_next(it); - if (result) { - ecs_iter_fini(it); + ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + return er; } - return result; -error: - return false; + er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); + er->event = event; + return er; } -ecs_entity_t ecs_iter_get_var( - ecs_iter_t *it, - int32_t var_id) -{ - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); +static +const ecs_event_record_t* flecs_event_record_get_if( + const ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_var_t *var = &it->variables[var_id]; - ecs_entity_t e = var->entity; - if (!e) { - ecs_table_t *table = var->range.table; - if (!table && !var_id) { - table = it->table; + 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 (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_table_entities(table)[var->range.offset]; - } + if (er->any) { + return er; + } + if (er->wildcard) { + return er; + } + if (er->wildcard_pair) { + return er; } - } else { - ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); } - return e; -error: - return 0; + return NULL; } -ecs_table_t* ecs_iter_get_var_as_table( - ecs_iter_t *it, - int32_t var_id) +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table && !var_id) { - table = it->table; + if (!er) { + return NULL; } - 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 (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 (table) { - if (var->range.offset) { - /* Don't return whole table if only partial table is matched */ - return NULL; - } +static +ecs_event_id_record_t* flecs_event_id_record_get_if( + const ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (!ider) { + return NULL; + } - if (!var->range.count || ecs_table_count(table) == var->range.count) { - /* Return table if count matches */ - return table; - } + if (ider->observer_count) { + return ider; } -error: return NULL; } -ecs_table_range_t ecs_iter_get_var_as_range( - ecs_iter_t *it, - int32_t var_id) +ecs_event_id_record_t* flecs_event_id_record_ensure( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_id_t id) { - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (ider) { + return ider; + } - ecs_table_range_t result = { 0 }; + ider = ecs_os_calloc_t(ecs_event_id_record_t); - ecs_var_t *var = &it->variables[var_id]; - ecs_table_t *table = var->range.table; - if (!table && !var_id) { - table = it->table; + 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; } - - 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; - } - } + + ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); + ecs_map_insert_ptr(&er->event_ids, id, ider); + return ider; +} + +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id) +{ + if (id == EcsAny) { + er->any = NULL; + } else if (id == EcsWildcard) { + er->wildcard = NULL; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + er->wildcard_pair = NULL; } else { - result.table = table; - result.offset = var->range.offset; - result.count = var->range.count; - if (!result.count) { - result.count = ecs_table_count(table); + ecs_map_remove(&er->event_ids, id); + if (!ecs_map_count(&er->event_ids)) { + ecs_map_fini(&er->event_ids); } } - - 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) +static +int32_t flecs_event_observers_get( + const ecs_event_record_t *er, + ecs_id_t id, + ecs_event_id_record_t **iders) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < FLECS_QUERY_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, - "cannot constrain variable while iterating"); - ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); + if (!er) { + return 0; + } - ecs_var_t *var = &it->variables[var_id]; - var->entity = entity; + /* Populate array with observer sets matching the id */ + int32_t count = 0; - 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; + if (id != EcsAny) { + iders[0] = flecs_event_id_record_get_if(er, EcsAny); + count += iders[count] != 0; } - it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); + iders[count] = flecs_event_id_record_get_if(er, id); + count += iders[count] != 0; - /* Update iterator for constrained iterator */ - flecs_query_iter_constrain(it); + if (id != EcsAny) { + if (ECS_IS_PAIR(id)) { + ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); + if (id_fwc != id) { + iders[count] = flecs_event_id_record_get_if(er, id_fwc); + count += iders[count] != 0; + } + if (id_swc != id) { + iders[count] = flecs_event_id_record_get_if(er, id_swc); + count += iders[count] != 0; + } + if (id_pwc != id) { + iders[count] = flecs_event_id_record_get_if(er, id_pwc); + count += iders[count] != 0; + } + } else if (id != EcsWildcard) { + iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); + count += iders[count] != 0; + } + } -error: - return; + return count; } -void ecs_iter_set_var_as_table( - ecs_iter_t *it, - int32_t var_id, - const ecs_table_t *table) +bool flecs_observers_exist( + ecs_observable_t *observable, + ecs_id_t id, + ecs_entity_t event) { - ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; - ecs_iter_set_var_as_range(it, var_id, &range); + const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + if (!er) { + return false; + } + + return flecs_event_id_record_get_if(er, id) != NULL; } -void ecs_iter_set_var_as_range( +static +void flecs_emit_propagate( + ecs_world_t *world, ecs_iter_t *it, - int32_t var_id, - const ecs_table_range_t *range) + ecs_component_record_t *cdr, + ecs_component_record_t *tgt_idr, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count); + +static +void flecs_emit_propagate_id( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cdr, + ecs_component_record_t *cur, + ecs_entity_t trav, + ecs_event_id_record_t **iders, + int32_t ider_count) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, - "invalid variable index %d", var_id); - ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, - "variable index %d out of bounds", var_id); - 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); + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + return; + } - ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, - "cannot set query variables while iterating"); + const ecs_table_record_t *tr; + int32_t event_cur = it->event_cur; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!ecs_table_count(table)) { + continue; + } - ecs_var_t *var = &it->variables[var_id]; - var->range = *range; + bool owned = flecs_component_get_table(cdr, table) != NULL; - if (range->count == 1) { - ecs_table_t *table = range->table; - var->entity = ecs_table_entities(table)[range->offset]; - } else { - var->entity = 0; - } + int32_t e, entity_count = ecs_table_count(table); + it->table = table; + it->other_table = NULL; + it->offset = 0; + it->count = entity_count; + it->up_fields = 1; + if (entity_count) { + it->entities = ecs_table_entities(table); + } - it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); + int32_t ider_i; + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); - /* Update iterator for constrained iterator */ - flecs_query_iter_constrain(it); + if (!owned) { + /* Owned takes precedence */ + flecs_observers_invoke(world, &ider->self_up, it, table, trav); + } + } -error: - return; -} + if (!table->_->traversable_count) { + continue; + } -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; + const ecs_entity_t *entities = ecs_table_entities(table); + for (e = 0; e < entity_count; e ++) { + ecs_record_t *r = flecs_entities_get(world, entities[e]); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *idr_t = r->cdr; + if (idr_t) { + /* Only notify for entities that are used in pairs with + * traversable relationships */ + flecs_emit_propagate(world, it, cdr, idr_t, trav, + iders, ider_count); + } + } + } + + it->event_cur = event_cur; + it->up_fields = 0; } static -void ecs_chained_iter_fini( - ecs_iter_t *it) +void flecs_emit_propagate( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cdr, + ecs_component_record_t *tgt_idr, + ecs_entity_t propagate_trav, + ecs_event_id_record_t **iders, + int32_t ider_count) { - ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_iter_fini(it->chain_it); + 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); + } - it->chain_it = NULL; -} + ecs_log_push_3(); -ecs_iter_t ecs_page_iter( - const ecs_iter_t *it, - int32_t offset, - int32_t limit) -{ - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); + /* Propagate to records of traversable relationships */ + ecs_component_record_t *cur = tgt_idr; + while ((cur = flecs_component_trav_next(cur))) { + cur->pair->reachable.generation ++; /* Invalidate cache */ - ecs_iter_t result = *it; - result.priv_.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ + /* Get traversed relationship */ + ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); + if (propagate_trav && propagate_trav != trav) { + if (propagate_trav != EcsIsA) { + continue; + } + } - 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); + flecs_emit_propagate_id( + world, it, cdr, cur, trav, iders, ider_count); + } - return result; -error: - return (ecs_iter_t){ 0 }; + ecs_log_pop_3(); } -bool ecs_page_next( - ecs_iter_t *it) +static +void flecs_emit_propagate_invalidate_tables( + ecs_world_t *world, + ecs_component_record_t *tgt_idr) { - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_iter_t *chain_it = it->chain_it; + 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); + } - do { - if (!ecs_iter_next(chain_it)) { - goto depleted; + /* Invalidate records of traversable relationships */ + ecs_component_record_t *cur = tgt_idr; + while ((cur = flecs_component_trav_next(cur))) { + ecs_reachable_cache_t *rc = &cur->pair->reachable; + if (rc->current != rc->generation) { + /* Subtree is already marked invalid */ + continue; } - 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 (!chain_it->table) { - goto yield; /* Task query */ - } + rc->generation ++; - int32_t offset = iter->offset; - int32_t limit = iter->limit; - if (!(offset || limit)) { - if (it->count) { - goto yield; - } else { - goto depleted; - } + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + continue; } - int32_t count = it->count; - int32_t remaining = iter->remaining; - - if (offset) { - if (offset > count) { - /* No entities to iterate in current table */ - iter->offset -= count; - it->count = 0; + 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; - } else { - iter->offset = 0; - it->offset = offset; - count = it->count -= offset; - it->entities = - &(ecs_table_entities(it->table)[it->offset]); } - } - if (remaining) { - if (remaining > count) { - iter->remaining -= count; - } else { - it->count = remaining; - iter->remaining = 0; + int32_t e, entity_count = ecs_table_count(table); + const ecs_entity_t *entities = ecs_table_entities(table); + + for (e = 0; e < entity_count; e ++) { + ecs_record_t *r = flecs_entities_get(world, entities[e]); + ecs_component_record_t *idr_t = r->cdr; + if (idr_t) { + /* Only notify for entities that are used in pairs with + * traversable relationships */ + flecs_emit_propagate_invalidate_tables(world, idr_t); + } } - } else if (limit) { - /* Limit hit: no more entities left to iterate */ - goto done; } - } while (it->count == 0); - -yield: - return true; - -done: - /* Cleanup iterator resources if it wasn't yet depleted */ - ecs_iter_fini(chain_it); - -depleted: -error: - return false; + } } -ecs_iter_t ecs_worker_iter( - const ecs_iter_t *it, - int32_t index, +void flecs_emit_propagate_invalidate( + ecs_world_t *world, + ecs_table_t *table, + int32_t offset, 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, - "invalid field index %d", index); - ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); + const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; + int32_t i; + for (i = 0; i < count; i ++) { + ecs_record_t *record = flecs_entities_get(world, entities[i]); + if (!record) { + /* If the event is emitted after a bulk operation, it's possible + * that it hasn't been populated with entities yet. */ + continue; + } - ecs_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 }; + ecs_component_record_t *idr_t = record->cdr; + if (idr_t) { + /* Event is used as target in traversable relationship, propagate */ + flecs_emit_propagate_invalidate_tables(world, idr_t); + } + } } -bool ecs_worker_next( - ecs_iter_t *it) +static +void flecs_propagate_entities( + ecs_world_t *world, + ecs_iter_t *it, + ecs_component_record_t *cdr, + const ecs_entity_t *entities, + int32_t count, + ecs_entity_t src, + ecs_event_id_record_t **iders, + int32_t ider_count) { - 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); - - 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, first; - - do { - if (!ecs_iter_next(chain_it)) { - return false; - } - - /* Copy everything up to the private iterator data */ - ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); + if (!count) { + return; + } - int32_t count = it->count; - per_worker = count / res_count; - first = per_worker * res_index; - count -= per_worker * res_count; + ecs_entity_t old_src = it->sources[0]; + ecs_table_t *old_table = it->table; + ecs_table_t *old_other_table = it->other_table; + const ecs_entity_t *old_entities = it->entities; + int32_t old_count = it->count; + int32_t old_offset = it->offset; - if (count) { - if (res_index < count) { - per_worker ++; - first += res_index; - } else { - first += count; - } + int32_t i; + for (i = 0; i < count; i ++) { + ecs_record_t *record = flecs_entities_get(world, entities[i]); + if (!record) { + /* If the event is emitted after a bulk operation, it's possible + * that it hasn't been populated with entities yet. */ + continue; } - 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; - } + ecs_component_record_t *idr_t = record->cdr; + if (idr_t) { + /* Entity is used as target in traversable pairs, propagate */ + ecs_entity_t e = src ? src : entities[i]; + it->sources[0] = e; + flecs_emit_propagate( + world, it, cdr, idr_t, 0, iders, ider_count); } - } while (!per_worker); - - it->frame_offset += first; - it->count = per_worker; - it->offset += first; - - it->entities = &(ecs_table_entities(it->table)[it->offset]); - - return true; -error: - return false; -} - -/** - * @file misc.c - * @brief Miscellaneous functions. - */ - -#include -#include - -#ifndef FLECS_NDEBUG -static int64_t flecs_s_min[] = { - [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; -static int64_t flecs_s_max[] = { - [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; -static uint64_t flecs_u_max[] = { - [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; - -uint64_t flecs_ito_( - size_t size, - bool is_signed, - bool lt_zero, - uint64_t u, - const char *err) -{ - union { - uint64_t u; - int64_t s; - } v; - - v.u = u; - - if (is_signed) { - ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); - ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); - } else { - ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); - ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); } - - return u; -} -#endif - -int32_t flecs_next_pow_of_2( - int32_t n) -{ - n --; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n ++; - - return n; -} - -/** Convert time to double */ -double ecs_time_to_double( - ecs_time_t t) -{ - double result; - result = t.sec; - return result + (double)t.nanosec / (double)1000000000; + + it->table = old_table; + it->other_table = old_other_table; + it->entities = old_entities; + it->count = old_count; + it->offset = old_offset; + it->sources[0] = old_src; } -ecs_time_t ecs_time_sub( - ecs_time_t t1, - ecs_time_t t2) +static +void flecs_override_copy( + ecs_world_t *world, + ecs_table_t *table, + const ecs_table_record_t *tr, + const ecs_type_info_t *ti, + void *dst, + const void *src, + int32_t offset, + int32_t count) { - ecs_time_t result; + void *ptr = dst; + ecs_copy_t copy = ti->hooks.copy; + ecs_size_t size = ti->size; + int32_t i; - if (t1.nanosec >= t2.nanosec) { - result.nanosec = t1.nanosec - t2.nanosec; - result.sec = t1.sec - t2.sec; + if (copy) { + for (i = 0; i < count; i ++) { + copy(ptr, src, 1, ti); + ptr = ECS_OFFSET(ptr, size); + } } else { - result.nanosec = t1.nanosec - t2.nanosec + 1000000000; - result.sec = t1.sec - t2.sec - 1; + for (i = 0; i < count; i ++) { + ecs_os_memcpy(ptr, src, size); + ptr = ECS_OFFSET(ptr, size); + } } - return result; + ecs_iter_action_t on_set = ti->hooks.on_set; + if (on_set) { + const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; + flecs_invoke_hook(world, table, tr, count, offset, entities, + ti->component, ti, EcsOnSet, on_set); + } } -void ecs_sleepf( - double t) +static +void* flecs_override( + ecs_iter_t *it, + const ecs_type_t *emit_ids, + ecs_id_t id, + ecs_table_t *table, + ecs_component_record_t *cdr) { - if (t > 0) { - int sec = (int)t; - int nsec = (int)((t - sec) * 1000000000); - ecs_os_sleep(sec, nsec); + if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { + return NULL; } -} -double ecs_time_measure( - ecs_time_t *start) -{ - ecs_time_t stop, temp; - ecs_os_get_time(&stop); - temp = stop; - stop = ecs_time_sub(stop, *start); - *start = temp; - return ecs_time_to_double(stop); -} + 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 emitted 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_component_get_table(cdr, table); + if (!tr) { + continue; + } -void* ecs_os_memdup( - const void *src, - ecs_size_t size) -{ - if (!src) { - return NULL; + int32_t index = tr->column; + ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + + ecs_column_t *column = &table->data.columns[index]; + ecs_size_t size = column->ti->size; + return ECS_ELEM(column->data, size, it->offset); + } } - - 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); + return NULL; } -int flecs_id_qsort_cmp(const void *a, const void *b) { - ecs_id_t id_a = *(const ecs_id_t*)a; - ecs_id_t id_b = *(const ecs_id_t*)b; - return (id_a > id_b) - (id_a < id_b); -} +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_component_record_t *cdr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids, + int32_t depth); -uint64_t flecs_string_hash( - const void *ptr) +static +void flecs_emit_forward_id( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cdr, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + int32_t column, + ecs_entity_t trav) { - const ecs_hashed_string_t *str = ptr; - ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); - return str->hash; -} + ecs_id_t id = cdr->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]; -char* flecs_vasprintf( - const char *fmt, - va_list args) -{ - ecs_size_t size = 0; - char *result = NULL; - va_list tmpa; + /* 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); + } - va_copy(tmpa, args); + if (!may_override && (!ider_count && !ider_onset_count)) { + return; + } - size = vsnprintf(result, 0, fmt, tmpa); + ecs_entity_t old_src = it->sources[0]; - va_end(tmpa); + it->ids[0] = id; + it->sources[0] = tgt; + it->event_id = id; + ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; /* safe, owned by observer */ + it->up_fields = 1; - if ((int32_t)size < 0) { - return NULL; + int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); + if (storage_i != -1) { + ecs_assert(cdr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *c = &tgt_table->data.columns[storage_i]; + it->trs[0] = &tgt_table->_->records[column]; + ECS_CONST_CAST(int32_t*, it->sizes)[0] = c->ti->size; /* safe, see above */ } - result = (char *) ecs_os_malloc(size + 1); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + bool owned = tr != NULL; - if (!result) { - return NULL; - } + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); - ecs_os_vsnprintf(result, size + 1, fmt, args); + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke(world, &ider->self_up, it, table, trav); + } + } - return result; -} + /* Emit OnSet events for newly inherited components */ + if (storage_i != -1) { + bool override = false; -char* flecs_asprintf( - const char *fmt, - ...) -{ - va_list args; - va_start(args, fmt); - char *result = flecs_vasprintf(fmt, args); - va_end(args); - return result; -} + /* If component was added together with IsA relationship, still emit + * OnSet event, as it's a new value for the entity. */ + const ecs_table_record_t *base_tr = it->trs[0]; + void *ptr = flecs_override(it, emit_ids, id, table, cdr); + if (ptr) { + override = true; + } -char* flecs_to_snake_case(const char *str) { - int32_t upper_count = 0, len = 1; - const char *ptr = str; - char ch, *out, *out_ptr; + if (ider_onset_count) { + it->event = er_onset->event; - for (ptr = &str[1]; (ch = *ptr); ptr ++) { - if (isupper(ch)) { - upper_count ++; - } - len ++; - } + 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); - 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 ++; + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke( + world, &ider->self_up, it, table, trav); + } else if (override) { + ecs_entity_t src = it->sources[0]; + it->sources[0] = 0; + it->trs[0] = tr; + flecs_observers_invoke(world, &ider->self, it, table, 0); + flecs_observers_invoke(world, &ider->self_up, it, table, 0); + it->sources[0] = src; + } } - out_ptr[0] = (char)tolower(ch); - out_ptr ++; - } else { - out_ptr[0] = ch; - out_ptr ++; + + it->event = event; + it->trs[0] = base_tr; } } - out_ptr[0] = '\0'; - return out; + it->sources[0] = old_src; + it->up_fields = 0; } -char* flecs_load_from_file( - const char *filename) +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_component_record_t *cdr, + ecs_entity_t tgt, + ecs_record_t *tgt_record, + ecs_table_t *tgt_table, + const ecs_table_record_t *tgt_tr, + int32_t column, + ecs_vec_t *reachable_ids, + ecs_entity_t trav) { - FILE* file; - char* content = NULL; - int32_t bytes; - size_t size; + /* 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 = cdr->id; +#ifndef NDEBUG + elem->table = tgt_table; +#endif + ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); - /* Open file for reading */ - ecs_os_fopen(&file, filename, "r"); - if (!file) { - ecs_err("%s (%s)", ecs_os_strerror(errno), filename); - goto error; - } + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, cdr, + tgt, tgt_table, column, trav); +} - /* Determine file size */ - fseek(file, 0, SEEK_END); - bytes = (int32_t)ftell(file); - if (bytes == -1) { - goto error; - } - fseek(file, 0, SEEK_SET); +static +int32_t flecs_emit_stack_at( + ecs_vec_t *stack, + ecs_component_record_t *cdr) +{ + int32_t sp = 0, stack_count = ecs_vec_count(stack); + ecs_table_t **stack_elems = ecs_vec_first(stack); - /* 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'; + for (sp = 0; sp < stack_count; sp ++) { + ecs_table_t *elem = stack_elems[sp]; + if (flecs_component_get_table(cdr, elem)) { + break; + } } - fclose(file); - - return content; -error: - if (file) { - fclose(file); - } - ecs_os_free(content); - return NULL; + return sp; } -char* flecs_chresc( - char *out, - char in, - char delimiter) +static +bool flecs_emit_stack_has( + ecs_vec_t *stack, + ecs_component_record_t *cdr) { - char *bptr = out; - switch(in) { - case '\a': - *bptr++ = '\\'; - *bptr = 'a'; - break; - case '\b': - *bptr++ = '\\'; - *bptr = 'b'; - break; - case '\f': - *bptr++ = '\\'; - *bptr = 'f'; - break; - case '\n': - *bptr++ = '\\'; - *bptr = 'n'; - break; - case '\r': - *bptr++ = '\\'; - *bptr = 'r'; - break; - case '\t': - *bptr++ = '\\'; - *bptr = 't'; - break; - case '\v': - *bptr++ = '\\'; - *bptr = 'v'; - break; - case '\\': - *bptr++ = '\\'; - *bptr = '\\'; - break; - case '\033': - *bptr = '['; /* Used for terminal colors */ - break; - default: - if (in == delimiter) { - *bptr++ = '\\'; - *bptr = delimiter; - } else { - *bptr = in; - } - break; - } - - *(++bptr) = '\0'; - - return bptr; + return flecs_emit_stack_at(stack, cdr) != ecs_vec_count(stack); } -const char* flecs_chrparse( - const char *in, - char *out) +static +void flecs_emit_forward_cached_ids( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_reachable_cache_t *rc, + ecs_vec_t *reachable_ids, + ecs_vec_t *stack, + ecs_entity_t trav) { - const char *result = in + 1; - char ch; + 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_component_record_t *rc_idr = (ecs_component_record_t*)rc_tr->hdr.cache; + ecs_record_t *rc_record = rc_elem->record; - if (in[0] == '\\') { - result ++; + 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); - 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; + if (flecs_emit_stack_has(stack, rc_idr)) { + continue; } - } else { - ch = in[0]; - } - if (out) { - *out = ch; + 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, + reachable_ids, trav); } - - return result; -error: - return NULL; } -ecs_size_t flecs_stresc( - char *out, - ecs_size_t n, - char delimiter, - const char *in) +static +void flecs_emit_dump_cache( + ecs_world_t *world, + const ecs_vec_t *vec) { - const char *ptr = in; - char ch, *bptr = out, buff[3]; - ecs_size_t written = 0; - while ((ch = *ptr++)) { - if ((written += (ecs_size_t)(flecs_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++; - } - } + 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); + #ifndef NDEBUG + ecs_table_t *table = elem->table; + #else + ecs_table_t *table = NULL; + #endif + (void)table; + ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", + idstr, (uint32_t)elem->id, + estr, (uint32_t)elem->src, + table); + ecs_os_free(idstr); + ecs_os_free(estr); } - - if (bptr) { - while (written < n) { - *bptr = '\0'; - bptr++; - written++; - } + if (!ecs_vec_count(vec)) { + ecs_dbg_3("- no entries"); } - return written; -error: - return 0; } -char* flecs_astresc( - char delimiter, - const char *in) +static +void flecs_emit_forward_table_up( + ecs_world_t *world, + const ecs_event_record_t *er, + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + ecs_record_t *tgt_record, + ecs_component_record_t *tgt_idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids, + int32_t depth) { - if (!in) { - return NULL; - } - - ecs_size_t len = flecs_stresc(NULL, 0, delimiter, in); - char *out = ecs_os_malloc_n(char, len + 1); - flecs_stresc(out, len, delimiter, in); - out[len] = '\0'; - return out; -} + 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 rc_child_offset = ecs_vec_count(reachable_ids); + int32_t stack_count = ecs_vec_count(stack); -const char* flecs_parse_digit( - const char *ptr, - char *token) -{ - char *tptr = token; - char ch = ptr[0]; + /* If tgt_idr is out of sync but is not the current component record being updated, + * keep track so that we can update two records for the cost of one. */ + ecs_assert(tgt_idr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_reachable_cache_t *rc = &tgt_idr->pair->reachable; + bool parent_revalidate = (reachable_ids != &rc->ids) && + (rc->current != rc->generation); + if (parent_revalidate) { + ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); + } - if (!isdigit(ch) && ch != '-') { - ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); - return NULL; + 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(); - tptr[0] = ch; - tptr ++; - ptr ++; + /* Function may have to copy values from overridden components if an IsA + * relationship was added together with other components. */ + ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id); + bool inherit = trav == EcsIsA; - for (; (ch = *ptr); ptr ++) { - if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { - break; + for (i = 0; i < id_count; i ++) { + ecs_id_t id = ids[i]; + ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; + ecs_component_record_t *cdr = (ecs_component_record_t*)tgt_tr->hdr.cache; + if (inherit && !(cdr->flags & EcsIdOnInstantiateInherit)) { + continue; } - tptr[0] = ch; - tptr ++; - } - - tptr[0] = '\0'; - - return ptr; -} - -const char* flecs_parse_ws_eol( - const char *ptr) -{ - while (isspace(*ptr)) { - ptr ++; - } + if (cdr == tgt_idr) { + char *idstr = ecs_id_str(world, cdr->id); + ecs_assert(cdr != tgt_idr, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; + } - return ptr; -} + /* Id has the same relationship, traverse to find ids for forwarding */ + if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { + ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, + ecs_table_t*); + t[0] = tgt_table; -/** - * @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_assert(cdr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_reachable_cache_t *idr_rc = &cdr->pair->reachable; + if (idr_rc->current == idr_rc->generation) { + /* Cache hit, use cached ids to prevent traversing the same + * hierarchy multiple times. This especially speeds up code + * where (deep) hierarchies are created. */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, id); + ecs_dbg_3("forward cached for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, + table, idr_rc, reachable_ids, stack, trav); + ecs_log_pop_3(); + } else { + /* Cache is dirty, traverse upwards */ + do { + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, + table, cdr, stack, reachable_ids, depth); + if (++i >= id_count) { + break; + } + id = ids[i]; + if (ECS_PAIR_FIRST(id) != trav) { + break; + } + } while (true); + } -void flecs_observable_init( - ecs_observable_t *observable) -{ - flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); - observable->on_add.event = EcsOnAdd; - observable->on_remove.event = EcsOnRemove; - observable->on_set.event = EcsOnSet; -} + ecs_vec_remove_last(stack); + continue; + } -void flecs_observable_fini( - ecs_observable_t *observable) -{ - ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids), - ECS_INTERNAL_ERROR, NULL); + int32_t stack_at = flecs_emit_stack_at(stack, cdr); + if (parent_revalidate && (stack_at == (stack_count - 1))) { + /* If parent component 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 = cdr->id; +#ifndef NDEBUG + elem->table = tgt_table; +#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; + /* Skip id if it's masked by a lower table in the tree */ + if (stack_at != stack_count) { + continue; + } - /* All observers should've unregistered by now */ - ecs_assert(!ecs_map_is_init(&er->event_ids), - ECS_INTERNAL_ERROR, NULL); + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, + table, cdr, tgt, tgt_record, tgt_table, tgt_tr, i, + reachable_ids, trav); } - flecs_sparse_fini(&observable->events); -} - -ecs_event_record_t* flecs_event_record_get( - const ecs_observable_t *o, - ecs_entity_t event) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Builtin events*/ - if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add); - else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove); - else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set); - else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); - - /* User events */ - return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); -} + 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_event_record_t* flecs_event_record_ensure( - ecs_observable_t *o, - ecs_entity_t event) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + /* Only add ids that were added for this table */ + int32_t count = ecs_vec_count(reachable_ids); + count -= rc_child_offset; - 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; -} + /* 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); + } -static -const ecs_event_record_t* flecs_event_record_get_if( - const ecs_observable_t *o, - ecs_entity_t event) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + rc->current = rc->generation; - 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; + 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); } } - return NULL; + ecs_log_pop_3(); } -ecs_event_id_record_t* flecs_event_id_record_get( +static +void flecs_emit_forward_up( + ecs_world_t *world, const ecs_event_record_t *er, - ecs_id_t id) + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cdr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids, + int32_t depth) { - if (!er) { - return NULL; + if (depth >= FLECS_DAG_DEPTH_MAX) { + char *idstr = ecs_id_str(world, cdr->id); + ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); + ecs_os_free(idstr); + return; } - 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; + ecs_id_t id = cdr->id; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + tgt = flecs_entities_get_alive(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *tgt_record = flecs_entities_try(world, tgt); + ecs_table_t *tgt_table; + if (!tgt_record || !(tgt_table = tgt_record->table)) { + return; } + + flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, + tgt, tgt_table, tgt_record, cdr, stack, reachable_ids, depth + 1); } static -ecs_event_id_record_t* flecs_event_id_record_get_if( +void flecs_emit_forward( + ecs_world_t *world, const ecs_event_record_t *er, - ecs_id_t id) + const ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_component_record_t *cdr) { - ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); - if (!ider) { - return NULL; - } + ecs_assert(cdr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_reachable_cache_t *rc = &cdr->pair->reachable; - if (ider->observer_count) { - return ider; - } + 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, cdr->id); + ecs_dbg_3("reachable cache miss for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); - return NULL; -} + 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, + cdr, &stack, &rc->ids, 0); + it->sources[0] = 0; + ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); -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; - } + 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; + } - ider = ecs_os_calloc_t(ecs_event_id_record_t); + if (ecs_should_log_3()) { + ecs_dbg_3("cache after rebuild:"); + flecs_emit_dump_cache(world, &rc->ids); + } - if (id == EcsAny) { - return er->any = ider; - } else if (id == EcsWildcard) { - return er->wildcard = ider; - } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { - return er->wildcard_pair = ider; - } + ecs_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, cdr->id); + ecs_dbg_3("reachable cache hit for %s", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); + } - ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); - ecs_map_insert_ptr(&er->event_ids, id, ider); - return ider; -} + ecs_entity_t trav = ECS_PAIR_FIRST(cdr->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_component_record_t *rc_idr = (ecs_component_record_t*)tr->hdr.cache; + ecs_record_t *r = elem->record; -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); + 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); + + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, + rc_idr, elem->src, r->table, tr->index, trav); } } -} -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; - } + /* Propagate events for new reachable ids downwards */ + if (table->_->traversable_count) { + int32_t i; + const ecs_entity_t *entities = ecs_table_entities(table); + entities = ECS_ELEM_T(entities, ecs_entity_t, it->offset); + for (i = 0; i < it->count; i ++) { + ecs_record_t *r = flecs_entities_get(world, entities[i]); + if (r->cdr) { + break; + } + } - /* Populate array with observer sets matching the id */ - int32_t count = 0; + if (i != it->count) { + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t 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_component_record_t *rc_idr = (ecs_component_record_t*)tr->hdr.cache; + ecs_record_t *r = elem->record; - if (id != EcsAny) { - iders[0] = flecs_event_id_record_get_if(er, EcsAny); - count += iders[count] != 0; - } + 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); + (void)r; - iders[count] = flecs_event_id_record_get_if(er, id); - count += iders[count] != 0; + /* If entities already have the component, don't propagate */ + if (flecs_component_get_table(rc_idr, it->table)) { + continue; + } - if (id != EcsAny) { - if (ECS_IS_PAIR(id)) { - ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); - ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); - ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); - if (id_fwc != id) { - iders[count] = flecs_event_id_record_get_if(er, id_fwc); - count += iders[count] != 0; - } - if (id_swc != id) { - iders[count] = flecs_event_id_record_get_if(er, id_swc); - count += iders[count] != 0; - } - if (id_pwc != id) { - iders[count] = flecs_event_id_record_get_if(er, id_pwc); - count += iders[count] != 0; + ecs_event_id_record_t *iders[5] = {0}; + int32_t ider_count = flecs_event_observers_get( + er, rc_idr->id, iders); + + flecs_propagate_entities(world, it, rc_idr, it->entities, + it->count, elem->src, iders, ider_count); } - } else if (id != EcsWildcard) { - iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); - count += iders[count] != 0; } } - - return count; } -bool flecs_observers_exist( - ecs_observable_t *observable, - ecs_id_t id, - ecs_entity_t event) +/* 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_flags64_t *set_mask, + ecs_event_desc_t *desc) { - const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); - if (!er) { - return false; - } + flecs_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); - return flecs_event_id_record_get_if(er, id) != NULL; -} + ecs_os_perf_trace_push("flecs.emit"); -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); + ecs_time_t t = {0}; + bool measure_time = world->flags & EcsWorldMeasureSystemTime; + if (measure_time) { + ecs_time_measure(&t); + } -static -void flecs_emit_propagate_id( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - ecs_id_record_t *cur, - ecs_entity_t trav, - ecs_event_id_record_t **iders, - int32_t ider_count) -{ - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { - return; + const ecs_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, count = desc->count; + ecs_flags32_t table_flags = table->flags; + + /* Deferring cannot be suspended for observers */ + int32_t defer = world->stages[0]->defer; + if (defer < 0) { + world->stages[0]->defer *= -1; } - const ecs_table_record_t *tr; - int32_t event_cur = it->event_cur; - while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { - ecs_table_t *table = tr->hdr.table; - if (!ecs_table_count(table)) { - continue; - } + /* 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; + } - bool owned = flecs_id_record_get_table(idr, table) != 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; - int32_t e, entity_count = ecs_table_count(table); - it->table = table; - it->other_table = NULL; - it->offset = 0; - it->count = entity_count; - it->up_fields = 1; - if (entity_count) { - it->entities = ecs_table_entities(table); - } + ecs_id_t ids_cache = 0; + ecs_size_t sizes_cache = 0; + const ecs_table_record_t* trs_cache = 0; + ecs_entity_t sources_cache = 0; - 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); + ecs_iter_t it = { + .world = stage, + .real_world = world, + .event = event, + .event_cur = evtx, + .table = table, + .field_count = 1, + .ids = &ids_cache, + .sizes = &sizes_cache, + .trs = (const ecs_table_record_t**)&trs_cache, + .sources = &sources_cache, + .other_table = other_table, + .offset = offset, + .count = count, + .param = ECS_CONST_CAST(void*, desc->param), + .flags = desc->flags | EcsIterIsValid + }; - if (!owned) { - /* Owned takes precedence */ - flecs_observers_invoke(world, &ider->self_up, it, table, trav); - } - } + ecs_observable_t *observable = ecs_get_observable(desc->observable); + ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - if (!table->_->traversable_count) { - continue; - } + /* 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 events. The + * latter to are used for automatically emitting OnSet 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_entity_t *entities = ecs_table_entities(table); - for (e = 0; e < entity_count; e ++) { - ecs_record_t *r = flecs_entities_get(world, entities[e]); - ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *idr_t = r->idr; - if (idr_t) { - /* Only notify for entities that are used in pairs with - * traversable relationships */ - flecs_emit_propagate(world, it, idr, idr_t, trav, - iders, ider_count); - } - } + ecs_data_t *storage = NULL; + ecs_column_t *columns = NULL; + if (count) { + storage = &table->data; + columns = storage->columns; + it.entities = &ecs_table_entities(table)[offset]; } - it->event_cur = event_cur; - it->up_fields = 0; -} - -static -void flecs_emit_propagate( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - ecs_id_record_t *tgt_idr, - ecs_entity_t propagate_trav, - ecs_event_id_record_t **iders, - int32_t ider_count) -{ - ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t id_count = ids->count; + ecs_id_t *id_array = ids->array; - 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); - } + /* 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)); - ecs_log_push_3(); + /* 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; - /* Propagate to records of traversable relationships */ - ecs_id_record_t *cur = tgt_idr; - while ((cur = cur->trav.next)) { - cur->reachable.generation ++; /* Invalidate cache */ + /* Does table has observed entities */ + bool has_observed = table_flags & EcsTableHasTraversable; - /* Get traversed relationship */ - ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); - if (propagate_trav && propagate_trav != trav) { - if (propagate_trav != EcsIsA) { - continue; - } - } + ecs_event_id_record_t *iders[5] = {0}; - flecs_emit_propagate_id( - world, it, idr, cur, trav, iders, ider_count); + if (count && can_forward && has_observed) { + flecs_emit_propagate_invalidate(world, table, offset, count); } - ecs_log_pop_3(); -} +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_component_record_t *cdr = NULL; + const ecs_type_info_t *ti = NULL; + ecs_id_t id = id_array[i]; + ecs_assert(id == EcsAny || !ecs_id_is_wildcard(id), + ECS_INVALID_PARAMETER, "cannot emit wildcard ids"); + int32_t ider_i, ider_count = 0; + bool is_pair = ECS_IS_PAIR(id); + void *override_ptr = NULL; + bool override_base_added = false; + ecs_table_record_t *base_tr = NULL; + ecs_entity_t base = 0; + bool id_can_override = can_override; -static -void flecs_emit_propagate_invalidate_tables( - ecs_world_t *world, - ecs_id_record_t *tgt_idr) -{ - ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + /* Only added components can trigger overriding */ + if (set_mask && event == EcsOnAdd) { + ecs_assert(i < 256, ECS_UNSUPPORTED, + "cannot add more than 256 components in a single operation"); + if (set_mask[i >> 6] & (1llu << (i & 63))) { + /* Component is already set, so don't override with prefab value */ + id_can_override = false; + } + } - 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); - } + /* 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) || id_can_override) { + cdr = flecs_components_get(world, id); + if (!cdr) { + /* Possible for union ids */ + continue; + } - /* 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; - } + ecs_flags32_t idr_flags = cdr->flags; - rc->generation ++; + 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); - ecs_table_cache_iter_t idt; - if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { - continue; - } + /* 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, NULL); + world->stages[0]->base = 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; + /* Adding an IsA relationship will emit OnSet events for + * any new reachable components. */ + er_fwd = er_onset; + } + } + + /* Forward events for components from pair target */ + flecs_emit_forward(world, er, er_fwd, ids, &it, table, cdr); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } - int32_t e, entity_count = ecs_table_count(table); - const ecs_entity_t *entities = ecs_table_entities(table); + if (id_can_override && !(idr_flags & EcsIdOnInstantiateDontInherit)) { + /* Initialize overridden components with value from base */ + ti = cdr->type_info; + if (ti) { + 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; + if (cdr->flags & EcsIdIsSparse) { + override_ptr = flecs_sparse_get_any( + cdr->sparse, 0, base); + } else { + 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); + override_ptr = base_table->data.columns[base_column].data; + override_ptr = ECS_ELEM(override_ptr, ti->size, base_row); + } - for (e = 0; e < entity_count; e ++) { - ecs_record_t *r = flecs_entities_get(world, entities[e]); - ecs_id_record_t *idr_t = r->idr; - if (idr_t) { - /* Only notify for entities that are used in pairs with - * traversable relationships */ - flecs_emit_propagate_invalidate_tables(world, idr_t); + /* For ids with override policy, check if base was added + * in same operation. This will determine later on + * whether we need to emit an OnSet event. */ + if (!(cdr->flags & + (EcsIdOnInstantiateInherit|EcsIdOnInstantiateDontInherit))) { + int32_t base_i; + for (base_i = 0; base_i < id_count; base_i ++) { + ecs_id_t base_id = id_array[base_i]; + if (!ECS_IS_PAIR(base_id)) { + continue; + } + if (ECS_PAIR_FIRST(base_id) != EcsIsA) { + continue; + } + if (ECS_PAIR_SECOND(base_id) == (uint32_t)base) { + override_base_added = true; + } + } + } + } } } } - } -} -void flecs_emit_propagate_invalidate( - ecs_world_t *world, - ecs_table_t *table, - int32_t offset, - int32_t count) -{ - const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; - int32_t i; - for (i = 0; i < count; i ++) { - ecs_record_t *record = flecs_entities_get(world, entities[i]); - if (!record) { - /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populated with entities yet. */ - continue; + 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); + cdr = cdr ? cdr : flecs_components_get(world, id); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); } - 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 (!ider_count && !override_ptr) { + /* If nothing more to do for this id, early out */ + continue; } - } -} -static -void flecs_propagate_entities( - ecs_world_t *world, - ecs_iter_t *it, - ecs_id_record_t *idr, - const ecs_entity_t *entities, - int32_t count, - ecs_entity_t src, - ecs_event_id_record_t **iders, - int32_t ider_count) -{ - if (!count) { - return; - } + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + ecs_table_record_t dummy_tr = { + .hdr.cache = (ecs_table_cache_t*)cdr, + .hdr.table = table, + .index = -1, + .column = -1, + .count = 0 + }; - ecs_entity_t old_src = it->sources[0]; - ecs_table_t *old_table = it->table; - ecs_table_t *old_other_table = it->other_table; - const ecs_entity_t *old_entities = it->entities; - int32_t old_count = it->count; - int32_t old_offset = it->offset; + if (id != EcsAny) { + 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; + } + } else { + /* When matching Any the table may not have a record for it */ + tr = &dummy_tr; + } - int32_t i; - for (i = 0; i < count; i ++) { - ecs_record_t *record = flecs_entities_get(world, entities[i]); - if (!record) { - /* If the event is emitted after a bulk operation, it's possible - * that it hasn't been populated with entities yet. */ - continue; - } + int32_t storage_i; + it.trs[0] = tr; + ECS_CONST_CAST(int32_t*, it.sizes)[0] = 0; /* safe, owned by observer */ + it.event_id = id; + it.ids[0] = id; - ecs_id_record_t *idr_t = record->idr; - if (idr_t) { - /* Entity is used as target in traversable pairs, propagate */ - ecs_entity_t e = src ? src : entities[i]; - it->sources[0] = e; - flecs_emit_propagate( - world, it, idr, idr_t, 0, iders, ider_count); - } - } - - it->table = old_table; - it->other_table = old_other_table; - it->entities = old_entities; - it->count = old_count; - it->offset = old_offset; - it->sources[0] = old_src; -} + if (count) { + storage_i = tr->column; + bool is_sparse = cdr->flags & EcsIdIsSparse; -static -void flecs_override_copy( - ecs_world_t *world, - ecs_table_t *table, - const ecs_table_record_t *tr, - const ecs_type_info_t *ti, - void *dst, - const void *src, - int32_t offset, - int32_t count) -{ - void *ptr = dst; - ecs_copy_t copy = ti->hooks.copy; - ecs_size_t size = ti->size; - int32_t i; + if (!ecs_id_is_wildcard(id) && (storage_i != -1 || is_sparse)) { + void *ptr; + ecs_size_t size = cdr->type_info->size; - if (copy) { - for (i = 0; i < count; i ++) { - copy(ptr, src, 1, ti); - ptr = ECS_OFFSET(ptr, size); - } - } else { - for (i = 0; i < count; i ++) { - ecs_os_memcpy(ptr, src, size); - ptr = ECS_OFFSET(ptr, size); - } - } + if (is_sparse) { + ecs_assert(count == 1, ECS_UNSUPPORTED, + "events for multiple entities are currently unsupported" + " for sparse components"); + ecs_entity_t e = ecs_table_entities(table)[offset]; + ptr = flecs_sparse_get(cdr->sparse, 0, e); + } else{ + ecs_assert(cdr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_column_t *c = &columns[storage_i]; + ptr = ECS_ELEM(c->data, size, offset); + } - ecs_iter_action_t on_set = ti->hooks.on_set; - if (on_set) { - const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; - flecs_invoke_hook(world, table, tr, count, offset, entities, - ti->component, ti, EcsOnSet, on_set); - } -} + /* Safe, owned by observer */ + ECS_CONST_CAST(int32_t*, it.sizes)[0] = size; -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 (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, tr, ti, ptr, + override_ptr, offset, count); - 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 emitted 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; - } + /* If the base for this component got added in the same + * operation, generate an OnSet event as this is the + * first time this value is observed for the entity. */ + if (override_base_added) { + 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); + 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, &it, table, 0); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke( + world, &ider->self_up, &it, table, 0); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + } + } + } else if (er_onset && it.other_table && + it.other_table->type.count) + { + /* 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.trs[0] = base_tr; + it.up_fields = 1; - int32_t index = tr->column; - ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { + ecs_event_id_record_t *ider = iders_set[ider_set_i]; + flecs_observers_invoke( + world, &ider->self_up, &it, table, EcsIsA); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke( + world, &ider->up, &it, table, EcsIsA); + ecs_assert(it.event_cur == evtx, + ECS_INTERNAL_ERROR, NULL); + } - ecs_column_t *column = &table->data.columns[index]; - ecs_size_t size = column->ti->size; - return ECS_ELEM(column->data, size, it->offset); + it.sources[0] = 0; + it.trs[0] = tr; + } + } + } + } } - } - return NULL; -} - -static -void flecs_emit_forward_up( - ecs_world_t *world, - const ecs_event_record_t *er, - const ecs_event_record_t *er_onset, - const ecs_type_t *emit_ids, - ecs_iter_t *it, - ecs_table_t *table, - ecs_id_record_t *idr, - ecs_vec_t *stack, - ecs_vec_t *reachable_ids, - int32_t depth); - -static -void flecs_emit_forward_id( - ecs_world_t *world, - const ecs_event_record_t *er, - const ecs_event_record_t *er_onset, - const ecs_type_t *emit_ids, - ecs_iter_t *it, - ecs_table_t *table, - ecs_id_record_t *idr, - ecs_entity_t tgt, - ecs_table_t *tgt_table, - int32_t column, - ecs_entity_t trav) -{ - ecs_id_t id = idr->id; - ecs_entity_t event = er ? er->event : 0; - bool inherit = trav == EcsIsA; - bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); - ecs_event_id_record_t *iders[5]; - ecs_event_id_record_t *iders_onset[5]; + /* Actually invoke observers for this event/id */ + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->self, &it, table, 0); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + flecs_observers_invoke(world, &ider->self_up, &it, table, 0); + ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); + } - /* Skip id if there are no observers for it */ - int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); - int32_t ider_onset_i, ider_onset_count = 0; - if (er_onset) { - ider_onset_count = flecs_event_observers_get( - er_onset, id, iders_onset); - } + if (!ider_count || !count || !has_observed) { + continue; + } - if (!may_override && (!ider_count && !ider_onset_count)) { - return; + /* 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. */ + flecs_propagate_entities( + world, &it, cdr, it.entities, count, 0, iders, ider_count); } - it->ids[0] = id; - it->sources[0] = tgt; - it->event_id = id; - ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; /* safe, owned by observer */ - it->up_fields = 1; + can_override = false; /* Don't override twice */ + can_forward = false; /* Don't forward twice */ - 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->trs[0] = &tgt_table->_->records[column]; - ECS_CONST_CAST(int32_t*, it->sizes)[0] = c->ti->size; /* safe, see above */ + if (wcer && er != wcer) { + /* Repeat event loop for Wildcard event */ + er = wcer; + it.event = event; + goto repeat_event; } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - bool owned = tr != NULL; +error: + world->stages[0]->defer = defer; - 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); + ecs_os_perf_trace_pop("flecs.emit"); - /* Owned takes precedence */ - if (!owned) { - flecs_observers_invoke(world, &ider->self_up, it, table, trav); - } + if (measure_time) { + world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); } + return; +} - /* Emit OnSet events for newly inherited components */ - if (storage_i != -1) { - bool override = false; +void ecs_emit( + ecs_world_t *stage, + ecs_event_desc_t *desc) +{ + ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, + "cannot set param and const_param at the same time"); - /* If component was added together with IsA relationship, still emit - * OnSet event, as it's a new value for the entity. */ - ecs_table_record_t *base_tr = ECS_CONST_CAST( - ecs_table_record_t*, it->trs[0]); - void *ptr = flecs_override(it, emit_ids, id, table, idr); - if (ptr) { - override = true; - } + 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); + desc->table = r->table; + desc->offset = ECS_RECORD_TO_ROW(r->row); + desc->count = 1; + } - if (ider_onset_count) { - it->event = er_onset->event; + if (!desc->observable) { + desc->observable = world; + } - 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); + ecs_type_t default_ids = (ecs_type_t){ + .count = 1, + .array = (ecs_id_t[]){ EcsAny } + }; - /* Owned takes precedence */ - if (!owned) { - flecs_observers_invoke( - world, &ider->self_up, it, table, trav); - } else if (override) { - ecs_entity_t src = it->sources[0]; - it->sources[0] = 0; - it->trs[0] = tr; - flecs_observers_invoke(world, &ider->self, it, table, 0); - flecs_observers_invoke(world, &ider->self_up, it, table, 0); - it->sources[0] = src; - } - } + if (!desc->ids || !desc->ids->count) { + desc->ids = &default_ids; + } - it->event = event; - it->trs[0] = base_tr; - } + if (desc->const_param) { + desc->param = ECS_CONST_CAST(void*, desc->const_param); + desc->const_param = NULL; } - it->up_fields = 0; + ecs_defer_begin(world); + flecs_emit(world, stage, 0, desc); + ecs_defer_end(world); + + if (desc->ids == &default_ids) { + desc->ids = NULL; + } +error: + return; } -static -void flecs_emit_forward_and_cache_id( +void ecs_enqueue( 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, - ecs_vec_t *reachable_ids, - ecs_entity_t trav) + ecs_event_desc_t *desc) { - /* 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 (!ecs_is_deferred(world)) { + ecs_emit(world, desc); + return; + } - flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, - tgt, tgt_table, column, trav); + ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_enqueue(world, stage, desc); } +/** + * @file observer.c + * @brief Observer implementation. + * + * The observer implementation contains functions for creating, deleting and + * invoking observers. The code is split up into single-term observers and + * multi-term observers. Multi-term observers are created from multiple single- + * term observers. + */ + +#include + static -int32_t flecs_emit_stack_at( - ecs_vec_t *stack, - ecs_id_record_t *idr) +ecs_entity_t flecs_get_observer_event( + ecs_term_t *term, + ecs_entity_t event) { - int32_t sp = 0, stack_count = ecs_vec_count(stack); - ecs_table_t **stack_elems = ecs_vec_first(stack); - - for (sp = 0; sp < stack_count; sp ++) { - ecs_table_t *elem = stack_elems[sp]; - if (flecs_id_record_get_table(idr, elem)) { - break; + /* If operator is Not, reverse the event */ + if (term->oper == EcsNot) { + if (event == EcsOnAdd || event == EcsOnSet) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; } } - return sp; + return event; } static -bool flecs_emit_stack_has( - ecs_vec_t *stack, - ecs_id_record_t *idr) +ecs_flags32_t flecs_id_flag_for_event( + ecs_entity_t e) { - return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; + } + if (e == EcsOnRemove) { + return EcsIdHasOnRemove; + } + if (e == EcsOnSet) { + return EcsIdHasOnSet; + } + if (e == EcsOnTableCreate) { + return EcsIdHasOnTableCreate; + } + if (e == EcsOnTableDelete) { + return EcsIdHasOnTableDelete; + } + if (e == EcsWildcard) { + return EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet| + EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; + } + return 0; } static -void flecs_emit_forward_cached_ids( +void flecs_inc_observer_count( ecs_world_t *world, - const ecs_event_record_t *er, - const ecs_event_record_t *er_onset, - const ecs_type_t *emit_ids, - ecs_iter_t *it, - ecs_table_t *table, - ecs_reachable_cache_t *rc, - ecs_vec_t *reachable_ids, - ecs_vec_t *stack, - ecs_entity_t trav) + ecs_entity_t event, + ecs_event_record_t *evt, + ecs_id_t id, + int32_t value) { - 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_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_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_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + cdr->flags |= flags; + } + } + } else if (result == 0) { + /* Ditto, but the reverse */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableNoTriggersForId, + .event = event + }); - if (flecs_emit_stack_has(stack, rc_idr)) { - continue; + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + cdr->flags &= ~flags; + } } - 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, - reachable_ids, trav); + flecs_event_id_record_remove(evt, id); + ecs_os_free(idt); } } static -void flecs_emit_dump_cache( - ecs_world_t *world, - const ecs_vec_t *vec) +ecs_id_t flecs_observer_id( + ecs_id_t id) { - 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"); + if (ECS_IS_PAIR(id)) { + if (ECS_PAIR_FIRST(id) == EcsAny) { + id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + } + if (ECS_PAIR_SECOND(id) == EcsAny) { + id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + } } + + return id; } static -void flecs_emit_forward_table_up( +void flecs_register_observer_for_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_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 depth) + ecs_observable_t *observable, + ecs_observer_t *o, + size_t offset) { - 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 rc_child_offset = ecs_vec_count(reachable_ids); - int32_t stack_count = ecs_vec_count(stack); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_id_t term_id = flecs_observer_id(impl->register_id); + ecs_term_t *term = &o->query->terms[0]; + ecs_entity_t trav = term->trav; - /* 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); - } + int i, j; + for (i = 0; i < o->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, o->events[i]); + for (j = 0; j < i; j ++) { + if (event == flecs_get_observer_event(term, o->events[j])) { + break; + } + } + if (i != j) { + /* Duplicate event, ignore */ + 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(); + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_ensure(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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; + /* 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); - 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 & EcsIdOnInstantiateInherit)) { - continue; - } + ecs_map_t *observers = ECS_OFFSET(idt, offset); + ecs_map_init_w_params_if(observers, &world->allocators.ptr); + ecs_map_insert_ptr(observers, impl->id, o); - if (idr == tgt_idr) { - char *idstr = ecs_id_str(world, idr->id); - ecs_assert(idr != tgt_idr, ECS_CYCLE_DETECTED, idstr); - ecs_os_free(idstr); - return; + flecs_inc_observer_count(world, event, er, term_id, 1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), 1); } + } +} - /* Id has the same relationship, traverse to find ids for forwarding */ - if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { - ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, - ecs_table_t*); - t[0] = tgt_table; +static +void flecs_uni_observer_register( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *o) +{ + ecs_term_t *term = &o->query->terms[0]; + ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); - ecs_reachable_cache_t *idr_rc = &idr->reachable; - if (idr_rc->current == idr_rc->generation) { - /* Cache hit, use cached ids to prevent traversing the same - * hierarchy multiple times. This especially speeds up code - * where (deep) hierarchies are created. */ - if (ecs_should_log_3()) { - char *idstr = ecs_id_str(world, id); - ecs_dbg_3("forward cached for %s", idstr); - ecs_os_free(idstr); - } - ecs_log_push_3(); - flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, - table, idr_rc, reachable_ids, stack, trav); - ecs_log_pop_3(); - } else { - /* Cache is dirty, traverse upwards */ - do { - flecs_emit_forward_up(world, er, er_onset, emit_ids, it, - table, idr, stack, reachable_ids, depth); - if (++i >= id_count) { - break; - } + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self_up)); + } else if (flags & EcsSelf) { + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self)); + } else if (flags & EcsUp) { + ecs_assert(term->trav != 0, ECS_INTERNAL_ERROR, NULL); + flecs_register_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, up)); + } +} - id = ids[i]; - if (ECS_PAIR_FIRST(id) != trav) { - break; - } - } while (true); - } +static +void flecs_unregister_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *o, + size_t offset) +{ + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_id_t term_id = flecs_observer_id(impl->register_id); + ecs_term_t *term = &o->query->terms[0]; + ecs_entity_t trav = term->trav; - ecs_vec_remove_last(stack); + int i, j; + for (i = 0; i < o->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, o->events[i]); + for (j = 0; j < i; j ++) { + if (event == flecs_get_observer_event(term, o->events[j])) { + break; + } + } + if (i != j) { + /* Duplicate event, ignore */ 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 + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_get(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get observers for (component) id */ + ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *id_observers = ECS_OFFSET(idt, offset); + ecs_map_remove(id_observers, impl->id); + if (!ecs_map_count(id_observers)) { + ecs_map_fini(id_observers); } - /* Skip id if it's masked by a lower table in the tree */ - if (stack_at != stack_count) { - continue; + flecs_inc_observer_count(world, event, er, term_id, -1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), -1); } + } +} - flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, - table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, - reachable_ids, trav); +static +void flecs_unregister_observer( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *o) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + if (o->query->term_count == 0) { + return; } - 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_term_t *term = &o->query->terms[0]; + ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); - /* Only add ids that were added for this table */ - int32_t count = ecs_vec_count(reachable_ids); - count -= rc_child_offset; + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self_up)); + } else if (flags & EcsSelf) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, self)); + } else if (flags & EcsUp) { + flecs_unregister_observer_for_id(world, observable, o, + offsetof(ecs_event_id_record_t, up)); + } +} - /* 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); - } +static +bool flecs_ignore_observer( + ecs_observer_t *o, + ecs_table_t *table, + ecs_iter_t *it) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - rc->current = rc->generation; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + int32_t *last_event_id = impl->last_event_id; + if (last_event_id && last_event_id[0] == it->event_cur) { + return true; + } - 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); - } + if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { + return true; } - ecs_log_pop_3(); + ecs_flags32_t table_flags = table->flags, query_flags = o->query->flags; + + bool result = (table_flags & EcsTableIsPrefab) && + !(query_flags & EcsQueryMatchPrefab); + result = result || ((table_flags & EcsTableIsDisabled) && + !(query_flags & EcsQueryMatchDisabled)); + + return result; } static -void flecs_emit_forward_up( +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; + o->callback(it); +} + +static +void flecs_observer_invoke( + ecs_observer_t *o, + ecs_iter_t *it) +{ + if (o->run) { + it->next = flecs_default_next_callback; + it->callback = o->callback; + it->interrupted_by = 0; + if (flecs_observer_impl(o)->flags & EcsObserverBypassQuery) { + it->ctx = o; + } else { + it->ctx = o->ctx; + } + o->run(it); + } else { + ecs_iter_action_t callback = o->callback; + it->callback = callback; + callback(it); + } +} + +static +void flecs_uni_observer_invoke( ecs_world_t *world, - const ecs_event_record_t *er, - const ecs_event_record_t *er_onset, - const ecs_type_t *emit_ids, + ecs_observer_t *o, ecs_iter_t *it, ecs_table_t *table, - ecs_id_record_t *idr, - ecs_vec_t *stack, - ecs_vec_t *reachable_ids, - int32_t depth) + ecs_entity_t trav) { - if (depth >= FLECS_DAG_DEPTH_MAX) { - char *idstr = ecs_id_str(world, idr->id); - ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); - ecs_os_free(idstr); + ecs_query_t *query = o->query; + ecs_term_t *term = &query->terms[0]; + if (flecs_ignore_observer(o, table, it)) { return; } - ecs_id_t id = idr->id; - ecs_entity_t tgt = ECS_PAIR_SECOND(id); - tgt = flecs_entities_get_alive(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)) { + ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); + if (trav && term->trav != trav) { return; } - flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, - tgt, tgt_table, tgt_record, idr, stack, reachable_ids, depth + 1); -} + if (ecs_should_log_3()) { + char *path = ecs_get_path(world, it->system); + ecs_dbg_3("observer: invoke %s", path); + ecs_os_free(path); + } -static -void flecs_emit_forward( - ecs_world_t *world, - const ecs_event_record_t *er, - const ecs_event_record_t *er_onset, - const ecs_type_t *emit_ids, - ecs_iter_t *it, - ecs_table_t *table, - ecs_id_record_t *idr) -{ - ecs_reachable_cache_t *rc = &idr->reachable; + ecs_log_push_3(); - if (rc->current != rc->generation) { - /* Cache miss, iterate the tree to find ids to forward */ - if (ecs_should_log_3()) { - char *idstr = ecs_id_str(world, idr->id); - ecs_dbg_3("reachable cache miss for %s", idstr); - ecs_os_free(idstr); - } - ecs_log_push_3(); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + bool is_filter = term->inout == EcsInOutNone; + ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); + it->system = o->entity; + it->ctx = o->ctx; + it->callback_ctx = o->callback_ctx; + it->run_ctx = o->run_ctx; + it->term_index = impl->term_index; + it->query = query; + it->ref_fields = query->fixed_fields | query->row_fields; + ecs_termset_t row_fields = it->row_fields; + it->row_fields = query->row_fields; - 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, 0); - it->sources[0] = 0; - ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); + ecs_entity_t event = it->event; + int32_t event_cur = it->event_cur; + it->event = flecs_get_observer_event(term, event); - 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; - } + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); - if (ecs_should_log_3()) { - ecs_dbg_3("cache after rebuild:"); - flecs_emit_dump_cache(world, &rc->ids); - } + bool match_this = query->flags & EcsQueryMatchThis; - ecs_log_pop_3(); + if (match_this) { + /* Invoke observer for $this field */ + flecs_observer_invoke(o, it); + ecs_os_inc(&query->eval_count); } 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); - } + /* Not a $this field, translate the iterator data from a $this field to + * a field with it->sources set. */ + ecs_entity_t observer_src = ECS_TERM_REF_ID(&term->src); + ecs_assert(observer_src != 0, ECS_INTERNAL_ERROR, NULL); + const ecs_entity_t *entities = it->entities; + int32_t i, count = it->count; + ecs_entity_t src = it->sources[0]; + ecs_table_t *old_table = it->table; - 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); + it->entities = NULL; + it->count = 0; + it->table = NULL; + + /* Loop all entities for which the event was emitted. Usually this is + * just one, but it is possible to emit events for a table range. */ 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_entity_t e = entities[i]; - 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); + /* Filter on the source of the observer field */ + if (observer_src == e) { + if (!src) { + /* Only overwrite source if event wasn't forwarded or + * propagated from another entity. */ + it->sources[0] = e; + } - flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, - rc_idr, elem->src, r->table, tr->index, trav); - } - } + flecs_observer_invoke(o, it); + ecs_os_inc(&query->eval_count); - /* Propagate events for new reachable ids downwards */ - if (table->_->traversable_count) { - int32_t i; - const ecs_entity_t *entities = ecs_table_entities(table); - entities = ECS_ELEM_T(entities, ecs_entity_t, it->offset); - for (i = 0; i < it->count; i ++) { - ecs_record_t *r = flecs_entities_get(world, entities[i]); - if (r->idr) { + /* Restore source */ + it->sources[0] = src; + + /* Observer can only match one source explicitly, so we don't + * have to check any other entities. */ break; } } - if (i != it->count) { - ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, - ecs_reachable_elem_t); - int32_t 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; + it->entities = entities; + it->count = count; + it->table = old_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); - ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); - (void)r; + flecs_stage_set_system(world->stages[0], old_system); - /* If entities already have the component, don't propagate */ - if (flecs_id_record_get_table(rc_idr, it->table)) { - continue; - } + it->event = event; + it->event_cur = event_cur; + it->row_fields = row_fields; - ecs_event_id_record_t *iders[5] = {0}; - int32_t ider_count = flecs_event_observers_get( - er, rc_idr->id, iders); + ecs_log_pop_3(); - flecs_propagate_entities(world, it, rc_idr, it->entities, - it->count, elem->src, iders, ider_count); - } - } - } + world->info.observers_ran_frame ++; } -/* 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( +void flecs_observers_invoke( ecs_world_t *world, - ecs_world_t *stage, - ecs_flags64_t set_mask, - ecs_event_desc_t *desc) + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav) { - flecs_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 (ecs_map_is_init(observers)) { + ecs_table_lock(it->world, table); - ecs_os_perf_trace_push("flecs.emit"); + 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); + } - ecs_time_t t = {0}; - bool measure_time = world->flags & EcsWorldMeasureSystemTime; - if (measure_time) { - ecs_time_measure(&t); + ecs_table_unlock(it->world, 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, count = desc->count; - ecs_flags32_t table_flags = table->flags; +static +void flecs_multi_observer_invoke( + ecs_iter_t *it) +{ + ecs_observer_t *o = it->ctx; + flecs_poly_assert(o, ecs_observer_t); - /* Deferring cannot be suspended for observers */ - int32_t defer = world->stages[0]->defer; - if (defer < 0) { - world->stages[0]->defer *= -1; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_world_t *world = it->real_world; + + if (impl->last_event_id[0] == it->event_cur) { + /* Already handled this event */ + return; } - /* 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_table_t *table = it->table; + ecs_table_t *prev_table = it->other_table; + int8_t pivot_term = it->term_index; + ecs_term_t *term = &o->query->terms[pivot_term]; + + bool is_not = term->oper == EcsNot; + if (is_not) { + table = it->other_table; + prev_table = it->table; } - /* 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; + table = table ? table : &world->store.root; + prev_table = prev_table ? prev_table : &world->store.root; - ecs_id_t ids_cache = 0; - ecs_size_t sizes_cache = 0; - const ecs_table_record_t* trs_cache = 0; - ecs_entity_t sources_cache = 0; + ecs_iter_t user_it; - ecs_iter_t it = { - .world = stage, - .real_world = world, - .event = event, - .event_cur = evtx, - .table = table, - .field_count = 1, - .ids = &ids_cache, - .sizes = &sizes_cache, - .trs = (const ecs_table_record_t**)&trs_cache, - .sources = &sources_cache, - .other_table = other_table, - .offset = offset, - .count = count, - .param = ECS_CONST_CAST(void*, desc->param), - .flags = desc->flags | EcsIterIsValid - }; - - ecs_observable_t *observable = ecs_get_observable(desc->observable); - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); + bool match; + if (is_not) { + match = ecs_query_has_table(o->query, table, &user_it); + if (match) { + /* The target table matches but the entity hasn't moved to it yet. + * Now match the not_query, which will populate the iterator with + * data from the table the entity is still stored in. */ + ecs_iter_fini(&user_it); + match = ecs_query_has_table(impl->not_query, prev_table, &user_it); - /* 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 events. The - * latter to are used for automatically emitting OnSet 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); + /* A not query replaces Not terms with Optional terms, so if the + * regular query matches, the not_query should also match. */ + ecs_assert(match, ECS_INTERNAL_ERROR, NULL); + } + } else { + ecs_table_range_t range = { + .table = table, + .offset = it->offset, + .count = it->count + }; - ecs_data_t *storage = NULL; - ecs_column_t *columns = NULL; - if (count) { - storage = &table->data; - columns = storage->columns; - it.entities = &ecs_table_entities(table)[offset]; + match = ecs_query_has_range(o->query, &range, &user_it); } - int32_t id_count = ids->count; - ecs_id_t *id_array = ids->array; - - /* If a table has IsA relationships, OnAdd/OnRemove events can trigger - * (un)overriding a component. When a component is overridden its value is - * initialized with the value of the overridden component. */ - bool can_override = count && (table_flags & EcsTableHasIsA) && ( - (event == EcsOnAdd) || (event == EcsOnRemove)); + if (match) { + /* Monitor observers only invoke when the query matches for the first + * time with an entity */ + if (impl->flags & EcsObserverIsMonitor) { + ecs_iter_t table_it; + if (ecs_query_has_table(o->query, prev_table, &table_it)) { + ecs_iter_fini(&table_it); + ecs_iter_fini(&user_it); + goto done; + } + } - /* 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; + impl->last_event_id[0] = it->event_cur; - /* Does table has observed entities */ - bool has_observed = table_flags & EcsTableHasTraversable; + /* Patch data from original iterator. If the observer query has + * wildcards which triggered the original event, the component id that + * got matched by ecs_query_has_range may not be the same as the one + * that caused the event. We need to make sure to communicate the + * component id that actually triggered the observer. */ + int8_t pivot_field = term->field_index; + ecs_assert(pivot_field >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pivot_field < user_it.field_count, ECS_INTERNAL_ERROR, NULL); + user_it.ids[pivot_field] = it->event_id; + user_it.trs[pivot_field] = it->trs[0]; + user_it.term_index = pivot_term; - ecs_event_id_record_t *iders[5] = {0}; + user_it.ctx = o->ctx; + user_it.callback_ctx = o->callback_ctx; + user_it.run_ctx = o->run_ctx; + user_it.param = it->param; + user_it.callback = o->callback; + user_it.system = o->entity; + user_it.event = it->event; + user_it.event_id = it->event_id; + user_it.other_table = it->other_table; - if (count && can_forward && has_observed) { - flecs_emit_propagate_invalidate(world, table, offset, count); - } + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); + ecs_table_lock(it->world, table); -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]; - ecs_assert(id == EcsAny || !ecs_id_is_wildcard(id), - ECS_INVALID_PARAMETER, "cannot emit wildcard ids"); - int32_t ider_i, ider_count = 0; - bool is_pair = ECS_IS_PAIR(id); - void *override_ptr = NULL; - bool override_base_added = false; - ecs_table_record_t *base_tr = NULL; - ecs_entity_t base = 0; - bool id_can_override = can_override; - ecs_flags64_t id_bit = 1llu << i; - if (id_bit & set_mask) { - /* Component is already set, so don't override with prefab value */ - id_can_override = false; + if (o->run) { + user_it.next = flecs_default_next_callback; + o->run(&user_it); + } else { + user_it.callback(&user_it); } - /* 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) || id_can_override) { - idr = flecs_id_record_get(world, id); - if (!idr) { - /* Possible for union ids */ - continue; - } - - 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, NULL); - world->stages[0]->base = 0; - } + ecs_iter_fini(&user_it); - /* Adding an IsA relationship will emit OnSet events for - * any new reachable components. */ - er_fwd = er_onset; - } - } + ecs_table_unlock(it->world, table); + flecs_stage_set_system(world->stages[0], old_system); + } else { + /* While the observer query was strictly speaking evaluated, it's more + * useful to measure how often the observer was actually invoked. */ + o->query->eval_count --; + } - /* Forward events for components from pair target */ - flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr); - ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); - } +done: + return; +} - if (id_can_override && !(idr_flags & EcsIdOnInstantiateDontInherit)) { - /* Initialize overridden components with value from base */ - ti = idr->type_info; - if (ti) { - 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; - if (idr->flags & EcsIdIsSparse) { - override_ptr = flecs_sparse_get_any( - idr->sparse, 0, base); - } else { - 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); - override_ptr = base_table->data.columns[base_column].data; - override_ptr = ECS_ELEM(override_ptr, ti->size, base_row); - } +static +void flecs_multi_observer_invoke_no_query( + ecs_iter_t *it) +{ + ecs_observer_t *o = it->ctx; + flecs_poly_assert(o, ecs_observer_t); - /* For ids with override policy, check if base was added - * in same operation. This will determine later on - * whether we need to emit an OnSet event. */ - if (!(idr->flags & - (EcsIdOnInstantiateInherit|EcsIdOnInstantiateDontInherit))) { - int32_t base_i; - for (base_i = 0; base_i < id_count; base_i ++) { - ecs_id_t base_id = id_array[base_i]; - if (!ECS_IS_PAIR(base_id)) { - continue; - } - if (ECS_PAIR_FIRST(base_id) != EcsIsA) { - continue; - } - if (ECS_PAIR_SECOND(base_id) == (uint32_t)base) { - override_base_added = true; - } - } - } - } - } - } - } + ecs_world_t *world = it->real_world; + ecs_table_t *table = it->table; + ecs_iter_t user_it = *it; - 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_id_record_get(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - } + user_it.ctx = o->ctx; + user_it.callback_ctx = o->callback_ctx; + user_it.run_ctx = o->run_ctx; + user_it.param = it->param; + user_it.callback = o->callback; + user_it.system = o->entity; + user_it.event = it->event; - if (!ider_count && !override_ptr) { - /* If nothing more to do for this id, early out */ - continue; - } + ecs_entity_t old_system = flecs_stage_set_system( + world->stages[0], o->entity); + ecs_table_lock(it->world, table); - 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; - } + if (o->run) { + user_it.next = flecs_default_next_callback; + o->run(&user_it); + } else { + user_it.callback(&user_it); + } - int32_t storage_i; - it.trs[0] = tr; - ECS_CONST_CAST(int32_t*, it.sizes)[0] = 0; /* safe, owned by observer */ - it.event_id = id; - it.ids[0] = id; + ecs_table_unlock(it->world, table); + flecs_stage_set_system(world->stages[0], old_system); +} - if (count) { - storage_i = tr->column; - bool is_sparse = idr->flags & EcsIdIsSparse; +/* For convenience, so applications can use a single run callback that uses + * ecs_iter_next to iterate results for systems/queries and observers. */ +bool flecs_default_next_callback(ecs_iter_t *it) { + if (it->interrupted_by) { + return false; + } else { + /* Use interrupted_by to signal the next iteration must return false */ + ecs_assert(it->system != 0, ECS_INTERNAL_ERROR, NULL); + it->interrupted_by = it->system; + return true; + } +} - if (!ecs_id_is_wildcard(id) && (storage_i != -1 || is_sparse)) { - void *ptr; - ecs_size_t size = idr->type_info->size; +/* Run action for children of multi observer */ +static +void flecs_multi_observer_builtin_run(ecs_iter_t *it) { + ecs_observer_t *o = it->ctx; + ecs_run_action_t run = o->run; - if (is_sparse) { - ecs_assert(count == 1, ECS_UNSUPPORTED, - "events for multiple entities are currently unsupported" - " for sparse components"); - ecs_entity_t e = ecs_table_entities(table)[offset]; - ptr = flecs_sparse_get(idr->sparse, 0, e); - } else{ - ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_column_t *c = &columns[storage_i]; - ptr = ECS_ELEM(c->data, size, offset); - } + if (run) { + if (flecs_observer_impl(o)->flags & EcsObserverBypassQuery) { + it->next = flecs_default_next_callback; + it->callback = flecs_multi_observer_invoke; + it->interrupted_by = 0; + it->run_ctx = o->run_ctx; + run(it); + return; + } + } - /* Safe, owned by observer */ - ECS_CONST_CAST(int32_t*, it.sizes)[0] = size; + flecs_multi_observer_invoke(it); +} - 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, tr, ti, ptr, - override_ptr, offset, count); +static +void flecs_observer_yield_existing( + ecs_world_t *world, + ecs_observer_t *o, + bool yield_on_remove) +{ + ecs_run_action_t run = o->run; + if (!run) { + run = flecs_multi_observer_invoke_no_query; + } - /* If the base for this component got added in the same - * operation, generate an OnSet event as this is the - * first time this value is observed for the entity. */ - if (override_base_added) { - 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); - 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, &it, table, 0); - ecs_assert(it.event_cur == evtx, - ECS_INTERNAL_ERROR, NULL); - flecs_observers_invoke( - world, &ider->self_up, &it, table, 0); - ecs_assert(it.event_cur == evtx, - ECS_INTERNAL_ERROR, NULL); - } - } - } else if (er_onset && it.other_table) { - /* 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.trs[0] = base_tr; - it.up_fields = 1; + ecs_defer_begin(world); - for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { - ecs_event_id_record_t *ider = iders_set[ider_set_i]; - flecs_observers_invoke( - world, &ider->self_up, &it, table, EcsIsA); - ecs_assert(it.event_cur == evtx, - ECS_INTERNAL_ERROR, NULL); - flecs_observers_invoke( - world, &ider->up, &it, table, EcsIsA); - ecs_assert(it.event_cur == evtx, - ECS_INTERNAL_ERROR, NULL); - } + /* If yield existing is enabled, invoke for each thing that matches + * the event, if the event is iterable. */ + int i, count = o->event_count; + for (i = 0; i < count; i ++) { + ecs_entity_t event = o->events[i]; - it.sources[0] = 0; - it.trs[0] = tr; - } - } - } + /* We only yield for OnRemove events if the observer is deleted. */ + if (event == EcsOnRemove) { + if (!yield_on_remove) { + continue; + } + } else { + if (yield_on_remove) { + continue; } } - /* Actually invoke observers for this event/id */ - for (ider_i = 0; ider_i < ider_count; ider_i ++) { - ecs_event_id_record_t *ider = iders[ider_i]; - flecs_observers_invoke(world, &ider->self, &it, table, 0); - ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); - flecs_observers_invoke(world, &ider->self_up, &it, table, 0); - ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); - } + ecs_iter_t it = ecs_query_iter(world, o->query); + it.system = o->entity; + it.ctx = o; + it.callback = flecs_default_uni_observer_run_callback; + it.callback_ctx = o->callback_ctx; + it.run_ctx = o->run_ctx; + it.event = o->events[i]; + while (ecs_query_next(&it)) { + it.event_id = it.ids[0]; + it.event_cur = ++ world->event_id; - if (!ider_count || !count || !has_observed) { - continue; + ecs_iter_next_action_t next = it.next; + it.next = flecs_default_next_callback; + run(&it); + it.next = next; + it.interrupted_by = 0; } - - /* 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. */ - flecs_propagate_entities( - world, &it, idr, it.entities, count, 0, iders, ider_count); - } - - can_override = false; /* Don't override twice */ - can_forward = false; /* Don't forward twice */ - - if (wcer && er != wcer) { - /* Repeat event loop for Wildcard event */ - er = wcer; - it.event = event; - goto repeat_event; } -error: - world->stages[0]->defer = defer; - - ecs_os_perf_trace_pop("flecs.emit"); - - if (measure_time) { - world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); - } - return; + ecs_defer_end(world); } -void ecs_emit( - ecs_world_t *stage, - ecs_event_desc_t *desc) +static +int flecs_uni_observer_init( + ecs_world_t *world, + ecs_observer_t *o, + const ecs_observer_desc_t *desc) { - ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, - "cannot set param and const_param at the same time"); - - if (desc->entity) { - ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); - ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); - ecs_record_t *r = flecs_entities_get(world, desc->entity); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* Empty entities can't trigger observers */ - return; - } - desc->table = table; - desc->offset = ECS_RECORD_TO_ROW(r->row); - desc->count = 1; - } - - if (!desc->observable) { - desc->observable = world; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_term_t *term = &o->query->terms[0]; + impl->last_event_id = desc->last_event_id; + if (!impl->last_event_id) { + impl->last_event_id = &impl->last_event_id_storage; } + impl->register_id = term->id; + term->field_index = flecs_ito(int8_t, desc->term_index_); - ecs_type_t default_ids = (ecs_type_t){ - .count = 1, - .array = (ecs_id_t[]){ EcsAny } - }; - - if (!desc->ids || !desc->ids->count) { - desc->ids = &default_ids; - } + if (ecs_id_is_tag(world, term->id)) { + /* If id is a tag, downgrade OnSet to OnAdd. */ + int32_t e, count = o->event_count; + bool has_on_add = false; + for (e = 0; e < count; e ++) { + if (o->events[e] == EcsOnAdd) { + has_on_add = true; + } + } - if (desc->const_param) { - desc->param = ECS_CONST_CAST(void*, desc->const_param); - desc->const_param = NULL; + for (e = 0; e < count; e ++) { + if (o->events[e] == EcsOnSet) { + if (has_on_add) { + /* Already registered */ + o->events[e] = 0; + } else { + o->events[e] = EcsOnAdd; + } + } + } } - ecs_defer_begin(world); - flecs_emit(world, stage, 0, desc); - ecs_defer_end(world); + flecs_uni_observer_register(world, o->observable, o); - if (desc->ids == &default_ids) { - desc->ids = NULL; - } -error: - return; + return 0; } -void ecs_enqueue( +static +int flecs_observer_add_child( ecs_world_t *world, - ecs_event_desc_t *desc) + ecs_observer_t *o, + const ecs_observer_desc_t *child_desc) { - if (!ecs_is_deferred(world)) { - ecs_emit(world, desc); - return; - } - - ecs_stage_t *stage = flecs_stage_from_world(&world); - flecs_enqueue(world, stage, desc); -} - -/** - * @file observer.c - * @brief Observer implementation. - * - * The observer implementation contains functions for creating, deleting and - * invoking observers. The code is split up into single-term observers and - * multi-term observers. Multi-term observers are created from multiple single- - * term observers. - */ - -#include + ecs_assert(child_desc->query.flags & EcsQueryNested, + ECS_INTERNAL_ERROR, NULL); -static -ecs_entity_t flecs_get_observer_event( - ecs_term_t *term, - ecs_entity_t event) -{ - /* If operator is Not, reverse the event */ - if (term->oper == EcsNot) { - if (event == EcsOnAdd || event == EcsOnSet) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; - } + ecs_observer_t *child_observer = flecs_observer_init( + world, 0, child_desc); + if (!child_observer) { + return -1; } - return event; -} - -static -ecs_flags32_t flecs_id_flag_for_event( - ecs_entity_t e) -{ - if (e == EcsOnAdd) { - return EcsIdHasOnAdd; - } - if (e == EcsOnRemove) { - return EcsIdHasOnRemove; - } - if (e == EcsOnSet) { - return EcsIdHasOnSet; - } - if (e == EcsOnTableCreate) { - return EcsIdHasOnTableCreate; - } - if (e == EcsOnTableDelete) { - return EcsIdHasOnTableDelete; - } - if (e == EcsWildcard) { - return EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet| - EcsIdHasOnTableFill|EcsIdHasOnTableEmpty| - EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; - } + ecs_observer_impl_t *impl = flecs_observer_impl(o); + ecs_vec_append_t(&world->allocator, &impl->children, + ecs_observer_t*)[0] = child_observer; + child_observer->entity = o->entity; return 0; } static -void flecs_inc_observer_count( +int flecs_multi_observer_init( ecs_world_t *world, - ecs_entity_t event, - ecs_event_record_t *evt, - ecs_id_t id, - int32_t value) + ecs_observer_t *o, + const ecs_observer_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); + ecs_observer_impl_t *impl = flecs_observer_impl(o); + + /* Create last event id for filtering out the same event that arrives from + * more than one term */ + impl->last_event_id = ecs_os_calloc_t(int32_t); - 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 - }); + /* Mark observer as multi observer */ + impl->flags |= EcsObserverIsMulti; - 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 - }); + /* Vector that stores a single-component observer for each query term */ + ecs_vec_init_t(&world->allocator, &impl->children, ecs_observer_t*, 2); - 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; + /* Create a child observer for each term in the query */ + ecs_query_t *query = o->query; + ecs_observer_desc_t child_desc = *desc; + child_desc.last_event_id = impl->last_event_id; + child_desc.run = NULL; + child_desc.callback = flecs_multi_observer_builtin_run; + child_desc.ctx = o; + child_desc.ctx_free = NULL; + child_desc.query.expr = NULL; + child_desc.callback_ctx = NULL; + child_desc.callback_ctx_free = NULL; + child_desc.run_ctx = NULL; + child_desc.run_ctx_free = NULL; + child_desc.yield_existing = false; + child_desc.flags_ &= ~(EcsObserverYieldOnCreate|EcsObserverYieldOnDelete); + ecs_os_zeromem(&child_desc.entity); + ecs_os_zeromem(&child_desc.query.terms); + ecs_os_zeromem(&child_desc.query); + ecs_os_memcpy_n(child_desc.events, o->events, ecs_entity_t, o->event_count); + + child_desc.query.flags |= EcsQueryNested; + + int i, term_count = query->term_count; + bool optional_only = query->flags & EcsQueryMatchThis; + bool has_not = false; + for (i = 0; i < term_count; i ++) { + if (query->terms[i].oper != EcsOptional) { + if (ecs_term_match_this(&query->terms[i])) { + optional_only = false; } } - flecs_event_id_record_remove(evt, id); - ecs_os_free(idt); + if ((query->terms[i].oper == EcsNot) && + (query->terms[i].inout != EcsInOutFilter)) + { + has_not = true; + } } -} -static -ecs_id_t flecs_observer_id( - ecs_id_t id) -{ - if (ECS_IS_PAIR(id)) { - if (ECS_PAIR_FIRST(id) == EcsAny) { - id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); - } - if (ECS_PAIR_SECOND(id) == EcsAny) { - id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + /* If an observer is only interested in table events, we only need to + * observe a single component, as each table event will be emitted for all + * components of the source table. */ + bool only_table_events = true; + for (i = 0; i < o->event_count; i ++) { + ecs_entity_t e = o->events[i]; + if (e != EcsOnTableCreate && e != EcsOnTableDelete) { + only_table_events = false; + break; } } - return id; -} - -static -void flecs_register_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o, - size_t offset) -{ - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_id_t term_id = flecs_observer_id(impl->register_id); - ecs_term_t *term = &o->query->terms[0]; - ecs_entity_t trav = term->trav; - - int i; - for (i = 0; i < o->event_count; i ++) { - ecs_entity_t event = flecs_get_observer_event( - term, o->events[i]); + if (query->flags & EcsQueryMatchPrefab) { + child_desc.query.flags |= EcsQueryMatchPrefab; + } - /* Get observers for event */ - ecs_event_record_t *er = flecs_event_record_ensure(observable, event); - ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + if (query->flags & EcsQueryMatchDisabled) { + child_desc.query.flags |= EcsQueryMatchDisabled; + } - /* 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); + bool self_term_handled = false; + for (i = 0; i < term_count; i ++) { + if (query->terms[i].inout == EcsInOutFilter) { + continue; + } - ecs_map_t *observers = ECS_OFFSET(idt, offset); - ecs_map_init_w_params_if(observers, &world->allocators.ptr); - ecs_map_insert_ptr(observers, impl->id, o); + ecs_term_t *term = &child_desc.query.terms[0]; + child_desc.term_index_ = query->terms[i].field_index; + *term = query->terms[i]; - flecs_inc_observer_count(world, event, er, term_id, 1); - if (trav) { - flecs_inc_observer_count(world, event, er, - ecs_pair(trav, EcsWildcard), 1); + /* Don't create observers for non-$this terms */ + if (!ecs_term_match_this(term) && term->src.id & EcsIsVariable) { + continue; } - } -} -static -void flecs_uni_observer_register( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o) -{ - ecs_term_t *term = &o->query->terms[0]; - ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); + int16_t oper = term->oper; + ecs_id_t id = term->id; - if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { - flecs_register_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self_up)); - } else if (flags & EcsSelf) { - flecs_register_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self)); - } else if (flags & EcsUp) { - ecs_assert(term->trav != 0, ECS_INTERNAL_ERROR, NULL); - flecs_register_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, up)); - } -} + if (only_table_events) { + /* For table event observers, only observe a single $this|self + * term. Make sure to create observers for non-self terms, as those + * require event propagation. */ + if (ecs_term_match_this(term) && + (term->src.id & EcsTraverseFlags) == EcsSelf) + { + if (oper == EcsAnd) { + if (!self_term_handled) { + self_term_handled = true; + } else { + continue; + } + } + } + } -static -void flecs_unregister_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o, - size_t offset) -{ - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_id_t term_id = flecs_observer_id(impl->register_id); - ecs_term_t *term = &o->query->terms[0]; - ecs_entity_t trav = term->trav; + /* AndFrom & OrFrom terms insert multiple observers */ + if (oper == EcsAndFrom || oper == EcsOrFrom) { + const ecs_type_t *type = ecs_get_type(world, id); + if (!type) { + continue; + } - int i; - for (i = 0; i < o->event_count; i ++) { - ecs_entity_t event = flecs_get_observer_event( - term, o->events[i]); + int32_t ti, ti_count = type->count; + ecs_id_t *ti_ids = type->array; - /* Get observers for event */ - ecs_event_record_t *er = flecs_event_record_get(observable, event); - ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + /* 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_component_record_t *cdr = flecs_components_get(world, ti_id); + if (cdr->flags & EcsIdOnInstantiateDontInherit) { + continue; + } - /* 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); + term->first.name = NULL; + term->first.id = ti_ids[ti]; + term->id = ti_ids[ti]; - ecs_map_t *id_observers = ECS_OFFSET(idt, offset); - ecs_map_remove(id_observers, impl->id); - if (!ecs_map_count(id_observers)) { - ecs_map_fini(id_observers); + if (flecs_observer_add_child(world, o, &child_desc)) { + goto error; + } + } + continue; } - flecs_inc_observer_count(world, event, er, term_id, -1); - if (trav) { - flecs_inc_observer_count(world, event, er, - ecs_pair(trav, EcsWildcard), -1); + /* Single component observers never use OR */ + if (oper == EcsOr) { + term->oper = EcsAnd; } - } -} -static -void flecs_unregister_observer( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *o) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - if (o->query->term_count == 0) { - return; - } + /* If observer only contains optional terms, match everything */ + if (optional_only) { + term->id = EcsAny; + term->first.id = EcsAny; + term->src.id = EcsThis | EcsIsVariable | EcsSelf; + term->second.id = 0; + } else if (term->oper == EcsOptional) { + if (only_table_events || desc->events[0] == EcsMonitor) { + /* For table events & monitors optional terms aren't necessary */ + continue; + } + } - ecs_term_t *term = &o->query->terms[0]; - ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); + if (flecs_observer_add_child(world, o, &child_desc)) { + goto error; + } - if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { - flecs_unregister_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self_up)); - } else if (flags & EcsSelf) { - flecs_unregister_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, self)); - } else if (flags & EcsUp) { - flecs_unregister_observer_for_id(world, observable, o, - offsetof(ecs_event_id_record_t, up)); + if (optional_only) { + break; + } } -} -static -bool flecs_ignore_observer( - ecs_observer_t *o, - ecs_table_t *table, - ecs_iter_t *it) -{ - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + /* If observer has Not terms, we need to create a query that replaces Not + * with Optional which we can use to populate the observer data for the + * table that the entity moved away from (or to, if it's an OnRemove + * observer). */ + if (has_not) { + ecs_query_desc_t not_desc = desc->query; + not_desc.expr = NULL; - ecs_observer_impl_t *impl = flecs_observer_impl(o); - int32_t *last_event_id = impl->last_event_id; - if (last_event_id && last_event_id[0] == it->event_cur) { - return true; - } + ecs_os_memcpy_n(not_desc.terms, o->query->terms, + ecs_term_t, term_count); /* cast suppresses warning */ + + for (i = 0; i < term_count; i ++) { + if (not_desc.terms[i].oper == EcsNot) { + not_desc.terms[i].oper = EcsOptional; + } + } - if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { - return true; + flecs_observer_impl(o)->not_query = + ecs_query_init(world, ¬_desc); } - ecs_flags32_t table_flags = table->flags, query_flags = o->query->flags; - - bool result = (table_flags & EcsTableIsPrefab) && - !(query_flags & EcsQueryMatchPrefab); - result = result || ((table_flags & EcsTableIsDisabled) && - !(query_flags & EcsQueryMatchDisabled)); - - return result; + return 0; +error: + return -1; } static -void flecs_observer_invoke( +void flecs_observer_poly_fini(void *ptr) { + flecs_observer_fini(ptr); +} + +ecs_observer_t* flecs_observer_init( ecs_world_t *world, - ecs_iter_t *it, - ecs_observer_t *o, - ecs_iter_action_t callback, - int32_t term_index) + ecs_entity_t entity, + const ecs_observer_desc_t *desc) { - ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(flecs_poly_is(world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_check(desc->callback != NULL || desc->run != NULL, + ECS_INVALID_OPERATION, + "cannot create observer: must at least specify callback or run"); - if (ecs_should_log_3()) { - char *path = ecs_get_path(world, it->system); - ecs_dbg_3("observer: invoke %s", path); - ecs_os_free(path); - } + ecs_observer_impl_t *impl = flecs_calloc_t( + &world->allocator, ecs_observer_impl_t); + ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); + impl->id = ++ world->observable.last_observer_id; - ecs_log_push_3(); + flecs_poly_init(impl, ecs_observer_t); + ecs_observer_t *o = &impl->pub; + impl->dtor = flecs_observer_poly_fini; - ecs_entity_t old_system = flecs_stage_set_system( - world->stages[0], o->entity); - world->info.observers_ran_frame ++; + /* Make writeable copy of query desc so that we can set name. This will + * make debugging easier, as any error messages related to creating the + * query will have the name of the observer. */ + ecs_query_desc_t query_desc = desc->query; + query_desc.entity = 0; + query_desc.cache_kind = EcsQueryCacheNone; - ecs_query_t *query = o->query; - ecs_assert(term_index < query->term_count, ECS_INTERNAL_ERROR, NULL); - ecs_term_t *term = &query->terms[term_index]; - if (it->table && (term->oper != EcsNot)) { - ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); + /* Create query */ + ecs_query_t *query = o->query = ecs_query_init( + world, &query_desc); + if (query == NULL) { + flecs_observer_fini(o); + return 0; } - ecs_termset_t row_fields = it->row_fields; - it->row_fields = query->row_fields; - - bool match_this = query->flags & EcsQueryMatchThis; - if (match_this) { - callback(it); - ecs_os_inc(&query->eval_count); - } else { - ecs_entity_t observer_src = ECS_TERM_REF_ID(&term->src); - if (observer_src && !(term->src.id & EcsIsEntity)) { - observer_src = 0; - } + flecs_poly_assert(query, ecs_query_t); - const 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); - ecs_os_inc(&query->eval_count); - } else if (observer_src == e) { - ecs_entity_t dummy = 0; - it->entities = &dummy; - if (!src) { - it->sources[0] = e; - } + ecs_check(o->query->term_count > 0, ECS_INVALID_PARAMETER, + "observer must have at least one term"); - callback(it); - ecs_os_inc(&query->eval_count); - it->sources[0] = src; - break; + int i, var_count = 0; + for (i = 0; i < o->query->term_count; i ++) { + ecs_term_t *term = &o->query->terms[i]; + if (!ecs_term_match_this(term)) { + if (term->src.id & EcsIsVariable) { + /* Term has a non-$this variable source */ + var_count ++; } } - - it->entities = entities; - it->count = count; } - it->row_fields = row_fields; + ecs_check(o->query->term_count > var_count, ECS_UNSUPPORTED, + "observers with only non-$this variable sources are not yet supported"); + (void)var_count; - flecs_stage_set_system(world->stages[0], old_system); + ecs_observable_t *observable = desc->observable; + if (!observable) { + observable = ecs_get_observable(world); + } - ecs_log_pop_3(); -} + o->run = desc->run; + o->callback = desc->callback; + o->ctx = desc->ctx; + o->callback_ctx = desc->callback_ctx; + o->run_ctx = desc->run_ctx; + o->ctx_free = desc->ctx_free; + o->callback_ctx_free = desc->callback_ctx_free; + o->run_ctx_free = desc->run_ctx_free; + o->observable = observable; + o->entity = entity; + impl->term_index = desc->term_index_; + impl->flags = desc->flags_; -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; + ecs_check(!(desc->yield_existing && + (desc->flags_ & (EcsObserverYieldOnCreate|EcsObserverYieldOnDelete))), + ECS_INVALID_PARAMETER, + "cannot set yield_existing and YieldOn* flags at the same time"); - if (ecs_should_log_3()) { - char *path = ecs_get_path(it->world, it->system); - ecs_dbg_3("observer %s", path); - ecs_os_free(path); - } + /* 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. */ + for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) { + ecs_entity_t event = desc->events[i]; + if (!event) { + break; + } - ecs_log_push_3(); - flecs_observer_invoke(it->real_world, it, o, o->callback, 0); - ecs_log_pop_3(); -} + if (event == EcsMonitor) { + ecs_check(i == 0, ECS_INVALID_PARAMETER, + "monitor observers can only have a single Monitor event"); -static -void flecs_uni_observer_invoke( - ecs_world_t *world, - ecs_observer_t *o, - ecs_iter_t *it, - ecs_table_t *table, - ecs_entity_t trav) -{ - ecs_query_t *query = o->query; - ecs_term_t *term = &query->terms[0]; - if (flecs_ignore_observer(o, table, it)) { - return; - } + o->events[0] = EcsOnAdd; + o->events[1] = EcsOnRemove; + o->event_count ++; + impl->flags |= EcsObserverIsMonitor; + if (desc->yield_existing) { + impl->flags |= EcsObserverYieldOnCreate; + impl->flags |= EcsObserverYieldOnDelete; + } + } else { + o->events[i] = event; + if (desc->yield_existing) { + if (event == EcsOnRemove) { + impl->flags |= EcsObserverYieldOnDelete; + } else { + impl->flags |= EcsObserverYieldOnCreate; + } + } + } - ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); - if (trav && term->trav != trav) { - return; + o->event_count ++; } - ecs_observer_impl_t *impl = flecs_observer_impl(o); - bool is_filter = term->inout == EcsInOutNone; - ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); - it->system = o->entity; - it->ctx = o->ctx; - it->callback_ctx = o->callback_ctx; - it->run_ctx = o->run_ctx; - it->term_index = impl->term_index; - it->query = query; - it->ref_fields = query->fixed_fields | query->row_fields; - it->row_fields = query->row_fields; - - ecs_entity_t event = it->event; - int32_t event_cur = it->event_cur; - it->event = flecs_get_observer_event(term, event); - - if (o->run) { - it->next = flecs_default_next_callback; - it->callback = flecs_default_uni_observer_run_callback; - it->ctx = o; - o->run(it); - } else { - ecs_iter_action_t callback = o->callback; - it->callback = callback; - flecs_observer_invoke(world, it, o, callback, 0); - } + /* Observer must have at least one event */ + ecs_check(o->event_count != 0, ECS_INVALID_PARAMETER, + "observer must have at least one event"); - it->event = event; - it->event_cur = event_cur; -} + bool multi = false; -void flecs_observers_invoke( - ecs_world_t *world, - ecs_map_t *observers, - ecs_iter_t *it, - ecs_table_t *table, - ecs_entity_t trav) -{ - if (ecs_map_is_init(observers)) { - ecs_table_lock(it->world, table); + if (query->term_count == 1 && !desc->last_event_id) { + ecs_term_t *term = &query->terms[0]; + /* If the query 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; + } - 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); + bool is_monitor = impl->flags & EcsObserverIsMonitor; + if (query->term_count == 1 && !is_monitor && !multi) { + if (flecs_uni_observer_init(world, o, desc)) { + goto error; + } + } else { + if (flecs_multi_observer_init(world, o, desc)) { + goto error; } + } - ecs_table_unlock(it->world, table); + if (impl->flags & EcsObserverYieldOnCreate) { + flecs_observer_yield_existing(world, o, false); } + + return o; +error: + return NULL; } -static -void flecs_multi_observer_invoke( - ecs_iter_t *it) +ecs_entity_t ecs_observer_init( + ecs_world_t *world, + const ecs_observer_desc_t *desc) { - ecs_observer_t *o = it->ctx; - flecs_poly_assert(o, ecs_observer_t); + ecs_entity_t entity = 0; + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, + "ecs_observer_desc_t was not initialized to zero"); + ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, + "cannot create observer while world is being deleted"); - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_world_t *world = it->real_world; - - if (impl->last_event_id[0] == it->event_cur) { - /* Already handled this event */ - return; + entity = desc->entity; + if (!entity) { + entity = ecs_entity(world, {0}); } - ecs_table_t *table = it->table; - ecs_table_t *prev_table = it->other_table; - int8_t pivot_term = it->term_index; - ecs_term_t *term = &o->query->terms[pivot_term]; - - bool is_not = term->oper == EcsNot; - if (is_not) { - table = it->other_table; - prev_table = it->table; - } + EcsPoly *poly = flecs_poly_bind(world, entity, ecs_observer_t); + if (!poly->poly) { + ecs_observer_t *o = flecs_observer_init(world, entity, desc); + ecs_assert(o->entity == entity, ECS_INTERNAL_ERROR, NULL); + poly->poly = o; - table = table ? table : &world->store.root; - prev_table = prev_table ? prev_table : &world->store.root; + if (ecs_get_name(world, entity)) { + ecs_trace("#[green]observer#[reset] %s created", + ecs_get_name(world, entity)); + } + } else { + flecs_poly_assert(poly->poly, ecs_observer_t); + ecs_observer_t *o = (ecs_observer_t*)poly->poly; - ecs_iter_t user_it; + if (o->ctx_free) { + if (o->ctx && o->ctx != desc->ctx) { + o->ctx_free(o->ctx); + } + } - bool match; - if (is_not) { - match = ecs_query_has_table(o->query, table, &user_it); - if (match) { - /* The target table matches but the entity hasn't moved to it yet. - * Now match the not_query, which will populate the iterator with - * data from the table the entity is still stored in. */ - ecs_iter_fini(&user_it); - match = ecs_query_has_table(impl->not_query, prev_table, &user_it); + if (o->callback_ctx_free) { + if (o->callback_ctx && o->callback_ctx != desc->callback_ctx) { + o->callback_ctx_free(o->callback_ctx); + o->callback_ctx_free = NULL; + o->callback_ctx = NULL; + } + } - /* A not query replaces Not terms with Optional terms, so if the - * regular query matches, the not_query should also match. */ - ecs_assert(match, ECS_INTERNAL_ERROR, NULL); + if (o->run_ctx_free) { + if (o->run_ctx && o->run_ctx != desc->run_ctx) { + o->run_ctx_free(o->run_ctx); + o->run_ctx_free = NULL; + o->run_ctx = NULL; + } } - } else { - ecs_table_range_t range = { - .table = table, - .offset = it->offset, - .count = it->count - }; - match = ecs_query_has_range(o->query, &range, &user_it); - } + if (desc->run) { + o->run = desc->run; + if (!desc->callback) { + o->callback = NULL; + } + } - if (match) { - /* Monitor observers only invoke when the query matches for the first - * time with an entity */ - if (impl->flags & EcsObserverIsMonitor) { - ecs_iter_t table_it; - if (ecs_query_has_table(o->query, prev_table, &table_it)) { - ecs_iter_fini(&table_it); - ecs_iter_fini(&user_it); - goto done; + if (desc->callback) { + o->callback = desc->callback; + if (!desc->run) { + o->run = NULL; } } - impl->last_event_id[0] = it->event_cur; + if (desc->ctx) { + o->ctx = desc->ctx; + } - /* Patch data from original iterator. If the observer query has - * wildcards which triggered the original event, the component id that - * got matched by ecs_query_has_range may not be the same as the one - * that caused the event. We need to make sure to communicate the - * component id that actually triggered the observer. */ - int8_t pivot_field = term->field_index; - ecs_assert(pivot_field >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pivot_field < user_it.field_count, ECS_INTERNAL_ERROR, NULL); - user_it.ids[pivot_field] = it->event_id; - user_it.trs[pivot_field] = it->trs[0]; - user_it.term_index = pivot_term; + if (desc->callback_ctx) { + o->callback_ctx = desc->callback_ctx; + } - user_it.ctx = o->ctx; - user_it.callback_ctx = o->callback_ctx; - user_it.run_ctx = o->run_ctx; - user_it.param = it->param; - user_it.callback = o->callback; - user_it.system = o->entity; - user_it.event = it->event; - user_it.event_id = it->event_id; - user_it.other_table = it->other_table; + if (desc->run_ctx) { + o->run_ctx = desc->run_ctx; + } - ecs_entity_t old_system = flecs_stage_set_system( - world->stages[0], o->entity); - ecs_table_lock(it->world, table); + if (desc->ctx_free) { + o->ctx_free = desc->ctx_free; + } - if (o->run) { - user_it.next = flecs_default_next_callback; - o->run(&user_it); - } else { - user_it.callback(&user_it); + if (desc->callback_ctx_free) { + o->callback_ctx_free = desc->callback_ctx_free; } - ecs_iter_fini(&user_it); + if (desc->run_ctx_free) { + o->run_ctx_free = desc->run_ctx_free; + } + } - ecs_table_unlock(it->world, table); - flecs_stage_set_system(world->stages[0], old_system); - } else { - /* While the observer query was strictly speaking evaluated, it's more - * useful to measure how often the observer was actually invoked. */ - o->query->eval_count --; + flecs_poly_modified(world, entity, ecs_observer_t); + + return entity; +error: + if (entity) { + ecs_delete(world, entity); } + return 0; +} -done: - return; +const ecs_observer_t* ecs_observer_get( + const ecs_world_t *world, + ecs_entity_t observer) +{ + return flecs_poly_get(world, observer, ecs_observer_t); } -static -void flecs_multi_observer_invoke_no_query( - ecs_iter_t *it) +void flecs_observer_fini( + ecs_observer_t *o) { - ecs_observer_t *o = it->ctx; - flecs_poly_assert(o, ecs_observer_t); + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_world_t *world = o->query->world; + ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_world_t *world = it->real_world; - ecs_table_t *table = it->table; - ecs_iter_t user_it = *it; + if (impl->flags & EcsObserverYieldOnDelete) { + flecs_observer_yield_existing(world, o, true); + } - user_it.ctx = o->ctx; - user_it.callback_ctx = o->callback_ctx; - user_it.run_ctx = o->run_ctx; - user_it.param = it->param; - user_it.callback = o->callback; - user_it.system = o->entity; - user_it.event = it->event; + if (impl->flags & EcsObserverIsMulti) { + ecs_observer_t **children = ecs_vec_first(&impl->children); + int32_t i, children_count = ecs_vec_count(&impl->children); - ecs_entity_t old_system = flecs_stage_set_system( - world->stages[0], o->entity); - ecs_table_lock(it->world, table); + for (i = 0; i < children_count; i ++) { + flecs_observer_fini(children[i]); + } - if (o->run) { - user_it.next = flecs_default_next_callback; - o->run(&user_it); + ecs_os_free(impl->last_event_id); } else { - user_it.callback(&user_it); + if (o->query->term_count) { + flecs_unregister_observer(world, o->observable, o); + } else { + /* Observer creation failed while creating query */ + } } - ecs_table_unlock(it->world, table); - flecs_stage_set_system(world->stages[0], old_system); -} + ecs_vec_fini_t(&world->allocator, &impl->children, ecs_observer_t*); -/* For convenience, so applications can (in theory) use a single run callback - * that uses ecs_iter_next to iterate results */ -bool flecs_default_next_callback(ecs_iter_t *it) { - if (it->interrupted_by) { - return false; - } else { - /* Use interrupted_by to signal the next iteration must return false */ - ecs_assert(it->system != 0, ECS_INTERNAL_ERROR, NULL); - it->interrupted_by = it->system; - return true; + /* Cleanup queries */ + ecs_query_fini(o->query); + if (impl->not_query) { + ecs_query_fini(impl->not_query); } -} -/* Run action for children of multi observer */ -static -void flecs_multi_observer_builtin_run(ecs_iter_t *it) { - ecs_observer_t *o = it->ctx; - ecs_run_action_t run = o->run; + /* Cleanup context */ + if (o->ctx_free) { + o->ctx_free(o->ctx); + } - if (run) { - if (flecs_observer_impl(o)->flags & EcsObserverBypassQuery) { - it->next = flecs_default_next_callback; - it->callback = flecs_multi_observer_invoke; - it->interrupted_by = 0; - it->run_ctx = o->run_ctx; - run(it); - return; - } + if (o->callback_ctx_free) { + o->callback_ctx_free(o->callback_ctx); } - flecs_multi_observer_invoke(it); + if (o->run_ctx_free) { + o->run_ctx_free(o->run_ctx); + } + + flecs_poly_fini(o, ecs_observer_t); + flecs_free_t(&world->allocator, ecs_observer_impl_t, o); } -static -void flecs_observer_yield_existing( +void flecs_observer_set_disable_bit( ecs_world_t *world, - ecs_observer_t *o, - bool yield_on_remove) + ecs_entity_t e, + ecs_flags32_t bit, + bool cond) { - ecs_run_action_t run = o->run; - if (!run) { - run = flecs_multi_observer_invoke_no_query; + const EcsPoly *poly = ecs_get_pair(world, e, EcsPoly, EcsObserver); + if (!poly || !poly->poly) { + return; } - ecs_defer_begin(world); - - /* If yield existing is enabled, invoke for each thing that matches - * the event, if the event is iterable. */ - int i, count = o->event_count; - for (i = 0; i < count; i ++) { - ecs_entity_t event = o->events[i]; - - /* We only yield for OnRemove events if the observer is deleted. */ - if (event == EcsOnRemove) { - if (!yield_on_remove) { - continue; - } - } else { - if (yield_on_remove) { - continue; + ecs_observer_t *o = poly->poly; + ecs_observer_impl_t *impl = flecs_observer_impl(o); + if (impl->flags & EcsObserverIsMulti) { + ecs_observer_t **children = ecs_vec_first(&impl->children); + int32_t i, children_count = ecs_vec_count(&impl->children); + if (children_count) { + for (i = 0; i < children_count; i ++) { + ECS_BIT_COND(flecs_observer_impl(children[i])->flags, bit, cond); } } + } else { + flecs_poly_assert(o, ecs_observer_t); + ECS_BIT_COND(impl->flags, bit, cond); + } +} - ecs_iter_t it = ecs_query_iter(world, o->query); - it.system = o->entity; - it.ctx = o; - it.callback = flecs_default_uni_observer_run_callback; - it.callback_ctx = o->callback_ctx; - it.run_ctx = o->run_ctx; - it.event = o->events[i]; - while (ecs_query_next(&it)) { - it.event_id = it.ids[0]; - it.event_cur = ++ world->event_id; +/** + * @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. + */ - ecs_iter_next_action_t next = it.next; - it.next = flecs_default_next_callback; - run(&it); - it.next = next; - it.interrupted_by = 0; - } +#include +#include + +void ecs_os_api_impl(ecs_os_api_t *api); + +static bool ecs_os_api_initialized = false; +static bool ecs_os_api_initializing = false; +static int ecs_os_api_init_count = 0; + +ecs_os_api_t ecs_os_api = { + .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, + .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +}; + +int64_t ecs_os_api_malloc_count = 0; +int64_t ecs_os_api_realloc_count = 0; +int64_t ecs_os_api_calloc_count = 0; +int64_t ecs_os_api_free_count = 0; + +void ecs_os_set_api( + ecs_os_api_t *os_api) +{ + if (!ecs_os_api_initialized) { + ecs_os_api = *os_api; + ecs_os_api_initialized = true; } +} - ecs_defer_end(world); +ecs_os_api_t ecs_os_get_api(void) { + return ecs_os_api; } -static -int flecs_uni_observer_init( - ecs_world_t *world, - ecs_observer_t *o, - const ecs_observer_desc_t *desc) +void ecs_os_init(void) { - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_term_t *term = &o->query->terms[0]; - impl->last_event_id = desc->last_event_id; - if (!impl->last_event_id) { - impl->last_event_id = &impl->last_event_id_storage; + if (!ecs_os_api_initialized) { + ecs_os_set_api_defaults(); } - impl->register_id = term->id; - term->field_index = flecs_ito(int8_t, desc->term_index_); - - if (ecs_id_is_tag(world, term->id)) { - /* If id is a tag, downgrade OnSet to OnAdd. */ - int32_t e, count = o->event_count; - bool has_on_add = false; - for (e = 0; e < count; e ++) { - if (o->events[e] == EcsOnAdd) { - has_on_add = true; - } + + if (!(ecs_os_api_init_count ++)) { + if (ecs_os_api.init_) { + ecs_os_api.init_(); } + } +} - for (e = 0; e < count; e ++) { - if (o->events[e] == EcsOnSet) { - if (has_on_add) { - /* Already registered */ - o->events[e] = 0; - } else { - o->events[e] = EcsOnAdd; - } - } +void ecs_os_fini(void) { + if (!--ecs_os_api_init_count) { + if (ecs_os_api.fini_) { + ecs_os_api.fini_(); } } - - flecs_uni_observer_register(world, o->observable, o); - - return 0; } -static -int flecs_observer_add_child( - ecs_world_t *world, - ecs_observer_t *o, - const ecs_observer_desc_t *child_desc) +/* 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__)) || defined(__COSMOCC__) +#define HAVE_EXECINFO 0 +#elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) +#define HAVE_EXECINFO 1 +#else +#define HAVE_EXECINFO 0 +#endif + +#if HAVE_EXECINFO +#include +#define ECS_BT_BUF_SIZE 100 + +void flecs_dump_backtrace( + void *stream) { - ecs_assert(child_desc->query.flags & EcsQueryNested, - ECS_INTERNAL_ERROR, NULL); + int nptrs; + void *buffer[ECS_BT_BUF_SIZE]; + char **strings; - ecs_observer_t *child_observer = flecs_observer_init( - world, 0, child_desc); - if (!child_observer) { - return -1; + nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + return; } - ecs_observer_impl_t *impl = flecs_observer_impl(o); - ecs_vec_append_t(&world->allocator, &impl->children, - ecs_observer_t*)[0] = child_observer; - child_observer->entity = o->entity; - return 0; + for (int j = 1; j < nptrs; j++) { + fprintf(stream, "%s\n", strings[j]); + } + + free(strings); +} +#else +void flecs_dump_backtrace( + void *stream) +{ + (void)stream; } +#endif +#undef HAVE_EXECINFO_H static -int flecs_multi_observer_init( - ecs_world_t *world, - ecs_observer_t *o, - const ecs_observer_desc_t *desc) +void flecs_log_msg( + int32_t level, + const char *file, + int32_t line, + const char *msg) { - ecs_observer_impl_t *impl = flecs_observer_impl(o); - - /* Create last event id for filtering out the same event that arrives from - * more than one term */ - impl->last_event_id = ecs_os_calloc_t(int32_t); - - /* Mark observer as multi observer */ - impl->flags |= EcsObserverIsMulti; - - /* Vector that stores a single-component observer for each query term */ - ecs_vec_init_t(&world->allocator, &impl->children, ecs_observer_t*, 2); + FILE *stream = ecs_os_api.log_out_; + if (!stream) { + stream = stdout; + } - /* Create a child observer for each term in the query */ - ecs_query_t *query = o->query; - ecs_observer_desc_t child_desc = *desc; - child_desc.last_event_id = impl->last_event_id; - child_desc.run = NULL; - child_desc.callback = flecs_multi_observer_builtin_run; - child_desc.ctx = o; - child_desc.ctx_free = NULL; - child_desc.query.expr = NULL; - child_desc.callback_ctx = NULL; - child_desc.callback_ctx_free = NULL; - child_desc.run_ctx = NULL; - child_desc.run_ctx_free = NULL; - child_desc.yield_existing = false; - child_desc.flags_ &= ~(EcsObserverYieldOnCreate|EcsObserverYieldOnDelete); - ecs_os_zeromem(&child_desc.entity); - ecs_os_zeromem(&child_desc.query.terms); - ecs_os_zeromem(&child_desc.query); - ecs_os_memcpy_n(child_desc.events, o->events, ecs_entity_t, o->event_count); + bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; + bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; + bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - child_desc.query.flags |= EcsQueryNested; + time_t now = 0; - int i, term_count = query->term_count; - bool optional_only = query->flags & EcsQueryMatchThis; - bool has_not = false; - for (i = 0; i < term_count; i ++) { - if (query->terms[i].oper != EcsOptional) { - if (ecs_term_match_this(&query->terms[i])) { - optional_only = false; - } + 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 ((query->terms[i].oper == EcsNot) && - (query->terms[i].inout != EcsInOutFilter)) - { - has_not = true; + if (delta) { + if (delta < 10) { + fputs(" ", stream); + } + if (delta < 100) { + fputs(" ", stream); + } + char time_buf[20]; + ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)delta); + fputs("+", stream); + fputs(time_buf, stream); + fputs(" ", stream); + } else { + fputs(" ", stream); } } - /* If an observer is only interested in table events, we only need to - * observe a single component, as each table event will be emitted for all - * components of the source table. */ - bool only_table_events = true; - for (i = 0; i < o->event_count; i ++) { - ecs_entity_t e = o->events[i]; - if (e != EcsOnTableCreate && e != EcsOnTableDelete) { - only_table_events = false; - break; + if (timestamp) { + if (!now) { + now = time(NULL); } + char time_buf[20]; + ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)now); + fputs(time_buf, stream); + fputs(" ", stream); } - if (query->flags & EcsQueryMatchPrefab) { - child_desc.query.flags |= EcsQueryMatchPrefab; - } - - if (query->flags & EcsQueryMatchDisabled) { - child_desc.query.flags |= EcsQueryMatchDisabled; - } - - bool self_term_handled = false; - for (i = 0; i < term_count; i ++) { - if (query->terms[i].inout == EcsInOutFilter) { - continue; + 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_term_t *term = &child_desc.query.terms[0]; - child_desc.term_index_ = query->terms[i].field_index; - *term = query->terms[i]; + if (use_colors) fputs(ECS_NORMAL, stream); + fputs(": ", stream); - int16_t oper = term->oper; - ecs_id_t id = term->id; + 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; - if (only_table_events) { - /* For table event observers, only observe a single $this|self - * term. Make sure to create observers for non-self terms, as those - * require event propagation. */ - if (ecs_term_match_this(term) && - (term->src.id & EcsTraverseFlags) == EcsSelf) - { - if (oper == EcsAnd) { - if (!self_term_handled) { - self_term_handled = true; - } else { - continue; - } - } + for (i = 0; i < indent_count; i ++) { + indent[i * 2] = '|'; + indent[i * 2 + 1] = ' '; } - } - /* AndFrom & OrFrom terms insert multiple observers */ - if (oper == EcsAndFrom || oper == EcsOrFrom) { - const ecs_type_t *type = ecs_get_type(world, id); - if (!type) { - continue; + if (ecs_os_api.log_indent_ != indent_count) { + indent[i * 2 - 2] = '+'; } - int32_t ti, ti_count = type->count; - ecs_id_t *ti_ids = type->array; - - /* Correct operator will be applied when an event occurs, and - * the observer is evaluated on the observer source */ - term->oper = EcsAnd; - for (ti = 0; ti < ti_count; ti ++) { - ecs_id_t ti_id = ti_ids[ti]; - ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (idr->flags & EcsIdOnInstantiateDontInherit) { - continue; - } - - term->first.name = NULL; - term->first.id = ti_ids[ti]; - term->id = ti_ids[ti]; + indent[i * 2] = '\0'; - if (flecs_observer_add_child(world, o, &child_desc)) { - goto error; - } - } - continue; + fputs(indent, stream); } + } - /* Single component observers never use OR */ - if (oper == EcsOr) { - term->oper = EcsAnd; - } + if (level < 0) { + if (file) { + const char *file_ptr = strrchr(file, '/'); + if (!file_ptr) { + file_ptr = strrchr(file, '\\'); + } - /* If observer only contains optional terms, match everything */ - if (optional_only) { - term->id = EcsAny; - term->first.id = EcsAny; - term->src.id = EcsThis | EcsIsVariable | EcsSelf; - term->second.id = 0; - } else if (term->oper == EcsOptional) { - if (only_table_events || desc->events[0] == EcsMonitor) { - /* For table events & monitors optional terms aren't necessary */ - continue; + if (file_ptr) { + file = file_ptr + 1; } - } - if (flecs_observer_add_child(world, o, &child_desc)) { - goto error; + fputs(file, stream); + fputs(": ", stream); } - if (optional_only) { - break; + if (line) { + fprintf(stream, "%d: ", line); } } - /* If observer has Not terms, we need to create a query that replaces Not - * with Optional which we can use to populate the observer data for the - * table that the entity moved away from (or to, if it's an OnRemove - * observer). */ - if (has_not) { - ecs_query_desc_t not_desc = desc->query; - not_desc.expr = NULL; + fputs(msg, stream); - ecs_os_memcpy_n(not_desc.terms, o->query->terms, - ecs_term_t, term_count); /* cast suppresses warning */ - - for (i = 0; i < term_count; i ++) { - if (not_desc.terms[i].oper == EcsNot) { - not_desc.terms[i].oper = EcsOptional; - } - } + fputs("\n", stream); - flecs_observer_impl(o)->not_query = - ecs_query_init(world, ¬_desc); + if (level == -4) { + flecs_dump_backtrace(stream); } - - return 0; -error: - return -1; } -static -void flecs_observer_poly_fini(void *ptr) { - flecs_observer_fini(ptr); +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); + } } -ecs_observer_t* flecs_observer_init( - ecs_world_t *world, - ecs_entity_t entity, - const ecs_observer_desc_t *desc) +void ecs_os_trace( + const char *file, + int32_t line, + const char *msg) { - ecs_assert(flecs_poly_is(world, ecs_world_t), - ECS_INTERNAL_ERROR, NULL); - ecs_check(desc->callback != NULL || desc->run != NULL, - ECS_INVALID_OPERATION, - "cannot create observer: must at least specify callback or run"); - - ecs_observer_impl_t *impl = flecs_calloc_t( - &world->allocator, ecs_observer_impl_t); - ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); - impl->id = ++ world->observable.last_observer_id; - - flecs_poly_init(impl, ecs_observer_t); - ecs_observer_t *o = &impl->pub; - impl->dtor = flecs_observer_poly_fini; - - /* Make writeable copy of query desc so that we can set name. This will - * make debugging easier, as any error messages related to creating the - * query will have the name of the observer. */ - ecs_query_desc_t query_desc = desc->query; - query_desc.entity = 0; - query_desc.cache_kind = EcsQueryCacheNone; - - /* Create query */ - ecs_query_t *query = o->query = ecs_query_init( - world, &query_desc); - if (query == NULL) { - flecs_observer_fini(o); - return 0; + if (ecs_os_api.log_) { + ecs_os_api.log_(0, file, line, msg); } +} - flecs_poly_assert(query, ecs_query_t); - - ecs_check(o->query->term_count > 0, ECS_INVALID_PARAMETER, - "observer must have at least one term"); - - ecs_observable_t *observable = desc->observable; - if (!observable) { - observable = ecs_get_observable(world); +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); } +} - o->run = desc->run; - o->callback = desc->callback; - o->ctx = desc->ctx; - o->callback_ctx = desc->callback_ctx; - o->run_ctx = desc->run_ctx; - o->ctx_free = desc->ctx_free; - o->callback_ctx_free = desc->callback_ctx_free; - o->run_ctx_free = desc->run_ctx_free; - o->observable = observable; - o->entity = entity; - impl->term_index = desc->term_index_; - impl->flags = desc->flags_; +void ecs_os_err( + const char *file, + int32_t line, + const char *msg) +{ + if (ecs_os_api.log_) { + ecs_os_api.log_(-3, file, line, msg); + } +} - ecs_check(!(desc->yield_existing && - (desc->flags_ & (EcsObserverYieldOnCreate|EcsObserverYieldOnDelete))), - ECS_INVALID_PARAMETER, - "cannot set yield_existing and YieldOn* flags at the same time"); +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); + } +} - /* 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 +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; - if (event == EcsMonitor) { - ecs_check(i == 0, ECS_INVALID_PARAMETER, - "monitor observers can only have a single Monitor event"); + assert(sec < UINT32_MAX); + assert((now - sec * 1000000000) < UINT32_MAX); - o->events[0] = EcsOnAdd; - o->events[1] = EcsOnRemove; - o->event_count ++; - impl->flags |= EcsObserverIsMonitor; - if (desc->yield_existing) { - impl->flags |= EcsObserverYieldOnCreate; - impl->flags |= EcsObserverYieldOnDelete; - } - } else { - o->events[i] = event; - if (desc->yield_existing) { - if (event == EcsOnRemove) { - impl->flags |= EcsObserverYieldOnDelete; - } else { - impl->flags |= EcsObserverYieldOnCreate; - } - } - } + time->sec = (uint32_t)sec; + time->nanosec = (uint32_t)(now - sec * 1000000000); +} - o->event_count ++; - } +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); +} - /* Observer must have at least one event */ - ecs_check(o->event_count != 0, ECS_INVALID_PARAMETER, - "observer must have at least one event"); +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); +} - bool multi = false; +static +void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { + ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - if (query->term_count == 1 && !desc->last_event_id) { - ecs_term_t *term = &query->terms[0]; - /* If the query has a single term but it is a *From operator, we - * need to create a multi observer */ - multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); - - /* An observer with only optional terms is a special case that is - * only handled by multi observers */ - multi |= term->oper == EcsOptional; + if (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); +} - bool is_monitor = impl->flags & EcsObserverIsMonitor; - if (query->term_count == 1 && !is_monitor && !multi) { - if (flecs_uni_observer_init(world, o, desc)) { - goto error; - } - } else { - if (flecs_multi_observer_init(world, o, desc)) { - goto error; - } +static +void ecs_os_api_free(void *ptr) { + if (ptr) { + ecs_os_linc(&ecs_os_api_free_count); } + free(ptr); +} - if (impl->flags & EcsObserverYieldOnCreate) { - flecs_observer_yield_existing(world, o, false); +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 o; -error: - return NULL; +void ecs_os_strset(char **str, const char *value) { + char *old = str[0]; + str[0] = ecs_os_strdup(value); + ecs_os_free(old); } -ecs_entity_t ecs_observer_init( - ecs_world_t *world, - const ecs_observer_desc_t *desc) -{ - ecs_entity_t entity = 0; - flecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, - "ecs_observer_desc_t was not initialized to zero"); - ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, - "cannot create observer while world is being deleted"); - - entity = desc->entity; - if (!entity) { - entity = ecs_entity(world, {0}); +void ecs_os_perf_trace_push_( + const char *file, + size_t line, + const char *name) +{ + if (ecs_os_api.perf_trace_push_) { + ecs_os_api.perf_trace_push_(file, line, name); } +} - EcsPoly *poly = flecs_poly_bind(world, entity, ecs_observer_t); - if (!poly->poly) { - ecs_observer_t *o = flecs_observer_init(world, entity, desc); - ecs_assert(o->entity == entity, ECS_INTERNAL_ERROR, NULL); - poly->poly = o; - - if (ecs_get_name(world, entity)) { - ecs_trace("#[green]observer#[reset] %s created", - ecs_get_name(world, entity)); - } - } else { - flecs_poly_assert(poly->poly, ecs_observer_t); - ecs_observer_t *o = (ecs_observer_t*)poly->poly; - - if (o->ctx_free) { - if (o->ctx && o->ctx != desc->ctx) { - o->ctx_free(o->ctx); - } - } - - if (o->callback_ctx_free) { - if (o->callback_ctx && o->callback_ctx != desc->callback_ctx) { - o->callback_ctx_free(o->callback_ctx); - o->callback_ctx_free = NULL; - o->callback_ctx = NULL; - } - } +void ecs_os_perf_trace_pop_( + const char *file, + size_t line, + const char *name) +{ + if (ecs_os_api.perf_trace_pop_) { + ecs_os_api.perf_trace_pop_(file, line, name); + } +} - if (o->run_ctx_free) { - if (o->run_ctx && o->run_ctx != desc->run_ctx) { - o->run_ctx_free(o->run_ctx); - o->run_ctx_free = NULL; - o->run_ctx = NULL; - } +/* 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; } + } - if (desc->run) { - o->run = desc->run; - if (!desc->callback) { - o->callback = NULL; - } - } + return base; +} - if (desc->callback) { - o->callback = desc->callback; - if (!desc->run) { - o->run = NULL; - } - } +static +char* ecs_os_api_module_to_dl(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - if (desc->ctx) { - o->ctx = desc->ctx; - } + /* Best guess, use module name with underscores + OS library extension */ + char *file_base = module_file_base(module, '_'); - if (desc->callback_ctx) { - o->callback_ctx = desc->callback_ctx; - } +# 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 - if (desc->run_ctx) { - o->run_ctx = desc->run_ctx; - } + ecs_os_free(file_base); - if (desc->ctx_free) { - o->ctx_free = desc->ctx_free; - } + return ecs_strbuf_get(&lib); +} - if (desc->callback_ctx_free) { - o->callback_ctx_free = desc->callback_ctx_free; - } +static +char* ecs_os_api_module_to_etc(const char *module) { + ecs_strbuf_t lib = ECS_STRBUF_INIT; - if (desc->run_ctx_free) { - o->run_ctx_free = desc->run_ctx_free; - } - } + /* Best guess, use module name with dashes + /etc */ + char *file_base = module_file_base(module, '-'); - flecs_poly_modified(world, entity, ecs_observer_t); + ecs_strbuf_appendstr(&lib, file_base); + ecs_strbuf_appendlit(&lib, "/etc"); - return entity; -error: - if (entity) { - ecs_delete(world, entity); - } - return 0; -} + ecs_os_free(file_base); -const ecs_observer_t* ecs_observer_get( - const ecs_world_t *world, - ecs_entity_t observer) -{ - return flecs_poly_get(world, observer, ecs_observer_t); + return ecs_strbuf_get(&lib); } -void flecs_observer_fini( - ecs_observer_t *o) +void ecs_os_set_api_defaults(void) { - ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_world_t *world = o->query->world; - ecs_observer_impl_t *impl = flecs_observer_impl(o); + /* Don't overwrite if already initialized */ + if (ecs_os_api_initialized != 0) { + return; + } - if (impl->flags & EcsObserverYieldOnDelete) { - flecs_observer_yield_existing(world, o, true); + if (ecs_os_api_initializing != 0) { + return; } - if (impl->flags & EcsObserverIsMulti) { - ecs_observer_t **children = ecs_vec_first(&impl->children); - int32_t i, children_count = ecs_vec_count(&impl->children); + 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; - for (i = 0; i < children_count; i ++) { - flecs_observer_fini(children[i]); - } + /* Strings */ + ecs_os_api.strdup_ = ecs_os_api_strdup; - ecs_os_free(impl->last_event_id); - } else { - if (o->query->term_count) { - flecs_unregister_observer(world, o->observable, o); - } else { - /* Observer creation failed while creating query */ - } - } + /* Time */ + ecs_os_api.get_time_ = ecs_os_gettime; - ecs_vec_fini_t(&world->allocator, &impl->children, ecs_observer_t*); + /* Logging */ + ecs_os_api.log_ = flecs_log_msg; - /* Cleanup queries */ - ecs_query_fini(o->query); - if (impl->not_query) { - ecs_query_fini(impl->not_query); + /* Modules */ + if (!ecs_os_api.module_to_dl_) { + ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; } - /* Cleanup context */ - if (o->ctx_free) { - o->ctx_free(o->ctx); + if (!ecs_os_api.module_to_etc_) { + ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } - if (o->callback_ctx_free) { - o->callback_ctx_free(o->callback_ctx); - } + ecs_os_api.abort_ = abort; - if (o->run_ctx_free) { - o->run_ctx_free(o->run_ctx); - } +# 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 - flecs_poly_fini(o, ecs_observer_t); - flecs_free_t(&world->allocator, ecs_observer_impl_t, o); + ecs_os_api_initializing = false; } -void flecs_observer_set_disable_bit( - ecs_world_t *world, - ecs_entity_t e, - ecs_flags32_t bit, - bool cond) -{ - const EcsPoly *poly = ecs_get_pair(world, e, EcsPoly, EcsObserver); - if (!poly || !poly->poly) { - return; - } +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); +} - ecs_observer_t *o = poly->poly; - ecs_observer_impl_t *impl = flecs_observer_impl(o); - if (impl->flags & EcsObserverIsMulti) { - ecs_observer_t **children = ecs_vec_first(&impl->children); - int32_t i, children_count = ecs_vec_count(&impl->children); - if (children_count) { - for (i = 0; i < children_count; i ++) { - ECS_BIT_COND(flecs_observer_impl(children[i])->flags, bit, cond); - } - } - } else { - flecs_poly_assert(o, ecs_observer_t); - ECS_BIT_COND(impl->flags, bit, cond); - } +bool ecs_os_has_threading(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.thread_new_ != NULL) && + (ecs_os_api.thread_join_ != NULL) && + (ecs_os_api.thread_self_ != NULL); +} + +bool ecs_os_has_task_support(void) { + return + (ecs_os_api.mutex_new_ != NULL) && + (ecs_os_api.mutex_free_ != NULL) && + (ecs_os_api.mutex_lock_ != NULL) && + (ecs_os_api.mutex_unlock_ != NULL) && + (ecs_os_api.cond_new_ != NULL) && + (ecs_os_api.cond_free_ != NULL) && + (ecs_os_api.cond_wait_ != NULL) && + (ecs_os_api.cond_signal_ != NULL) && + (ecs_os_api.cond_broadcast_ != NULL) && + (ecs_os_api.task_new_ != NULL) && + (ecs_os_api.task_join_ != NULL); +} + +bool ecs_os_has_time(void) { + return + (ecs_os_api.get_time_ != NULL) && + (ecs_os_api.sleep_ != NULL) && + (ecs_os_api.now_ != NULL); +} + +bool ecs_os_has_logging(void) { + return (ecs_os_api.log_ != NULL); +} + +bool ecs_os_has_dl(void) { + return + (ecs_os_api.dlopen_ != NULL) && + (ecs_os_api.dlproc_ != NULL) && + (ecs_os_api.dlclose_ != NULL); +} + +bool ecs_os_has_modules(void) { + return + (ecs_os_api.module_to_dl_ != NULL) && + (ecs_os_api.module_to_etc_ != NULL); +} + +#if defined(ECS_TARGET_WINDOWS) +static char error_str[255]; +#endif + +const char* ecs_os_strerror(int err) { +# if defined(ECS_TARGET_WINDOWS) + strerror_s(error_str, 255, err); + return error_str; +# else + return strerror(err); +# endif } /** - * @file os_api.c - * @brief Operating system abstraction API. + * @file poly.c + * @brief Functions for managing poly objects. * - * 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. + * 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. */ -#include -#include -void ecs_os_api_impl(ecs_os_api_t *api); +static const char* mixin_kind_str[] = { + [EcsMixinWorld] = "world", + [EcsMixinEntity] = "entity", + [EcsMixinObservable] = "observable", + [EcsMixinDtor] = "dtor", + [EcsMixinMax] = "max (should never be requested by application)" +}; -static bool ecs_os_api_initialized = false; -static bool ecs_os_api_initializing = false; -static int ecs_os_api_init_count = 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), + } +}; -ecs_os_api_t ecs_os_api = { - .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, - .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ +ecs_mixins_t ecs_stage_t_mixins = { + .type_name = "ecs_stage_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_stage_t, world) + } }; -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; +ecs_mixins_t ecs_observer_t_mixins = { + .type_name = "ecs_observer_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_observer_t, world), + [EcsMixinEntity] = offsetof(ecs_observer_t, entity), + [EcsMixinDtor] = offsetof(ecs_observer_impl_t, dtor) + } +}; -void ecs_os_set_api( - ecs_os_api_t *os_api) +static +void* assert_mixin( + const ecs_poly_t *poly, + ecs_mixin_kind_t kind) { - if (!ecs_os_api_initialized) { - ecs_os_api = *os_api; - ecs_os_api_initialized = true; - } -} + 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, + "invalid/freed pointer to flecs object detected"); -ecs_os_api_t ecs_os_get_api(void) { - return ecs_os_api; + const ecs_mixins_t *mixins = hdr->mixins; + ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t offset = mixins->elems[kind]; + ecs_assert(offset != 0, ECS_INVALID_PARAMETER, + "mixin %s not available for type %s", + mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); + (void)mixin_kind_str; + + /* Object has mixin, return its address */ + return ECS_OFFSET(hdr, offset); } -void ecs_os_init(void) +void* flecs_poly_init_( + ecs_poly_t *poly, + int32_t type, + ecs_size_t size, + ecs_mixins_t *mixins) { - 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_(); - } - } -} + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); -void ecs_os_fini(void) { - if (!--ecs_os_api_init_count) { - if (ecs_os_api.fini_) { - ecs_os_api.fini_(); - } - } -} + ecs_header_t *hdr = poly; + ecs_os_memset(poly, 0, size); -/* 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 + hdr->magic = ECS_OBJECT_MAGIC; + hdr->type = type; + hdr->refcount = 1; + hdr->mixins = mixins; -#if HAVE_EXECINFO -#include -#define ECS_BT_BUF_SIZE 100 + return poly; +} -void flecs_dump_backtrace( - void *stream) +void flecs_poly_fini_( + ecs_poly_t *poly, + int32_t type) { - int nptrs; - void *buffer[ECS_BT_BUF_SIZE]; - char **strings; + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + (void)type; - nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); + ecs_header_t *hdr = poly; - strings = backtrace_symbols(buffer, nptrs); - if (strings == NULL) { - return; - } + /* Don't deinit poly that wasn't initialized */ + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, + "incorrect function called to free flecs object"); + hdr->magic = 0; +} - for (int j = 1; j < nptrs; j++) { - fprintf(stream, "%s\n", strings[j]); +int32_t flecs_poly_claim_( + ecs_poly_t *poly) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + if (ecs_os_has_threading()) { + return ecs_os_ainc(&hdr->refcount); + } else { + return ++hdr->refcount; } - - free(strings); -} -#else -void flecs_dump_backtrace( - void *stream) -{ - (void)stream; } -#endif -#undef HAVE_EXECINFO_H -static -void flecs_log_msg( - int32_t level, - const char *file, - int32_t line, - const char *msg) +int32_t flecs_poly_release_( + ecs_poly_t *poly) { - FILE *stream = ecs_os_api.log_out_; - if (!stream) { - stream = stdout; + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + if (ecs_os_has_threading()) { + return ecs_os_adec(&hdr->refcount); + } else { + return --hdr->refcount; } +} - bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; - bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; - bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; - - time_t now = 0; - - if (deltatime) { - now = time(NULL); - int64_t delta = 0; - if (ecs_os_api.log_last_timestamp_) { - delta = now - ecs_os_api.log_last_timestamp_; - } - ecs_os_api.log_last_timestamp_ = (int64_t)now; - - if (delta) { - if (delta < 10) { - fputs(" ", stream); - } - if (delta < 100) { - fputs(" ", stream); - } - char time_buf[20]; - ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)delta); - fputs("+", stream); - fputs(time_buf, stream); - fputs(" ", stream); - } else { - fputs(" ", stream); - } - } - - if (timestamp) { - if (!now) { - now = time(NULL); - } - char time_buf[20]; - ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)now); - fputs(time_buf, stream); - fputs(" ", stream); - } +int32_t flecs_poly_refcount( + ecs_poly_t *poly) +{ + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + return hdr->refcount; +} - 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); +EcsPoly* flecs_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); } - if (use_colors) fputs(ECS_NORMAL, stream); - fputs(": ", stream); - - if (level >= 0) { - if (ecs_os_api.log_indent_) { - char indent[32]; - int i, indent_count = ecs_os_api.log_indent_; - if (indent_count > 15) indent_count = 15; - - for (i = 0; i < indent_count; i ++) { - indent[i * 2] = '|'; - indent[i * 2 + 1] = ' '; - } - - if (ecs_os_api.log_indent_ != indent_count) { - indent[i * 2 - 2] = '+'; - } - - indent[i * 2] = '\0'; - - fputs(indent, stream); - } + /* Never defer creation of a poly object */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + ecs_defer_suspend(world); } - if (level < 0) { - if (file) { - const char *file_ptr = strrchr(file, '/'); - if (!file_ptr) { - file_ptr = strrchr(file, '\\'); - } - - if (file_ptr) { - file = file_ptr + 1; - } - - fputs(file, stream); - fputs(": ", stream); - } + /* If 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_ensure_pair(world, entity, EcsPoly, tag); - if (line) { - fprintf(stream, "%d: ", line); - } + if (deferred) { + ecs_defer_resume(world); } - fputs(msg, stream); - - fputs("\n", stream); - - if (level == -4) { - flecs_dump_backtrace(stream); - } + return result; } -void ecs_os_dbg( - const char *file, - int32_t line, - const char *msg) +void flecs_poly_modified_( + ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) { - if (ecs_os_api.log_) { - ecs_os_api.log_(1, file, line, msg); - } + ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); } -void ecs_os_trace( - const char *file, - int32_t line, - const char *msg) +const EcsPoly* flecs_poly_bind_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) { - if (ecs_os_api.log_) { - ecs_os_api.log_(0, file, line, msg); - } + return ecs_get_pair(world, entity, EcsPoly, tag); } -void ecs_os_warn( - const char *file, - int32_t line, - const char *msg) +ecs_poly_t* flecs_poly_get_( + const ecs_world_t *world, + ecs_entity_t entity, + ecs_entity_t tag) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-2, file, line, msg); + const EcsPoly *p = flecs_poly_bind_get_(world, entity, tag); + if (p) { + return p->poly; } + return NULL; } -void ecs_os_err( - const char *file, - int32_t line, - const char *msg) +bool flecs_poly_is_( + const ecs_poly_t *poly, + int32_t type) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-3, file, line, msg); - } + ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); + + const ecs_header_t *hdr = poly; + ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, + "invalid/freed pointer to flecs object detected"); + return hdr->type == type; } -void ecs_os_fatal( - const char *file, - int32_t line, - const char *msg) +ecs_observable_t* ecs_get_observable( + const ecs_poly_t *poly) { - if (ecs_os_api.log_) { - ecs_os_api.log_(-4, file, line, msg); - } + return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); } -static -void ecs_os_gettime(ecs_time_t *time) { - ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); - - uint64_t now = ecs_os_now(); - uint64_t sec = now / 1000000000; - - assert(sec < UINT32_MAX); - assert((now - sec * 1000000000) < UINT32_MAX); - - time->sec = (uint32_t)sec; - time->nanosec = (uint32_t)(now - sec * 1000000000); +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); } -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); +ecs_entity_t ecs_get_entity( + const ecs_poly_t *poly) +{ + return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); } -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); +flecs_poly_dtor_t* ecs_get_dtor( + const ecs_poly_t *poly) +{ + return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); } -static -void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { - ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); - - if (ptr) { - ecs_os_linc(&ecs_os_api_realloc_count); - } else { - /* If not actually reallocing, treat as malloc */ - ecs_os_linc(&ecs_os_api_malloc_count); - } - - return realloc(ptr, (size_t)size); -} +/** + * @file search.c + * @brief Search functions to find (component) ids in table types. + * + * Search functions are used to find the column index of a (component) id in a + * table. Additionally, search functions implement the logic for finding a + * component id by following a relationship upwards. + */ -static -void ecs_os_api_free(void *ptr) { - if (ptr) { - ecs_os_linc(&ecs_os_api_free_count); - } - free(ptr); -} static -char* ecs_os_api_strdup(const char *str) { - if (str) { - int len = ecs_os_strlen(str); - char *result = ecs_os_malloc(len + 1); - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - ecs_os_strcpy(result, str); - return result; - } else { - return NULL; +int32_t flecs_type_search( + const ecs_table_t *table, + ecs_component_record_t *cdr, + ecs_id_t *ids, + ecs_id_t *id_out, + ecs_table_record_t **tr_out) +{ + ecs_table_record_t *tr = ecs_table_cache_get(&cdr->cache, table); + if (tr) { + int32_t r = tr->index; + if (tr_out) tr_out[0] = tr; + if (id_out) { + id_out[0] = ids[r]; + } + return r; } -} -void ecs_os_strset(char **str, const char *value) { - char *old = str[0]; - str[0] = ecs_os_strdup(value); - ecs_os_free(old); + return -1; } -void ecs_os_perf_trace_push_( - const char *file, - size_t line, - const char *name) +static +int32_t flecs_type_offset_search( + int32_t offset, + ecs_id_t id, + ecs_id_t *ids, + int32_t count, + ecs_id_t *id_out) { - if (ecs_os_api.perf_trace_push_) { - ecs_os_api.perf_trace_push_(file, line, name); + ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + while (offset < count) { + ecs_id_t type_id = ids[offset ++]; + if (ecs_id_match(type_id, id)) { + if (id_out) { + id_out[0] = type_id; + } + return offset - 1; + } } + + return -1; } -void ecs_os_perf_trace_pop_( - const char *file, - size_t line, - const char *name) +bool flecs_type_can_inherit_id( + const ecs_world_t *world, + const ecs_table_t *table, + const ecs_component_record_t *cdr, + ecs_id_t id) { - if (ecs_os_api.perf_trace_pop_) { - ecs_os_api.perf_trace_pop_(file, line, name); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + if (cdr->flags & EcsIdOnInstantiateDontInherit) { + return false; } -} -/* 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; + if (cdr->flags & EcsIdExclusive) { + if (ECS_HAS_ID_FLAG(id, PAIR)) { + ecs_entity_t er = ECS_PAIR_FIRST(id); + ecs_component_record_t *cdr_wc = flecs_components_get( + world, ecs_pair(er, EcsWildcard)); + if (cdr_wc && flecs_component_get_table(cdr_wc, table)) { + return false; + } } } - return base; + return true; } static -char* ecs_os_api_module_to_dl(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; +int32_t flecs_type_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_component_record_t *cdr, + ecs_id_t rel, + ecs_component_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; - /* Best guess, use module name with underscores + OS library extension */ - char *file_base = module_file_base(module, '_'); + 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, cdr, ids, id_out, tr_out); + if (r != -1) { + return r; + } + } + } -# if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) - ecs_strbuf_appendlit(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendlit(&lib, ".so"); -# elif defined(ECS_TARGET_DARWIN) - ecs_strbuf_appendlit(&lib, "lib"); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendlit(&lib, ".dylib"); -# elif defined(ECS_TARGET_WINDOWS) - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendlit(&lib, ".dll"); -# endif + ecs_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; - ecs_os_free(file_base); + if (!flecs_type_can_inherit_id(world, table, cdr, id)) { + return -1; + } + } - return ecs_strbuf_get(&lib); -} + if (!idr_r) { + idr_r = flecs_components_get(world, rel); + if (!idr_r) { + return -1; + } + } -static -char* ecs_os_api_module_to_etc(const char *module) { - ecs_strbuf_t lib = ECS_STRBUF_INIT; + 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, 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); - /* Best guess, use module name with dashes + /etc */ - char *file_base = module_file_base(module, '-'); + ecs_record_t *rec = flecs_entities_get_any(world, obj); + ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_strbuf_appendstr(&lib, file_base); - ecs_strbuf_appendlit(&lib, "/etc"); + ecs_table_t *tgt_table = rec->table; + ecs_assert(tgt_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tgt_table != table, ECS_CYCLE_DETECTED, NULL); + + r = flecs_type_search_relation(world, tgt_table, 0, id, cdr, + 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; + } - ecs_os_free(file_base); + if (!is_a) { + r = flecs_type_search_relation(world, tgt_table, 0, id, cdr, + 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; + } + } - return ecs_strbuf_get(&lib); + r_column = flecs_type_offset_search( + r_column + 1, rel, ids, count, &id_r); + } + } + + return -1; } -void ecs_os_set_api_defaults(void) +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_flags64_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out, + ecs_component_record_t *cdr) { - /* Don't overwrite if already initialized */ - if (ecs_os_api_initialized != 0) { - return; + flecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + flags = flags ? flags : (EcsSelf|EcsUp); + + if (!cdr) { + cdr = flecs_components_get(world, id); + if (!cdr) { + return -1; + } } - if (ecs_os_api_initializing != 0) { - return; + 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, cdr, table->type.array, id_out, tr_out); + } } - 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; + int32_t result = flecs_type_search_relation(world, table, offset, id, cdr, + ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, + id_out, tr_out); - /* Strings */ - ecs_os_api.strdup_ = ecs_os_api_strdup; + return result; +} - /* Time */ - ecs_os_api.get_time_ = ecs_os_gettime; +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_flags64_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - /* Logging */ - ecs_os_api.log_ = flecs_log_msg; + flags = flags ? flags : (EcsSelf|EcsUp); - /* Modules */ - if (!ecs_os_api.module_to_dl_) { - ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; + if (subject_out) subject_out[0] = 0; + if (!(flags & EcsUp)) { + return ecs_search_offset(world, table, offset, id, id_out); } - if (!ecs_os_api.module_to_etc_) { - ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + return -1; } - ecs_os_api.abort_ = abort; + int32_t result = flecs_type_search_relation(world, table, offset, id, cdr, + ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, + id_out, tr_out); -# ifdef FLECS_OS_API_IMPL - /* Initialize defaults to OS API IMPL addon, but still allow for overriding - * by the application */ - ecs_set_os_api_impl(); - ecs_os_api_initialized = false; -# endif - - ecs_os_api_initializing = false; + return result; } -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); -} +int32_t flecs_search_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t *id_out, + ecs_component_record_t *cdr) +{ + flecs_poly_assert(world, ecs_world_t); + (void)world; -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); + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return flecs_type_search(table, cdr, ids, id_out, 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); -} +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); -bool ecs_os_has_time(void) { - return - (ecs_os_api.get_time_ != NULL) && - (ecs_os_api.sleep_ != NULL) && - (ecs_os_api.now_ != NULL); -} + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + return -1; + } -bool ecs_os_has_logging(void) { - return (ecs_os_api.log_ != NULL); + ecs_type_t type = table->type; + ecs_id_t *ids = type.array; + return flecs_type_search(table, cdr, ids, id_out, 0); } -bool ecs_os_has_dl(void) { - return - (ecs_os_api.dlopen_ != NULL) && - (ecs_os_api.dlproc_ != NULL) && - (ecs_os_api.dlclose_ != NULL); +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) { + flecs_poly_assert(world, ecs_world_t); + return ecs_search(world, table, id, id_out); + } + + 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); } -bool ecs_os_has_modules(void) { - return - (ecs_os_api.module_to_dl_ != NULL) && - (ecs_os_api.module_to_etc_ != NULL); +static +int32_t flecs_relation_depth_walk( + const ecs_world_t *world, + const ecs_component_record_t *cdr, + const ecs_table_t *first, + const ecs_table_t *table) +{ + int32_t result = 0; + + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr) { + return 0; + } + + int32_t i = tr->index, end = i + tr->count; + for (; i != end; i ++) { + ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); + ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); + + ecs_table_t *ot = ecs_get_table(world, o); + if (!ot) { + continue; + } + + ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); + int32_t cur = flecs_relation_depth_walk(world, cdr, first, ot); + if (cur > result) { + result = cur; + } + } + + return result + 1; } -#if defined(ECS_TARGET_WINDOWS) -static char error_str[255]; -#endif +int32_t flecs_relation_depth( + const ecs_world_t *world, + ecs_entity_t r, + const ecs_table_t *table) +{ + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair(r, EcsWildcard)); + if (!cdr) { + return 0; + } -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 + return flecs_relation_depth_walk(world, cdr, table, table); } /** - * @file poly.c - * @brief Functions for managing poly objects. + * @file stage.c + * @brief Staging implementation. * - * 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. + * 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. * - * 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. + * Stages contain additional state to enable other API functionality without + * having to mutate the world, such as setting the current scope, and allocators + * that are local to a stage. + * + * In a multi threaded application, each thread has its own stage which allows + * threads to insert mutations without having to lock administration. */ -static const char* mixin_kind_str[] = { - [EcsMixinWorld] = "world", - [EcsMixinEntity] = "entity", - [EcsMixinObservable] = "observable", - [EcsMixinDtor] = "dtor", - [EcsMixinMax] = "max (should never be requested by application)" -}; +static +ecs_cmd_t* flecs_cmd_new( + ecs_stage_t *stage) +{ + ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, + ecs_cmd_t); + cmd->is._1.value = NULL; + cmd->id = 0; + cmd->next_for_entity = 0; + cmd->entry = NULL; + cmd->system = stage->system; + return cmd; +} -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), - } -}; +static +ecs_cmd_t* flecs_cmd_new_batched( + ecs_stage_t *stage, + ecs_entity_t e) +{ + ecs_vec_t *cmds = &stage->cmd->queue; + ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( + &stage->cmd->entries, ecs_cmd_entry_t, e); -ecs_mixins_t ecs_stage_t_mixins = { - .type_name = "ecs_stage_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_stage_t, world) + int32_t cur = ecs_vec_count(cmds); + ecs_cmd_t *cmd = flecs_cmd_new(stage); + bool is_new = false; + if (entry) { + if (entry->first == -1) { + /* Existing but invalidated entry */ + entry->first = cur; + cmd->entry = entry; + } else { + int32_t last = entry->last; + ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); + if (arr[last].entity == e) { + 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 { + /* Entity with different version was in the same queue. Discard + * the old entry and create a new one. */ + is_new = true; + } + } + } else { + is_new = true; } -}; -ecs_mixins_t ecs_observer_t_mixins = { - .type_name = "ecs_observer_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_observer_t, world), - [EcsMixinEntity] = offsetof(ecs_observer_t, entity), - [EcsMixinDtor] = offsetof(ecs_observer_impl_t, dtor) + if (is_new) { + cmd->entry = entry = flecs_sparse_ensure_fast_t( + &stage->cmd->entries, ecs_cmd_entry_t, e); + entry->first = cur; } -}; - -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, - "invalid/freed pointer to flecs object detected"); - - const ecs_mixins_t *mixins = hdr->mixins; - ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_size_t offset = mixins->elems[kind]; - ecs_assert(offset != 0, ECS_INVALID_PARAMETER, - "mixin %s not available for type %s", - mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); - (void)mixin_kind_str; + entry->last = cur; - /* Object has mixin, return its address */ - return ECS_OFFSET(hdr, offset); + return cmd; } -void* flecs_poly_init_( - ecs_poly_t *poly, - int32_t type, - ecs_size_t size, - ecs_mixins_t *mixins) +static +void flecs_stage_merge( + ecs_world_t *world) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_header_t *hdr = poly; - ecs_os_memset(poly, 0, size); + bool is_stage = flecs_poly_is(world, ecs_stage_t); + ecs_stage_t *stage = flecs_stage_from_world(&world); - hdr->magic = ECS_OBJECT_MAGIC; - hdr->type = type; - hdr->refcount = 1; - hdr->mixins = mixins; + bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), + EcsWorldMeasureFrameTime); - return poly; -} + ecs_time_t t_start = {0}; + if (measure_frame_time) { + ecs_os_get_time(&t_start); + } -void flecs_poly_fini_( - ecs_poly_t *poly, - int32_t type) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - (void)type; + ecs_dbg_3("#[magenta]merge"); + ecs_log_push_3(); - ecs_header_t *hdr = poly; + 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. */ + 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); + flecs_poly_assert(s, ecs_stage_t); + flecs_defer_end(world, s); + } + } - /* Don't deinit poly that wasn't initialized */ - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, - "incorrect function called to free flecs object"); - hdr->magic = 0; -} + flecs_eval_component_monitors(world); -int32_t flecs_poly_claim_( - ecs_poly_t *poly) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - if (ecs_os_has_threading()) { - return ecs_os_ainc(&hdr->refcount); - } else { - return ++hdr->refcount; + if (measure_frame_time) { + world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } -} -int32_t flecs_poly_release_( - ecs_poly_t *poly) -{ - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - if (ecs_os_has_threading()) { - return ecs_os_adec(&hdr->refcount); - } else { - return --hdr->refcount; + world->info.merge_count_total ++; + + /* If stage is unmanaged, deferring is always enabled */ + if (stage->id == -1) { + flecs_defer_begin(world, stage); } + + ecs_log_pop_3(); } -int32_t flecs_poly_refcount( - ecs_poly_t *poly) +bool flecs_defer_begin( + ecs_world_t *world, + ecs_stage_t *stage) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - return hdr->refcount; + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); + (void)world; + if (stage->defer < 0) return false; + return (++ stage->defer) == 1; } -EcsPoly* flecs_poly_bind_( - ecs_world_t *world, - ecs_entity_t entity, - ecs_entity_t tag) +bool flecs_defer_cmd( + ecs_stage_t *stage) { - /* Add tag to the entity for easy querying. This will make it possible to - * query for `Query` instead of `(Poly, Query) */ - if (!ecs_has_id(world, entity, tag)) { - ecs_add_id(world, entity, tag); - } - - /* Never defer creation of a poly object */ - bool deferred = false; - if (ecs_is_deferred(world)) { - deferred = true; - ecs_defer_suspend(world); + if (stage->defer) { + return (stage->defer > 0); } - /* 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_ensure_pair(world, entity, EcsPoly, tag); + stage->defer ++; + return false; +} - if (deferred) { - ecs_defer_resume(world); +bool flecs_defer_new( + ecs_stage_t *stage, + ecs_entity_t entity) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + if (cmd) { + cmd->kind = EcsCmdNew; + cmd->entity = entity; + } + return true; } - - return result; + return false; } -void flecs_poly_modified_( - ecs_world_t *world, +bool flecs_defer_modified( + ecs_stage_t *stage, ecs_entity_t entity, - ecs_entity_t tag) + ecs_id_t id) { - ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + if (cmd) { + cmd->kind = EcsCmdModified; + cmd->id = id; + cmd->entity = entity; + } + return true; + } + return false; } -const EcsPoly* flecs_poly_bind_get_( - const ecs_world_t *world, +bool flecs_defer_clone( + ecs_stage_t *stage, ecs_entity_t entity, - ecs_entity_t tag) -{ - return ecs_get_pair(world, entity, EcsPoly, tag); + ecs_entity_t src, + bool clone_value) +{ + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdClone; + cmd->id = src; + cmd->entity = entity; + cmd->is._1.clone_value = clone_value; + return true; + } + return false; } -ecs_poly_t* flecs_poly_get_( - const ecs_world_t *world, +bool flecs_defer_path( + ecs_stage_t *stage, + ecs_entity_t parent, ecs_entity_t entity, - ecs_entity_t tag) + const char *name) { - const EcsPoly *p = flecs_poly_bind_get_(world, entity, tag); - if (p) { - return p->poly; + if (stage->defer > 0) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdPath; + cmd->entity = entity; + cmd->id = parent; + cmd->is._1.value = ecs_os_strdup(name); + return true; } - return NULL; + return false; } -bool flecs_poly_is_( - const ecs_poly_t *poly, - int32_t type) +bool flecs_defer_delete( + ecs_stage_t *stage, + ecs_entity_t entity) { - ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_header_t *hdr = poly; - ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, - "invalid/freed pointer to flecs object detected"); - return hdr->type == type; + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdDelete; + cmd->entity = entity; + return true; + } + return false; } -ecs_observable_t* ecs_get_observable( - const ecs_poly_t *poly) +bool flecs_defer_clear( + ecs_stage_t *stage, + ecs_entity_t entity) { - return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdClear; + cmd->entity = entity; + return true; + } + return false; } -const ecs_world_t* ecs_get_world( - const ecs_poly_t *poly) +bool flecs_defer_on_delete_action( + ecs_stage_t *stage, + ecs_id_t id, + ecs_entity_t action) { - if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { - return poly; + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdOnDeleteAction; + cmd->id = id; + cmd->entity = action; + return true; } - return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); + return false; } -ecs_entity_t ecs_get_entity( - const ecs_poly_t *poly) +bool flecs_defer_enable( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id, + bool enable) { - return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); -} - -flecs_poly_dtor_t* ecs_get_dtor( - const ecs_poly_t *poly) -{ - return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); -} - -/** - * @file search.c - * @brief Search functions to find (component) ids in table types. - * - * Search functions are used to find the column index of a (component) id in a - * table. Additionally, search functions implement the logic for finding a - * component id by following a relationship upwards. - */ - - -static -int32_t flecs_type_search( - const ecs_table_t *table, - ecs_id_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) { - id_out[0] = ids[r]; - } - return r; + if (flecs_defer_cmd(stage)) { + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; + cmd->entity = entity; + cmd->id = id; + return true; } - - return -1; + return false; } -static -int32_t flecs_type_offset_search( - int32_t offset, - ecs_id_t id, - ecs_id_t *ids, +bool flecs_defer_bulk_new( + ecs_world_t *world, + ecs_stage_t *stage, int32_t count, - ecs_id_t *id_out) + ecs_id_t id, + const ecs_entity_t **ids_out) { - ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + if (flecs_defer_cmd(stage)) { + ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); - while (offset < count) { - ecs_id_t type_id = ids[offset ++]; - if (ecs_id_match(type_id, id)) { - if (id_out) { - id_out[0] = type_id; - } - return offset - 1; + /* Use ecs_new_id as this is thread safe */ + int i; + for (i = 0; i < count; i ++) { + ids[i] = ecs_new(world); } - } - return -1; + *ids_out = ids; + + /* Store data in op */ + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdBulkNew; + cmd->id = id; + cmd->is._n.entities = ids; + cmd->is._n.count = count; + cmd->entity = 0; + return true; + } + return false; } -bool flecs_type_can_inherit_id( - const ecs_world_t *world, - const ecs_table_t *table, - const ecs_id_record_t *idr, +bool flecs_defer_add( + ecs_stage_t *stage, + ecs_entity_t entity, ecs_id_t id) { - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - if (idr->flags & EcsIdOnInstantiateDontInherit) { - return false; + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdAdd; + cmd->id = id; + cmd->entity = entity; + return true; } + return false; +} - 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; - } - } +bool flecs_defer_remove( + ecs_stage_t *stage, + ecs_entity_t entity, + ecs_id_t id) +{ + if (flecs_defer_cmd(stage)) { + ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); + cmd->kind = EcsCmdRemove; + cmd->id = id; + cmd->entity = entity; + return true; } - return true; + return false; } -static -int32_t flecs_type_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, +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_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_size_t size, + void *value, + bool *is_new) { - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - int32_t count = type.count; + ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - if (self) { - if (offset) { - int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); - if (r != -1) { - return r; - } + /* Find type info for id */ + const ecs_type_info_t *ti = NULL; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + /* If cdr doesn't exist yet, create it but only if the + * application is not multithreaded. */ + if (world->flags & EcsWorldMultiThreaded) { + ti = ecs_get_type_info(world, id); } else { - int32_t r = flecs_type_search(table, idr, ids, id_out, tr_out); - if (r != -1) { - return r; - } + /* When not in multi threaded mode, it's safe to find or + * create the component record. */ + cdr = flecs_components_ensure(world, id); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get type_info from component record. We could have called + * ecs_get_type_info directly, but since this function can be + * expensive for pairs, creating the component record ensures we can + * find the type_info quickly for subsequent operations. */ + ti = cdr->type_info; } + } else { + ti = cdr->type_info; } - 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 the id isn't associated with a type, we can't set anything */ + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "provided component is not a type"); - if (!flecs_type_can_inherit_id(world, table, idr, id)) { - return -1; - } - } + /* Make sure the size of the value equals the type size */ + ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, + "mismatching size specified for component in ensure/emplace/set"); + size = ti->size; - if (!idr_r) { - idr_r = flecs_id_record_get(world, rel); - if (!idr_r) { - return -1; + /* 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 (cdr) { + /* Entity can only have existing component if component record exists */ + ecs_record_t *r = flecs_entities_get(world, entity); + table = r->table; + if (r && table) { + const ecs_table_record_t *tr = flecs_component_get_table( + cdr, table); + if (tr) { + if (tr->column != -1) { + /* Entity has the component */ + existing = table->data.columns[tr->column].data; + existing = ECS_ELEM(existing, size, + ECS_RECORD_TO_ROW(r->row)); + } else { + ecs_assert(cdr->flags & EcsIdIsSparse, + ECS_NOT_A_COMPONENT, NULL); + existing = flecs_sparse_get_any(cdr->sparse, 0, entity); + } } } + } - 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, 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); + /* Get existing value from storage */ + void *cmd_value = existing; + bool emplace = cmd_kind == EcsCmdEmplace; - ecs_record_t *rec = flecs_entities_get_any(world, obj); - ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); + /* If the component does not yet exist, create a temporary value. This is + * necessary so we can store a component value in the deferred command, + * without adding the component to the entity which is not allowed in + * deferred mode. */ + if (!existing) { + ecs_stack_t *stack = &stage->cmd->stack; + cmd_value = flecs_stack_alloc(stack, size, ti->alignment); - 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 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); } - - 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; - } + } 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 */ - r_column = flecs_type_offset_search( - r_column + 1, rel, ids, count, &id_r); + /* Check if entity inherits component */ + void *base = NULL; + if (table && (table->flags & EcsTableHasIsA)) { + base = flecs_get_base_component(world, table, id, cdr, 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); } } - return -1; -} - -int32_t flecs_search_relation_w_idr( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - ecs_flags64_t flags, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out, - ecs_id_record_t *idr) -{ - if (!table) return -1; - - flecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - flags = flags ? flags : (EcsSelf|EcsUp); - - if (!idr) { - idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; + if (!cmd) { + /* If cmd is NULL, entity was already deleted. Check if we need to + * insert a command into the queue. */ + if (!ti->hooks.dtor) { + /* If temporary memory does not need to be destructed, it'll get + * freed when the stack allocator is reset. This prevents us + * from having to insert a command when the entity was + * already deleted. */ + return cmd_value; } + cmd = flecs_cmd_new(stage); } - if (subject_out) subject_out[0] = 0; - if (!(flags & EcsUp)) { - if (offset) { - return ecs_search_offset(world, table, offset, id, id_out); + if (!existing) { + /* If component didn't exist yet, insert command that will create it */ + cmd->kind = cmd_kind; + cmd->id = id; + cmd->cdr = cdr; + cmd->entity = entity; + cmd->is._1.size = size; + cmd->is._1.value = cmd_value; + + if (is_new) { + *is_new = true; + } + } else { + /* If component already exists, still insert an Add command to ensure + * that any preceding remove commands won't remove the component. If the + * operation is a set, also insert a Modified command. */ + if (cmd_kind == EcsCmdSet) { + cmd->kind = EcsCmdAddModified; } else { - return flecs_type_search( - table, idr, table->type.array, id_out, tr_out); + cmd->kind = EcsCmdAdd; } - } + cmd->id = id; + cmd->entity = entity; - 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); + if (is_new) { + *is_new = false; + } + } - return result; + return cmd_value; +error: + return NULL; } -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_flags64_t flags, - ecs_entity_t *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out) +void flecs_enqueue( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_event_desc_t *desc) { - if (!table) return -1; - - flecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - flags = flags ? flags : (EcsSelf|EcsUp); + ecs_cmd_t *cmd = flecs_cmd_new(stage); + cmd->kind = EcsCmdEvent; + cmd->entity = desc->entity; - if (subject_out) subject_out[0] = 0; - if (!(flags & EcsUp)) { - return ecs_search_offset(world, table, offset, id, id_out); - } + ecs_stack_t *stack = &stage->cmd->stack; + ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); + ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; + if (desc->ids && desc->ids->count != 0) { + ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); + int32_t id_count = desc->ids->count; + type_cmd->count = id_count; + type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); + ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); + desc_cmd->ids = type_cmd; + } else { + desc_cmd->ids = NULL; } - 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); + cmd->is._1.value = desc_cmd; + cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); - return result; -} + if (desc->param || desc->const_param) { + ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, + "cannot set param and const_param at the same time"); -int32_t flecs_search_w_idr( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t *id_out, - ecs_id_record_t *idr) -{ - if (!table) return -1; + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); + ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, + "can only enqueue events with data for events that are components"); - flecs_poly_assert(world, ecs_world_t); - (void)world; + void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); + ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); + if (desc->param) { + if (ti->hooks.move_ctor) { + ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); + } else { + ecs_os_memcpy(param_cmd, desc->param, ti->size); + } + } else { + if (ti->hooks.copy_ctor) { + ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); + } else { + ecs_os_memcpy(param_cmd, desc->const_param, ti->size); + } + } - ecs_type_t type = table->type; - ecs_id_t *ids = type.array; - return flecs_type_search(table, idr, ids, id_out, 0); + desc_cmd->param = param_cmd; + desc_cmd->const_param = NULL; + } } -int32_t ecs_search( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out) +void flecs_stage_merge_post_frame( + ecs_world_t *world, + ecs_stage_t *stage) { - if (!table) return -1; - - flecs_poly_assert(world, ecs_world_t); - ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; + /* 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_type_t type = table->type; - ecs_id_t *ids = type.array; - return flecs_type_search(table, idr, ids, id_out, 0); + ecs_vec_clear(&stage->post_frame_actions); } -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) +void flecs_commands_init( + ecs_stage_t *stage, + ecs_commands_t *cmd) { - if (!offset) { - flecs_poly_assert(world, ecs_world_t); - return ecs_search(world, table, id, id_out); - } + flecs_stack_init(&cmd->stack); + ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); + flecs_sparse_init_t(&cmd->entries, &stage->allocator, + &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); +} + +void flecs_commands_fini( + ecs_stage_t *stage, + ecs_commands_t *cmd) +{ + /* Make sure stage has no unmerged data */ + ecs_assert(ecs_vec_count(&cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); - if (!table) return -1; + flecs_stack_fini(&cmd->stack); + ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); + flecs_sparse_fini(&cmd->entries); +} - 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); +ecs_entity_t flecs_stage_set_system( + ecs_stage_t *stage, + ecs_entity_t system) +{ + ecs_entity_t old = stage->system; + stage->system = system; + return old; } 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) +ecs_stage_t* flecs_stage_new( + ecs_world_t *world) { - int32_t result = 0; + flecs_poly_assert(world, ecs_world_t); + ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); + flecs_poly_init(stage, ecs_stage_t); - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return 0; - } + stage->world = world; + stage->thread_ctx = world; - 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); + 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); + flecs_ballocator_init_t(&stage->allocators.query_impl, ecs_query_impl_t); + flecs_ballocator_init_t(&stage->allocators.query_cache, ecs_query_cache_t); - 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; - } + ecs_allocator_t *a = &stage->allocator; + ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); + + int32_t i; + for (i = 0; i < 2; i ++) { + flecs_commands_init(stage, &stage->cmd_stack[i]); } - - return result + 1; + + stage->cmd = &stage->cmd_stack[0]; + return stage; } -int32_t flecs_relation_depth( - const ecs_world_t *world, - ecs_entity_t r, - const ecs_table_t *table) +static +void flecs_stage_free( + ecs_world_t *world, + ecs_stage_t *stage) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); - if (!idr) { - return 0; + (void)world; + flecs_poly_assert(world, ecs_world_t); + flecs_poly_assert(stage, ecs_stage_t); + + flecs_poly_fini(stage, ecs_stage_t); + + ecs_allocator_t *a = &stage->allocator; + + ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); + ecs_vec_fini(NULL, &stage->variables, 0); + ecs_vec_fini(NULL, &stage->operations, 0); + + int32_t i; + for (i = 0; i < 2; i ++) { + flecs_commands_fini(stage, &stage->cmd_stack[i]); } - return flecs_relation_depth_walk(world, idr, table, table); -} +#ifdef FLECS_SCRIPT + if (stage->runtime) { + ecs_script_runtime_free(stage->runtime); + } +#endif -/** - * @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. - */ + flecs_stack_fini(&stage->allocators.iter_stack); + flecs_stack_fini(&stage->allocators.deser_stack); + flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); + flecs_ballocator_fini(&stage->allocators.query_impl); + flecs_ballocator_fini(&stage->allocators.query_cache); + flecs_allocator_fini(&stage->allocator); + ecs_os_free(stage); +} -static -ecs_cmd_t* flecs_cmd_new( - ecs_stage_t *stage) +ecs_allocator_t* flecs_stage_get_allocator( + ecs_world_t *world) { - ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, - ecs_cmd_t); - cmd->is._1.value = NULL; - cmd->id = 0; - cmd->next_for_entity = 0; - cmd->entry = NULL; - cmd->system = stage->system; - return cmd; + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + return &stage->allocator; } -static -ecs_cmd_t* flecs_cmd_new_batched( - ecs_stage_t *stage, - ecs_entity_t e) +ecs_stack_t* flecs_stage_get_stack_allocator( + ecs_world_t *world) { - ecs_vec_t *cmds = &stage->cmd->queue; - ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( - &stage->cmd->entries, ecs_cmd_entry_t, e); + ecs_stage_t *stage = flecs_stage_from_world( + ECS_CONST_CAST(ecs_world_t**, &world)); + return &stage->allocators.iter_stack; +} - int32_t cur = ecs_vec_count(cmds); - ecs_cmd_t *cmd = flecs_cmd_new(stage); - if (entry) { - if (entry->first == -1) { - /* Existing but invalidated entry */ - entry->first = cur; - cmd->entry = entry; - } else { - int32_t last = entry->last; - ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); - ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); - ecs_cmd_t *last_op = &arr[last]; - last_op->next_for_entity = cur; - if (last == entry->first) { - /* Flip sign bit so flush logic can tell which command - * is the first for an entity */ - last_op->next_for_entity *= -1; - } - } - } else { - cmd->entry = entry = flecs_sparse_ensure_fast_t( - &stage->cmd->entries, ecs_cmd_entry_t, e); - entry->first = cur; - } +ecs_world_t* ecs_stage_new( + ecs_world_t *world) +{ + ecs_stage_t *stage = flecs_stage_new(world); + stage->id = -1; - entry->last = cur; + flecs_defer_begin(world, stage); - return cmd; + return (ecs_world_t*)stage; } -static -void flecs_stage_merge( +void ecs_stage_free( ecs_world_t *world) { - bool is_stage = flecs_poly_is(world, ecs_stage_t); - ecs_stage_t *stage = flecs_stage_from_world(&world); + flecs_poly_assert(world, ecs_stage_t); + ecs_stage_t *stage = (ecs_stage_t*)world; + ecs_check(stage->id == -1, ECS_INVALID_PARAMETER, + "cannot free stage that's owned by world"); + flecs_stage_free(stage->world, stage); +error: + return; +} - bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), - EcsWorldMeasureFrameTime); +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stage_count) +{ + flecs_poly_assert(world, ecs_world_t); - ecs_time_t t_start = {0}; - if (measure_frame_time) { - ecs_os_get_time(&t_start); - } + /* World must have at least one default stage */ + ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), + ECS_INTERNAL_ERROR, NULL); - ecs_dbg_3("#[magenta]merge"); - ecs_log_push_3(); + const ecs_entity_t *lookup_path = NULL; + if (world->stage_count >= 1) { + lookup_path = world->stages[0]->lookup_path; + } - 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. */ - 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); - flecs_poly_assert(s, ecs_stage_t); - flecs_defer_end(world, s); + int32_t i, count = world->stage_count; + if (stage_count < count) { + for (i = stage_count; 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_stage_t *stage = world->stages[i]; + flecs_poly_assert(stage, ecs_stage_t); + ecs_check(stage->thread == 0, ECS_INVALID_OPERATION, + "cannot mix using set_stage_count and set_threads"); + flecs_stage_free(world, stage); } } - flecs_eval_component_monitors(world); + if (stage_count) { + world->stages = ecs_os_realloc_n( + world->stages, ecs_stage_t*, stage_count); - if (measure_frame_time) { - world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); - } + for (i = count; i < stage_count; i ++) { + ecs_stage_t *stage = world->stages[i] = flecs_stage_new(world); + stage->id = i; - world->info.merge_count_total ++; + /* 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 */ + ecs_os_free(world->stages); + world->stages = NULL; + } - /* If stage is unmanaged, deferring is always enabled */ - if (stage->id == -1) { - flecs_defer_begin(world, stage); + /* 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]->lookup_path = lookup_path; } - - ecs_log_pop_3(); + + world->stage_count = stage_count; +error: + return; } -bool flecs_defer_begin( - ecs_world_t *world, - ecs_stage_t *stage) +int32_t ecs_get_stage_count( + const ecs_world_t *world) { - flecs_poly_assert(world, ecs_world_t); - flecs_poly_assert(stage, ecs_stage_t); - (void)world; - if (stage->defer < 0) return false; - return (++ stage->defer) == 1; + world = ecs_get_world(world); + return world->stage_count; } -bool flecs_defer_cmd( - ecs_stage_t *stage) +int32_t ecs_stage_get_id( + const ecs_world_t *world) { - if (stage->defer) { - return (stage->defer > 0); - } + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - stage->defer ++; - return false; -} + if (flecs_poly_is(world, ecs_stage_t)) { + ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); -bool flecs_defer_modified( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - if (cmd) { - cmd->kind = EcsCmdModified; - cmd->id = id; - cmd->entity = entity; - } - return true; + /* Index 0 is reserved for main stage */ + return stage->id; + } else if (flecs_poly_is(world, ecs_world_t)) { + return 0; + } else { + ecs_throw(ECS_INTERNAL_ERROR, NULL); } - return false; +error: + return 0; } -bool flecs_defer_clone( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_entity_t src, - bool clone_value) -{ - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = EcsCmdClone; - cmd->id = src; - cmd->entity = entity; - cmd->is._1.clone_value = clone_value; - return true; - } - return false; +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id) +{ + flecs_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 flecs_defer_path( - ecs_stage_t *stage, - ecs_entity_t parent, - ecs_entity_t entity, - const char *name) +bool ecs_readonly_begin( + ecs_world_t *world, + bool multi_threaded) { - if (stage->defer > 0) { - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = EcsCmdPath; - cmd->entity = entity; - cmd->id = parent; - cmd->is._1.value = ecs_os_strdup(name); - return true; + flecs_poly_assert(world, ecs_world_t); + + ecs_dbg_3("#[bold]readonly"); + ecs_log_push_3(); + + int32_t i, count = ecs_get_stage_count(world); + for (i = 0; i < count; i ++) { + ecs_stage_t *stage = world->stages[i]; + stage->lookup_path = world->stages[0]->lookup_path; + ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, + "deferred mode cannot be enabled when entering readonly mode"); + flecs_defer_begin(world, stage); } - return false; + + bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); + + /* From this point on, the world is "locked" for mutations, and it is only + * allowed to enqueue commands from stages */ + ECS_BIT_SET(world->flags, EcsWorldReadonly); + ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, multi_threaded); + + return is_readonly; } -bool flecs_defer_delete( - ecs_stage_t *stage, - ecs_entity_t entity) +void ecs_readonly_end( + ecs_world_t *world) { - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = EcsCmdDelete; - cmd->entity = entity; - return true; - } - return false; + flecs_poly_assert(world, ecs_world_t); + ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, + "world is not in readonly mode"); + + /* After this it is safe again to mutate the world directly */ + ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); + ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); + + ecs_log_pop_3(); + + flecs_stage_merge(world); +error: + return; } -bool flecs_defer_clear( - ecs_stage_t *stage, - ecs_entity_t entity) +void ecs_merge( + ecs_world_t *world) { - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - cmd->kind = EcsCmdClear; - cmd->entity = entity; - return true; - } - return false; + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(flecs_poly_is(world, ecs_world_t) || + flecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); + flecs_stage_merge(world); +error: + return; } -bool flecs_defer_on_delete_action( - ecs_stage_t *stage, - ecs_id_t id, - ecs_entity_t action) +bool ecs_stage_is_readonly( + const ecs_world_t *stage) { - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = EcsCmdOnDeleteAction; - cmd->id = id; - cmd->entity = action; - return true; + const ecs_world_t *world = ecs_get_world(stage); + + if (flecs_poly_is(stage, ecs_stage_t)) { + if (((const ecs_stage_t*)stage)->id == -1) { + /* Stage is not owned by world, so never readonly */ + return false; + } + } + + if (world->flags & EcsWorldReadonly) { + if (flecs_poly_is(stage, ecs_world_t)) { + return true; + } + } else { + if (flecs_poly_is(stage, ecs_stage_t)) { + return true; + } } + return false; } -bool flecs_defer_enable( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id, - bool enable) +bool ecs_is_deferred( + const ecs_world_t *world) { - if (flecs_defer_cmd(stage)) { - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; - cmd->entity = entity; - cmd->id = id; - return true; - } + 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; } -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)); +/** + * @file value.c + * @brief Utility functions to work with non-trivial pointers of user types. + */ - /* Use ecs_new_id as this is thread safe */ - int i; - for (i = 0; i < count; i ++) { - ids[i] = ecs_new(world); - } - *ids_out = ids; +int ecs_value_init_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - /* Store data in op */ - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = EcsCmdBulkNew; - cmd->id = id; - cmd->is._n.entities = ids; - cmd->is._n.count = count; - cmd->entity = 0; - return true; + ecs_xtor_t ctor; + if ((ctor = ti->hooks.ctor)) { + ctor(ptr, 1, ti); + } else { + ecs_os_memset(ptr, 0, ti->size); } - return false; -} -bool flecs_defer_add( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) -{ - if (flecs_defer_cmd(stage)) { - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - cmd->kind = EcsCmdAdd; - cmd->id = id; - cmd->entity = entity; - return true; - } - return false; + return 0; +error: + return -1; } -bool flecs_defer_remove( - ecs_stage_t *stage, - ecs_entity_t entity, - ecs_id_t id) +int ecs_value_init( + const ecs_world_t *world, + ecs_entity_t type, + void *ptr) { - if (flecs_defer_cmd(stage)) { - ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - cmd->kind = EcsCmdRemove; - cmd->id = id; - cmd->entity = entity; - return true; - } - return false; + flecs_poly_assert(world, ecs_world_t); + const ecs_type_info_t *ti = ecs_get_type_info(world, type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); + return ecs_value_init_w_type_info(world, ti, ptr); +error: + return -1; } -void* flecs_defer_set( +void* ecs_value_new_w_type_info( 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 *is_new) + const ecs_type_info_t *ti) { - ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); - - /* Find type info for id */ - const ecs_type_info_t *ti = NULL; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - /* If idr doesn't exist yet, create it but only if the - * application is not multithreaded. */ - if (world->flags & EcsWorldMultiThreaded) { - ti = ecs_get_type_info(world, id); - } else { - /* When not in multi threaded mode, it's safe to find or - * create the id record. */ - idr = flecs_id_record_ensure(world, id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Get type_info from id record. We could have called - * ecs_get_type_info directly, but since this function can be - * expensive for pairs, creating the id record ensures we can - * find the type_info quickly for subsequent operations. */ - ti = idr->type_info; - } - } else { - ti = idr->type_info; - } - - /* If the id isn't associated with a type, we can't set anything */ - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, - "provided component is not a type"); - - /* Make sure the size of the value equals the type size */ - ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, - "mismatching size specified for component in ensure/emplace/set"); - size = ti->size; + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - /* 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) { - if (tr->column != -1) { - /* Entity has the component */ - existing = table->data.columns[tr->column].data; - existing = ECS_ELEM(existing, size, - ECS_RECORD_TO_ROW(r->row)); - } else { - ecs_assert(idr->flags & EcsIdIsSparse, - ECS_NOT_A_COMPONENT, NULL); - existing = flecs_sparse_get_any(idr->sparse, 0, entity); - } - } - } + void *result = flecs_alloc_w_dbg_info( + &world->allocator, ti->size, ti->name); + if (ecs_value_init_w_type_info(world, ti, result) != 0) { + flecs_free(&world->allocator, ti->size, result); + goto error; } - /* Get existing value from storage */ - void *cmd_value = existing; - bool emplace = cmd_kind == EcsCmdEmplace; - - /* If the component does not yet exist, create a temporary value. This is - * necessary so we can store a component value in the deferred command, - * without adding the component to the entity which is not allowed in - * deferred mode. */ - if (!existing) { - ecs_stack_t *stack = &stage->cmd->stack; - cmd_value = flecs_stack_alloc(stack, size, ti->alignment); + return result; +error: + return NULL; +} - /* 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 */ +void* ecs_value_new( + ecs_world_t *world, + ecs_entity_t type) +{ + flecs_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"); - /* Check if entity inherits component */ - void *base = NULL; - if (table && (table->flags & EcsTableHasIsA)) { - base = flecs_get_base_component(world, table, id, idr, 0); - } + return ecs_value_new_w_type_info(world, ti); +error: + return NULL; +} - 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); - } - } +int ecs_value_fini_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void *ptr) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - if (!cmd) { - /* If cmd is NULL, entity was already deleted. Check if we need to - * insert a command into the queue. */ - if (!ti->hooks.dtor) { - /* If temporary memory does not need to be destructed, it'll get - * freed when the stack allocator is reset. This prevents us - * from having to insert a command when the entity was - * already deleted. */ - return cmd_value; - } - cmd = flecs_cmd_new(stage); + ecs_xtor_t dtor; + if ((dtor = ti->hooks.dtor)) { + dtor(ptr, 1, ti); } - 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; - - if (is_new) { - *is_new = true; - } - } else { - /* If component already exists, still insert an Add command to ensure - * that any preceding remove commands won't remove the component. If the - * operation is a set, also insert a Modified command. */ - if (cmd_kind == EcsCmdSet) { - cmd->kind = EcsCmdAddModified; - } else { - cmd->kind = EcsCmdAdd; - } - cmd->id = id; - cmd->entity = entity; - - if (is_new) { - *is_new = false; - } - } + return 0; +error: + return -1; +} - return cmd_value; +int ecs_value_fini( + const ecs_world_t *world, + ecs_entity_t type, + void* ptr) +{ + flecs_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 NULL; + return -1; } -void flecs_enqueue( +int ecs_value_free( ecs_world_t *world, - ecs_stage_t *stage, - ecs_event_desc_t *desc) + ecs_entity_t type, + void* ptr) { - ecs_cmd_t *cmd = flecs_cmd_new(stage); - cmd->kind = EcsCmdEvent; - cmd->entity = desc->entity; - - ecs_stack_t *stack = &stage->cmd->stack; - ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); - ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); - - if (desc->ids && desc->ids->count != 0) { - ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); - int32_t id_count = desc->ids->count; - type_cmd->count = id_count; - type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); - ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); - desc_cmd->ids = type_cmd; - } else { - desc_cmd->ids = NULL; + flecs_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; } - cmd->is._1.value = desc_cmd; - cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); - - if (desc->param || desc->const_param) { - ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, - "cannot set param and const_param at the same time"); - - const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); - ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, - "can only enqueue events with data for events that are components"); - - void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); - ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); - if (desc->param) { - if (ti->hooks.move_ctor) { - ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); - } else { - ecs_os_memcpy(param_cmd, desc->param, ti->size); - } - } else { - if (ti->hooks.copy_ctor) { - ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); - } else { - ecs_os_memcpy(param_cmd, desc->const_param, ti->size); - } - } + flecs_free(&world->allocator, ti->size, ptr); - desc_cmd->param = param_cmd; - desc_cmd->const_param = NULL; - } + return 0; +error: + return -1; } -void flecs_stage_merge_post_frame( - ecs_world_t *world, - ecs_stage_t *stage) +int ecs_value_copy_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + const void *src) { - /* 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); + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; + + ecs_copy_t copy; + if ((copy = ti->hooks.copy)) { + copy(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); } - ecs_vec_clear(&stage->post_frame_actions); + return 0; +error: + return -1; } -void flecs_commands_init( - ecs_stage_t *stage, - ecs_commands_t *cmd) +int ecs_value_copy( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + const void *src) { - flecs_stack_init(&cmd->stack); - ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); - flecs_sparse_init_t(&cmd->entries, &stage->allocator, - &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); + flecs_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; } -void flecs_commands_fini( - ecs_stage_t *stage, - ecs_commands_t *cmd) +int ecs_value_move_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src) { - /* Make sure stage has no unmerged data */ - ecs_assert(ecs_vec_count(&cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); + flecs_poly_assert(world, ecs_world_t); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - flecs_stack_fini(&cmd->stack); - ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); - flecs_sparse_fini(&cmd->entries); + ecs_move_t move; + if ((move = ti->hooks.move)) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); + } + + return 0; +error: + return -1; } -ecs_entity_t flecs_stage_set_system( - ecs_stage_t *stage, - ecs_entity_t system) +int ecs_value_move( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src) { - ecs_entity_t old = stage->system; - stage->system = system; - return old; + flecs_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; } -static -ecs_stage_t* flecs_stage_new( - ecs_world_t *world) +int ecs_value_move_ctor_w_type_info( + const ecs_world_t *world, + const ecs_type_info_t *ti, + void* dst, + void *src) { flecs_poly_assert(world, ecs_world_t); - ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); - flecs_poly_init(stage, ecs_stage_t); - - stage->world = world; - stage->thread_ctx = world; - - 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); - flecs_ballocator_init_t(&stage->allocators.query_impl, ecs_query_impl_t); - flecs_ballocator_init_t(&stage->allocators.query_cache, ecs_query_cache_t); - - ecs_allocator_t *a = &stage->allocator; - ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); + (void)world; - int32_t i; - for (i = 0; i < 2; i ++) { - flecs_commands_init(stage, &stage->cmd_stack[i]); + ecs_move_t move; + if ((move = ti->hooks.move_ctor)) { + move(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, ti->size); } - stage->cmd = &stage->cmd_stack[0]; - return stage; + return 0; +error: + return -1; } -static -void flecs_stage_free( - ecs_world_t *world, - ecs_stage_t *stage) +int ecs_value_move_ctor( + const ecs_world_t *world, + ecs_entity_t type, + void* dst, + void *src) { - (void)world; flecs_poly_assert(world, ecs_world_t); - flecs_poly_assert(stage, ecs_stage_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; +} - flecs_poly_fini(stage, ecs_stage_t); +/** + * @file world.c + * @brief World-level API. + */ - ecs_allocator_t *a = &stage->allocator; - - ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); - ecs_vec_fini(NULL, &stage->variables, 0); - ecs_vec_fini(NULL, &stage->operations, 0); - int32_t i; - for (i = 0; i < 2; i ++) { - flecs_commands_fini(stage, &stage->cmd_stack[i]); - } +/* Id flags */ +const ecs_id_t ECS_PAIR = (1ull << 63); +const ecs_id_t ECS_AUTO_OVERRIDE = (1ull << 62); +const ecs_id_t ECS_TOGGLE = (1ull << 61); -#ifdef FLECS_SCRIPT - if (stage->runtime) { - ecs_script_runtime_free(stage->runtime); - } -#endif +/** 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(EcsPoly) = 3; - flecs_stack_fini(&stage->allocators.iter_stack); - flecs_stack_fini(&stage->allocators.deser_stack); - flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); - flecs_ballocator_fini(&stage->allocators.query_impl); - flecs_ballocator_fini(&stage->allocators.query_cache); - flecs_allocator_fini(&stage->allocator); +/* Poly target components */ +const ecs_entity_t EcsQuery = 5; +const ecs_entity_t EcsObserver = 6; +const ecs_entity_t EcsSystem = 7; - ecs_os_free(stage); -} +/* 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 EcsNotQueryable = FLECS_HI_COMPONENT_ID + 8; -ecs_allocator_t* flecs_stage_get_allocator( - ecs_world_t *world) -{ - ecs_stage_t *stage = flecs_stage_from_world( - ECS_CONST_CAST(ecs_world_t**, &world)); - return &stage->allocator; -} +const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 9; +const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 10; -ecs_stack_t* flecs_stage_get_stack_allocator( - ecs_world_t *world) -{ - ecs_stage_t *stage = flecs_stage_from_world( - ECS_CONST_CAST(ecs_world_t**, &world)); - return &stage->allocators.iter_stack; -} +/* Marker entities for query encoding */ +const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 11; +const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 12; +const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 13; +const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 14; -ecs_world_t* ecs_stage_new( - ecs_world_t *world) -{ - ecs_stage_t *stage = flecs_stage_new(world); - stage->id = -1; +/* Traits */ +const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 15; +const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 16; +const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 17; +const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 18; +const ecs_entity_t EcsInheritable = FLECS_HI_COMPONENT_ID + 19; + +const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 20; +const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 21; +const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 22; +const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 23; +const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 24; +const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 25; +const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 26; +const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 27; +const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 28; +const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 29; +const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 30; +const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 31; +const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 32; +const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 33; - flecs_defer_begin(world, stage); - return (ecs_world_t*)stage; -} +/* Builtin relationships */ +const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 34; +const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 35; +const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 36; -void ecs_stage_free( - ecs_world_t *world) -{ - flecs_poly_assert(world, ecs_stage_t); - ecs_stage_t *stage = (ecs_stage_t*)world; - ecs_check(stage->id == -1, ECS_INVALID_PARAMETER, - "cannot free stage that's owned by world"); - flecs_stage_free(stage->world, stage); -error: - return; -} +/* Identifier tags */ +const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 37; +const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 38; +const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 39; -void ecs_set_stage_count( - ecs_world_t *world, - int32_t stage_count) -{ - flecs_poly_assert(world, ecs_world_t); +/* Events */ +const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 40; +const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 41; +const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 42; +const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; +const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; +const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; +const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; - /* World must have at least one default stage */ - ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), - ECS_INTERNAL_ERROR, NULL); - - const ecs_entity_t *lookup_path = NULL; - if (world->stage_count >= 1) { - lookup_path = world->stages[0]->lookup_path; - } - - int32_t i, count = world->stage_count; - if (stage_count < count) { - for (i = stage_count; 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_stage_t *stage = world->stages[i]; - flecs_poly_assert(stage, ecs_stage_t); - ecs_check(stage->thread == 0, ECS_INVALID_OPERATION, - "cannot mix using set_stage_count and set_threads"); - flecs_stage_free(world, stage); - } - } - - if (stage_count) { - world->stages = ecs_os_realloc_n( - world->stages, ecs_stage_t*, stage_count); - - for (i = count; i < stage_count; i ++) { - ecs_stage_t *stage = world->stages[i] = flecs_stage_new(world); - stage->id = i; - - /* Set thread_ctx to stage, as this stage might be used in a - * multithreaded context */ - stage->thread_ctx = (ecs_world_t*)stage; - stage->thread = 0; - } - } else { - /* Set to NULL to prevent double frees */ - ecs_os_free(world->stages); - 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]->lookup_path = lookup_path; - } - - 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_stage_get_id( - const ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - - if (flecs_poly_is(world, ecs_stage_t)) { - ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); - - /* Index 0 is reserved for main stage */ - return stage->id; - } else if (flecs_poly_is(world, ecs_world_t)) { - return 0; - } else { - ecs_throw(ECS_INTERNAL_ERROR, NULL); - } -error: - return 0; -} - -ecs_world_t* ecs_get_stage( - const ecs_world_t *world, - int32_t stage_id) -{ - flecs_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, - bool multi_threaded) -{ - flecs_poly_assert(world, ecs_world_t); - - ecs_dbg_3("#[bold]readonly"); - ecs_log_push_3(); - - int32_t i, count = ecs_get_stage_count(world); - for (i = 0; i < count; i ++) { - ecs_stage_t *stage = world->stages[i]; - stage->lookup_path = world->stages[0]->lookup_path; - ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, - "deferred mode cannot be enabled when entering readonly mode"); - flecs_defer_begin(world, stage); - } - - bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); - - /* From this point on, the world is "locked" for mutations, and it is only - * allowed to enqueue commands from stages */ - ECS_BIT_SET(world->flags, EcsWorldReadonly); - ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, multi_threaded); - - return is_readonly; -} - -void ecs_readonly_end( - ecs_world_t *world) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, - "world is not in readonly mode"); - - /* After this it is safe again to mutate the world directly */ - ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); - ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); - - ecs_log_pop_3(); - - flecs_stage_merge(world); -error: - return; -} - -void ecs_merge( - ecs_world_t *world) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(flecs_poly_is(world, ecs_world_t) || - flecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); - flecs_stage_merge(world); -error: - return; -} - -bool ecs_stage_is_readonly( - const ecs_world_t *stage) -{ - const ecs_world_t *world = ecs_get_world(stage); - - if (flecs_poly_is(stage, ecs_stage_t)) { - if (((const ecs_stage_t*)stage)->id == -1) { - /* Stage is not owned by world, so never readonly */ - return false; - } - } - - if (world->flags & EcsWorldReadonly) { - if (flecs_poly_is(stage, ecs_world_t)) { - return true; - } - } else { - if (flecs_poly_is(stage, ecs_stage_t)) { - return true; - } - } - - 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; -} - -/** - * @file value.c - * @brief Utility functions to work with non-trivial pointers of user types. - */ - - -int ecs_value_init_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void *ptr) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_xtor_t ctor; - if ((ctor = ti->hooks.ctor)) { - ctor(ptr, 1, ti); - } else { - ecs_os_memset(ptr, 0, ti->size); - } - - return 0; -error: - return -1; -} - -int ecs_value_init( - const ecs_world_t *world, - ecs_entity_t type, - void *ptr) -{ - flecs_poly_assert(world, ecs_world_t); - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - return ecs_value_init_w_type_info(world, ti, ptr); -error: - return -1; -} - -void* ecs_value_new_w_type_info( - ecs_world_t *world, - const ecs_type_info_t *ti) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - void *result = flecs_alloc_w_dbg_info( - &world->allocator, ti->size, ti->name); - if (ecs_value_init_w_type_info(world, ti, result) != 0) { - flecs_free(&world->allocator, ti->size, result); - goto error; - } - - return result; -error: - return NULL; -} - -void* ecs_value_new( - ecs_world_t *world, - ecs_entity_t type) -{ - flecs_poly_assert(world, ecs_world_t); - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - - return ecs_value_new_w_type_info(world, ti); -error: - return NULL; -} - -int ecs_value_fini_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void *ptr) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_xtor_t dtor; - if ((dtor = ti->hooks.dtor)) { - dtor(ptr, 1, ti); - } - - return 0; -error: - return -1; -} - -int ecs_value_fini( - const ecs_world_t *world, - ecs_entity_t type, - void* ptr) -{ - flecs_poly_assert(world, ecs_world_t); - (void)world; - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - return ecs_value_fini_w_type_info(world, ti, ptr); -error: - return -1; -} - -int ecs_value_free( - ecs_world_t *world, - ecs_entity_t type, - void* ptr) -{ - flecs_poly_assert(world, ecs_world_t); - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { - goto error; - } - - flecs_free(&world->allocator, ti->size, ptr); - - return 0; -error: - return -1; -} - -int ecs_value_copy_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void* dst, - const void *src) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_copy_t copy; - if ((copy = ti->hooks.copy)) { - copy(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); - } - - return 0; -error: - return -1; -} - -int ecs_value_copy( - const ecs_world_t *world, - ecs_entity_t type, - void* dst, - const void *src) -{ - flecs_poly_assert(world, ecs_world_t); - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - return ecs_value_copy_w_type_info(world, ti, dst, src); -error: - return -1; -} - -int ecs_value_move_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void* dst, - void *src) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_move_t move; - if ((move = ti->hooks.move)) { - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); - } - - return 0; -error: - return -1; -} - -int ecs_value_move( - const ecs_world_t *world, - ecs_entity_t type, - void* dst, - void *src) -{ - flecs_poly_assert(world, ecs_world_t); - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - return ecs_value_move_w_type_info(world, ti, dst, src); -error: - return -1; -} - -int ecs_value_move_ctor_w_type_info( - const ecs_world_t *world, - const ecs_type_info_t *ti, - void* dst, - void *src) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); - (void)world; - - ecs_move_t move; - if ((move = ti->hooks.move_ctor)) { - move(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, ti->size); - } - - return 0; -error: - return -1; -} - -int ecs_value_move_ctor( - const ecs_world_t *world, - ecs_entity_t type, - void* dst, - void *src) -{ - flecs_poly_assert(world, ecs_world_t); - const ecs_type_info_t *ti = ecs_get_type_info(world, type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); - return ecs_value_move_w_type_info(world, ti, dst, src); -error: - return -1; -} - -/** - * @file world.c - * @brief World-level API. - */ - - -/* Id flags */ -const ecs_id_t ECS_PAIR = (1ull << 63); -const ecs_id_t ECS_AUTO_OVERRIDE = (1ull << 62); -const ecs_id_t ECS_TOGGLE = (1ull << 61); - -/** 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(EcsPoly) = 3; - -/* 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 EcsNotQueryable = FLECS_HI_COMPONENT_ID + 8; - -const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 9; -const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 10; - -/* Marker entities for query encoding */ -const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 11; -const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 12; -const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 13; -const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 14; - -/* Traits */ -const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 15; -const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 16; -const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 17; -const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 18; -const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 19; -const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 20; -const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 21; -const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 22; -const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 23; -const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 24; -const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 25; -const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 26; -const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 27; -const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 28; -const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 29; -const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 30; -const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 31; -const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 32; - - -/* Builtin relationships */ -const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 33; -const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 34; -const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 35; - -/* Identifier tags */ -const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 36; -const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 37; -const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 38; - -/* Events */ -const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 39; -const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 40; -const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 41; -const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; -const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; -const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; -const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; - -/* Timers */ -const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; -const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 50; -const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 51; +/* Timers */ +const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; +const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 50; +const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 51; /* Actions */ const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 52; @@ -18501,10 +17547,11 @@ const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; -const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; #endif +const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; + /* Doc module components */ #ifdef FLECS_DOC const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; @@ -18939,9 +17986,6 @@ void flecs_init_store( /* Initialize table map */ flecs_table_hashmap_init(world, &world->store.table_map); - - /* Initialize root table */ - flecs_init_root_table(world); } static @@ -18949,13 +17993,6 @@ void flecs_clean_tables( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->store.tables); - - /* Ensure that first table in sparse set has id 0. This is a dummy table - * that only exists so that there is no table with id 0 */ - ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, - ecs_table_t, 0); - (void)first; - for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); @@ -18979,7 +18016,7 @@ void flecs_clean_tables( static void flecs_fini_root_tables( ecs_world_t *world, - ecs_id_record_t *idr, + ecs_component_record_t *cdr, bool fini_targets) { ecs_stage_t *stage0 = world->stages[0]; @@ -18990,7 +18027,7 @@ void flecs_fini_root_tables( ecs_size_t queue_size = 0; finished = true; - bool has_roots = flecs_table_cache_iter(&idr->cache, &it); + bool has_roots = flecs_table_cache_iter(&cdr->cache, &it); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; @@ -19054,18 +18091,18 @@ static void flecs_fini_roots( ecs_world_t *world) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair(EcsChildOf, 0)); /* 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_fini_root_tables(world, cdr, true); flecs_defer_end(world, world->stages[0]); flecs_defer_begin(world, world->stages[0]); - flecs_fini_root_tables(world, idr, false); + flecs_fini_root_tables(world, cdr, false); flecs_defer_end(world, world->stages[0]); } @@ -19103,8 +18140,9 @@ void flecs_world_allocators_init( flecs_ballocator_init_t(&a->query_table_match, ecs_query_cache_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->id_record, ecs_component_record_t); + flecs_ballocator_init_t(&a->pair_id_record, ecs_pair_id_record_t); + flecs_ballocator_init_n(&a->id_record_chunk, ecs_component_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); @@ -19124,6 +18162,7 @@ void flecs_world_allocators_fini( flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->id_record); + flecs_ballocator_fini(&a->pair_id_record); flecs_ballocator_fini(&a->id_record_chunk); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); @@ -19156,9 +18195,6 @@ static const char *flecs_addons_info[] = { #ifdef FLECS_MODULE "FLECS_MODULE", #endif -#ifdef FLECS_SCRIPT - "FLECS_SCRIPT", -#endif #ifdef FLECS_STATS "FLECS_STATS", #endif @@ -19201,6 +18237,12 @@ static const char *flecs_addons_info[] = { #ifdef FLECS_OS_API_IMPL "FLECS_OS_API_IMPL", #endif +#ifdef FLECS_PARSER + "FLECS_PARSER", +#endif +#ifdef FLECS_QUERY_DSL + "FLECS_QUERY_DSL", +#endif #ifdef FLECS_SCRIPT "FLECS_SCRIPT", #endif @@ -19310,7 +18352,7 @@ ecs_world_t *ecs_mini(void) { ecs_map_init(&world->type_info, a); 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); + ecs_component_record_t*, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); flecs_name_index_init(&world->aliases, a); @@ -19429,7 +18471,7 @@ void flecs_notify_tables( flecs_poly_assert(world, ecs_world_t); /* If no id is specified, broadcast to all tables */ - if (!id) { + if (!id || id == EcsAny) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { @@ -19439,15 +18481,15 @@ void flecs_notify_tables( /* If id is specified, only broadcast to tables with id */ } else { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { return; } ecs_table_cache_iter_t it; const ecs_table_record_t *tr; - flecs_table_cache_all_iter(&idr->cache, &it); + flecs_table_cache_all_iter(&cdr->cache, &it); while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_table_notify(world, tr->hdr.table, id, event); } @@ -19531,6 +18573,11 @@ void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, cl->dtor(src_ptr, count, ti); } +static +bool flecs_default_equals(const void *a_ptr, const void *b_ptr, const ecs_type_info_t* ti) { + return ti->hooks.cmp(a_ptr, b_ptr, ti) == 0; +} + ECS_NORETURN static void flecs_ctor_illegal( void * dst, @@ -19602,6 +18649,28 @@ void flecs_move_ctor_illegal( ecs_abort(ECS_INVALID_OPERATION, "invalid move construct for %s", ti->name); } +ECS_NORETURN static +int flecs_comp_illegal( + const void *dst, + const void *src, + const ecs_type_info_t *ti) +{ + (void)dst; /* silence unused warning */ + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "invalid compare hook for %s", ti->name); +} + +ECS_NORETURN static +bool flecs_equals_illegal( + const void *dst, + const void *src, + const ecs_type_info_t *ti) +{ + (void)dst; /* silence unused warning */ + (void)src; + ecs_abort(ECS_INVALID_OPERATION, "invalid equals hook for %s", ti->name); +} + void ecs_set_hooks_id( ecs_world_t *world, ecs_entity_t component, @@ -19613,27 +18682,56 @@ void ecs_set_hooks_id( ecs_flags32_t flags = h->flags; flags &= ~((ecs_flags32_t)ECS_TYPE_HOOKS); - /* TODO: enable asserts once RTT API is updated */ - /* - ecs_check(!(h->flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) || !h->ctor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) || !h->dtor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_COPY_ILLEGAL) || !h->copy, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) || !h->move, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) || !h->copy_ctor, - ECS_INVALID_PARAMETER, - "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) || !h->move_ctor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) || - !h->ctor_move_dtor, ECS_INVALID_PARAMETER, - "cannot specify both hook and illegal flag"); - ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) || !h->move_dtor, - ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); - */ + ecs_check(!(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL && + h->ctor != NULL && + h->ctor != flecs_ctor_illegal), + ECS_INVALID_PARAMETER, "cannot specify both ctor hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL && + h->dtor != NULL && + h->dtor != flecs_dtor_illegal), + ECS_INVALID_PARAMETER, "cannot specify both dtor hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_COPY_ILLEGAL && + h->copy != NULL && + h->copy != flecs_copy_illegal), + ECS_INVALID_PARAMETER, "cannot specify both copy hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL && + h->move != NULL && + h->move != flecs_move_illegal), + ECS_INVALID_PARAMETER, "cannot specify both move hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL && + h->copy_ctor != NULL && + h->copy_ctor != flecs_copy_ctor_illegal), + ECS_INVALID_PARAMETER, "cannot specify both copy ctor hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL && + h->move_ctor != NULL && + h->move_ctor != flecs_move_ctor_illegal), + ECS_INVALID_PARAMETER, "cannot specify both move ctor hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL && + h->ctor_move_dtor != NULL && + h->ctor_move_dtor != flecs_move_ctor_illegal), + ECS_INVALID_PARAMETER, "cannot specify both ctor move dtor hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL && + h->move_dtor != NULL && + h->move_dtor != flecs_move_ctor_illegal), + ECS_INVALID_PARAMETER, "cannot specify both move dtor hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_CMP_ILLEGAL && + h->cmp != NULL && + h->cmp != flecs_comp_illegal), + ECS_INVALID_PARAMETER, "cannot specify both compare hook and illegal flag"); + + ecs_check(!(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL && + h->equals != NULL && + h->equals != flecs_equals_illegal), + ECS_INVALID_PARAMETER, "cannot specify both equals hook and illegal flag"); + flecs_stage_from_world(&world); @@ -19671,6 +18769,8 @@ void ecs_set_hooks_id( 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->cmp) ti->hooks.cmp = h->cmp; + if (h->equals) ti->hooks.equals = h->equals; if (h->on_add) ti->hooks.on_add = h->on_add; if (h->on_remove) ti->hooks.on_remove = h->on_remove; @@ -19769,6 +18869,19 @@ void ecs_set_hooks_id( } } + if(!h->cmp) { + flags |= ECS_TYPE_HOOK_CMP_ILLEGAL; + } + + if (!h->equals || h->equals == flecs_equals_illegal) { + if(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) { + flags |= ECS_TYPE_HOOK_EQUALS_ILLEGAL; + } else { + ti->hooks.equals = flecs_default_equals; + flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; + } + } + ti->hooks.flags = flags; if (ti->hooks.ctor) ti->hooks.flags |= ECS_TYPE_HOOK_CTOR; @@ -19779,11 +18892,15 @@ void ecs_set_hooks_id( if (ti->hooks.move_dtor) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE_DTOR; if (ti->hooks.copy) ti->hooks.flags |= ECS_TYPE_HOOK_COPY; if (ti->hooks.copy_ctor) ti->hooks.flags |= ECS_TYPE_HOOK_COPY_CTOR; + if (ti->hooks.cmp) ti->hooks.flags |= ECS_TYPE_HOOK_CMP; + if (ti->hooks.equals) ti->hooks.flags |= ECS_TYPE_HOOK_EQUALS; if(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ti->hooks.ctor = flecs_ctor_illegal; if(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ti->hooks.dtor = flecs_dtor_illegal; if(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ti->hooks.copy = flecs_copy_illegal; if(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ti->hooks.move = flecs_move_illegal; + if(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ti->hooks.cmp = flecs_comp_illegal; + if(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ti->hooks.equals = flecs_equals_illegal; if(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) { ti->hooks.copy_ctor = flecs_copy_ctor_illegal; @@ -19798,9 +18915,10 @@ void ecs_set_hooks_id( } if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) { - ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; + ti->hooks.move_dtor = flecs_move_ctor_illegal; } + error: return; } @@ -19982,7 +19100,7 @@ int ecs_fini( ecs_dbg_1("#[bold]cleanup world data structures"); ecs_log_push_1(); flecs_entities_fini(world); - flecs_fini_id_records(world); + flecs_components_fini(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); flecs_name_index_fini(&world->aliases); @@ -20155,6 +19273,25 @@ ecs_entity_t ecs_get_max_id( return 0; } +void flecs_increment_table_version( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); + + world->table_version[table->id & ECS_TABLE_VERSION_ARRAY_BITMASK] ++; + return; +} + +uint32_t flecs_get_table_version( + const ecs_world_t *world, + const uint64_t table_id) +{ + flecs_poly_assert(world, ecs_world_t); + return world->table_version[table_id & ECS_TABLE_VERSION_ARRAY_BITMASK]; +} + const ecs_type_info_t* flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component) @@ -20229,36 +19366,36 @@ bool flecs_type_info_init_id( } } - /* 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; + /* Set type info for component record of component */ + ecs_component_record_t *cdr = flecs_components_ensure(world, component); + changed |= flecs_component_set_type_info(world, cdr, ti); + bool is_tag = cdr->flags & EcsIdTag; /* All id records with component as relationship inherit type info */ - idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); + cdr = flecs_components_ensure(world, ecs_pair(component, EcsWildcard)); do { if (is_tag) { - changed |= flecs_id_record_set_type_info(world, idr, NULL); + changed |= flecs_component_set_type_info(world, cdr, 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_component_set_type_info(world, cdr, ti); + } else if ((cdr->type_info != NULL) && + (cdr->type_info->component == component)) { - changed |= flecs_id_record_set_type_info(world, idr, NULL); + changed |= flecs_component_set_type_info(world, cdr, NULL); } - } while ((idr = idr->first.next)); + } while ((cdr = flecs_component_first_next(cdr))); /* 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)); + cdr = flecs_components_ensure(world, ecs_pair(EcsWildcard, component)); do { - if (!(idr->flags & EcsIdTag) && !idr->type_info) { - changed |= flecs_id_record_set_type_info(world, idr, ti); + if (!(cdr->flags & EcsIdTag) && !cdr->type_info) { + changed |= flecs_component_set_type_info(world, cdr, ti); } - } while ((idr = idr->first.next)); + } while ((cdr = flecs_component_first_next(cdr))); /* Type info of (*, component) should always point to component */ - ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> + ecs_assert(flecs_components_get(world, ecs_pair(EcsWildcard, component))-> type_info == ti, ECS_INTERNAL_ERROR, NULL); return changed; @@ -20490,9 +19627,9 @@ void flecs_process_empty_queries( { flecs_poly_assert(world, ecs_world_t); - ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair(ecs_id(EcsPoly), EcsQuery)); - if (!idr) { + if (!cdr) { return; } @@ -20502,7 +19639,7 @@ void flecs_process_empty_queries( ecs_table_cache_iter_t it; const ecs_table_record_t *tr; - if (flecs_table_cache_iter(&idr->cache, &it)) { + if (flecs_table_cache_iter(&cdr->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); @@ -20525,12 +19662,12 @@ 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) { + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { return false; } - return (flecs_table_cache_count(&idr->cache) != 0); + return (flecs_table_cache_count(&cdr->cache) != 0); } void ecs_run_aperiodic( @@ -20559,11 +19696,10 @@ int32_t ecs_delete_empty_tables( ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0; bool time_budget = false; + int32_t measure_budget_after = 100; - ecs_id_t id = desc->id; uint16_t clear_generation = desc->clear_generation; uint16_t delete_generation = desc->delete_generation; - int32_t min_id_count = desc->min_id_count; double time_budget_seconds = desc->time_budget_seconds; if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { @@ -20574,36 +19710,35 @@ int32_t ecs_delete_empty_tables( time_budget = true; } - if (!id) { - id = EcsAny; /* Iterate all empty tables */ - } + int32_t i, count = flecs_sparse_count(&world->store.tables); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { - ecs_table_record_t *tr; - while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { - if (time_budget) { - cur = start; - if (ecs_time_measure(&cur) > time_budget_seconds) { - goto done; - } - } + for (i = count - 1; i >= 0; i --) { + ecs_table_t *table = flecs_sparse_get_dense_t(&world->store.tables, + ecs_table_t, i); - ecs_table_t *table = tr->hdr.table; - ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); + measure_budget_after --; - if (table->type.count < min_id_count) { - continue; + if (time_budget && !measure_budget_after) { + cur = start; + if (ecs_time_measure(&cur) > time_budget_seconds) { + goto done; } - uint16_t gen = ++ table->_->generation; - if (delete_generation && (gen > delete_generation)) { - flecs_table_fini(world, table); - delete_count ++; - } else if (clear_generation && (gen > clear_generation)) { - flecs_table_shrink(world, table); - } + measure_budget_after = 100; + } + + if (!table->id || ecs_table_count(table) != 0) { + continue; + } + + uint16_t gen = ++ table->_->generation; + if (delete_generation && (gen > delete_generation)) { + flecs_table_fini(world, table); + delete_count ++; + measure_budget_after = 1; + } else if (clear_generation && (gen > clear_generation)) { + flecs_table_shrink(world, table); + measure_budget_after = 1; } } @@ -20696,6 +19831,28 @@ void flecs_component_ids_set( ecs_vec_get_t(&world->component_ids, ecs_entity_t, index)[0] = component; } +void ecs_shrink( + ecs_world_t *world) +{ + flecs_entity_index_shrink(&world->store.entity_index); + + ecs_sparse_t *tables = &world->store.tables; + int32_t i, count = flecs_sparse_count(tables); + + for (i = count - 1; i > 0; i --) { + ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); + if (ecs_table_count(table)) { + flecs_table_shrink(world, table); + } else { + flecs_table_fini(world, table); + } + } + + flecs_table_shrink(world, &world->store.root); + + flecs_sparse_shrink(&world->store.tables); +} + /** * @file addons/alerts.c * @brief Alerts addon. @@ -21263,14 +20420,14 @@ ecs_entity_t ecs_alert_init( alert->id = desc->id; } - ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - if (!idr->type_info) { + ecs_component_record_t *cdr = flecs_components_ensure(world, alert->id); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + if (!cdr->type_info) { ecs_err("ecs_alert_desc_t::id must be a component"); goto error; } - ecs_entity_t type = idr->type_info->component; + ecs_entity_t type = cdr->type_info->component; if (type != ecs_get_parent(world, desc->member)) { char *type_name = ecs_get_path(world, type); ecs_err("member '%s' is not a member of '%s'", @@ -21310,7 +20467,7 @@ ecs_entity_t ecs_alert_init( } alert->offset = member->offset; - alert->size = idr->type_info->size; + alert->size = cdr->type_info->size; alert->kind = pr->kind; alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); alert->var_id = var_id; @@ -22114,7 +21271,7 @@ const char* ecs_cpp_trim_module( char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); if (path) { ecs_size_t len = ecs_os_strlen(path); - if (!ecs_os_strncmp(path, type_name, len)) { + if (!ecs_os_strncmp(path, type_name, len) && type_name[len] == ':') { // Type is a child of current parent, trim name of parent type_name += len; ecs_assert(type_name[0], ECS_INVALID_PARAMETER, @@ -22133,68 +21290,132 @@ const char* ecs_cpp_trim_module( } } + ecs_os_free(path); return type_name; } -ecs_entity_t ecs_cpp_component_find( +ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, ecs_entity_t id, + int32_t ids_index, const char *name, - const char *symbol, + const char *cpp_name, + const char *cpp_symbol, size_t size, size_t alignment, - bool implicit_name, + bool is_component, + bool explicit_registration, + bool *registered_out, bool *existing_out) { - (void)size; - (void)alignment; - + ecs_assert(registered_out != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t c = flecs_component_ids_get(world, ids_index); + + if (!c || !ecs_is_alive(world, c)) { + } else { + return c; + } + + const char *user_name = NULL; + bool implicit_name = true; + ecs_entity_t module = 0; + + if (explicit_registration) { + user_name = name; + implicit_name = false; + + if (!user_name) { + user_name = cpp_name; + + /* Keep track of whether name was explicitly set. If not, and + * the component was already registered, just use the registered + * name. The registered name may differ from the typename as the + * registered name includes the flecs scope. This can in theory + * be different from the C++ namespace though it is good + * practice to keep them the same */ + implicit_name = true; + } + + /* If component is registered by module, ensure it's registered in + * the scope of the module. */ + module = ecs_get_scope(world); + + /* Strip off the namespace part of the component name, unless a name + * was explicitly provided by the application. */ + if (module && implicit_name) { + /* If the type is a template type, make sure to ignore :: + * inside the template parameter list. */ + const char *start = strchr(user_name, '<'), *last_elem = NULL; + if (start) { + const char *ptr = start; + while (ptr[0] && (ptr[0] != ':') && (ptr > user_name)) { + ptr --; + } + if (ptr[0] == ':') { + last_elem = ptr; + } + } + + if (last_elem) { + name = last_elem + 1; + } + } + } + + /* At this point it is possible that the type was already registered + * with the world, just not for this binary. The registration code + * uses the type symbol to check if it was already registered. Note + * that the symbol is separate from the typename, as an application + * can override a component name when registering a type. */ + /* 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. */ ecs_entity_t prev_scope = ecs_set_scope(world, 0); - ecs_entity_t ent; if (id) { - ent = id; + c = id; } else { - ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); - *existing_out = ent != 0 && ecs_has(world, ent, EcsComponent); + c = ecs_lookup_path_w_sep(world, 0, user_name, "::", "::", false); + *existing_out = c != 0 && ecs_has(world, c, 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 (c) { + const EcsComponent *component = ecs_get(world, c, 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. + const char *sym = ecs_get_symbol(world, c); + if (sym && ecs_os_strcmp(sym, cpp_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. + * 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). + * 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. * - * 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_path(world, ent); - if (ecs_os_strcmp(type_path, 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_path(world, c); + if (ecs_os_strcmp(type_path, cpp_symbol)) { ecs_err( "component with name '%s' is already registered for"\ " type '%s' (trying to register for type '%s')", - name, sym, symbol); + name, sym, cpp_symbol); ecs_abort(ECS_NAME_IN_USE, NULL); } @@ -22209,85 +21430,84 @@ ecs_entity_t ecs_cpp_component_find( ecs_os_free(type_path); } else if (!sym) { - ecs_set_symbol(world, ent, symbol); + ecs_set_symbol(world, c, cpp_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); + c = ecs_lookup_symbol(world, cpp_symbol, false, false); + ecs_assert(c == 0 || (c == id), + ECS_INCONSISTENT_COMPONENT_ID, cpp_symbol); } - return ent; -} + const char *symbol = NULL; -ecs_entity_t ecs_cpp_component_register( - 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 (c) { + symbol = ecs_get_symbol(world, c); + } - ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, NULL); + if (!symbol) { + symbol = cpp_symbol; + } - // 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) { + /* When a component is implicitly registered, ensure that it's not + * registered in the current scope of the application/that "with" + * components get added to the component entity. */ + prev_scope = ecs_set_scope(world, module); + ecs_entity_t prev_with = ecs_set_with(world, 0); + char *existing_name = NULL; + + /* 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 (!c) { 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, "::", "::"); + /* If no name was provided first check if a type with the + * provided symbol was already registered. */ + ecs_id_t e = ecs_lookup_symbol(world, symbol, false, false); + if (e) { + existing_name = ecs_get_path_w_sep(world, 0, e, "::", "::"); name = existing_name; *existing_out = true; } else { - // If type is not yet known, derive from type name - name = ecs_cpp_trim_module(world, type_name); + /* If type is not yet known, derive from type name */ + name = ecs_cpp_trim_module(world, cpp_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); + /* If an explicit id is provided but it has no name, inherit + * the name from the type. */ + if (!ecs_is_valid(world, c) || !ecs_get_name(world, c)) { + name = ecs_cpp_trim_module(world, cpp_name); } } - ecs_entity_t entity; if (is_component || size != 0) { - entity = ecs_entity(world, { - .id = s_id, + c = ecs_entity(world, { + .id = c, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); - ecs_assert(entity != 0, ECS_INVALID_OPERATION, + + ecs_assert(c != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); - entity = ecs_component_init(world, &(ecs_component_desc_t){ - .entity = entity, + c = ecs_component_init(world, &(ecs_component_desc_t){ + .entity = c, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); - ecs_assert(entity != 0, ECS_INVALID_OPERATION, + + ecs_assert(c != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); } else { - entity = ecs_entity(world, { - .id = s_id, + c = ecs_entity(world, { + .id = c, .name = name, .sep = "::", .root_sep = "::", @@ -22296,11 +21516,18 @@ ecs_entity_t ecs_cpp_component_register( }); } - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); + ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); - return entity; + ecs_set_with(world, prev_with); + ecs_set_scope(world, prev_scope); + + /* Set world local component id */ + flecs_component_ids_set(world, ids_index, c); + + *registered_out = true; + + return c; } void ecs_cpp_enum_init( @@ -22316,6 +21543,10 @@ void ecs_cpp_enum_init( world = flecs_suspend_readonly(world, &readonly_state); ecs_set(world, id, EcsEnum, { .underlying_type = underlying_type }); flecs_resume_readonly(world, &readonly_state); +#else + /* Make sure that enums still behave the same even without meta */ + ecs_add_id(world, id, EcsExclusive); + ecs_add_id(world, id, EcsOneOf); #endif } @@ -22328,10 +21559,13 @@ ecs_entity_t ecs_cpp_enum_constant_register( ecs_entity_t value_type, size_t value_size) { -#ifdef FLECS_META ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); + ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); + 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)) { @@ -22358,6 +21592,7 @@ ecs_entity_t ecs_cpp_enum_constant_register( flecs_resume_readonly(world, &readonly_state); +#ifdef FLECS_META if (ecs_should_log(0)) { ecs_value_t v = { .type = value_type, .ptr = value }; char *str = NULL; @@ -22368,19 +21603,9 @@ ecs_entity_t ecs_cpp_enum_constant_register( ecs_get_name(world, parent), name, str); ecs_os_free(str); } +#endif return id; -#else - (void)world; - (void)parent; - (void)id; - (void)name; - (void)value; - (void)value_type; - (void)value_size; - ecs_err("enum reflection not supported without FLECS_META addon"); - return 0; -#endif } #ifdef FLECS_META @@ -25004,13 +24229,13 @@ typedef struct { /** 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_component_record_t *cdr; /**< component record for monitored component */ } ecs_id_metric_ctx_t; /** Context for metric that monitors whether entity has pair target */ typedef struct { ecs_metric_ctx_t metric; - ecs_id_record_t *idr; /**< Id record for monitored component */ + ecs_component_record_t *cdr; /**< component 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; @@ -25018,7 +24243,7 @@ typedef struct { /** 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_component_record_t *cdr; /**< component record for monitored component */ ecs_map_t targets; /**< Map of counters for each target */ } ecs_count_targets_metric_ctx_t; @@ -25274,8 +24499,8 @@ static void UpdateIdInstance(ecs_iter_t *it, bool counter) { } ecs_id_metric_ctx_t *ctx = mi[i].ctx; - ecs_id_record_t *idr = ctx->idr; - if (flecs_search_w_idr(world, table, NULL, idr) != -1) { + ecs_component_record_t *cdr = ctx->cdr; + if (flecs_search_w_idr(world, table, NULL, cdr) != -1) { if (!counter) { m[i].value = 1.0; } else { @@ -25323,9 +24548,9 @@ static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { continue; } - ecs_id_record_t *idr = ctx->idr; + ecs_component_record_t *cdr = ctx->cdr; ecs_id_t id; - if (flecs_search_w_idr(world, mtable, &id, idr) == -1) { + if (flecs_search_w_idr(world, mtable, &id, cdr) == -1) { ecs_delete(it->world, it->entities[i]); continue; } @@ -25362,8 +24587,8 @@ static void UpdateCountTargets(ecs_iter_t *it) { 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_component_record_t *cur = ctx->cdr; + while ((cur = flecs_component_first_next(cur))) { ecs_id_t id = cur->id; ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); if (!mi[0]) { @@ -25556,8 +24781,8 @@ int flecs_id_metric_init( 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_INTERNAL_ERROR, NULL); + ctx->cdr = flecs_components_ensure(world, desc->id); + ecs_check(ctx->cdr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_observer(world, { .entity = metric, @@ -25588,8 +24813,8 @@ int flecs_oneof_metric_init( 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_INTERNAL_ERROR, NULL); + ctx->cdr = flecs_components_ensure(world, desc->id); + ecs_check(ctx->cdr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->target_offset, NULL); /* Add member for each child of oneof to metric, so it can be used as metric @@ -25657,8 +24882,8 @@ int flecs_count_id_targets_metric_init( 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_INTERNAL_ERROR, NULL); + ctx->cdr = flecs_components_ensure(world, desc->id); + ecs_check(ctx->cdr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->targets, NULL); ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); @@ -26182,7 +25407,7 @@ struct ecs_pipeline_state_t { 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 */ + ecs_component_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) */ @@ -26758,6 +25983,7 @@ bool flecs_rest_script( (void)world; (void)req; (void)reply; + (void)path; #ifdef FLECS_SCRIPT ecs_entity_t script = flecs_rest_entity_from_path(world, reply, path); if (!script) { @@ -26907,7 +26133,7 @@ bool flecs_rest_reply_existing_query( const char *vars = ecs_http_get_param(req, "vars"); if (vars) { - #ifdef FLECS_SCRIPT + #ifdef FLECS_QUERY_DSL if (ecs_query_args_parse(q, &it, vars) == NULL) { flecs_rest_reply_set_captured_log(reply); return true; @@ -27418,6 +26644,7 @@ const char* flecs_rest_cmd_kind_to_str( ecs_cmd_kind_t kind) { switch(kind) { + case EcsCmdNew: return "New"; case EcsCmdClone: return "Clone"; case EcsCmdBulkNew: return "BulkNew"; case EcsCmdAdd: return "Add"; @@ -27445,6 +26672,7 @@ bool flecs_rest_cmd_has_id( const ecs_cmd_t *cmd) { switch(cmd->kind) { + case EcsCmdNew: case EcsCmdClear: case EcsCmdDelete: case EcsCmdClone: @@ -29324,3985 +28552,5572 @@ void FlecsUnitsImport( ecs_doc_set_brief(world, EcsTemperature, "Units of temperature (e.g. \"5 degrees Celsius\")"); - ecs_doc_set_brief(world, EcsData, - "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); + ecs_doc_set_brief(world, EcsData, + "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); + + ecs_doc_set_brief(world, EcsDataRate, + "Units of data transmission (e.g. \"100 megabits/second\")"); + + ecs_doc_set_brief(world, EcsAngle, + "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); + + ecs_doc_set_brief(world, EcsFrequency, + "The number of occurrences of a repeating event per unit of time."); + + ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); +#endif +} + +#endif + +/** + * @file datastructures/allocator.c + * @brief Allocator for any size. + * + * Allocators create a block allocator for each requested size. + */ + + +static +ecs_size_t flecs_allocator_size( + ecs_size_t size) +{ + return ECS_ALIGN(size, 16); +} + +static +ecs_size_t flecs_allocator_size_hash( + ecs_size_t size) +{ + return size >> 4; +} + +void flecs_allocator_init( + ecs_allocator_t *a) +{ + flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, + FLECS_SPARSE_PAGE_SIZE); + flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); +} + +void flecs_allocator_fini( + ecs_allocator_t *a) +{ + ecs_assert(a != NULL, ECS_INVALID_PARAMETER, NULL); + + int32_t i = 0, count = flecs_sparse_count(&a->sizes); + for (i = 0; i < count; i ++) { + ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( + &a->sizes, ecs_block_allocator_t, i); + flecs_ballocator_fini(ba); + } + flecs_sparse_fini(&a->sizes); + + flecs_ballocator_fini(&a->chunks); +} + +ecs_block_allocator_t* flecs_allocator_get( + ecs_allocator_t *a, + ecs_size_t size) +{ + ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); + if (!size) { + return NULL; + } + + ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); + size = flecs_allocator_size(size); + ecs_size_t hash = flecs_allocator_size_hash(size); + ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + + if (!result) { + result = flecs_sparse_ensure_fast_t(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + flecs_ballocator_init(result, size); + } + + ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); + + return result; +} + +char* flecs_strdup( + ecs_allocator_t *a, + const char* str) +{ + ecs_size_t len = ecs_os_strlen(str); + char *result = flecs_alloc_n(a, char, len + 1); + ecs_os_memcpy(result, str, len + 1); + return result; +} + +void flecs_strfree( + ecs_allocator_t *a, + char* str) +{ + ecs_size_t len = ecs_os_strlen(str); + flecs_free_n(a, char, len + 1, str); +} + +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src) +{ + ecs_block_allocator_t *ba = flecs_allocator_get(a, size); + if (ba) { + void *dst = flecs_balloc(ba); + ecs_os_memcpy(dst, src, size); + return dst; + } else { + return NULL; + } +} + +/** + * @file datastructures/bitset.c + * @brief Bitset data structure. + * + * Simple bitset implementation. The bitset allows for storage of arbitrary + * numbers of bits. + */ + + +static +void ensure( + ecs_bitset_t *bs, + ecs_size_t size) +{ + if (!bs->size) { + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + bs->data = ecs_os_calloc(new_size); + } else if (size > bs->size) { + int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->size = ((size - 1) / 64 + 1) * 64; + int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); + bs->data = ecs_os_realloc(bs->data, new_size); + ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + } +} + +void flecs_bitset_init( + ecs_bitset_t* bs) +{ + bs->size = 0; + bs->count = 0; + bs->data = NULL; +} + +void flecs_bitset_ensure( + ecs_bitset_t *bs, + int32_t count) +{ + if (count > bs->count) { + bs->count = count; + ensure(bs, count); + } +} + +void flecs_bitset_fini( + ecs_bitset_t *bs) +{ + ecs_os_free(bs->data); + bs->data = NULL; + bs->count = 0; +} + +void flecs_bitset_addn( + ecs_bitset_t *bs, + int32_t count) +{ + int32_t elem = bs->count += count; + ensure(bs, elem); +} + +void flecs_bitset_set( + ecs_bitset_t *bs, + int32_t elem, + bool value) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + uint32_t hi = ((uint32_t)elem) >> 6; + uint32_t lo = ((uint32_t)elem) & 0x3F; + uint64_t v = bs->data[hi]; + bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); +error: + return; +} + +bool flecs_bitset_get( + const ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); +error: + return false; +} + +int32_t flecs_bitset_count( + const ecs_bitset_t *bs) +{ + return bs->count; +} + +void flecs_bitset_remove( + ecs_bitset_t *bs, + int32_t elem) +{ + ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); + int32_t last = bs->count - 1; + bool last_value = flecs_bitset_get(bs, last); + flecs_bitset_set(bs, elem, last_value); + flecs_bitset_set(bs, last, 0); + bs->count --; +error: + return; +} + +void flecs_bitset_swap( + ecs_bitset_t *bs, + int32_t elem_a, + int32_t elem_b) +{ + ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); + ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); + + bool a = flecs_bitset_get(bs, elem_a); + bool b = flecs_bitset_get(bs, elem_b); + flecs_bitset_set(bs, elem_a, b); + flecs_bitset_set(bs, elem_b, a); +error: + return; +} + +/** + * @file datastructures/block_allocator.c + * @brief Block allocator. + * + * A block allocator is an allocator for a fixed size that allocates blocks of + * memory with N elements of the requested size. + */ + + +// #ifdef FLECS_SANITIZE +// #define FLECS_MEMSET_UNINITIALIZED +// #endif + +int64_t ecs_block_allocator_alloc_count = 0; +int64_t ecs_block_allocator_free_count = 0; + +#ifndef FLECS_USE_OS_ALLOC + +/* Bypass block allocator if chunks per block is lower than the configured + * value. This prevents holding on to large memory chunks when they're freed, + * which can add up especially in scenarios where an array is reallocated + * several times to a large size. + * A value of 1 seems to yield the best results. Higher values only impact lower + * allocation sizes, which are more likely to be reused. */ +#define FLECS_MIN_CHUNKS_PER_BLOCK 1 + +static +ecs_block_allocator_chunk_header_t* flecs_balloc_block( + ecs_block_allocator_t *allocator) +{ + if (!allocator->chunk_size) { + return NULL; + } + + ecs_block_allocator_block_t *block = + ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + + allocator->block_size); + ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, + ECS_SIZEOF(ecs_block_allocator_block_t)); + + block->memory = first_chunk; + block->next = NULL; + + if (allocator->block_head) { + block->next = allocator->block_head; + } + + allocator->block_head = block; + + ecs_block_allocator_chunk_header_t *chunk = first_chunk; + int32_t i, end; + for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { + chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); + chunk = chunk->next; + } + + ecs_os_linc(&ecs_block_allocator_alloc_count); + + chunk->next = NULL; + return first_chunk; +} + +#endif + +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 + ba->alloc_count = 0; + if (size != 24) { /* Prevent stack overflow as map uses block allocator */ + ba->outstanding = ecs_os_malloc_t(ecs_map_t); + ecs_map_init(ba->outstanding, NULL); + } + 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; +} + +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 + if (ba->alloc_count != 0) { + ecs_err("Leak detected! (size %u, remaining = %d)", + (uint32_t)ba->data_size, ba->alloc_count); + if (ba->outstanding) { + ecs_map_iter_t it = ecs_map_iter(ba->outstanding); + while (ecs_map_next(&it)) { + uint64_t key = ecs_map_key(&it); + char *type_name = ecs_map_ptr(&it); + if (type_name) { + printf(" - %p (%s)\n", (void*)key, type_name); + } else { + printf(" - %p (unknown type)\n", (void*)key); + } + } + } + ecs_abort(ECS_LEAK_DETECTED, NULL); + } + if (ba->outstanding) { + ecs_map_fini(ba->outstanding); + ecs_os_free(ba->outstanding); + } +#endif + + ecs_block_allocator_block_t *block; + for (block = ba->block_head; block;) { + ecs_block_allocator_block_t *next = block->next; + ecs_os_free(block); + ecs_os_linc(&ecs_block_allocator_free_count); + block = next; + } + + ba->block_head = NULL; +} + +void flecs_ballocator_free( + ecs_block_allocator_t *ba) +{ + flecs_ballocator_fini(ba); + ecs_os_free(ba); +} + +void* flecs_balloc( + ecs_block_allocator_t *ba) +{ + return flecs_balloc_w_dbg_info(ba, NULL); +} + +void* flecs_balloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) +{ + (void)type_name; + void *result; + + if (!ba) return NULL; + +#ifdef FLECS_USE_OS_ALLOC + result = ecs_os_malloc(ba->data_size); +#else + + if (ba->chunks_per_block <= FLECS_MIN_CHUNKS_PER_BLOCK) { + return ecs_os_malloc(ba->data_size); + } + + if (!ba->head) { + ba->head = flecs_balloc_block(ba); + ecs_assert(ba->head != NULL, ECS_INTERNAL_ERROR, NULL); + } + + result = ba->head; + ba->head = ba->head->next; + +#ifdef FLECS_SANITIZE + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); + if (ba->outstanding) { + uint64_t *v = ecs_map_ensure(ba->outstanding, (uintptr_t)result); + *(const char**)v = type_name; + } + ba->alloc_count ++; + *(int64_t*)result = (uintptr_t)ba; + result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); +#endif +#endif + +#ifdef FLECS_MEMSET_UNINITIALIZED + ecs_os_memset(result, 0xAA, ba->data_size); +#endif + + return result; +} + +void* flecs_bcalloc( + ecs_block_allocator_t *ba) +{ + return flecs_bcalloc_w_dbg_info(ba, NULL); +} + +void* flecs_bcalloc_w_dbg_info( + ecs_block_allocator_t *ba, + const char *type_name) +{ + (void)type_name; + +#ifdef FLECS_USE_OS_ALLOC + ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_os_calloc(ba->data_size); +#else + if (!ba) return NULL; + void *result = flecs_balloc_w_dbg_info(ba, type_name); + ecs_os_memset(result, 0, ba->data_size); + return result; +#endif +} + +void flecs_bfree( + ecs_block_allocator_t *ba, + void *memory) +{ + flecs_bfree_w_dbg_info(ba, memory, NULL); +} + +void flecs_bfree_w_dbg_info( + ecs_block_allocator_t *ba, + void *memory, + const char *type_name) +{ + (void)type_name; + +#ifdef FLECS_USE_OS_ALLOC + (void)ba; + ecs_os_free(memory); + return; +#else + + if (!ba) { + ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); + return; + } + + if (memory == NULL) { + return; + } + + if (ba->chunks_per_block <= FLECS_MIN_CHUNKS_PER_BLOCK) { + ecs_os_free(memory); + return; + } + +#ifdef FLECS_SANITIZE + memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); + ecs_block_allocator_t *actual = *(ecs_block_allocator_t**)memory; + if (actual != ba) { + if (type_name) { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub, type = %s)", + memory, actual->data_size, ba->data_size, type_name); + } else { + ecs_err("chunk %p returned to wrong allocator " + "(chunk = %ub, allocator = %ub)", + memory, actual->data_size, ba->chunk_size); + } + ecs_abort(ECS_INTERNAL_ERROR, NULL); + } + + if (ba->outstanding) { + ecs_map_remove(ba->outstanding, (uintptr_t)memory); + } + + ba->alloc_count --; + ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, + "corrupted allocator (size = %d)", ba->chunk_size); +#endif + + ecs_block_allocator_chunk_header_t *chunk = memory; + chunk->next = ba->head; + ba->head = chunk; +#endif +} + +void* flecs_brealloc( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory) +{ + return flecs_brealloc_w_dbg_info(dst, src, memory, NULL); +} + +void* flecs_brealloc_w_dbg_info( + ecs_block_allocator_t *dst, + ecs_block_allocator_t *src, + void *memory, + const char *type_name) +{ + (void)type_name; + + void *result; +#ifdef FLECS_USE_OS_ALLOC + (void)src; + result = ecs_os_realloc(memory, dst->data_size); +#else + if (dst == src) { + return memory; + } + + result = flecs_balloc_w_dbg_info(dst, type_name); + 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_w_dbg_info(src, memory, type_name); +#endif +#ifdef FLECS_MEMSET_UNINITIALIZED + if (dst && src && (dst->data_size > src->data_size)) { + ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, + dst->data_size - src->data_size); + } else if (dst && !src) { + ecs_os_memset(result, 0xAA, dst->data_size); + } +#endif + + return result; +} + +void* flecs_bdup( + ecs_block_allocator_t *ba, + void *memory) +{ +#ifdef FLECS_USE_OS_ALLOC + if (memory && ba->chunk_size) { + return ecs_os_memdup(memory, ba->data_size); + } else { + return NULL; + } +#else + void *result = flecs_balloc(ba); + if (result) { + ecs_os_memcpy(result, memory, ba->data_size); + } + return result; +#endif +} + +// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) +// main repo: https://github.com/wangyi-fudan/wyhash +// author: 王一 Wang Yi +// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, +// Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, +// Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, +// hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric + +/* quick example: + string s="fjsakfdsjkf"; + uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); +*/ + + +#ifndef WYHASH_CONDOM +//protections that produce different results: +//1: normal valid behavior +//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" +#define WYHASH_CONDOM 1 +#endif + +#ifndef WYHASH_32BIT_MUM +//0: normal version, slow on 32 bit systems +//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function +#define WYHASH_32BIT_MUM 0 +#endif + +//includes +#include +#include +#if defined(_MSC_VER) && defined(_M_X64) + #include + #pragma intrinsic(_umul128) +#endif + +//likely and unlikely macros +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + #define likely_(x) __builtin_expect(x,1) + #define unlikely_(x) __builtin_expect(x,0) +#else + #define likely_(x) (x) + #define unlikely_(x) (x) +#endif + +//128bit multiply function +static inline void wymum_(uint64_t *A, uint64_t *B){ +#if(WYHASH_32BIT_MUM) + uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; + #if(WYHASH_CONDOM>1) + *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; + #else + *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; + #endif +#elif defined(__SIZEOF_INT128__) + __uint128_t r=*A; r*=*B; + #if(WYHASH_CONDOM>1) + *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); + #else + *A=(uint64_t)r; *B=(uint64_t)(r>>64); + #endif +#elif defined(_MSC_VER) && defined(_M_X64) + #if(WYHASH_CONDOM>1) + uint64_t a, b; + a=_umul128(*A,*B,&b); + *A^=a; *B^=b; + #else + *A=_umul128(*A,*B,B); + #endif +#else + uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; + uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; + #if(WYHASH_CONDOM>1) + *A^=lo; *B^=hi; + #else + *A=lo; *B=hi; + #endif +#endif +} + +//multiply and xor mix function, aka MUM +static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } + +//endian macros +#ifndef WYHASH_LITTLE_ENDIAN + #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 1 + #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define WYHASH_LITTLE_ENDIAN 0 + #else + #warning could not determine endianness! Falling back to little endian. + #define WYHASH_LITTLE_ENDIAN 1 + #endif +#endif + +//read functions +#if (WYHASH_LITTLE_ENDIAN) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} +#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} +#elif defined(_MSC_VER) +static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} +static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} +#else +static inline uint64_t wyr8_(const uint8_t *p) { + uint64_t v; memcpy(&v, p, 8); + return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); +} +static inline uint64_t wyr4_(const uint8_t *p) { + uint32_t v; memcpy(&v, p, 4); + return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); +} +#endif +static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} + +//wyhash main function +static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ + const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; + if(likely_(len<=16)){ + if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } + else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} + else a=b=0; + } + else{ + size_t i=len; + if(unlikely_(i>48)){ + uint64_t see1=seed, see2=seed; + do{ + seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); + see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); + see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); + p+=48; i-=48; + }while(likely_(i>48)); + seed^=see1^see2; + } + while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } + a=wyr8_(p+i-16); b=wyr8_(p+i-8); + } + a^=secret[1]; b^=seed; wymum_(&a,&b); + return wymix_(a^secret[0]^len,b^secret[1]); +} + +//the default secret parameters +static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; + +uint64_t flecs_hash( + const void *data, + ecs_size_t length) +{ + return wyhash(data, flecs_ito(size_t, length), 0, wyp_); +} + +/** + * @file datastructures/hashmap.c + * @brief Hashmap data structure. + * + * The hashmap data structure is built on top of the map data structure. Where + * the map data structure can only work with 64bit key values, the hashmap can + * hash keys of any size, and handles collisions between hashes. + */ + + +static +int32_t flecs_hashmap_find_key( + const ecs_hashmap_t *map, + ecs_vec_t *keys, + ecs_size_t key_size, + const void *key) +{ + int32_t i, count = ecs_vec_count(keys); + void *key_array = ecs_vec_first(keys); + for (i = 0; i < count; i ++) { + void *key_ptr = ECS_OFFSET(key_array, key_size * i); + if (map->compare(key_ptr, key) == 0) { + return i; + } + } + return -1; +} + +void flecs_hashmap_init_( + ecs_hashmap_t *map, + ecs_size_t key_size, + ecs_size_t value_size, + ecs_hash_value_action_t hash, + ecs_compare_action_t compare, + ecs_allocator_t *allocator) +{ + map->key_size = key_size; + map->value_size = value_size; + map->hash = hash; + map->compare = compare; + flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); + ecs_map_init(&map->impl, allocator); +} + +void flecs_hashmap_fini( + ecs_hashmap_t *map) +{ + ecs_allocator_t *a = map->impl.allocator; + ecs_map_iter_t it = ecs_map_iter(&map->impl); + + while (ecs_map_next(&it)) { + ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); + ecs_vec_fini(a, &bucket->keys, map->key_size); + ecs_vec_fini(a, &bucket->values, map->value_size); +#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) + flecs_bfree(&map->bucket_allocator, bucket); +#endif + } + + flecs_ballocator_fini(&map->bucket_allocator); + ecs_map_fini(&map->impl); +} + +void flecs_hashmap_copy( + ecs_hashmap_t *dst, + const ecs_hashmap_t *src) +{ + ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); + + flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, + src->compare, src->impl.allocator); + ecs_map_copy(&dst->impl, &src->impl); + + ecs_allocator_t *a = dst->impl.allocator; + ecs_map_iter_t it = ecs_map_iter(&dst->impl); + while (ecs_map_next(&it)) { + ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); + ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; + ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); + bucket_ptr[0] = dst_bucket; + dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); + dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); + } +} + +void* flecs_hashmap_get_( + const ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return NULL; + } + + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return NULL; + } + + return ecs_vec_get(&bucket->values, value_size, index); +} + +flecs_hashmap_result_t flecs_hashmap_ensure_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); + ecs_hm_bucket_t *bucket = r[0]; + if (!bucket) { + bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); + } + + ecs_allocator_t *a = map->impl.allocator; + void *value_ptr, *key_ptr; + ecs_vec_t *keys = &bucket->keys; + ecs_vec_t *values = &bucket->values; + if (!keys->array) { + ecs_vec_init(a, &bucket->keys, key_size, 1); + ecs_vec_init(a, &bucket->values, value_size, 1); + keys = &bucket->keys; + values = &bucket->values; + key_ptr = ecs_vec_append(a, keys, key_size); + value_ptr = ecs_vec_append(a, values, value_size); + ecs_os_memcpy(key_ptr, key, key_size); + ecs_os_memset(value_ptr, 0, value_size); + } else { + int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); + if (index == -1) { + key_ptr = ecs_vec_append(a, keys, key_size); + value_ptr = ecs_vec_append(a, values, value_size); + ecs_os_memcpy(key_ptr, key, key_size); + ecs_os_memset(value_ptr, 0, value_size); + } else { + key_ptr = ecs_vec_get(keys, key_size, index); + value_ptr = ecs_vec_get(values, value_size, index); + } + } + + return (flecs_hashmap_result_t){ + .key = key_ptr, .value = value_ptr, .hash = hash + }; +} + +void flecs_hashmap_set_( + ecs_hashmap_t *map, + ecs_size_t key_size, + void *key, + ecs_size_t value_size, + const void *value) +{ + void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; + ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_os_memcpy(value_ptr, value, value_size); +} + +ecs_hm_bucket_t* flecs_hashmap_get_bucket( + const ecs_hashmap_t *map, + uint64_t hash) +{ + ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); +} + +void flecs_hm_bucket_remove( + ecs_hashmap_t *map, + ecs_hm_bucket_t *bucket, + uint64_t hash, + int32_t index) +{ + ecs_vec_remove(&bucket->keys, map->key_size, index); + ecs_vec_remove(&bucket->values, map->value_size, index); + + if (!ecs_vec_count(&bucket->keys)) { + ecs_allocator_t *a = map->impl.allocator; + ecs_vec_fini(a, &bucket->keys, map->key_size); + ecs_vec_fini(a, &bucket->values, map->value_size); + ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); + ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; + flecs_bfree(&map->bucket_allocator, bucket); + } +} + +void flecs_hashmap_remove_w_hash_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size, + uint64_t hash) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + (void)value_size; + + ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, + ecs_hm_bucket_t, hash); + if (!bucket) { + return; + } + + int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); + if (index == -1) { + return; + } + + flecs_hm_bucket_remove(map, bucket, hash, index); +} + +void flecs_hashmap_remove_( + ecs_hashmap_t *map, + ecs_size_t key_size, + const void *key, + ecs_size_t value_size) +{ + ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + + uint64_t hash = map->hash(key); + flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); +} + +flecs_hashmap_iter_t flecs_hashmap_iter( + ecs_hashmap_t *map) +{ + return (flecs_hashmap_iter_t){ + .it = ecs_map_iter(&map->impl) + }; +} + +void* flecs_hashmap_next_( + flecs_hashmap_iter_t *it, + ecs_size_t key_size, + void *key_out, + ecs_size_t value_size) +{ + int32_t index = ++ it->index; + ecs_hm_bucket_t *bucket = it->bucket; + while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { + ecs_map_next(&it->it); + bucket = it->bucket = ecs_map_ptr(&it->it); + if (!bucket) { + return NULL; + } + index = it->index = 0; + } + + if (key_out) { + *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); + } + + return ecs_vec_get(&bucket->values, value_size, index); +} + +/** + * @file datastructures/map.c + * @brief Map data structure. + * + * Map data structure for 64bit keys and dynamic payload size. + */ + + +/* The ratio used to determine whether the map should flecs_map_rehash. If + * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ +#define ECS_LOAD_FACTOR (12) +#define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) +#define ECS_MAP_ALLOC(a, T) a ? flecs_alloc_t(a, T) : ecs_os_malloc_t(T) +#define ECS_MAP_CALLOC_N(a, T, n) a ? flecs_calloc_n(a, T, n) : ecs_os_calloc_n(T, n) +#define ECS_MAP_FREE(a, T, ptr) a ? flecs_free_t(a, T, ptr) : ecs_os_free(ptr) +#define ECS_MAP_FREE_N(a, T, n, ptr) a ? flecs_free_n(a, T, n, ptr) : ecs_os_free(ptr) + +static +uint8_t flecs_log2(uint32_t v) { + static const uint8_t log2table[32] = + {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; +} + +/* Get bucket count for number of elements */ +static +int32_t flecs_map_get_bucket_count( + int32_t count) +{ + return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); +} + +/* Get bucket shift amount for a given bucket count */ +static +uint8_t flecs_map_get_bucket_shift( + int32_t bucket_count) +{ + return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); +} + +/* Get bucket index for provided map key */ +static +int32_t flecs_map_get_bucket_index( + uint16_t bucket_shift, + ecs_map_key_t key) +{ + ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); + return (int32_t)((11400714819323198485ull * key) >> bucket_shift); +} + +/* Get bucket for key */ +static +ecs_bucket_t* flecs_map_get_bucket( + const ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t bucket_id = flecs_map_get_bucket_index((uint16_t)map->bucket_shift, key); + ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); + return &map->buckets[bucket_id]; +} + +/* Add element to bucket */ +static +ecs_map_val_t* flecs_map_bucket_add( + ecs_allocator_t *a, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *new_entry = ECS_MAP_ALLOC(a, ecs_bucket_entry_t); + new_entry->key = key; + new_entry->next = bucket->first; + bucket->first = new_entry; + return &new_entry->value; +} + +/* Remove element from bucket */ +static +ecs_map_val_t flecs_map_bucket_remove( + ecs_map_t *map, + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + ecs_map_val_t value = entry->value; + ecs_bucket_entry_t **next_holder = &bucket->first; + while(*next_holder != entry) { + next_holder = &(*next_holder)->next; + } + *next_holder = entry->next; + ECS_MAP_FREE(map->allocator, ecs_bucket_entry_t, entry); + map->count --; + return value; + } + } + + return 0; +} + +/* Free contents of bucket */ +static +void flecs_map_bucket_clear( + ecs_allocator_t *allocator, + ecs_bucket_t *bucket) +{ + ecs_bucket_entry_t *entry = bucket->first; + while(entry) { + ecs_bucket_entry_t *next = entry->next; + ECS_MAP_FREE(allocator, ecs_bucket_entry_t, entry); + entry = next; + } +} + +/* Get payload pointer for key from bucket */ +static +ecs_map_val_t* flecs_map_bucket_get( + ecs_bucket_t *bucket, + ecs_map_key_t key) +{ + ecs_bucket_entry_t *entry; + for (entry = bucket->first; entry; entry = entry->next) { + if (entry->key == key) { + return &entry->value; + } + } + return NULL; +} + +/* Grow number of buckets */ +static +void flecs_map_rehash( + ecs_map_t *map, + int32_t count) +{ + count = flecs_next_pow_of_2(count); + if (count < 2) { + count = 2; + } + ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); + + int32_t old_count = map->bucket_count; + ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); + + map->buckets = ECS_MAP_CALLOC_N(map->allocator, ecs_bucket_t, count); + map->bucket_count = count; + map->bucket_shift = flecs_map_get_bucket_shift(count) & 0x3fu; + + /* 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( + (uint16_t)map->bucket_shift, entry->key); + ecs_bucket_t *bucket = &map->buckets[bucket_index]; + entry->next = bucket->first; + bucket->first = entry; + entry = next; + } + } + + ECS_MAP_FREE_N(map->allocator, ecs_bucket_t, old_count, buckets); +} + +void ecs_map_params_init( + ecs_map_params_t *params, + ecs_allocator_t *allocator) +{ + params->allocator = allocator; +} + +void ecs_map_params_fini( + ecs_map_params_t *params) +{ + flecs_ballocator_fini(¶ms->entry_allocator); +} + +void ecs_map_init_w_params( + ecs_map_t *result, + ecs_map_params_t *params) +{ + ecs_os_zeromem(result); + + result->allocator = params->allocator; + + flecs_map_rehash(result, 0); +} + +void ecs_map_init_w_params_if( + ecs_map_t *result, + ecs_map_params_t *params) +{ + if (!ecs_map_is_init(result)) { + ecs_map_init_w_params(result, params); + } +} + +void ecs_map_init( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + ecs_map_init_w_params(result, &(ecs_map_params_t) { + .allocator = allocator + }); +} + +void ecs_map_init_if( + ecs_map_t *result, + ecs_allocator_t *allocator) +{ + if (!ecs_map_is_init(result)) { + ecs_map_init(result, allocator); + } +} + +void ecs_map_fini( + ecs_map_t *map) +{ + if (!ecs_map_is_init(map)) { + return; + } + + ecs_allocator_t *a = map->allocator; + ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; + while (bucket != end) { + flecs_map_bucket_clear(a, bucket); + bucket ++; + } + + map->bucket_shift = 0; + + ECS_MAP_FREE_N(a, ecs_bucket_t, map->bucket_count, map->buckets); +} + +ecs_map_val_t* ecs_map_get( + const ecs_map_t *map, + ecs_map_key_t key) +{ + return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); +} + +void* ecs_map_get_deref_( + const ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_map_val_t* ptr = flecs_map_bucket_get( + flecs_map_get_bucket(map, key), key); + if (ptr) { + return (void*)(uintptr_t)ptr[0]; + } + return NULL; +} + +void ecs_map_insert( + ecs_map_t *map, + ecs_map_key_t key, + ecs_map_val_t value) +{ + ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); + int32_t map_count = ++map->count; + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); + int32_t bucket_count = map->bucket_count; + if (tgt_bucket_count > bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + } + + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + flecs_map_bucket_add(map->allocator, bucket, key)[0] = value; +} + +void* ecs_map_insert_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + void *elem = ecs_os_calloc(elem_size); + ecs_map_insert_ptr(map, key, (uintptr_t)elem); + return elem; +} + +ecs_map_val_t* ecs_map_ensure( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); + ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); + if (result) { + return result; + } + + int32_t map_count = ++map->count; + int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); + int32_t bucket_count = map->bucket_count; + if (tgt_bucket_count > bucket_count) { + flecs_map_rehash(map, tgt_bucket_count); + bucket = flecs_map_get_bucket(map, key); + } + + ecs_map_val_t* v = flecs_map_bucket_add(map->allocator, bucket, key); + *v = 0; + return v; +} + +void* ecs_map_ensure_alloc( + ecs_map_t *map, + ecs_size_t elem_size, + ecs_map_key_t key) +{ + ecs_map_val_t *val = ecs_map_ensure(map, key); + if (!*val) { + void *elem = ecs_os_calloc(elem_size); + *val = (ecs_map_val_t)(uintptr_t)elem; + return elem; + } else { + return (void*)(uintptr_t)*val; + } +} + +ecs_map_val_t ecs_map_remove( + ecs_map_t *map, + ecs_map_key_t key) +{ + return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); +} + +void ecs_map_remove_free( + ecs_map_t *map, + ecs_map_key_t key) +{ + ecs_map_val_t val = ecs_map_remove(map, key); + if (val) { + ecs_os_free((void*)(uintptr_t)val); + } +} + +void ecs_map_clear( + ecs_map_t *map) +{ + ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); + int32_t i, count = map->bucket_count; + for (i = 0; i < count; i ++) { + flecs_map_bucket_clear(map->allocator, &map->buckets[i]); + } + ECS_MAP_FREE_N(map->allocator, ecs_bucket_t, count, map->buckets); + map->buckets = NULL; + map->bucket_count = 0; + map->count = 0; + flecs_map_rehash(map, 2); +} + +ecs_map_iter_t ecs_map_iter( + const ecs_map_t *map) +{ + if (ecs_map_is_init(map)) { + return (ecs_map_iter_t){ + .map = map, + .bucket = NULL, + .entry = NULL + }; + } else { + return (ecs_map_iter_t){ 0 }; + } +} + +bool ecs_map_next( + ecs_map_iter_t *iter) +{ + const ecs_map_t *map = iter->map; + ecs_bucket_t *end; + if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { + return false; + } + + ecs_bucket_entry_t *entry = NULL; + if (!iter->bucket) { + for (iter->bucket = map->buckets; + iter->bucket != end; + ++iter->bucket) + { + if (iter->bucket->first) { + entry = iter->bucket->first; + break; + } + } + if (iter->bucket == end) { + return false; + } + } else if ((entry = iter->entry) == NULL) { + do { + ++iter->bucket; + if (iter->bucket == end) { + return false; + } + } while(!iter->bucket->first); + entry = iter->bucket->first; + } + + ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); + iter->entry = entry->next; + iter->res = &entry->key; - ecs_doc_set_brief(world, EcsDataRate, - "Units of data transmission (e.g. \"100 megabits/second\")"); + return true; +} - ecs_doc_set_brief(world, EcsAngle, - "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); +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_doc_set_brief(world, EcsFrequency, - "The number of occurrences of a repeating event per unit of time."); + ecs_map_init(dst, src->allocator); - ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); -#endif + 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 datastructures/allocator.c - * @brief Allocator for any size. - * - * Allocators create a block allocator for each requested size. + * @file datastructures/name_index.c + * @brief Data structure for resolving 64bit keys by string (name). */ static -ecs_size_t flecs_allocator_size( - ecs_size_t size) +uint64_t flecs_name_index_hash( + const void *ptr) { - return ECS_ALIGN(size, 16); + const ecs_hashed_string_t *str = ptr; + ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); + return str->hash; } static -ecs_size_t flecs_allocator_size_hash( - ecs_size_t size) +int flecs_name_index_compare( + const void *ptr1, + const void *ptr2) { - return size >> 4; + const ecs_hashed_string_t *str1 = ptr1; + const ecs_hashed_string_t *str2 = ptr2; + ecs_size_t len1 = str1->length; + ecs_size_t len2 = str2->length; + if (len1 != len2) { + return (len1 > len2) - (len1 < len2); + } + + return ecs_os_memcmp(str1->value, str2->value, len1); } -void flecs_allocator_init( - ecs_allocator_t *a) +void flecs_name_index_init( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) { - 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); + flecs_hashmap_init_(hm, + ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), + flecs_name_index_hash, + flecs_name_index_compare, + allocator); } -void flecs_allocator_fini( - ecs_allocator_t *a) +void flecs_name_index_init_if( + ecs_hashmap_t *hm, + ecs_allocator_t *allocator) { - ecs_assert(a != NULL, ECS_INVALID_PARAMETER, NULL); - - 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); + if (!hm->compare) { + flecs_name_index_init(hm, allocator); } - flecs_sparse_fini(&a->sizes); +} - flecs_ballocator_fini(&a->chunks); +bool flecs_name_index_is_init( + const ecs_hashmap_t *hm) +{ + return hm->compare != NULL; } -ecs_block_allocator_t* flecs_allocator_get( - ecs_allocator_t *a, - ecs_size_t size) +ecs_hashmap_t* flecs_name_index_new( + ecs_world_t *world, + ecs_allocator_t *allocator) { - ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); - if (!size) { - return NULL; - } + ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); + flecs_name_index_init(result, allocator); + result->hashmap_allocator = &world->allocators.hashmap; + return result; +} - 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); +void flecs_name_index_fini( + ecs_hashmap_t *map) +{ + flecs_hashmap_fini(map); +} - if (!result) { - result = flecs_sparse_ensure_fast_t(&a->sizes, - ecs_block_allocator_t, (uint32_t)hash); - flecs_ballocator_init(result, size); +void flecs_name_index_free( + ecs_hashmap_t *map) +{ + if (map) { + flecs_name_index_fini(map); + flecs_bfree(map->hashmap_allocator, map); } - - ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); - - return result; } -char* flecs_strdup( - ecs_allocator_t *a, - const char* str) +ecs_hashmap_t* flecs_name_index_copy( + ecs_hashmap_t *map) { - ecs_size_t len = ecs_os_strlen(str); - char *result = flecs_alloc_n(a, char, len + 1); - ecs_os_memcpy(result, str, len + 1); + ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); + result->hashmap_allocator = map->hashmap_allocator; + flecs_hashmap_copy(result, map); return result; } -void flecs_strfree( - ecs_allocator_t *a, - char* str) +ecs_hashed_string_t flecs_get_hashed_string( + const char *name, + ecs_size_t length, + uint64_t hash) { - ecs_size_t len = ecs_os_strlen(str); - flecs_free_n(a, char, len + 1, str); + if (!length) { + length = ecs_os_strlen(name); + } else { + ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); + } + + if (!hash) { + hash = flecs_hash(name, length); + } else { + ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); + } + + return (ecs_hashed_string_t) { + .value = ECS_CONST_CAST(char*, name), + .length = length, + .hash = hash + }; } -void* flecs_dup( - ecs_allocator_t *a, - ecs_size_t size, - const void *src) +const uint64_t* flecs_name_index_find_ptr( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) { - 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 { + 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; } -} -/** - * @file datastructures/bitset.c - * @brief Bitset data structure. - * - * Simple bitset implementation. The bitset allows for storage of arbitrary - * numbers of bits. - */ + ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); + int32_t i, count = ecs_vec_count(&b->keys); + for (i = 0; i < count; i ++) { + ecs_hashed_string_t *key = &keys[i]; + ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); -static -void ensure( - ecs_bitset_t *bs, - ecs_size_t size) -{ - if (!bs->size) { - int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->size = ((size - 1) / 64 + 1) * 64; - bs->data = ecs_os_calloc(new_size); - } else if (size > bs->size) { - int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->size = ((size - 1) / 64 + 1) * 64; - int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); - bs->data = ecs_os_realloc(bs->data, new_size); - ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); + if (hs.length != key->length) { + continue; + } + + if (!ecs_os_strcmp(name, key->value)) { + uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + return e; + } } -} -void flecs_bitset_init( - ecs_bitset_t* bs) -{ - bs->size = 0; - bs->count = 0; - bs->data = NULL; + return NULL; } -void flecs_bitset_ensure( - ecs_bitset_t *bs, - int32_t count) +uint64_t flecs_name_index_find( + const ecs_hashmap_t *map, + const char *name, + ecs_size_t length, + uint64_t hash) { - if (count > bs->count) { - bs->count = count; - ensure(bs, count); + const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); + if (id) { + return id[0]; } + return 0; } -void flecs_bitset_fini( - ecs_bitset_t *bs) +void flecs_name_index_remove( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash) { - ecs_os_free(bs->data); - bs->data = NULL; - bs->count = 0; -} + ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); + if (!b) { + return; + } -void flecs_bitset_addn( - ecs_bitset_t *bs, - int32_t count) -{ - int32_t elem = bs->count += count; - ensure(bs, elem); + uint64_t *ids = ecs_vec_first(&b->values); + int32_t i, count = ecs_vec_count(&b->values); + for (i = 0; i < count; i ++) { + if (ids[i] == e) { + flecs_hm_bucket_remove(map, b, hash, i); + break; + } + } } -void flecs_bitset_set( - ecs_bitset_t *bs, - int32_t elem, - bool value) +void flecs_name_index_update_name( + ecs_hashmap_t *map, + uint64_t e, + uint64_t hash, + const char *name) { - 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_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); + if (!b) { + return; + } -bool flecs_bitset_get( - const ecs_bitset_t *bs, - int32_t elem) -{ - ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); - return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); -error: - return false; -} + 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; + } + } -int32_t flecs_bitset_count( - const ecs_bitset_t *bs) -{ - return bs->count; + /* Record must already have been in the index */ + ecs_abort(ECS_INTERNAL_ERROR, NULL); } -void flecs_bitset_remove( - ecs_bitset_t *bs, - int32_t elem) +void flecs_name_index_ensure( + ecs_hashmap_t *map, + uint64_t id, + const char *name, + ecs_size_t length, + uint64_t hash) { - 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(name != NULL, 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_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); + } + } - 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); + flecs_hashmap_result_t hmr = flecs_hashmap_ensure( + map, &key, uint64_t); + *((uint64_t*)hmr.value) = id; 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. + * @file datastructures/sparse.c + * @brief Sparse set data structure. */ -// #ifdef FLECS_SANITIZE -// #define FLECS_MEMSET_UNINITIALIZED -// #endif - -int64_t ecs_block_allocator_alloc_count = 0; -int64_t ecs_block_allocator_free_count = 0; +/* Utility to get a pointer to the payload */ +#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) -#ifndef FLECS_USE_OS_ALLOC +typedef struct ecs_sparse_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_sparse_page_t; static -ecs_block_allocator_chunk_header_t* flecs_balloc_block( - ecs_block_allocator_t *allocator) +ecs_sparse_page_t* flecs_sparse_page_new( + ecs_sparse_t *sparse, + int32_t page_index) { - if (!allocator->chunk_size) { - return NULL; - } - - ecs_block_allocator_block_t *block = - ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + - allocator->block_size); - ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, - ECS_SIZEOF(ecs_block_allocator_block_t)); + ecs_allocator_t *a = sparse->allocator; + ecs_block_allocator_t *ca = sparse->page_allocator; + int32_t count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages; - 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; + if (count <= page_index) { + ecs_vec_set_count_t(a, &sparse->pages, ecs_sparse_page_t, page_index + 1); + pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); + ecs_os_memset_n(&pages[count], 0, ecs_sparse_page_t, (1 + page_index - count)); } else { - block->next = NULL; - allocator->block_tail->next = block; - allocator->block_tail = block; + pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); } - ecs_block_allocator_chunk_header_t *chunk = first_chunk; - int32_t i, end; - for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { - chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); - chunk = chunk->next; - } + ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_linc(&ecs_block_allocator_alloc_count); + ecs_sparse_page_t *result = &pages[page_index]; + ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); - chunk->next = NULL; - return first_chunk; -} + /* 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); -#endif + /* 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); -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 - ba->alloc_count = 0; - if (size != 24) { /* Prevent stack overflow as map uses block allocator */ - ba->outstanding = ecs_os_malloc_t(ecs_map_t); - ecs_map_init(ba->outstanding, NULL); - } - 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_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); -ecs_block_allocator_t* flecs_ballocator_new( - ecs_size_t size) -{ - ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); - flecs_ballocator_init(result, size); return result; } -void flecs_ballocator_fini( - ecs_block_allocator_t *ba) +static +void flecs_sparse_page_free( + ecs_sparse_t *sparse, + ecs_sparse_page_t *page) { - ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = sparse->allocator; + ecs_block_allocator_t *ca = sparse->page_allocator; -#ifdef FLECS_SANITIZE - if (ba->alloc_count != 0) { - ecs_err("Leak detected! (size %u, remaining = %d)", - (uint32_t)ba->data_size, ba->alloc_count); - if (ba->outstanding) { - ecs_map_iter_t it = ecs_map_iter(ba->outstanding); - while (ecs_map_next(&it)) { - uint64_t key = ecs_map_key(&it); - char *type_name = ecs_map_ptr(&it); - if (type_name) { - printf(" - %p (%s)\n", (void*)key, type_name); - } else { - printf(" - %p (unknown type)\n", (void*)key); - } - } - } - ecs_abort(ECS_LEAK_DETECTED, NULL); + if (ca) { + flecs_bfree(ca, page->sparse); + } else { + ecs_os_free(page->sparse); } - if (ba->outstanding) { - ecs_map_fini(ba->outstanding); - ecs_os_free(ba->outstanding); + if (a) { + flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); + } else { + ecs_os_free(page->data); + } +} + +static +ecs_sparse_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; } -#endif + return ecs_vec_get_t(&sparse->pages, ecs_sparse_page_t, page_index); +} - 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; +static +ecs_sparse_page_t* flecs_sparse_get_or_create_page( + ecs_sparse_t *sparse, + int32_t page_index) +{ + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, page_index); + if (page && page->sparse) { + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); + return page; } - ba->block_head = NULL; + return flecs_sparse_page_new(sparse, page_index); } -void flecs_ballocator_free( - ecs_block_allocator_t *ba) +static +void flecs_sparse_grow_dense( + ecs_sparse_t *sparse) { - flecs_ballocator_fini(ba); - ecs_os_free(ba); + ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } -void* flecs_balloc( - ecs_block_allocator_t *ba) +static +uint64_t flecs_sparse_strip_generation( + uint64_t *index_out) { - return flecs_balloc_w_dbg_info(ba, NULL); + 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; } -void* flecs_balloc_w_dbg_info( - ecs_block_allocator_t *ba, - const char *type_name) +static +void flecs_sparse_assign_index( + ecs_sparse_page_t * page, + uint64_t * dense_array, + uint64_t index, + int32_t dense) { - (void)type_name; - 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); - ecs_assert(ba->head != NULL, ECS_INTERNAL_ERROR, NULL); - } - - result = ba->head; - ba->head = ba->head->next; - -#ifdef FLECS_SANITIZE - ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); - if (ba->outstanding) { - uint64_t *v = ecs_map_ensure(ba->outstanding, (uintptr_t)result); - *(const char**)v = type_name; - } - ba->alloc_count ++; - *(int64_t*)result = (uintptr_t)ba; - 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; + /* Initialize sparse-dense pair. This assigns the dense index to the sparse + * array, and the sparse index to the dense array .*/ + page->sparse[FLECS_SPARSE_OFFSET(index)] = dense; + dense_array[dense] = index; } -void* flecs_bcalloc( - ecs_block_allocator_t *ba) +static +uint64_t flecs_sparse_inc_gen( + uint64_t index) { - return flecs_bcalloc_w_dbg_info(ba, NULL); + /* When an index is deleted, its generation is increased so that we can do + * liveliness checking while recycling ids */ + return ECS_GENERATION_INC(index); } -void* flecs_bcalloc_w_dbg_info( - ecs_block_allocator_t *ba, - const char *type_name) +static +uint64_t flecs_sparse_inc_id( + ecs_sparse_t *sparse) { - (void)type_name; - -#ifdef FLECS_USE_OS_ALLOC - ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_os_calloc(ba->data_size); -#else - if (!ba) return NULL; - void *result = flecs_balloc_w_dbg_info(ba, type_name); - ecs_os_memset(result, 0, ba->data_size); - return result; -#endif + /* 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; } -void flecs_bfree( - ecs_block_allocator_t *ba, - void *memory) +static +uint64_t flecs_sparse_get_id( + const ecs_sparse_t *sparse) { - flecs_bfree_w_dbg_info(ba, memory, NULL); + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + return sparse->max_id; } -void flecs_bfree_w_dbg_info( - ecs_block_allocator_t *ba, - void *memory, - const char *type_name) +static +void flecs_sparse_set_id( + ecs_sparse_t *sparse, + uint64_t value) { - (void)type_name; - -#ifdef FLECS_USE_OS_ALLOC - (void)ba; - ecs_os_free(memory); - return; -#else - - 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)); - ecs_block_allocator_t *actual = *(ecs_block_allocator_t**)memory; - if (actual != ba) { - if (type_name) { - ecs_err("chunk %p returned to wrong allocator " - "(chunk = %ub, allocator = %ub, type = %s)", - memory, actual->data_size, ba->data_size, type_name); - } else { - ecs_err("chunk %p returned to wrong allocator " - "(chunk = %ub, allocator = %ub)", - memory, actual->data_size, ba->chunk_size); - } - ecs_abort(ECS_INTERNAL_ERROR, NULL); - } - - if (ba->outstanding) { - ecs_map_remove(ba->outstanding, (uintptr_t)memory); - } - - ba->alloc_count --; - ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, - "corrupted allocator (size = %d)", ba->chunk_size); -#endif - - ecs_block_allocator_chunk_header_t *chunk = memory; - chunk->next = ba->head; - ba->head = chunk; -#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; } -void* flecs_brealloc( - ecs_block_allocator_t *dst, - ecs_block_allocator_t *src, - void *memory) +/* Pair dense id with new sparse id */ +static +uint64_t flecs_sparse_create_id( + ecs_sparse_t *sparse, + int32_t dense) { - return flecs_brealloc_w_dbg_info(dst, src, memory, NULL); + uint64_t index = flecs_sparse_inc_id(sparse); + flecs_sparse_grow_dense(sparse); + + ecs_sparse_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); + ecs_assert(page->sparse[FLECS_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; } -void* flecs_brealloc_w_dbg_info( - ecs_block_allocator_t *dst, - ecs_block_allocator_t *src, - void *memory, - const char *type_name) +/* Create new id */ +static +uint64_t flecs_sparse_new_index( + ecs_sparse_t *sparse) { - (void)type_name; - - void *result; -#ifdef FLECS_USE_OS_ALLOC - (void)src; - result = ecs_os_realloc(memory, dst->data_size); -#else - if (dst == src) { - return memory; - } + int32_t dense_count = ecs_vec_count(&sparse->dense); + int32_t count = sparse->count ++; - result = flecs_balloc_w_dbg_info(dst, type_name); - 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_w_dbg_info(src, memory, type_name); -#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); + 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); } -#endif - - return result; } -void* flecs_bdup( - ecs_block_allocator_t *ba, - void *memory) +/* 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) { -#ifdef FLECS_USE_OS_ALLOC - if (memory && ba->chunk_size) { - return ecs_os_memdup(memory, ba->data_size); - } else { + flecs_sparse_strip_generation(&index); + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { return NULL; } -#else - void *result = flecs_balloc(ba); - if (result) { - ecs_os_memcpy(result, memory, ba->data_size); - } - return result; -#endif -} - -// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) -// main repo: https://github.com/wangyi-fudan/wyhash -// author: 王一 Wang Yi -// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, -// Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, -// Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, -// hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric - -/* quick example: - string s="fjsakfdsjkf"; - uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); -*/ - - -#ifndef WYHASH_CONDOM -//protections that produce different results: -//1: normal valid behavior -//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" -#define WYHASH_CONDOM 1 -#endif -#ifndef WYHASH_32BIT_MUM -//0: normal version, slow on 32 bit systems -//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function -#define WYHASH_32BIT_MUM 0 -#endif + int32_t offset = FLECS_SPARSE_OFFSET(index); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + (void)dense; -//includes -#include -#include -#if defined(_MSC_VER) && defined(_M_X64) - #include - #pragma intrinsic(_umul128) -#endif + return DATA(page->data, sparse->size, offset); +} -//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 +/* 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_sparse_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]; -//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 + ecs_sparse_page_t *page_b = flecs_sparse_get_or_create_page(sparse, FLECS_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); } -//multiply and xor mix function, aka MUM -static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } +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; -//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_vec_init_t(allocator, &result->pages, ecs_sparse_page_t, 0); + ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); + result->dense.count = 1; -//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];} + /* 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; -//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]); + result->count = 1; } -//the default secret parameters -static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; - -uint64_t flecs_hash( - const void *data, - ecs_size_t length) +void flecs_sparse_clear( + ecs_sparse_t *sparse) { - return wyhash(data, flecs_ito(size_t, length), 0, wyp_); -} - -/** - * @file datastructures/hashmap.c - * @brief Hashmap data structure. - * - * The hashmap data structure is built on top of the map data structure. Where - * the map data structure can only work with 64bit key values, the hashmap can - * hash keys of any size, and handles collisions between hashes. - */ - + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); -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); + int32_t i, count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); 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; + int32_t *indices = pages[i].sparse; + if (indices) { + ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); } } - 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_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); + + sparse->count = 1; + sparse->max_id = 0; } -void flecs_hashmap_fini( - ecs_hashmap_t *map) +void flecs_sparse_fini( + ecs_sparse_t *sparse) { - 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); -#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) - flecs_bfree(&map->bucket_allocator, bucket); -#endif + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); + for (i = 0; i < count; i ++) { + flecs_sparse_page_free(sparse, &pages[i]); } - flecs_ballocator_fini(&map->bucket_allocator); - ecs_map_fini(&map->impl); + ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_sparse_page_t); + ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } -void flecs_hashmap_copy( - ecs_hashmap_t *dst, - const ecs_hashmap_t *src) +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse) { - ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + return flecs_sparse_new_index(sparse); +} - flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, - src->compare, src->impl.allocator); - ecs_map_copy(&dst->impl, &src->impl); +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_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, size, FLECS_SPARSE_OFFSET(index)); +} - 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); - } +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse) +{ + ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + return dense_array[sparse->count - 1]; } -void* flecs_hashmap_get_( - const ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) +void* flecs_sparse_ensure( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; - uint64_t hash = map->hash(key); - ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, - ecs_hm_bucket_t, hash); - if (!bucket) { - return NULL; - } + uint64_t gen = flecs_sparse_strip_generation(&index); + ecs_sparse_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; - int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); - if (index == -1) { - return 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; + + /* First unused element is now last used element */ + sparse->count ++; + + /* Set dense element to new generation */ + ecs_vec_first_t(&sparse->dense, uint64_t)[dense] = index | gen; + } 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_sparse_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, FLECS_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_vec_get(&bucket->values, value_size, index); + return DATA(page->data, sparse->size, offset); } -flecs_hashmap_result_t flecs_hashmap_ensure_( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) +void* flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index_long) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; - uint64_t 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); - } + uint32_t index = (uint32_t)index_long; + ecs_sparse_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + int32_t count = sparse->count; - 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) { - ecs_vec_init(a, &bucket->keys, key_size, 1); - ecs_vec_init(a, &bucket->values, value_size, 1); - keys = &bucket->keys; - values = &bucket->values; - 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); + 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 (flecs_hashmap_result_t){ - .key = key_ptr, .value = value_ptr, .hash = hash - }; + return DATA(page->data, sparse->size, offset); } -void flecs_hashmap_set_( - ecs_hashmap_t *map, - ecs_size_t key_size, - void *key, - ecs_size_t value_size, - const void *value) +void* flecs_sparse_remove_fast( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - 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_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; -ecs_hm_bucket_t* flecs_hashmap_get_bucket( - const ecs_hashmap_t *map, - uint64_t hash) -{ - ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); - return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); -} + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return 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); + flecs_sparse_strip_generation(&index); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; - 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); + if (dense) { + 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 --; + } + + /* Reset memory to zero on remove */ + return DATA(page->data, sparse->size, offset); + } else { + /* Element is not paired and thus not alive, nothing to be done */ + return NULL; } } -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) + +void flecs_sparse_remove( + ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - 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_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; - ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, - ecs_hm_bucket_t, hash); - if (!bucket) { + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { return; } - int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); - if (index == -1) { + uint64_t gen = flecs_sparse_strip_generation(&index); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + + if (dense) { + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + if (gen != cur_gen) { + /* Generation doesn't match which means that the provided entity is + * already not alive. */ + return; + } + + /* Increase generation */ + dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen); + + int32_t count = sparse->count; + + if (dense == (count - 1)) { + /* If dense is the last used element, simply decrease count */ + sparse->count --; + } else if (dense < count) { + /* If element is alive, move it to unused elements */ + flecs_sparse_swap_dense(sparse, page, dense, count - 1); + sparse->count --; + } else { + /* Element is not alive, nothing to be done */ + return; + } + + /* Reset memory to zero on remove */ + void *ptr = DATA(page->data, sparse->size, offset); + ecs_os_memset(ptr, 0, size); + } else { + /* Element is not paired and thus not alive, nothing to be done */ return; } - - flecs_hm_bucket_remove(map, bucket, hash, index); } -void flecs_hashmap_remove_( - ecs_hashmap_t *map, - ecs_size_t key_size, - const void *key, - ecs_size_t value_size) +void* flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t size, + int32_t dense_index) { - ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); + 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; - uint64_t hash = map->hash(key); - flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); -} + dense_index ++; -flecs_hashmap_iter_t flecs_hashmap_iter( - ecs_hashmap_t *map) -{ - return (flecs_hashmap_iter_t){ - .it = ecs_map_iter(&map->impl) - }; + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); } -void* flecs_hashmap_next_( - flecs_hashmap_iter_t *it, - ecs_size_t key_size, - void *key_out, - ecs_size_t value_size) +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t index) { - 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; + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return false; } - if (key_out) { - *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return false; } - - return ecs_vec_get(&bucket->values, value_size, index); -} - -/** - * @file datastructures/map.c - * @brief Map data structure. - * - * Map data structure for 64bit keys and dynamic payload size. - */ + 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; -/* 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}; + if (cur_gen != gen) { + return false; + } - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return true; } -/* Get bucket count for number of elements */ -static -int32_t flecs_map_get_bucket_count( - int32_t count) +void* flecs_sparse_try( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); -} + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } -/* Get bucket shift amount for a given bucket count */ -static -uint8_t flecs_map_get_bucket_shift ( - int32_t bucket_count) -{ - return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); -} + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + if (!dense || (dense >= sparse->count)) { + return 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_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); - return (int32_t)((11400714819323198485ull * key) >> bucket_shift); -} + 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; + } -/* 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]; + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, 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) +void* flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - 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; + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; + + ecs_sparse_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_sparse_page_t, FLECS_SPARSE_PAGE(index)); + int32_t offset = FLECS_SPARSE_OFFSET(index); + int32_t dense = page->sparse[offset]; + ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t gen = flecs_sparse_strip_generation(&index); + uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); + uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; + (void)cur_gen; (void)gen; + + ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); + ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); + ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); + return DATA(page->data, sparse->size, offset); } -/* 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) +void* flecs_sparse_get_any( + const ecs_sparse_t *sparse, + ecs_size_t size, + uint64_t index) { - 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; - } - } + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); + (void)size; - return 0; + flecs_sparse_strip_generation(&index); + ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); + if (!page || !page->sparse) { + return NULL; + } + + int32_t offset = FLECS_SPARSE_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); } -/* Free contents of bucket */ -static -void flecs_map_bucket_clear( - ecs_block_allocator_t *allocator, - ecs_bucket_t *bucket) +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse) { - ecs_bucket_entry_t *entry = bucket->first; - while(entry) { - ecs_bucket_entry_t *next = entry->next; - flecs_bfree(allocator, entry); - entry = next; + if (!sparse || !sparse->count) { + return 0; } + + return sparse->count - 1; } -/* 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) +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse) { - ecs_bucket_entry_t *entry; - for (entry = bucket->first; entry; entry = entry->next) { - if (entry->key == key) { - return &entry->value; - } + 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; } - return NULL; } -/* Grow number of buckets */ -static -void flecs_map_rehash( - ecs_map_t *map, - int32_t count) +void flecs_sparse_shrink( + ecs_sparse_t *sparse) { - 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); + int32_t i, e, max_page_index = 0, count = ecs_vec_count(&sparse->pages); + ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, + ecs_sparse_page_t); + for (i = 0; i < count; i ++) { + ecs_sparse_page_t *page = &pages[i]; + if (!page->sparse) { + ecs_assert(page->data == NULL, ECS_INTERNAL_ERROR, NULL); + continue; + } - 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); + bool has_alive = false; + for (e = 0; e < FLECS_SPARSE_PAGE_SIZE; e ++) { + uint64_t index = + flecs_ito(uint64_t, (i * FLECS_SPARSE_PAGE_SIZE) + e); - /* 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 (flecs_sparse_is_alive(sparse, index)) { + has_alive = true; + break; + } } - } - if (map->allocator) { - flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); - } else { - ecs_os_free(buckets); + if (!has_alive) { + flecs_sparse_page_free(sparse, page); + } else { + max_page_index = i; + } } + + ecs_vec_set_count_t( + sparse->allocator, &sparse->pages, ecs_sparse_page_t, + max_page_index + 1); + ecs_vec_reclaim_t(sparse->allocator, &sparse->pages, ecs_sparse_page_t); } -void ecs_map_params_init( - ecs_map_params_t *params, - ecs_allocator_t *allocator) +void ecs_sparse_init( + ecs_sparse_t *sparse, + ecs_size_t elem_size) { - params->allocator = allocator; - flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); + flecs_sparse_init(sparse, NULL, NULL, elem_size); } -void ecs_map_params_fini( - ecs_map_params_t *params) +void* ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size) { - flecs_ballocator_fini(¶ms->entry_allocator); + return flecs_sparse_add(sparse, elem_size); } -void ecs_map_init_w_params( - ecs_map_t *result, - ecs_map_params_t *params) +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse) { - ecs_os_zeromem(result); - - result->allocator = params->allocator; - - if (params->entry_allocator.chunk_size) { - result->entry_allocator = ¶ms->entry_allocator; - result->shared_allocator = true; - } else { - result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); - } - - flecs_map_rehash(result, 0); + return flecs_sparse_last_id(sparse); } -void ecs_map_init_w_params_if( - ecs_map_t *result, - ecs_map_params_t *params) +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse) { - if (!ecs_map_is_init(result)) { - ecs_map_init_w_params(result, params); - } + return flecs_sparse_count(sparse); } -void ecs_map_init( - ecs_map_t *result, - ecs_allocator_t *allocator) +void* ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index) { - ecs_map_init_w_params(result, &(ecs_map_params_t) { - .allocator = allocator - }); + return flecs_sparse_get_dense(sparse, elem_size, index); } -void ecs_map_init_if( - ecs_map_t *result, - ecs_allocator_t *allocator) +void* ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id) { - if (!ecs_map_is_init(result)) { - ecs_map_init(result, allocator); - } + return flecs_sparse_get(sparse, elem_size, id); } -void ecs_map_fini( - ecs_map_t *map) +/** + * @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; + result->sp = 0; + 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) { - if (!ecs_map_is_init(map)) { - return; - } + ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); - bool sanitize = false; -#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) - sanitize = true; -#endif + ecs_stack_page_t *page = stack->tail_page; + ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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 ++; + int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); + int16_t next_sp = flecs_ito(int16_t, sp + size); + void *result = NULL; + + if (next_sp > ECS_STACK_PAGE_SIZE) { + if (size > ECS_STACK_PAGE_SIZE) { + result = ecs_os_malloc(size); /* Too large for page */ + goto done; } - } - if (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); + 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; } - map->bucket_shift = 0; + page->sp = next_sp; + result = ECS_OFFSET(page->data, sp); + +done: + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); +#ifdef FLECS_SANITIZE + ecs_os_memset(result, 0xAA, size); +#endif + return result; } -ecs_map_val_t* ecs_map_get( - const ecs_map_t *map, - ecs_map_key_t key) +void* flecs_stack_calloc( + ecs_stack_t *stack, + ecs_size_t size, + ecs_size_t align) { - return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); + void *ptr = flecs_stack_alloc(stack, size, align); + ecs_os_memset(ptr, 0, size); + return ptr; } -void* ecs_map_get_deref_( - const ecs_map_t *map, - ecs_map_key_t key) +void flecs_stack_free( + void *ptr, + ecs_size_t size) { - ecs_map_val_t* ptr = flecs_map_bucket_get( - flecs_map_get_bucket(map, key), key); - if (ptr) { - return (void*)(uintptr_t)ptr[0]; + if (size > ECS_STACK_PAGE_SIZE) { + ecs_os_free(ptr); } - return NULL; } -void ecs_map_insert( - ecs_map_t *map, - ecs_map_key_t key, - ecs_map_val_t value) +ecs_stack_cursor_t* flecs_stack_get_cursor( + ecs_stack_t *stack) { - ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); - int32_t map_count = ++map->count; - int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); - int32_t bucket_count = map->bucket_count; - if (tgt_bucket_count > bucket_count) { - flecs_map_rehash(map, tgt_bucket_count); - } + ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); - flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; -} + 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; -void* ecs_map_insert_alloc( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) -{ - void *elem = ecs_os_calloc(elem_size); - ecs_map_insert_ptr(map, key, (uintptr_t)elem); - return elem; +#ifdef FLECS_DEBUG + ++ stack->cursor_count; + result->owner = stack; +#endif + + result->prev = stack->tail_cursor; + stack->tail_cursor = result; + return result; } -ecs_map_val_t* ecs_map_ensure( - ecs_map_t *map, - ecs_map_key_t key) +#define FLECS_STACK_LEAK_MSG \ + "a stack allocator leak is most likely due to an unterminated " \ + "iteration: call ecs_iter_fini to fix" + +void flecs_stack_restore_cursor( + ecs_stack_t *stack, + ecs_stack_cursor_t *cursor) { - 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 (!cursor) { + return; } - int32_t map_count = ++map->count; - int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); - int32_t bucket_count = map->bucket_count; - if (tgt_bucket_count > bucket_count) { - flecs_map_rehash(map, tgt_bucket_count); - bucket = flecs_map_get_bucket(map, key); + ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, + "attempting to restore a cursor for the wrong stack"); + ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, + "double free detected in stack allocator"); + ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, + "double free detected in stack allocator"); + + 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; } - ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); - *v = 0; - return v; + /* Iterate freed cursors to know how much memory we can free */ + do { + ecs_stack_cursor_t* prev = cursor->prev; + if (!prev || !prev->is_free) { + break; /* Found active cursor, free up until this point */ + } + cursor = prev; + } while (cursor); + + stack->tail_cursor = cursor->prev; + stack->tail_page = cursor->page; + stack->tail_page->sp = cursor->sp; + + /* If the cursor count is zero, stack should be empty + * if the cursor count is non-zero, stack should not be empty */ + ecs_dbg_assert((stack->cursor_count == 0) == + (stack->tail_page == stack->first && stack->tail_page->sp == 0), + ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); } -void* ecs_map_ensure_alloc( - ecs_map_t *map, - ecs_size_t elem_size, - ecs_map_key_t key) +void flecs_stack_reset( + ecs_stack_t *stack) { - ecs_map_val_t *val = ecs_map_ensure(map, key); - if (!*val) { - void *elem = ecs_os_calloc(elem_size); - *val = (ecs_map_val_t)(uintptr_t)elem; - return elem; - } else { - return (void*)(uintptr_t)*val; - } + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + stack->tail_page = stack->first; + stack->first->sp = 0; + stack->tail_cursor = NULL; } -ecs_map_val_t ecs_map_remove( - ecs_map_t *map, - ecs_map_key_t key) +void flecs_stack_init( + ecs_stack_t *stack) { - return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); + ecs_os_zeromem(stack); + stack->first = flecs_stack_page_new(0); + stack->first->sp = 0; + stack->tail_page = stack->first; } -void ecs_map_remove_free( - ecs_map_t *map, - ecs_map_key_t key) +void flecs_stack_fini( + ecs_stack_t *stack) { - ecs_map_val_t val = ecs_map_remove(map, key); - if (val) { - ecs_os_free((void*)(uintptr_t)val); - } + ecs_stack_page_t *next, *cur = stack->first; + ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + ecs_assert(stack->tail_page == stack->first, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, + FLECS_STACK_LEAK_MSG); + + do { + next = cur->next; + ecs_os_linc(&ecs_stack_allocator_free_count); + ecs_os_free(cur); + } while ((cur = next)); } -void ecs_map_clear( - ecs_map_t *map) +/** + * @file datastructures/strbuf.c + * @brief Utility for constructing strings. + * + * A buffer builds up a list of elements which individually can be up to N bytes + * large. While appending, data is added to these elements. More elements are + * added on the fly when needed. When an application calls ecs_strbuf_get, all + * elements are combined in one string and the element administration is freed. + * + * This approach prevents reallocs of large blocks of memory, and therefore + * copying large blocks of memory when appending to a large buffer. A buffer + * preallocates some memory for the element overhead so that for small strings + * there is hardly any overhead, while for large strings the overhead is offset + * by the reduced time spent on copying memory. + * + * The functionality provided by strbuf is similar to std::stringstream. + */ + +#include + +/** + * stm32tpl -- STM32 C++ Template Peripheral Library + * Visit https://github.com/antongus/stm32tpl for new versions + * + * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA + */ + +#define MAX_PRECISION (10) +#define EXP_THRESHOLD (3) +#define INT64_MAX_F ((double)INT64_MAX) + +static const double rounders[MAX_PRECISION + 1] = { - 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); -} + 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 +}; -ecs_map_iter_t ecs_map_iter( - const ecs_map_t *map) +static +char* flecs_strbuf_itoa( + char *buf, + int64_t v) { - if (ecs_map_is_init(map)) { - return (ecs_map_iter_t){ - .map = map, - .bucket = NULL, - .entry = NULL - }; + char *ptr = buf; + char * p1; + char c; + + if (!v) { + *ptr++ = '0'; } else { - return (ecs_map_iter_t){ 0 }; - } + if (v < 0) { + ptr[0] = '-'; + ptr ++; + v *= -1; + } + + char *p = ptr; + while (v) { + int64_t vdiv = v / 10; + int64_t vmod = v - (vdiv * 10); + p[0] = (char)('0' + vmod); + p ++; + v = vdiv; + } + + p1 = p; + + while (p > ptr) { + c = *--p; + *p = *ptr; + *ptr++ = c; + } + ptr = p1; + } + return ptr; } -bool ecs_map_next( - ecs_map_iter_t *iter) +static +void flecs_strbuf_ftoa( + ecs_strbuf_t *out, + double f, + int precision, + char nan_delim) { - const ecs_map_t *map = iter->map; - ecs_bucket_t *end; - if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { - return false; - } + char buf[64]; + char * ptr = buf; + char c; + int64_t intPart; + int64_t exp = 0; - 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 (ecs_os_isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "NaN"); + ecs_strbuf_appendch(out, nan_delim); + return; + } else { + ecs_strbuf_appendlit(out, "NaN"); + return; } - if (iter->bucket == end) { - return false; + } + if (ecs_os_isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendlit(out, "Inf"); + ecs_strbuf_appendch(out, nan_delim); + return; + } else { + ecs_strbuf_appendlit(out, "Inf"); + return; } - } else if ((entry = iter->entry) == NULL) { - do { - ++iter->bucket; - if (iter->bucket == end) { - return false; - } - } while(!iter->bucket->first); - entry = iter->bucket->first; } - ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); - iter->entry = entry->next; - iter->res = &entry->key; + if (precision > MAX_PRECISION) { + precision = MAX_PRECISION; + } - return true; -} + if (f < 0) { + f = -f; + *ptr++ = '-'; + } -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 (precision < 0) { + if (f < 1.0) precision = 6; + else if (f < 10.0) precision = 5; + else if (f < 100.0) precision = 4; + else if (f < 1000.0) precision = 3; + else if (f < 10000.0) precision = 2; + else if (f < 100000.0) precision = 1; + else precision = 0; + } + + if (precision) { + f += rounders[precision]; } - - if (!ecs_map_is_init(src)) { - return; + + /* Make sure that number can be represented as 64bit int, increase exp */ + while (f > INT64_MAX_F) { + f /= 1000 * 1000 * 1000; + exp += 9; } - ecs_map_init(dst, src->allocator); + intPart = (int64_t)f; + f -= (double)intPart; - 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)); - } -} + ptr = flecs_strbuf_itoa(ptr, intPart); -/** - * @file datastructures/name_index.c - * @brief Data structure for resolving 64bit keys by string (name). - */ + if (precision) { + *ptr++ = '.'; + while (precision--) { + f *= 10.0; + c = (char)f; + *ptr++ = (char)('0' + c); + f -= c; + } + } + *ptr = 0; + /* Remove trailing 0s */ + while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { + ptr[-1] = '\0'; + ptr --; + } + if (ptr != buf && ptr[-1] == '.') { + ptr[-1] = '\0'; + ptr --; + } -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; -} + /* 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 --; + } -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); + if (exp || ((ptr - cur) > EXP_THRESHOLD)) { + cur[0] = '\0'; + exp += (ptr - cur); + ptr = cur; } - return ecs_os_memcmp(str1->value, str2->value, len1); -} + if (exp) { + char *p1 = &buf[1]; + if (nan_delim) { + ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); + buf[0] = nan_delim; + p1 ++; + } -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); -} + /* Make sure that exp starts after first character */ + c = p1[0]; -void flecs_name_index_init_if( - ecs_hashmap_t *hm, - ecs_allocator_t *allocator) -{ - if (!hm->compare) { - flecs_name_index_init(hm, allocator); - } -} + if (c) { + p1[0] = '.'; + do { + char t = (++p1)[0]; + if (t == '.') { + exp ++; + p1 --; + break; + } + p1[0] = c; + c = t; + exp ++; + } while (c); + ptr = p1 + 1; + } else { + ptr = p1; + } -bool flecs_name_index_is_init( - const ecs_hashmap_t *hm) -{ - return hm->compare != NULL; -} + ptr[0] = 'e'; + ptr = flecs_strbuf_itoa(ptr + 1, exp); -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; -} + if (nan_delim) { + ptr[0] = nan_delim; + ptr ++; + } -void flecs_name_index_fini( - ecs_hashmap_t *map) -{ - flecs_hashmap_fini(map); + ptr[0] = '\0'; + } + + ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } -void flecs_name_index_free( - ecs_hashmap_t *map) +/* Add an extra element to the buffer */ +static +void flecs_strbuf_grow( + ecs_strbuf_t *b) { - if (map) { - flecs_name_index_fini(map); - flecs_bfree(map->hashmap_allocator, map); + if (!b->content) { + b->content = b->small_string; + b->size = ECS_STRBUF_SMALL_STRING_SIZE; + } else if (b->content == b->small_string) { + b->size *= 2; + b->content = ecs_os_malloc_n(char, b->size); + ecs_os_memcpy(b->content, b->small_string, b->length); + } else { + b->size *= 2; + if (b->size < 16) b->size = 16; + b->content = ecs_os_realloc_n(b->content, char, b->size); } } -ecs_hashmap_t* flecs_name_index_copy( - ecs_hashmap_t *map) +static +char* flecs_strbuf_ptr( + ecs_strbuf_t *b) { - ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); - result->hashmap_allocator = map->hashmap_allocator; - flecs_hashmap_copy(result, map); - return result; + ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); + return &b->content[b->length]; } -ecs_hashed_string_t flecs_get_hashed_string( - const char *name, - ecs_size_t length, - uint64_t hash) +/* Append a format string to a buffer */ +static +void flecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* str, + va_list args) { - if (!length) { - length = ecs_os_strlen(name); - } else { - ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); - } + va_list arg_cpy; - if (!hash) { - hash = flecs_hash(name, length); - } else { - ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); + if (!str) { + return; } - return (ecs_hashed_string_t) { - .value = ECS_CONST_CAST(char*, name), - .length = length, - .hash = hash - }; -} - -const uint64_t* flecs_name_index_find_ptr( - const ecs_hashmap_t *map, - const char *name, - ecs_size_t length, - uint64_t hash) -{ - ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); - ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); - if (!b) { - return NULL; - } + /* Compute the memory required to add the string to the buffer. If user + * provided buffer, use space left in buffer, otherwise use space left in + * current element. */ + int32_t mem_left = b->size - b->length; + int32_t mem_required; - ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); - int32_t i, count = ecs_vec_count(&b->keys); + va_copy(arg_cpy, args); - for (i = 0; i < count; i ++) { - ecs_hashed_string_t *key = &keys[i]; - ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); + if (b->content) { + mem_required = ecs_os_vsnprintf( + flecs_strbuf_ptr(b), + flecs_itosize(mem_left), str, args); + } else { + mem_required = ecs_os_vsnprintf(NULL, 0, str, args); + mem_left = 0; + } - if (hs.length != key->length) { - continue; - } + ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); - 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 ((mem_required + 1) >= mem_left) { + while ((mem_required + 1) >= mem_left) { + flecs_strbuf_grow(b); + mem_left = b->size - b->length; } + ecs_os_vsnprintf(flecs_strbuf_ptr(b), + flecs_itosize(mem_required + 1), str, arg_cpy); } - return NULL; -} + b->length += mem_required; -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; + va_end(arg_cpy); } -void flecs_name_index_remove( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash) +static +void flecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str, + int n) { - ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); - if (!b) { - return; + int32_t mem_left = b->size - b->length; + while (n >= mem_left) { + flecs_strbuf_grow(b); + mem_left = b->size - b->length; } - 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; - } - } + ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); + b->length += n; } -void flecs_name_index_update_name( - ecs_hashmap_t *map, - uint64_t e, - uint64_t hash, - const char *name) +static +void flecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) { - ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); - if (!b) { - return; - } - - uint64_t *ids = ecs_vec_first(&b->values); - int32_t i, count = ecs_vec_count(&b->values); - for (i = 0; i < count; i ++) { - if (ids[i] == e) { - ecs_hashed_string_t *key = ecs_vec_get_t( - &b->keys, ecs_hashed_string_t, i); - key->value = ECS_CONST_CAST(char*, name); - ecs_assert(ecs_os_strlen(name) == key->length, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_hash(name, key->length) == key->hash, - ECS_INTERNAL_ERROR, NULL); - return; - } + if (b->size == b->length) { + flecs_strbuf_grow(b); } - /* Record must already have been in the index */ - ecs_abort(ECS_INTERNAL_ERROR, NULL); + flecs_strbuf_ptr(b)[0] = ch; + b->length ++; } -void flecs_name_index_ensure( - ecs_hashmap_t *map, - uint64_t id, - const char *name, - ecs_size_t length, - uint64_t hash) +void ecs_strbuf_vappend( + ecs_strbuf_t *b, + const char* fmt, + va_list args) { - ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); - - uint64_t existing = flecs_name_index_find( - map, name, key.length, key.hash); - if (existing) { - if (existing != id) { - ecs_abort(ECS_ALREADY_DEFINED, - "conflicting id registered with name '%s'", name); - } - } - - flecs_hashmap_result_t hmr = flecs_hashmap_ensure( - map, &key, uint64_t); - *((uint64_t*)hmr.value) = id; -error: - return; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_vappend(b, fmt, args); } -/** - * @file datastructures/sparse.c - * @brief Sparse set data structure. - */ - - -/* Utility to get a pointer to the payload */ -#define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) - -typedef struct ecs_page_t { - int32_t *sparse; /* Sparse array with indices to dense array */ - void *data; /* Store data in sparse array to reduce - * indirection and provide stable pointers. */ -} ecs_page_t; - -static -ecs_page_t* flecs_sparse_page_new( - ecs_sparse_t *sparse, - int32_t page_index) +void ecs_strbuf_append( + ecs_strbuf_t *b, + const char* fmt, + ...) { - ecs_allocator_t *a = sparse->allocator; - ecs_block_allocator_t *ca = sparse->page_allocator; - int32_t count = ecs_vec_count(&sparse->pages); - ecs_page_t *pages; - - if (count <= page_index) { - ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); - pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); - ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); - } else { - pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); - } - - ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_page_t *result = &pages[page_index]; - ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, 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); + va_list args; + va_start(args, fmt); + flecs_strbuf_vappend(b, fmt, args); + va_end(args); +} - /* 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); +void ecs_strbuf_appendstrn( + ecs_strbuf_t *b, + const char* str, + int32_t len) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendstr(b, str, len); +} - ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); +void ecs_strbuf_appendch( + ecs_strbuf_t *b, + char ch) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendch(b, ch); +} - return result; +void ecs_strbuf_appendint( + ecs_strbuf_t *b, + int64_t v) +{ + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char numbuf[32]; + char *ptr = flecs_strbuf_itoa(numbuf, v); + ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } -static -void flecs_sparse_page_free( - ecs_sparse_t *sparse, - ecs_page_t *page) +void ecs_strbuf_appendflt( + ecs_strbuf_t *b, + double flt, + char nan_delim) { - ecs_allocator_t *a = sparse->allocator; - ecs_block_allocator_t *ca = sparse->page_allocator; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_ftoa(b, flt, 10, nan_delim); +} - 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); +void ecs_strbuf_appendbool( + ecs_strbuf_t *buffer, + bool v) +{ + ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); + if (v) { + ecs_strbuf_appendlit(buffer, "true"); } else { - ecs_os_free(page->data); + ecs_strbuf_appendlit(buffer, "false"); } } -static -ecs_page_t* flecs_sparse_get_page( - const ecs_sparse_t *sparse, - int32_t page_index) +void ecs_strbuf_appendstr( + ecs_strbuf_t *b, + const char* str) { - 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); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } -static -ecs_page_t* flecs_sparse_get_or_create_page( - ecs_sparse_t *sparse, - int32_t page_index) +void ecs_strbuf_mergebuff( + ecs_strbuf_t *b, + ecs_strbuf_t *src) { - ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); - if (page && page->sparse) { - return page; + if (src->content) { + ecs_strbuf_appendstr(b, src->content); } - - return flecs_sparse_page_new(sparse, page_index); + ecs_strbuf_reset(src); } -static -void flecs_sparse_grow_dense( - ecs_sparse_t *sparse) +char* ecs_strbuf_get( + ecs_strbuf_t *b) { - ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char *result = b->content; + if (!result) { + return NULL; + } + + ecs_strbuf_appendch(b, '\0'); + result = b->content; + + if (result == b->small_string) { + result = ecs_os_memdup_n(result, char, b->length + 1); + } + + b->length = 0; + b->content = NULL; + b->size = 0; + b->list_sp = 0; + return result; } -static -uint64_t flecs_sparse_strip_generation( - uint64_t *index_out) +char* ecs_strbuf_get_small( + ecs_strbuf_t *b) { - 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; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + char *result = b->content; + result[b->length] = '\0'; + b->length = 0; + b->content = NULL; + b->size = 0; + return result; } -static -void flecs_sparse_assign_index( - ecs_page_t * page, - uint64_t * dense_array, - uint64_t index, - int32_t dense) +void ecs_strbuf_reset( + ecs_strbuf_t *b) { - /* Initialize sparse-dense pair. This assigns the dense index to the sparse - * array, and the sparse index to the dense array .*/ - page->sparse[FLECS_SPARSE_OFFSET(index)] = dense; - dense_array[dense] = index; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + if (b->content && b->content != b->small_string) { + ecs_os_free(b->content); + } + *b = ECS_STRBUF_INIT; } -static -uint64_t flecs_sparse_inc_gen( - uint64_t index) +void ecs_strbuf_list_push( + ecs_strbuf_t *b, + const char *list_open, + const char *separator) { - /* When an index is deleted, its generation is increased so that we can do - * liveliness checking while recycling ids */ - return ECS_GENERATION_INC(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, + "strbuf list is corrupt"); + b->list_sp ++; + ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, + ECS_INVALID_OPERATION, "max depth for strbuf list stack exceeded"); + + b->list_stack[b->list_sp].count = 0; + b->list_stack[b->list_sp].separator = separator; + + if (list_open) { + char ch = list_open[0]; + if (ch && !list_open[1]) { + ecs_strbuf_appendch(b, ch); + } else { + ecs_strbuf_appendstr(b, list_open); + } + } } -static -uint64_t flecs_sparse_inc_id( - ecs_sparse_t *sparse) +void ecs_strbuf_list_pop( + ecs_strbuf_t *b, + const char *list_close) { - /* 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; + 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, + "pop called more often than push for strbuf list"); + + 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); + } + } } -static -uint64_t flecs_sparse_get_id( - const ecs_sparse_t *sparse) +void ecs_strbuf_list_next( + ecs_strbuf_t *b) { - ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); - return sparse->max_id; + 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 ++; } -static -void flecs_sparse_set_id( - ecs_sparse_t *sparse, - uint64_t value) +void ecs_strbuf_list_appendch( + ecs_strbuf_t *b, + char ch) { - /* 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; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_list_next(b); + flecs_strbuf_appendch(b, ch); } -/* Pair dense id with new sparse id */ -static -uint64_t flecs_sparse_create_id( - ecs_sparse_t *sparse, - int32_t dense) +void ecs_strbuf_list_append( + ecs_strbuf_t *b, + const char *fmt, + ...) { - uint64_t index = flecs_sparse_inc_id(sparse); - flecs_sparse_grow_dense(sparse); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); - ecs_assert(page->sparse[FLECS_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; + ecs_strbuf_list_next(b); + + va_list args; + va_start(args, fmt); + flecs_strbuf_vappend(b, fmt, args); + va_end(args); } -/* Create new id */ -static -uint64_t flecs_sparse_new_index( - ecs_sparse_t *sparse) +void ecs_strbuf_list_appendstr( + ecs_strbuf_t *b, + const char *str) { - int32_t dense_count = ecs_vec_count(&sparse->dense); - int32_t count = sparse->count ++; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - 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); - } + ecs_strbuf_list_next(b); + ecs_strbuf_appendstr(b, str); } -/* 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) +void ecs_strbuf_list_appendstrn( + ecs_strbuf_t *b, + const char *str, + int32_t n) { - flecs_sparse_strip_generation(&index); - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return NULL; - } - - int32_t offset = FLECS_SPARSE_OFFSET(index); - ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); - (void)dense; + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - return DATA(page->data, sparse->size, offset); + ecs_strbuf_list_next(b); + ecs_strbuf_appendstrn(b, str, n); } -/* 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) +int32_t ecs_strbuf_written( + const ecs_strbuf_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, FLECS_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); + ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + return b->length; } -void flecs_sparse_init( - ecs_sparse_t *result, - struct ecs_allocator_t *allocator, - ecs_block_allocator_t *page_allocator, - ecs_size_t size) -{ - ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); - result->size = size; - result->max_id = UINT64_MAX; - result->allocator = allocator; - result->page_allocator = page_allocator; - ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); - ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); - result->dense.count = 1; +static +ecs_switch_page_t* flecs_switch_page_ensure( + ecs_switch_t* sw, + uint32_t elem) +{ + int32_t page_index = FLECS_SPARSE_PAGE(elem); + ecs_vec_set_min_count_zeromem_t( + sw->hdrs.allocator, &sw->pages, ecs_switch_page_t, page_index + 1); - /* Consume first value in dense array as 0 is used in the sparse array to - * indicate that a sparse element hasn't been paired yet. */ - ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; + ecs_switch_page_t *page = ecs_vec_get_t( + &sw->pages, ecs_switch_page_t, page_index); + if (!ecs_vec_count(&page->nodes)) { + ecs_vec_init_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, + FLECS_SPARSE_PAGE_SIZE); + ecs_vec_init_t(sw->hdrs.allocator, &page->values, uint64_t, + FLECS_SPARSE_PAGE_SIZE); + ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->nodes, + ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); + ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->values, + uint64_t, FLECS_SPARSE_PAGE_SIZE); + } - result->count = 1; + return page; } -void flecs_sparse_clear( - ecs_sparse_t *sparse) +static +ecs_switch_page_t* flecs_switch_page_get( + const ecs_switch_t* sw, + uint32_t elem) { - 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); - } + int32_t page_index = FLECS_SPARSE_PAGE(elem); + if (page_index >= ecs_vec_count(&sw->pages)) { + return NULL; } - ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); + ecs_switch_page_t *page = ecs_vec_get_t( + &sw->pages, ecs_switch_page_t, page_index); + if (!ecs_vec_count(&page->nodes)) { + return NULL; + } - sparse->count = 1; - sparse->max_id = 0; + return page; } -void flecs_sparse_fini( - ecs_sparse_t *sparse) +static +void flecs_switch_page_fini( + ecs_switch_t* sw, + ecs_switch_page_t *page) { - 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 (ecs_vec_count(&page->nodes)) { + ecs_vec_fini_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t); + ecs_vec_fini_t(sw->hdrs.allocator, &page->values, uint64_t); } - - ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); - ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse) +static +ecs_switch_node_t* flecs_switch_get_node( + ecs_switch_t* sw, + uint32_t element) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - return flecs_sparse_new_index(sparse); + if (!element) { + return NULL; + } + + ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); + int32_t page_offset = FLECS_SPARSE_OFFSET(element); + return ecs_vec_get_t(&page->nodes, ecs_switch_node_t, page_offset); } -void* flecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t size) +void flecs_switch_init( + ecs_switch_t* sw, + ecs_allocator_t *allocator) { - 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, FLECS_SPARSE_PAGE(index)); - ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); - return DATA(page->data, size, FLECS_SPARSE_OFFSET(index)); + ecs_map_init(&sw->hdrs, allocator); + ecs_vec_init_t(allocator, &sw->pages, ecs_switch_page_t, 0); } -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse) +void flecs_switch_fini( + ecs_switch_t* sw) { - 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]; + int32_t i, count = ecs_vec_count(&sw->pages); + ecs_switch_page_t *pages = ecs_vec_first(&sw->pages); + for (i = 0; i < count; i ++) { + flecs_switch_page_fini(sw, &pages[i]); + } + ecs_vec_fini_t(sw->hdrs.allocator, &sw->pages, ecs_switch_page_t); + ecs_map_fini(&sw->hdrs); } -void* flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +bool flecs_switch_set( + ecs_switch_t *sw, + uint32_t element, + uint64_t value) { - 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_switch_page_t *page = flecs_switch_page_ensure(sw, element); + int32_t page_offset = FLECS_SPARSE_OFFSET(element); - uint64_t gen = flecs_sparse_strip_generation(&index); - ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; + uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); + if (elem[0] == value) { + return false; + } - 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; + ecs_switch_node_t *node = ecs_vec_get_t( + &page->nodes, ecs_switch_node_t, page_offset); - /* First unused element is now last used element */ - sparse->count ++; + uint64_t prev_value = elem[0]; + if (prev_value) { + ecs_switch_node_t *prev = flecs_switch_get_node(sw, node->prev); + if (prev) { + prev->next = node->next; + } - /* Set dense element to new generation */ - ecs_vec_first_t(&sparse->dense, uint64_t)[dense] = index | gen; - } else { - /* Dense is already alive, nothing to be done */ + ecs_switch_node_t *next = flecs_switch_get_node(sw, node->next); + if (next) { + next->prev = node->prev; } - /* 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); + if (!prev) { + uint64_t *hdr = ecs_map_get(&sw->hdrs, prev_value); + ecs_assert(hdr[0] == (uint64_t)element, ECS_INTERNAL_ERROR, NULL); + hdr[0] = (uint64_t)node->next; + } + } - 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 ++; + elem[0] = value; - /* If index is larger than max id, update max id */ - if (index >= flecs_sparse_get_id(sparse)) { - flecs_sparse_set_id(sparse, index); - } + if (value) { + uint64_t *hdr = ecs_map_ensure(&sw->hdrs, value); - 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, FLECS_SPARSE_PAGE(unused)); - flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count); + if (!hdr[0]) { + hdr[0] = (uint64_t)element; + node->next = 0; + } else { + ecs_switch_node_t *head = flecs_switch_get_node(sw, (uint32_t)hdr[0]); + ecs_assert(head->prev == 0, ECS_INTERNAL_ERROR, NULL); + head->prev = element; + + node->next = (uint32_t)hdr[0]; + hdr[0] = (uint64_t)element; + ecs_assert(node->next != element, ECS_INTERNAL_ERROR, NULL); } - flecs_sparse_assign_index(page, dense_array, index, count); - dense_array[count] |= gen; + node->prev = 0; } - return DATA(page->data, sparse->size, offset); + return true; } -void* flecs_sparse_ensure_fast( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index_long) +bool flecs_switch_reset( + ecs_switch_t *sw, + uint32_t element) { - 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; + return flecs_switch_set(sw, element, 0); +} - uint32_t index = (uint32_t)index_long; - ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - int32_t count = sparse->count; +uint64_t flecs_switch_get( + const ecs_switch_t *sw, + uint32_t element) +{ + ecs_switch_page_t *page = flecs_switch_page_get(sw, element); + if (!page) { + return 0; + } - 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); - } + int32_t page_offset = FLECS_SPARSE_OFFSET(element); + uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); + return elem[0]; +} - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - flecs_sparse_assign_index(page, dense_array, index, count); +uint32_t flecs_switch_first( + const ecs_switch_t *sw, + uint64_t value) +{ + uint64_t *hdr = ecs_map_get(&sw->hdrs, value); + if (!hdr) { + return 0; } - return DATA(page->data, sparse->size, offset); + return (uint32_t)hdr[0]; } -void* flecs_sparse_remove_fast( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +FLECS_DBG_API +uint32_t flecs_switch_next( + const ecs_switch_t *sw, + uint32_t previous) { - 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, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return NULL; + ecs_switch_page_t *page = flecs_switch_page_get(sw, previous); + if (!page) { + return 0; } - flecs_sparse_strip_generation(&index); - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - - if (dense) { - 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 --; - } - - /* Reset memory to zero on remove */ - return DATA(page->data, sparse->size, offset); - } else { - /* Element is not paired and thus not alive, nothing to be done */ - return NULL; - } + int32_t offset = FLECS_SPARSE_OFFSET(previous); + ecs_switch_node_t *elem = ecs_vec_get_t( + &page->nodes, ecs_switch_node_t, offset); + return elem->next; } - -void flecs_sparse_remove( - ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +ecs_map_iter_t flecs_switch_targets( + const ecs_switch_t *sw) { - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); - (void)size; + return ecs_map_iter(&sw->hdrs); +} - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return; - } +/** + * @file datastructures/vec.c + * @brief Vector with allocator support. + */ - uint64_t gen = flecs_sparse_strip_generation(&index); - int32_t offset = FLECS_SPARSE_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; - } +void ecs_vec_init( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) +{ + ecs_vec_init_w_dbg_info(allocator, v, size, elem_count, NULL); +} - /* 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 --; +void ecs_vec_init_w_dbg_info( + struct ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count, + const char *type_name) +{ + (void)type_name; + ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); + v->array = NULL; + v->count = 0; + if (elem_count) { + if (allocator) { + v->array = flecs_alloc_w_dbg_info( + allocator, size * elem_count, type_name); } else { - /* Element is not alive, nothing to be done */ - return; + v->array = ecs_os_malloc(size * elem_count); } - - /* 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; } + v->size = elem_count; +#ifdef FLECS_SANITIZE + v->elem_size = size; + v->type_name = type_name; +#endif } -void* flecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t size, - int32_t dense_index) +void ecs_vec_init_if( + ecs_vec_t *vec, + ecs_size_t size) { - 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); + ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); + (void)vec; (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]); +#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 } -bool flecs_sparse_is_alive( - const ecs_sparse_t *sparse, - uint64_t index) +void ecs_vec_fini( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) { - ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return false; - } - - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - if (!dense || (dense >= sparse->count)) { - return false; + 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; } +} - uint64_t gen = flecs_sparse_strip_generation(&index); - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - - if (cur_gen != gen) { - return false; +ecs_vec_t* ecs_vec_reset( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) +{ + if (!v->size) { + ecs_vec_init(allocator, v, size, 0); + } else { + ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); + ecs_vec_clear(v); } + return v; +} - ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); - return true; +void ecs_vec_clear( + ecs_vec_t *vec) +{ + vec->count = 0; } -void* flecs_sparse_try( - const ecs_sparse_t *sparse, - ecs_size_t size, - uint64_t index) +ecs_vec_t ecs_vec_copy( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) { - 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, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return NULL; + 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 + }; +} - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - if (!dense || (dense >= sparse->count)) { - return NULL; +ecs_vec_t ecs_vec_copy_shrink( + ecs_allocator_t *allocator, + const ecs_vec_t *v, + ecs_size_t size) +{ + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + int32_t count = v->count; + void *array = NULL; + if (count) { + if (allocator) { + array = flecs_dup(allocator, size * count, v->array); + } else { + array = ecs_os_memdup(v->array, size * count); + } } + return (ecs_vec_t) { + .count = count, + .size = count, + .array = array +#ifdef FLECS_SANITIZE + , .elem_size = size +#endif + }; +} - 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; +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); + } } - - 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, +void ecs_vec_set_size( + ecs_allocator_t *allocator, + ecs_vec_t *v, ecs_size_t size, - uint64_t index) + int32_t elem_count) { - 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, FLECS_SPARSE_PAGE(index)); - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); - - uint64_t gen = flecs_sparse_strip_generation(&index); - uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); - uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; - (void)cur_gen; (void)gen; + ecs_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_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); + elem_count = flecs_next_pow_of_2(elem_count); + if (elem_count < 2) { + elem_count = 2; + } + if (elem_count != v->size) { + if (allocator) { +#ifdef FLECS_SANITIZE + v->array = flecs_realloc_w_dbg_info( + allocator, size * elem_count, size * v->size, v->array, + v->type_name); +#else + v->array = flecs_realloc( + allocator, size * elem_count, size * v->size, v->array); +#endif + } else { + v->array = ecs_os_realloc(v->array, size * elem_count); + } + v->size = elem_count; + } + } } -void* flecs_sparse_get_any( - const ecs_sparse_t *sparse, +void ecs_vec_set_min_size( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, ecs_size_t size, - uint64_t index) + int32_t elem_count) { - 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, FLECS_SPARSE_PAGE(index)); - if (!page || !page->sparse) { - return NULL; + if (elem_count > vec->size) { + ecs_vec_set_size(allocator, vec, size, elem_count); } +} - int32_t offset = FLECS_SPARSE_OFFSET(index); - int32_t dense = page->sparse[offset]; - bool in_use = dense && (dense < sparse->count); - if (!in_use) { - return NULL; +void ecs_vec_set_min_count( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) +{ + ecs_vec_set_min_size(allocator, vec, size, elem_count); + if (vec->count < elem_count) { + vec->count = elem_count; } - - 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) +void ecs_vec_set_min_count_zeromem( + struct ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size, + int32_t elem_count) { - if (!sparse || !sparse->count) { - return 0; + 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)); } - - return sparse->count - 1; } -const uint64_t* flecs_sparse_ids( - const ecs_sparse_t *sparse) +void ecs_vec_set_count( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) { - 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; + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + if (v->count != elem_count) { + if (v->size < elem_count) { + ecs_vec_set_size(allocator, v, size, elem_count); + } + + v->count = elem_count; } } -void ecs_sparse_init( - ecs_sparse_t *sparse, - ecs_size_t elem_size) +void* ecs_vec_grow( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size, + int32_t elem_count) { - flecs_sparse_init(sparse, NULL, NULL, elem_size); + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); + int32_t count = v->count; + ecs_vec_set_count(allocator, v, size, count + elem_count); + return ECS_ELEM(v->array, size, count); } -void* ecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size) +void* ecs_vec_append( + ecs_allocator_t *allocator, + ecs_vec_t *v, + ecs_size_t size) { - return flecs_sparse_add(sparse, elem_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); } -uint64_t ecs_sparse_last_id( - const ecs_sparse_t *sparse) +void ecs_vec_remove( + ecs_vec_t *v, + ecs_size_t size, + int32_t index) { - return flecs_sparse_last_id(sparse); + ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + if (index == --v->count) { + return; + } + + ecs_os_memcpy( + ECS_ELEM(v->array, size, index), + ECS_ELEM(v->array, size, v->count), + size); } -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse) +void ecs_vec_remove_last( + ecs_vec_t *v) { - return flecs_sparse_count(sparse); + v->count --; } -void* ecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index) +int32_t ecs_vec_count( + const ecs_vec_t *v) { - return flecs_sparse_get_dense(sparse, elem_size, index); + return v->count; } -void* ecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id) +int32_t ecs_vec_size( + const ecs_vec_t *v) { - return flecs_sparse_get(sparse, elem_size, id); + return v->size; } -/** - * @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; - result->sp = 0; - ecs_os_linc(&ecs_stack_allocator_alloc_count); - return result; +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 >= 0, ECS_OUT_OF_RANGE, NULL); + ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); + return ECS_ELEM(v->array, size, index); } -void* flecs_stack_alloc( - ecs_stack_t *stack, - ecs_size_t size, - ecs_size_t align) +void* ecs_vec_last( + const ecs_vec_t *v, + ecs_size_t size) { - ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); + ecs_san_assert(!v->elem_size || size == v->elem_size, + ECS_INVALID_PARAMETER, NULL); + return ECS_ELEM(v->array, size, v->count - 1); +} - ecs_stack_page_t *page = stack->tail_page; - ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); +void* ecs_vec_first( + const ecs_vec_t *v) +{ + return v->array; +} - 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; + /** + * @file queries/api.c + * @brief User facing API for rules. + */ - if (next_sp > ECS_STACK_PAGE_SIZE) { - if (size > ECS_STACK_PAGE_SIZE) { - result = ecs_os_malloc(size); /* Too large for page */ - goto done; - } +#include - 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; +/* Placeholder arrays for queries that only have $this variable */ +ecs_query_var_t flecs_this_array = { + .kind = EcsVarTable, + .table_id = EcsVarNone +}; +char *flecs_this_name_array = NULL; + +ecs_mixins_t ecs_query_t_mixins = { + .type_name = "ecs_query_t", + .elems = { + [EcsMixinWorld] = offsetof(ecs_query_impl_t, pub.real_world), + [EcsMixinEntity] = offsetof(ecs_query_impl_t, pub.entity), + [EcsMixinDtor] = offsetof(ecs_query_impl_t, dtor) } +}; - page->sp = next_sp; - result = ECS_OFFSET(page->data, sp); +int32_t ecs_query_find_var( + const ecs_query_t *q, + const char *name) +{ + flecs_poly_assert(q, ecs_query_t); -done: - ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); -#ifdef FLECS_SANITIZE - ecs_os_memset(result, 0xAA, size); -#endif - return result; + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_var_id_t var_id = flecs_query_find_var_id(impl, name, EcsVarEntity); + if (var_id == EcsVarNone) { + if (q->flags & EcsQueryMatchThis) { + if (!ecs_os_strcmp(name, EcsThisName)) { + var_id = 0; + } + } + if (var_id == EcsVarNone) { + return -1; + } + } + return (int32_t)var_id; } -void* flecs_stack_calloc( - ecs_stack_t *stack, - ecs_size_t size, - ecs_size_t align) +const char* ecs_query_var_name( + const ecs_query_t *q, + int32_t var_id) { - void *ptr = flecs_stack_alloc(stack, size, align); - ecs_os_memset(ptr, 0, size); - return ptr; + flecs_poly_assert(q, ecs_query_t); + + if (var_id) { + ecs_assert(var_id < flecs_query_impl(q)->var_count, + ECS_INVALID_PARAMETER, NULL); + return flecs_query_impl(q)->vars[var_id].name; + } else { + return EcsThisName; + } } -void flecs_stack_free( - void *ptr, - ecs_size_t size) +bool ecs_query_var_is_entity( + const ecs_query_t *q, + int32_t var_id) { - if (size > ECS_STACK_PAGE_SIZE) { - ecs_os_free(ptr); - } + flecs_poly_assert(q, ecs_query_t); + + return flecs_query_impl(q)->vars[var_id].kind == EcsVarEntity; } -ecs_stack_cursor_t* flecs_stack_get_cursor( - ecs_stack_t *stack) +static +int flecs_query_set_caching_policy( + ecs_query_impl_t *impl, + const ecs_query_desc_t *desc) { - ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_query_cache_kind_t kind = desc->cache_kind; + bool group_order_by = desc->group_by || desc->group_by_callback || + desc->order_by || desc->order_by_callback; - 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; + /* If the query has a Cascade term it'll use group_by */ + int32_t i, term_count = impl->pub.term_count; + const ecs_term_t *terms = impl->pub.terms; + for (i = 0; i < term_count; i ++) { + const ecs_term_t *term = &terms[i]; + if (term->src.id & EcsCascade) { + group_order_by = true; + break; + } + } -#ifdef FLECS_DEBUG - ++ stack->cursor_count; - result->owner = stack; +#ifdef FLECS_DEFAULT_TO_UNCACHED_QUERIES + if (kind == EcsQueryCacheDefault && !group_order_by) { + kind = EcsQueryCacheNone; + } #endif - result->prev = stack->tail_cursor; - stack->tail_cursor = result; - return result; -} + /* If caching policy is default, try to pick a policy that does the right + * thing in most cases. */ + if (kind == EcsQueryCacheDefault) { + if (desc->entity || group_order_by) { + /* If the query is created with an entity handle (typically + * indicating that the query is named or belongs to a system) the + * chance is very high that the query will be reused, so enable + * caching. + * Additionally, if the query uses features that require a cache + * such as group_by/order_by, also enable caching. */ + kind = EcsQueryCacheAuto; + } else { + /* Be conservative in other scenario's, as caching adds significant + * overhead to the cost of query creation which doesn't offset the + * benefit of faster iteration if it's only used once. */ + kind = EcsQueryCacheNone; + } + } -#define FLECS_STACK_LEAK_MSG \ - "a stack allocator leak is most likely due to an unterminated " \ - "iteration: call ecs_iter_fini to fix" + /* Don't cache query, even if it has cacheable terms */ + if (kind == EcsQueryCacheNone) { + impl->pub.cache_kind = EcsQueryCacheNone; + if (group_order_by && !(impl->pub.flags & EcsQueryNested)) { + ecs_err("cannot create uncached query with group_by/order_by"); + return -1; + } + return 0; + } -void flecs_stack_restore_cursor( - ecs_stack_t *stack, - ecs_stack_cursor_t *cursor) -{ - if (!cursor) { - return; + /* Entire query must be cached */ + if (desc->cache_kind == EcsQueryCacheAll) { + if (impl->pub.flags & EcsQueryIsCacheable) { + impl->pub.cache_kind = EcsQueryCacheAll; + return 0; + } else { + ecs_err("cannot enforce QueryCacheAll, " + "query contains uncacheable terms"); + return -1; + } } - ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, - "attempting to restore a cursor for the wrong stack"); - ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, - "double free detected in stack allocator"); - ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, - "double free detected in stack allocator"); + /* Only cache terms that are cacheable */ + if (kind == EcsQueryCacheAuto) { + if (impl->pub.flags & EcsQueryIsCacheable) { + /* If all terms of the query are cacheable, just set the policy to + * All which simplifies work for the compiler. */ + impl->pub.cache_kind = EcsQueryCacheAll; + } else if (!(impl->pub.flags & EcsQueryHasCacheable)) { + /* Same for when the query has no cacheable terms */ + impl->pub.cache_kind = EcsQueryCacheNone; + } else { + /* Part of the query is cacheable. Make sure to only create a cache + * if the cacheable part of the query contains not just not/optional + * terms, as this would build a cache that contains all tables. */ + int32_t not_optional_terms = 0, cacheable_terms = 0; + if (!group_order_by) { + for (i = 0; i < term_count; i ++) { + const ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsCacheable) { + cacheable_terms ++; + if (term->oper == EcsNot || term->oper == EcsOptional) { + not_optional_terms ++; + } + } + } + } - cursor->is_free = true; + if (group_order_by || cacheable_terms != not_optional_terms) { + impl->pub.cache_kind = EcsQueryCacheAuto; + } else { + impl->pub.cache_kind = EcsQueryCacheNone; + } + } + } -#ifdef FLECS_DEBUG - -- stack->cursor_count; -#endif + return 0; +} - /* If cursor is not the last on the stack no memory should be freed */ - if (cursor != stack->tail_cursor) { - return; +static +int flecs_query_create_cache( + ecs_query_impl_t *impl, + ecs_query_desc_t *desc) +{ + ecs_query_t *q = &impl->pub; + if (flecs_query_set_caching_policy(impl, desc)) { + return -1; } - /* 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 */ + if ((q->cache_kind != EcsQueryCacheNone) && !q->entity) { + /* Cached queries need an entity handle for observer components */ + q->entity = ecs_new(q->world); + desc->entity = q->entity; + } + + if (q->cache_kind == EcsQueryCacheAll) { + /* Create query cache for all terms */ + if (!flecs_query_cache_init(impl, desc)) { + goto error; } - cursor = prev; - } while (cursor); + } else if (q->cache_kind == EcsQueryCacheAuto) { + /* Query is partially cached */ + ecs_query_desc_t cache_desc = *desc; + ecs_os_memset_n(&cache_desc.terms, 0, ecs_term_t, FLECS_TERM_COUNT_MAX); + cache_desc.expr = NULL; - stack->tail_cursor = cursor->prev; - stack->tail_page = cursor->page; - stack->tail_page->sp = cursor->sp; + /* Maps field indices from cache to query */ + int8_t field_map[FLECS_TERM_COUNT_MAX]; - /* 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, FLECS_STACK_LEAK_MSG); -} + int32_t i, count = q->term_count, dst_count = 0, dst_field = 0; + ecs_term_t *terms = q->terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->flags_ & EcsTermIsCacheable) { + cache_desc.terms[dst_count] = *term; + field_map[dst_field] = flecs_ito(int8_t, term->field_index); + dst_count ++; + if (i) { + dst_field += term->field_index != term[-1].field_index; + } else { + dst_field ++; + } + } + } -void flecs_stack_reset( - ecs_stack_t *stack) -{ - ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, - FLECS_STACK_LEAK_MSG); - stack->tail_page = stack->first; - stack->first->sp = 0; - stack->tail_cursor = NULL; -} + if (dst_count) { + if (!flecs_query_cache_init(impl, &cache_desc)) { + goto error; + } -void flecs_stack_init( - ecs_stack_t *stack) -{ - ecs_os_zeromem(stack); - stack->first = flecs_stack_page_new(0); - stack->first->sp = 0; - stack->tail_page = stack->first; -} + impl->cache->field_map = flecs_alloc_n(&impl->stage->allocator, + int8_t, FLECS_TERM_COUNT_MAX); -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, - FLECS_STACK_LEAK_MSG); - ecs_assert(stack->tail_page == stack->first, ECS_LEAK_DETECTED, - FLECS_STACK_LEAK_MSG); - ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, - FLECS_STACK_LEAK_MSG); + ecs_os_memcpy_n(impl->cache->field_map, field_map, int8_t, dst_count); + } + } else { + /* Check if query has features that are unsupported for uncached */ + ecs_assert(q->cache_kind == EcsQueryCacheNone, ECS_INTERNAL_ERROR, NULL); - do { - next = cur->next; - ecs_os_linc(&ecs_stack_allocator_free_count); - ecs_os_free(cur); - } while ((cur = next)); + if (!(q->flags & EcsQueryNested)) { + /* If uncached query is not create to populate a cached query, it + * should not have cascade modifiers */ + int32_t i, count = q->term_count; + ecs_term_t *terms = q->terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->src.id & EcsCascade) { + char *query_str = ecs_query_str(q); + ecs_err( + "cascade is unsupported for uncached query\n %s", + query_str); + ecs_os_free(query_str); + goto error; + } + } + } + } + + return 0; +error: + return -1; } -/** - * @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. - */ +static +void flecs_query_fini( + ecs_query_impl_t *impl) +{ + ecs_stage_t *stage = impl->stage; + ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &stage->allocator; -#include + if (impl->ctx_free) { + impl->ctx_free(impl->pub.ctx); + } -/** - * stm32tpl -- STM32 C++ Template Peripheral Library - * Visit https://github.com/antongus/stm32tpl for new versions - * - * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA - */ + if (impl->binding_ctx_free) { + impl->binding_ctx_free(impl->pub.binding_ctx); + } -#define MAX_PRECISION (10) -#define EXP_THRESHOLD (3) -#define INT64_MAX_F ((double)INT64_MAX) + if (impl->vars != &flecs_this_array) { + flecs_free(a, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * + impl->var_size, impl->vars); + flecs_name_index_fini(&impl->tvar_index); + flecs_name_index_fini(&impl->evar_index); + } -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 -}; + flecs_free_n(a, ecs_query_op_t, impl->op_count, impl->ops); + flecs_free_n(a, ecs_var_id_t, impl->pub.field_count, impl->src_vars); + flecs_free_n(a, int32_t, impl->pub.field_count, impl->monitor); -static -char* flecs_strbuf_itoa( - char *buf, - int64_t v) -{ - char *ptr = buf; - char * p1; - char c; + ecs_query_t *q = &impl->pub; + int i, count = q->term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &q->terms[i]; + if (!(term->flags_ & EcsTermKeepAlive)) { + continue; + } - if (!v) { - *ptr++ = '0'; - } else { - if (v < 0) { - ptr[0] = '-'; - ptr ++; - v *= -1; + ecs_component_record_t *cdr = flecs_components_get(q->real_world, term->id); + if (cdr) { + if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { + if (ecs_os_has_threading()) { + int32_t idr_keep_alive = ecs_os_adec(&cdr->keep_alive); + ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); + (void)idr_keep_alive; + } else { + cdr->keep_alive --; + ecs_assert(cdr->keep_alive >= 0, ECS_INTERNAL_ERROR, 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; - } + if (impl->tokens) { + flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); + } - p1 = p; + if (impl->cache) { + flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->cache->field_map); + flecs_query_cache_fini(impl); + } - while (p > ptr) { - c = *--p; - *p = *ptr; - *ptr++ = c; - } - ptr = p1; - } - return ptr; + flecs_poly_fini(impl, ecs_query_t); + flecs_bfree(&stage->allocators.query_impl, impl); } static -void flecs_strbuf_ftoa( - ecs_strbuf_t *out, - double f, - int precision, - char nan_delim) -{ - char buf[64]; - char * ptr = buf; - char c; - int64_t intPart; - int64_t exp = 0; +void flecs_query_poly_fini(void *ptr) { + flecs_query_fini(ptr); +} - if (ecs_os_isnan(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendlit(out, "NaN"); - ecs_strbuf_appendch(out, nan_delim); - return; - } else { - ecs_strbuf_appendlit(out, "NaN"); - return; - } - } - if (ecs_os_isinf(f)) { - if (nan_delim) { - ecs_strbuf_appendch(out, nan_delim); - ecs_strbuf_appendlit(out, "Inf"); - ecs_strbuf_appendch(out, nan_delim); - return; - } else { - ecs_strbuf_appendlit(out, "Inf"); - return; +static +void flecs_query_add_self_ref( + ecs_query_t *q) +{ + if (q->entity) { + int32_t t, term_count = q->term_count; + ecs_term_t *terms = q->terms; + + for (t = 0; t < term_count; t ++) { + ecs_term_t *term = &terms[t]; + if (ECS_TERM_REF_ID(&term->src) == q->entity) { + ecs_add_id(q->world, q->entity, term->id); + } } } +} - if (precision > MAX_PRECISION) { - precision = MAX_PRECISION; - } +void ecs_query_fini( + ecs_query_t *q) +{ + flecs_poly_assert(q, ecs_query_t); - if (f < 0) { - f = -f; - *ptr++ = '-'; - } + if (q->entity) { + /* If query is associated with entity, use poly dtor path */ + ecs_delete(q->world, q->entity); + } else { + flecs_query_fini(flecs_query_impl(q)); + } +} - 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_query_t* ecs_query_init( + ecs_world_t *world, + const ecs_query_desc_t *const_desc) +{ + ecs_world_t *world_arg = world; + ecs_stage_t *stage = flecs_stage_from_world(&world); + ecs_query_impl_t *result = flecs_bcalloc(&stage->allocators.query_impl); + flecs_poly_init(result, ecs_query_t); + + ecs_query_desc_t desc = *const_desc; + ecs_entity_t entity = const_desc->entity; - if (precision) { - f += rounders[precision]; + if (entity) { + /* Remove existing query if entity has one */ + bool deferred = false; + if (ecs_is_deferred(world)) { + deferred = true; + /* Ensures that remove operation doesn't get applied after bind */ + ecs_defer_suspend(world); + } + ecs_remove_pair(world, entity, ecs_id(EcsPoly), EcsQuery); + if (deferred) { + ecs_defer_resume(world); + } } - /* Make sure that number can be represented as 64bit int, increase exp */ - while (f > INT64_MAX_F) { - f /= 1000 * 1000 * 1000; - exp += 9; - } + /* Initialize the query */ + result->pub.entity = entity; + result->pub.real_world = world; + result->pub.world = world_arg; + result->stage = stage; - intPart = (int64_t)f; - f -= (double)intPart; + ecs_assert(flecs_poly_is(result->pub.real_world, ecs_world_t), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_poly_is(result->stage, ecs_stage_t), + ECS_INTERNAL_ERROR, NULL); - ptr = flecs_strbuf_itoa(ptr, intPart); + /* Validate input, translate to canonical query representation */ + if (flecs_query_finalize_query(world, &result->pub, &desc)) { + goto error; + } - if (precision) { - *ptr++ = '.'; - while (precision--) { - f *= 10.0; - c = (char)f; - *ptr++ = (char)('0' + c); - f -= c; - } - } - *ptr = 0; + /* If query terms have itself as source, add term ids to self. This makes it + * easy to attach components to queries, which is one of the ways + * applications can attach data to systems. */ + flecs_query_add_self_ref(&result->pub); - /* Remove trailing 0s */ - while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { - ptr[-1] = '\0'; - ptr --; - } - if (ptr != buf && ptr[-1] == '.') { - ptr[-1] = '\0'; - ptr --; + /* Initialize static context */ + result->pub.ctx = const_desc->ctx; + result->pub.binding_ctx = const_desc->binding_ctx; + result->ctx_free = const_desc->ctx_free; + result->binding_ctx_free = const_desc->binding_ctx_free; + result->dtor = flecs_query_poly_fini; + result->cache = NULL; + + /* Initialize query cache if necessary */ + if (flecs_query_create_cache(result, &desc)) { + goto error; } - /* If 0s before . exceed threshold, convert to exponent to save space - * without losing precision. */ - char *cur = ptr; - while ((&cur[-1] != buf) && (cur[-1] == '0')) { - cur --; + if (flecs_query_compile(world, stage, result)) { + goto error; } - if (exp || ((ptr - cur) > EXP_THRESHOLD)) { - cur[0] = '\0'; - exp += (ptr - cur); - ptr = cur; + /* Entity could've been set by finalize query if query is cached */ + entity = result->pub.entity; + if (entity) { + EcsPoly *poly = flecs_poly_bind(world, entity, ecs_query_t); + poly->poly = result; + flecs_poly_modified(world, entity, ecs_query_t); } - if (exp) { - char *p1 = &buf[1]; - if (nan_delim) { - ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); - buf[0] = nan_delim; - p1 ++; - } + return &result->pub; +error: + result->pub.entity = 0; + ecs_query_fini(&result->pub); + return NULL; +} - /* Make sure that exp starts after first character */ - c = p1[0]; +bool ecs_query_has( + ecs_query_t *q, + ecs_entity_t entity, + ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); - if (c) { - p1[0] = '.'; - do { - char t = (++p1)[0]; - if (t == '.') { - exp ++; - p1 --; - break; - } - p1[0] = c; - c = t; - exp ++; - } while (c); - ptr = p1 + 1; - } else { - ptr = p1; - } + *it = ecs_query_iter(q->world, q); + ecs_iter_set_var(it, 0, entity); + return ecs_query_next(it); +error: + return false; +} - ptr[0] = 'e'; - ptr = flecs_strbuf_itoa(ptr + 1, exp); +bool ecs_query_has_table( + ecs_query_t *q, + ecs_table_t *table, + ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); - if (nan_delim) { - ptr[0] = nan_delim; - ptr ++; + *it = ecs_query_iter(q->world, q); + ecs_iter_set_var_as_table(it, 0, table); + return ecs_query_next(it); +error: + return false; +} + +bool ecs_query_has_range( + ecs_query_t *q, + ecs_table_range_t *range, + ecs_iter_t *it) +{ + flecs_poly_assert(q, ecs_query_t); + + if (q->flags & EcsQueryMatchThis) { + if (range->table) { + if ((range->offset + range->count) > ecs_table_count(range->table)) { + return false; + } } + } - ptr[0] = '\0'; + *it = ecs_query_iter(q->world, q); + if (q->flags & EcsQueryMatchThis) { + ecs_iter_set_var_as_range(it, 0, range); } - - ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); + + return ecs_query_next(it); } -/* Add an extra element to the buffer */ -static -void flecs_strbuf_grow( - ecs_strbuf_t *b) +ecs_query_count_t ecs_query_count( + const ecs_query_t *q) { - if (!b->content) { - b->content = b->small_string; - b->size = ECS_STRBUF_SMALL_STRING_SIZE; - } else if (b->content == b->small_string) { - b->size *= 2; - b->content = ecs_os_malloc_n(char, b->size); - ecs_os_memcpy(b->content, b->small_string, b->length); - } else { - b->size *= 2; - if (b->size < 16) b->size = 16; - b->content = ecs_os_realloc_n(b->content, char, b->size); + flecs_poly_assert(q, ecs_query_t); + ecs_query_count_t result = {0}; + + if (!(q->flags & EcsQueryMatchThis)) { + return result; + } + + ecs_iter_t it = flecs_query_iter(q->world, q); + it.flags |= EcsIterNoData; + + while (ecs_query_next(&it)) { + result.results ++; + result.entities += it.count; + ecs_iter_skip(&it); } + + return result; } -static -char* flecs_strbuf_ptr( - ecs_strbuf_t *b) +bool ecs_query_is_true( + const ecs_query_t *q) { - ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); - return &b->content[b->length]; + flecs_poly_assert(q, ecs_query_t); + + ecs_iter_t it = flecs_query_iter(q->world, q); + return ecs_iter_is_true(&it); } -/* Append a format string to a buffer */ -static -void flecs_strbuf_vappend( - ecs_strbuf_t *b, - const char* str, - va_list args) +int32_t ecs_query_match_count( + const ecs_query_t *q) { - va_list arg_cpy; + flecs_poly_assert(q, ecs_query_t); - if (!str) { - return; + ecs_query_impl_t *impl = flecs_query_impl(q); + if (!impl->cache) { + return 0; + } else { + return impl->cache->match_count; } +} - /* Compute the memory required to add the string to the buffer. If user - * provided buffer, use space left in buffer, otherwise use space left in - * current element. */ - int32_t mem_left = b->size - b->length; - int32_t mem_required; - - va_copy(arg_cpy, args); +const ecs_query_t* ecs_query_get_cache_query( + const ecs_query_t *q) +{ + ecs_query_impl_t *impl = flecs_query_impl(q); + if (!impl->cache) { + return NULL; + } else { + return impl->cache->query; + } +} - if (b->content) { - mem_required = ecs_os_vsnprintf( - flecs_strbuf_ptr(b), - flecs_itosize(mem_left), str, args); +const ecs_query_t* ecs_query_get( + const ecs_world_t *world, + ecs_entity_t query) +{ + const EcsPoly *poly_comp = ecs_get_pair(world, query, EcsPoly, EcsQuery); + if (!poly_comp) { + return NULL; } else { - mem_required = ecs_os_vsnprintf(NULL, 0, str, args); - mem_left = 0; + flecs_poly_assert(poly_comp->poly, ecs_query_t); + return poly_comp->poly; } +} - ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); +/** + * @file query/util.c + * @brief Query utilities. + */ - if ((mem_required + 1) >= mem_left) { - while ((mem_required + 1) >= mem_left) { - flecs_strbuf_grow(b); - mem_left = b->size - b->length; - } - ecs_os_vsnprintf(flecs_strbuf_ptr(b), - flecs_itosize(mem_required + 1), str, arg_cpy); - } - b->length += mem_required; +ecs_query_lbl_t flecs_itolbl(int64_t val) { + return flecs_ito(int16_t, val); +} - va_end(arg_cpy); +ecs_var_id_t flecs_itovar(int64_t val) { + return flecs_ito(uint8_t, val); } -static -void flecs_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str, - int n) +ecs_var_id_t flecs_utovar(uint64_t val) { + return flecs_uto(uint8_t, val); +} + +bool flecs_term_is_builtin_pred( + ecs_term_t *term) { - int32_t mem_left = b->size - b->length; - while (n >= mem_left) { - flecs_strbuf_grow(b); - mem_left = b->size - b->length; + if (term->first.id & EcsIsEntity) { + ecs_entity_t id = ECS_TERM_REF_ID(&term->first); + if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { + return true; + } } - - ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); - b->length += n; + return false; } -static -void flecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) +const char* flecs_term_ref_var_name( + ecs_term_ref_t *ref) { - if (b->size == b->length) { - flecs_strbuf_grow(b); + if (!(ref->id & EcsIsVariable)) { + return NULL; } - flecs_strbuf_ptr(b)[0] = ch; - b->length ++; + if (ECS_TERM_REF_ID(ref) == EcsThis) { + return EcsThisName; + } + + return ref->name; } -void ecs_strbuf_vappend( - ecs_strbuf_t *b, - const char* fmt, - va_list args) +bool flecs_term_ref_is_wildcard( + ecs_term_ref_t *ref) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_vappend(b, fmt, args); + if ((ref->id & EcsIsVariable) && + ((ECS_TERM_REF_ID(ref) == EcsWildcard) || (ECS_TERM_REF_ID(ref) == EcsAny))) + { + return true; + } + return false; } -void ecs_strbuf_append( - ecs_strbuf_t *b, - const char* fmt, - ...) +bool flecs_term_is_fixed_id( + ecs_query_t *q, + ecs_term_t *term) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + /* Transitive/inherited terms have variable ids */ + if (term->flags_ & (EcsTermTransitive|EcsTermIdInherited)) { + return false; + } - va_list args; - va_start(args, fmt); - flecs_strbuf_vappend(b, fmt, args); - va_end(args); + /* Or terms can match different ids */ + if (term->oper == EcsOr) { + return false; + } + if ((term != q->terms) && term[-1].oper == EcsOr) { + return false; + } + + /* Wildcards can assume different ids */ + if (ecs_id_is_wildcard(term->id)) { + return false; + } + + /* Any terms can have fixed ids, but they require special handling */ + if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { + return false; + } + + /* First terms that are Not or Optional require special handling */ + if (term->oper == EcsNot || term->oper == EcsOptional) { + if (term == q->terms) { + return false; + } + } + + return true; } -void ecs_strbuf_appendstrn( - ecs_strbuf_t *b, - const char* str, - int32_t len) +bool flecs_term_is_or( + const ecs_query_t *q, + const ecs_term_t *term) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_appendstr(b, str, len); + bool first_term = term == q->terms; + return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); } -void ecs_strbuf_appendch( - ecs_strbuf_t *b, - char ch) +ecs_flags16_t flecs_query_ref_flags( + ecs_flags16_t flags, + ecs_flags16_t kind) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_appendch(b, ch); + return (flags >> kind) & (EcsQueryIsVar | EcsQueryIsEntity); } -void ecs_strbuf_appendint( - ecs_strbuf_t *b, - int64_t v) +bool flecs_query_is_written( + ecs_var_id_t var_id, + uint64_t written) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - char numbuf[32]; - char *ptr = flecs_strbuf_itoa(numbuf, v); - ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); + if (var_id == EcsVarNone) { + return true; + } + + ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); + return (written & (1ull << var_id)) != 0; } -void ecs_strbuf_appendflt( - ecs_strbuf_t *b, - double flt, - char nan_delim) +void flecs_query_write( + ecs_var_id_t var_id, + uint64_t *written) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_ftoa(b, flt, 10, nan_delim); + ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); + *written |= (1ull << var_id); } -void ecs_strbuf_appendbool( - ecs_strbuf_t *buffer, - bool v) +void flecs_query_write_ctx( + ecs_var_id_t var_id, + ecs_query_compile_ctx_t *ctx, + bool cond_write) { - ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); - if (v) { - ecs_strbuf_appendlit(buffer, "true"); - } else { - ecs_strbuf_appendlit(buffer, "false"); + bool is_written = flecs_query_is_written(var_id, ctx->written); + flecs_query_write(var_id, &ctx->written); + if (!is_written) { + if (cond_write) { + flecs_query_write(var_id, &ctx->cond_written); + } + } +} + +bool flecs_ref_is_written( + const ecs_query_op_t *op, + const ecs_query_ref_t *ref, + ecs_flags16_t kind, + uint64_t written) +{ + ecs_flags16_t flags = flecs_query_ref_flags(op->flags, kind); + if (flags & EcsQueryIsEntity) { + ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); + if (ref->entity) { + return true; + } + } else if (flags & EcsQueryIsVar) { + return flecs_query_is_written(ref->var, written); } + return false; } -void ecs_strbuf_appendstr( - ecs_strbuf_t *b, - const char* str) +ecs_allocator_t* flecs_query_get_allocator( + const ecs_iter_t *it) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); - flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); + ecs_world_t *world = it->world; + if (flecs_poly_is(world, ecs_world_t)) { + return &world->allocator; + } else { + ecs_assert(flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); + return &((ecs_stage_t*)world)->allocator; + } } -void ecs_strbuf_mergebuff( - ecs_strbuf_t *b, - ecs_strbuf_t *src) +const char* flecs_query_op_str( + uint16_t kind) { - if (src->content) { - ecs_strbuf_appendstr(b, src->content); + switch(kind) { + case EcsQueryAll: return "all "; + case EcsQueryAnd: return "and "; + case EcsQueryAndAny: return "andany "; + case EcsQueryTriv: return "triv "; + case EcsQueryCache: return "cache "; + case EcsQueryIsCache: return "xcache "; + case EcsQueryUp: return "up "; + case EcsQuerySelfUp: return "selfup "; + case EcsQueryWith: return "with "; + case EcsQueryTrav: return "trav "; + case EcsQueryAndFrom: return "andfrom "; + case EcsQueryOrFrom: return "orfrom "; + case EcsQueryNotFrom: return "notfrom "; + case EcsQueryIds: return "ids "; + case EcsQueryIdsRight: return "idsr "; + case EcsQueryIdsLeft: return "idsl "; + case EcsQueryEach: return "each "; + case EcsQueryStore: return "store "; + case EcsQueryReset: return "reset "; + case EcsQueryOr: return "or "; + case EcsQueryOptional: return "option "; + case EcsQueryIfVar: return "ifvar "; + case EcsQueryIfSet: return "ifset "; + case EcsQueryEnd: return "end "; + case EcsQueryNot: return "not "; + case EcsQueryPredEq: return "eq "; + case EcsQueryPredNeq: return "neq "; + case EcsQueryPredEqName: return "eq_nm "; + case EcsQueryPredNeqName: return "neq_nm "; + case EcsQueryPredEqMatch: return "eq_m "; + case EcsQueryPredNeqMatch: return "neq_m "; + case EcsQueryMemberEq: return "membereq "; + case EcsQueryMemberNeq: return "memberneq "; + case EcsQueryToggle: return "toggle "; + case EcsQueryToggleOption: return "togglopt "; + case EcsQueryUnionEq: return "union "; + case EcsQueryUnionEqWith: return "union_w "; + case EcsQueryUnionNeq: return "unionneq "; + case EcsQueryUnionEqUp: return "union_up "; + case EcsQueryUnionEqSelfUp: return "union_sup "; + case EcsQueryLookup: return "lookup "; + case EcsQuerySetVars: return "setvars "; + case EcsQuerySetThis: return "setthis "; + case EcsQuerySetFixed: return "setfix "; + case EcsQuerySetIds: return "setids "; + case EcsQuerySetId: return "setid "; + case EcsQueryContain: return "contain "; + case EcsQueryPairEq: return "pair_eq "; + case EcsQueryYield: return "yield "; + case EcsQueryNothing: return "nothing "; + default: return "!invalid "; } - ecs_strbuf_reset(src); } -char* ecs_strbuf_get( - ecs_strbuf_t *b) +static +int32_t flecs_query_op_ref_str( + const ecs_query_impl_t *query, + ecs_query_ref_t *ref, + ecs_flags16_t flags, + ecs_strbuf_t *buf) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - char *result = b->content; - if (!result) { - return NULL; - } - - ecs_strbuf_appendch(b, '\0'); - result = b->content; - - if (result == b->small_string) { - result = ecs_os_memdup_n(result, char, b->length + 1); + int32_t color_chars = 0; + if (flags & EcsQueryIsVar) { + ecs_assert(ref->var < query->var_count, ECS_INTERNAL_ERROR, NULL); + ecs_query_var_t *var = &query->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 & EcsQueryIsEntity) { + char *path = ecs_get_path(query->pub.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]"); } - - b->length = 0; - b->content = NULL; - b->size = 0; - b->list_sp = 0; - return result; + return color_chars; } -char* ecs_strbuf_get_small( - ecs_strbuf_t *b) +static +void flecs_query_str_append_bitset( + ecs_strbuf_t *buf, + ecs_flags64_t bitset) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - char *result = b->content; - result[b->length] = '\0'; - b->length = 0; - b->content = NULL; - b->size = 0; - return result; + ecs_strbuf_list_push(buf, "{", ","); + int8_t b; + for (b = 0; b < 64; b ++) { + if (bitset & (1llu << b)) { + ecs_strbuf_list_append(buf, "%d", b); + } + } + ecs_strbuf_list_pop(buf, "}"); } -void ecs_strbuf_reset( - ecs_strbuf_t *b) +static +void flecs_query_plan_w_profile( + const ecs_query_t *q, + const ecs_iter_t *it, + ecs_strbuf_t *buf) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - if (b->content && b->content != b->small_string) { - ecs_os_free(b->content); + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_query_op_t *ops = impl->ops; + int32_t i, count = impl->op_count, indent = 0; + if (!count) { + ecs_strbuf_append(buf, ""); + return; /* No plan */ } - *b = ECS_STRBUF_INIT; -} -void ecs_strbuf_list_push( - ecs_strbuf_t *b, - const char *list_open, - const char *separator) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, - "strbuf list is corrupt"); - b->list_sp ++; - ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, - ECS_INVALID_OPERATION, "max depth for strbuf list stack exceeded"); + for (i = 0; i < count; i ++) { + ecs_query_op_t *op = &ops[i]; + ecs_flags16_t flags = op->flags; + ecs_flags16_t src_flags = flecs_query_ref_flags(flags, EcsQuerySrc); + ecs_flags16_t first_flags = flecs_query_ref_flags(flags, EcsQueryFirst); + ecs_flags16_t second_flags = flecs_query_ref_flags(flags, EcsQuerySecond); - b->list_stack[b->list_sp].count = 0; - b->list_stack[b->list_sp].separator = separator; + if (it) { +#ifdef FLECS_DEBUG + const ecs_query_iter_t *rit = &it->priv_.iter.query; + ecs_strbuf_append(buf, + "#[green]%4d -> #[red]%4d <- #[grey] | ", + rit->profile[i].count[0], + rit->profile[i].count[1]); +#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); + 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 == EcsQueryEnd) { + indent --; } - } -} -void ecs_strbuf_list_pop( - ecs_strbuf_t *b, - const char *list_close) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, - "pop called more often than push for strbuf list"); + ecs_strbuf_append(buf, "%*s", indent, ""); + ecs_strbuf_appendstr(buf, flecs_query_op_str(op->kind)); + ecs_strbuf_appendstr(buf, " "); - b->list_sp --; + int32_t written = ecs_strbuf_written(buf); + for (int32_t j = 0; j < (12 - (written - start)); j ++) { + ecs_strbuf_appendch(buf, ' '); + } - 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); + hidden_chars = flecs_query_op_ref_str(impl, &op->src, src_flags, buf); + + if (op->kind == EcsQueryNot || + op->kind == EcsQueryOr || + op->kind == EcsQueryOptional || + op->kind == EcsQueryIfVar || + op->kind == EcsQueryIfSet) + { + indent ++; } - } -} -void ecs_strbuf_list_next( - ecs_strbuf_t *b) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); + if (op->kind == EcsQueryTriv) { + flecs_query_str_append_bitset(buf, op->src.entity); + } - 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); + if (op->kind == EcsQueryIfSet) { + ecs_strbuf_append(buf, "[%d]\n", op->other); + continue; } - } - b->list_stack[list_sp].count ++; -} -void ecs_strbuf_list_appendch( - ecs_strbuf_t *b, - char ch) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_strbuf_list_next(b); - flecs_strbuf_appendch(b, ch); -} + bool is_toggle = op->kind == EcsQueryToggle || + op->kind == EcsQueryToggleOption; -void ecs_strbuf_list_append( - ecs_strbuf_t *b, - const char *fmt, - ...) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); + if (!first_flags && !second_flags && !is_toggle) { + ecs_strbuf_appendstr(buf, "\n"); + continue; + } - ecs_strbuf_list_next(b); + written = ecs_strbuf_written(buf) - hidden_chars; + for (int32_t j = 0; j < (30 - (written - start)); j ++) { + ecs_strbuf_appendch(buf, ' '); + } - va_list args; - va_start(args, fmt); - flecs_strbuf_vappend(b, fmt, args); - va_end(args); -} + if (is_toggle) { + if (op->first.entity) { + flecs_query_str_append_bitset(buf, op->first.entity); + } + if (op->second.entity) { + if (op->first.entity) { + ecs_strbuf_appendlit(buf, ", !"); + } + flecs_query_str_append_bitset(buf, op->second.entity); + } + ecs_strbuf_appendstr(buf, "\n"); + continue; + } -void ecs_strbuf_list_appendstr( - ecs_strbuf_t *b, - const char *str) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_appendstr(buf, "("); + if (op->kind == EcsQueryMemberEq || op->kind == EcsQueryMemberNeq) { + uint32_t offset = (uint32_t)op->first.entity; + uint32_t size = (uint32_t)(op->first.entity >> 32); + ecs_strbuf_append(buf, "#[yellow]elem#[reset]([%d], 0x%x, 0x%x)", + op->field_index, size, offset); + } else { + flecs_query_op_ref_str(impl, &op->first, first_flags, buf); + } - ecs_strbuf_list_next(b); - ecs_strbuf_appendstr(b, str); -} + if (second_flags) { + ecs_strbuf_appendstr(buf, ", "); + flecs_query_op_ref_str(impl, &op->second, second_flags, buf); + } else { + switch (op->kind) { + case EcsQueryPredEqName: + case EcsQueryPredNeqName: + case EcsQueryPredEqMatch: + case EcsQueryPredNeqMatch: { + int8_t term_index = op->term_index; + ecs_strbuf_appendstr(buf, ", #[yellow]\""); + ecs_strbuf_appendstr(buf, q->terms[term_index].second.name); + ecs_strbuf_appendstr(buf, "\"#[reset]"); + break; + } + case EcsQueryLookup: { + ecs_var_id_t src_id = op->src.var; + ecs_strbuf_appendstr(buf, ", #[yellow]\""); + ecs_strbuf_appendstr(buf, impl->vars[src_id].lookup); + ecs_strbuf_appendstr(buf, "\"#[reset]"); + break; + } + default: + break; + } + } -void ecs_strbuf_list_appendstrn( - ecs_strbuf_t *b, - const char *str, - int32_t n) -{ - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_strbuf_appendch(buf, ')'); - ecs_strbuf_list_next(b); - ecs_strbuf_appendstrn(b, str, n); + ecs_strbuf_appendch(buf, '\n'); + } } -int32_t ecs_strbuf_written( - const ecs_strbuf_t *b) +char* ecs_query_plan_w_profile( + const ecs_query_t *q, + const ecs_iter_t *it) { - ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return b->length; -} + flecs_poly_assert(q, ecs_query_t); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + flecs_query_plan_w_profile(q, it, &buf); + // ecs_query_impl_t *impl = flecs_query_impl(q); + // if (impl->cache) { + // ecs_strbuf_appendch(&buf, '\n'); + // flecs_query_plan_w_profile(impl->cache->query, it, &buf); + // } -static -ecs_switch_page_t* flecs_switch_page_ensure( - ecs_switch_t* sw, - uint32_t elem) -{ - int32_t page_index = FLECS_SPARSE_PAGE(elem); - ecs_vec_set_min_count_zeromem_t( - sw->hdrs.allocator, &sw->pages, ecs_switch_page_t, page_index + 1); +#ifdef FLECS_LOG + char *str = ecs_strbuf_get(&buf); + flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); + ecs_os_free(str); +#endif - ecs_switch_page_t *page = ecs_vec_get_t( - &sw->pages, ecs_switch_page_t, page_index); - if (!ecs_vec_count(&page->nodes)) { - ecs_vec_init_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, - FLECS_SPARSE_PAGE_SIZE); - ecs_vec_init_t(sw->hdrs.allocator, &page->values, uint64_t, - FLECS_SPARSE_PAGE_SIZE); - ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->nodes, - ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); - ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->values, - uint64_t, FLECS_SPARSE_PAGE_SIZE); - } + return ecs_strbuf_get(&buf); +} - return page; +char* ecs_query_plan( + const ecs_query_t *q) +{ + return ecs_query_plan_w_profile(q, NULL); } static -ecs_switch_page_t* flecs_switch_page_get( - const ecs_switch_t* sw, - uint32_t elem) +void flecs_query_str_add_id( + const ecs_world_t *world, + ecs_strbuf_t *buf, + const ecs_term_t *term, + const ecs_term_ref_t *ref, + bool is_src) { - int32_t page_index = FLECS_SPARSE_PAGE(elem); - if (page_index >= ecs_vec_count(&sw->pages)) { - return NULL; + bool is_added = false; + ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); + if (ref->id & EcsIsVariable && !ecs_id_is_wildcard(ref_id)){ + ecs_strbuf_appendlit(buf, "$"); } - ecs_switch_page_t *page = ecs_vec_get_t( - &sw->pages, ecs_switch_page_t, page_index); - if (!ecs_vec_count(&page->nodes)) { - return NULL; + if (ref_id) { + char *path = ecs_get_path(world, ref_id); + ecs_strbuf_appendstr(buf, path); + ecs_os_free(path); + } else if (ref->name) { + ecs_strbuf_appendstr(buf, ref->name); + } else { + ecs_strbuf_appendlit(buf, "#0"); } + is_added = true; - return page; -} - -static -void flecs_switch_page_fini( - ecs_switch_t* sw, - ecs_switch_page_t *page) -{ - if (ecs_vec_count(&page->nodes)) { - ecs_vec_fini_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t); - ecs_vec_fini_t(sw->hdrs.allocator, &page->values, uint64_t); + ecs_flags64_t flags = ECS_TERM_REF_FLAGS(ref); + 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 |= EcsSelf; } -} -static -ecs_switch_node_t* flecs_switch_get_node( - ecs_switch_t* sw, - uint32_t element) -{ - if (!element) { - return NULL; - } + if ((flags & EcsTraverseFlags) != EcsSelf) { + if (is_added) { + ecs_strbuf_list_push(buf, "|", "|"); + } else { + ecs_strbuf_list_push(buf, "", "|"); + } + if (is_src) { + if (flags & EcsSelf) { + ecs_strbuf_list_appendstr(buf, "self"); + } - ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); - int32_t page_offset = FLECS_SPARSE_OFFSET(element); - return ecs_vec_get_t(&page->nodes, ecs_switch_node_t, page_offset); -} + if (flags & EcsCascade) { + ecs_strbuf_list_appendstr(buf, "cascade"); + } else if (flags & EcsUp) { + ecs_strbuf_list_appendstr(buf, "up"); + } -void flecs_switch_init( - ecs_switch_t* sw, - ecs_allocator_t *allocator) -{ - ecs_map_init(&sw->hdrs, allocator); - ecs_vec_init_t(allocator, &sw->pages, ecs_switch_page_t, 0); -} + if (flags & EcsDesc) { + ecs_strbuf_list_appendstr(buf, "desc"); + } + + if (term->trav) { + char *rel_path = ecs_get_path(world, term->trav); + ecs_strbuf_appendlit(buf, " "); + ecs_strbuf_appendstr(buf, rel_path); + ecs_os_free(rel_path); + } + } -void flecs_switch_fini( - ecs_switch_t* sw) -{ - int32_t i, count = ecs_vec_count(&sw->pages); - ecs_switch_page_t *pages = ecs_vec_first(&sw->pages); - for (i = 0; i < count; i ++) { - flecs_switch_page_fini(sw, &pages[i]); + ecs_strbuf_list_pop(buf, ""); } - ecs_vec_fini_t(sw->hdrs.allocator, &sw->pages, ecs_switch_page_t); - ecs_map_fini(&sw->hdrs); } -bool flecs_switch_set( - ecs_switch_t *sw, - uint32_t element, - uint64_t value) +void flecs_term_to_buf( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_strbuf_t *buf, + int32_t t) { - ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); - int32_t page_offset = FLECS_SPARSE_OFFSET(element); + const ecs_term_ref_t *src = &term->src; + const ecs_term_ref_t *first = &term->first; + const ecs_term_ref_t *second = &term->second; - uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); - if (elem[0] == value) { - return false; - } + ecs_entity_t src_id = ECS_TERM_REF_ID(src); + ecs_entity_t first_id = ECS_TERM_REF_ID(first); - ecs_switch_node_t *node = ecs_vec_get_t( - &page->nodes, ecs_switch_node_t, page_offset); + bool src_set = !ecs_term_match_0(term); + bool second_set = ecs_term_ref_is_set(second); - uint64_t prev_value = elem[0]; - if (prev_value) { - ecs_switch_node_t *prev = flecs_switch_get_node(sw, node->prev); - if (prev) { - prev->next = node->next; + if (first_id == EcsScopeOpen) { + ecs_strbuf_appendlit(buf, "{"); + return; + } else if (first_id == EcsScopeClose) { + ecs_strbuf_appendlit(buf, "}"); + return; + } + + if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || + (ECS_TERM_REF_ID(&term->first) == EcsPredMatch)) && + (term->first.id & EcsIsEntity)) + { + ecs_strbuf_appendlit(buf, "$"); + if (ECS_TERM_REF_ID(&term->src) == EcsThis && + (term->src.id & EcsIsVariable)) + { + ecs_strbuf_appendlit(buf, "this"); + } else if (term->src.id & EcsIsVariable) { + if (term->src.name) { + ecs_strbuf_appendstr(buf, term->src.name); + } else { + ecs_strbuf_appendstr(buf, "<>"); + } + } else { + /* Shouldn't happen */ } - ecs_switch_node_t *next = flecs_switch_get_node(sw, node->next); - if (next) { - next->prev = node->prev; + if (ECS_TERM_REF_ID(&term->first) == EcsPredEq) { + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, " != "); + } else { + ecs_strbuf_appendlit(buf, " == "); + } + } else if (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) { + ecs_strbuf_appendlit(buf, " ~= "); } - if (!prev) { - uint64_t *hdr = ecs_map_get(&sw->hdrs, prev_value); - ecs_assert(hdr[0] == (uint64_t)element, ECS_INTERNAL_ERROR, NULL); - hdr[0] = (uint64_t)node->next; + if (term->second.id & EcsIsEntity) { + if (term->second.id != 0) { + ecs_get_path_w_sep_buf(world, 0, ECS_TERM_REF_ID(&term->second), + ".", NULL, buf, false); + } + } else { + if (term->second.id & EcsIsVariable) { + ecs_strbuf_appendlit(buf, "$"); + if (term->second.name) { + ecs_strbuf_appendstr(buf, term->second.name); + } else if (ECS_TERM_REF_ID(&term->second) == EcsThis) { + ecs_strbuf_appendlit(buf, "this"); + } + } else if (term->second.id & EcsIsName) { + ecs_strbuf_appendlit(buf, "\""); + if ((ECS_TERM_REF_ID(&term->first) == EcsPredMatch) && + (term->oper == EcsNot)) + { + ecs_strbuf_appendlit(buf, "!"); + } + ecs_strbuf_appendstr(buf, term->second.name); + ecs_strbuf_appendlit(buf, "\""); + } } + + return; } - elem[0] = value; + if (!t || !(term[-1].oper == EcsOr)) { + if (term->inout == EcsIn) { + ecs_strbuf_appendlit(buf, "[in] "); + } else if (term->inout == EcsInOut) { + ecs_strbuf_appendlit(buf, "[inout] "); + } else if (term->inout == EcsOut) { + ecs_strbuf_appendlit(buf, "[out] "); + } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { + ecs_strbuf_appendlit(buf, "[none] "); + } + } - if (value) { - uint64_t *hdr = ecs_map_ensure(&sw->hdrs, value); + if (term->oper == EcsNot) { + ecs_strbuf_appendlit(buf, "!"); + } else if (term->oper == EcsOptional) { + ecs_strbuf_appendlit(buf, "?"); + } - if (!hdr[0]) { - hdr[0] = (uint64_t)element; - node->next = 0; + if (!src_set) { + flecs_query_str_add_id(world, buf, term, &term->first, false); + if (!second_set) { + ecs_strbuf_appendlit(buf, "()"); } else { - ecs_switch_node_t *head = flecs_switch_get_node(sw, (uint32_t)hdr[0]); - ecs_assert(head->prev == 0, ECS_INTERNAL_ERROR, NULL); - head->prev = element; - - node->next = (uint32_t)hdr[0]; - hdr[0] = (uint64_t)element; - ecs_assert(node->next != element, ECS_INTERNAL_ERROR, NULL); + ecs_strbuf_appendlit(buf, "(#0,"); + flecs_query_str_add_id(world, buf, term, &term->second, false); + ecs_strbuf_appendlit(buf, ")"); + } + } else { + ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; + if (flags && !ECS_HAS_ID_FLAG(flags, PAIR)) { + ecs_strbuf_appendstr(buf, ecs_id_flag_str(flags)); + ecs_strbuf_appendch(buf, '|'); } - node->prev = 0; + flecs_query_str_add_id(world, buf, term, &term->first, false); + ecs_strbuf_appendlit(buf, "("); + if (term->src.id & EcsIsEntity && src_id == first_id) { + ecs_strbuf_appendlit(buf, "$"); + } else { + flecs_query_str_add_id(world, buf, term, &term->src, true); + } + if (second_set) { + ecs_strbuf_appendlit(buf, ","); + flecs_query_str_add_id(world, buf, term, &term->second, false); + } + ecs_strbuf_appendlit(buf, ")"); } - - return true; } -bool flecs_switch_reset( - ecs_switch_t *sw, - uint32_t element) +char* ecs_term_str( + const ecs_world_t *world, + const ecs_term_t *term) { - return flecs_switch_set(sw, element, 0); + ecs_strbuf_t buf = ECS_STRBUF_INIT; + flecs_term_to_buf(world, term, &buf, 0); + return ecs_strbuf_get(&buf); } -uint64_t flecs_switch_get( - const ecs_switch_t *sw, - uint32_t element) +char* ecs_query_str( + const ecs_query_t *q) { - ecs_switch_page_t *page = flecs_switch_page_get(sw, element); - if (!page) { - return 0; - } + ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world = q->world; - int32_t page_offset = FLECS_SPARSE_OFFSET(element); - uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); - return elem[0]; -} + ecs_strbuf_t buf = ECS_STRBUF_INIT; + const ecs_term_t *terms = q->terms; + int32_t i, count = q->term_count; -uint32_t flecs_switch_first( - const ecs_switch_t *sw, - uint64_t value) -{ - uint64_t *hdr = ecs_map_get(&sw->hdrs, value); - if (!hdr) { - return 0; - } + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &terms[i]; - return (uint32_t)hdr[0]; -} + flecs_term_to_buf(world, term, &buf, i); -FLECS_DBG_API -uint32_t flecs_switch_next( - const ecs_switch_t *sw, - uint32_t previous) -{ - ecs_switch_page_t *page = flecs_switch_page_get(sw, previous); - if (!page) { - return 0; + if (i != (count - 1)) { + if (term->oper == EcsOr) { + ecs_strbuf_appendlit(&buf, " || "); + } else { + if (ECS_TERM_REF_ID(&term->first) != EcsScopeOpen) { + if (ECS_TERM_REF_ID(&term[1].first) != EcsScopeClose) { + ecs_strbuf_appendlit(&buf, ", "); + } + } + } + } } - int32_t offset = FLECS_SPARSE_OFFSET(previous); - ecs_switch_node_t *elem = ecs_vec_get_t( - &page->nodes, ecs_switch_node_t, offset); - return elem->next; + return ecs_strbuf_get(&buf); +error: + return NULL; } -ecs_map_iter_t flecs_switch_targets( - const ecs_switch_t *sw) +int32_t flecs_query_pivot_term( + const ecs_world_t *world, + const ecs_query_t *query) { - return ecs_map_iter(&sw->hdrs); -} + ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); -/** - * @file datastructures/vec.c - * @brief Vector with allocator support. - */ + const ecs_term_t *terms = query->terms; + int32_t i, term_count = query->term_count; + int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; + for (i = 0; i < term_count; i ++) { + const ecs_term_t *term = &terms[i]; + ecs_id_t id = term->id; -void ecs_vec_init( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size, - int32_t elem_count) -{ - ecs_vec_init_w_dbg_info(allocator, v, size, elem_count, NULL); -} + if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { + continue; + } -void ecs_vec_init_w_dbg_info( - struct ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size, - int32_t elem_count, - const char *type_name) -{ - (void)type_name; - ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); - v->array = NULL; - v->count = 0; - if (elem_count) { - if (allocator) { - v->array = flecs_alloc_w_dbg_info( - allocator, size * elem_count, type_name); - } else { - v->array = ecs_os_malloc(size * elem_count); + if (!ecs_term_match_this(term)) { + continue; } - } - v->size = elem_count; -#ifdef FLECS_SANITIZE - v->elem_size = size; - v->type_name = type_name; -#endif -} -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 -} + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + /* If one of the terms does not match with any data, iterator + * should not return anything */ + return -2; /* -2 indicates query doesn't match anything */ + } -void ecs_vec_fini( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size) -{ - if (v->array) { - ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); - if (allocator) { - flecs_free(allocator, size * v->size, v->array); - } else { - ecs_os_free(v->array); + int32_t table_count = flecs_table_cache_count(&cdr->cache); + if (min_count == -1 || table_count < min_count) { + min_count = table_count; + pivot_term = i; + if ((term->src.id & EcsTraverseFlags) == EcsSelf) { + self_pivot_term = i; + } } - v->array = NULL; - v->count = 0; - v->size = 0; } -} -ecs_vec_t* ecs_vec_reset( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size) -{ - if (!v->size) { - ecs_vec_init(allocator, v, size, 0); - } else { - ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); - ecs_vec_clear(v); + if (self_pivot_term != -1) { + pivot_term = self_pivot_term; } - return v; + + return pivot_term; +error: + return -2; } -void ecs_vec_clear( - ecs_vec_t *vec) +void flecs_query_apply_iter_flags( + ecs_iter_t *it, + const ecs_query_t *query) { - vec->count = 0; + ECS_BIT_COND(it->flags, EcsIterHasCondSet, + ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); + ECS_BIT_COND(it->flags, EcsIterNoData, query->data_fields == 0); } -ecs_vec_t ecs_vec_copy( - ecs_allocator_t *allocator, - const ecs_vec_t *v, - ecs_size_t size) -{ - ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); - void *array; - if (allocator) { - array = flecs_dup(allocator, size * v->size, v->array); - } else { - array = ecs_os_memdup(v->array, size * v->size); - } - return (ecs_vec_t) { - .count = v->count, - .size = v->size, - .array = array -#ifdef FLECS_SANITIZE - , .elem_size = size +/** + * @file query/validator.c + * @brief Validate and finalize queries. + */ + +#include + +#ifdef FLECS_QUERY_DSL #endif - }; -} -ecs_vec_t ecs_vec_copy_shrink( - ecs_allocator_t *allocator, - const ecs_vec_t *v, - ecs_size_t size) +static +void flecs_query_validator_error( + const ecs_query_validator_ctx_t *ctx, + const char *fmt, + ...) { - ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); - int32_t count = v->count; - void *array = NULL; - if (count) { - if (allocator) { - array = flecs_dup(allocator, size * count, v->array); - } else { - array = ecs_os_memdup(v->array, size * count); - } + ecs_strbuf_t buf = ECS_STRBUF_INIT; + + if (ctx->desc && ctx->desc->expr) { + ecs_strbuf_appendlit(&buf, "expr: "); + ecs_strbuf_appendstr(&buf, ctx->desc->expr); + ecs_strbuf_appendlit(&buf, "\n"); } - return (ecs_vec_t) { - .count = count, - .size = count, - .array = array -#ifdef FLECS_SANITIZE - , .elem_size = size -#endif - }; -} -void ecs_vec_reclaim( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size) -{ - ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); - int32_t count = v->count; - if (count < v->size) { - if (count) { - if (allocator) { - v->array = flecs_realloc( - allocator, size * count, size * v->size, v->array); + if (ctx->query) { + ecs_query_t *query = ctx->query; + const ecs_term_t *terms = query->terms; + int32_t i, count = query->term_count; + + for (i = 0; i < count; i ++) { + const ecs_term_t *term = &terms[i]; + if (ctx->term_index == i) { + ecs_strbuf_appendlit(&buf, " > "); } else { - v->array = ecs_os_realloc(v->array, size * count); + ecs_strbuf_appendlit(&buf, " "); } - v->size = count; - } else { - ecs_vec_fini(allocator, v, size); + flecs_term_to_buf(ctx->world, term, &buf, i); + if (term->oper == EcsOr) { + ecs_strbuf_appendlit(&buf, " ||"); + } else if (i != (count - 1)) { + ecs_strbuf_appendlit(&buf, ","); + } + ecs_strbuf_appendlit(&buf, "\n"); } + } else { + ecs_strbuf_appendlit(&buf, " > "); + flecs_term_to_buf(ctx->world, ctx->term, &buf, 0); + ecs_strbuf_appendlit(&buf, "\n"); + } + + char *expr = ecs_strbuf_get(&buf); + const char *name = NULL; + if (ctx->query && ctx->query->entity) { + name = ecs_get_name(ctx->query->world, ctx->query->entity); } + + va_list args; + va_start(args, fmt); + char *msg = flecs_vasprintf(fmt, args); + ecs_parser_error(name, NULL, 0, "%s\n%s", msg, expr); + ecs_os_free(msg); + ecs_os_free(expr); + + va_end(args); } -void ecs_vec_set_size( - ecs_allocator_t *allocator, - ecs_vec_t *v, - ecs_size_t size, - int32_t elem_count) +static +int flecs_term_ref_finalize_flags( + ecs_term_ref_t *ref, + ecs_query_validator_ctx_t *ctx) { - 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; - } + if ((ref->id & EcsIsEntity) && (ref->id & EcsIsVariable)) { + flecs_query_validator_error(ctx, "cannot set both IsEntity and IsVariable"); + return -1; + } - elem_count = flecs_next_pow_of_2(elem_count); - if (elem_count < 2) { - elem_count = 2; + if (ref->name && ref->name[0] == '$') { + if (!ref->name[1]) { + if (!(ref->id & EcsIsName)) { + if (ref->id & ~EcsTermRefFlags) { + flecs_query_validator_error(ctx, + "conflicting values for .name and .id"); + return -1; + } + + ref->id |= EcsVariable; + ref->id |= EcsIsVariable; + } + } else { + ref->name = &ref->name[1]; + ref->id |= EcsIsVariable; } - if (elem_count != v->size) { - if (allocator) { -#ifdef FLECS_SANITIZE - v->array = flecs_realloc_w_dbg_info( - allocator, size * elem_count, size * v->size, v->array, - v->type_name); -#else - v->array = flecs_realloc( - allocator, size * elem_count, size * v->size, v->array); -#endif + } + + if (!(ref->id & (EcsIsEntity|EcsIsVariable|EcsIsName))) { + if (ECS_TERM_REF_ID(ref) || ref->name) { + if (ECS_TERM_REF_ID(ref) == EcsThis || + ECS_TERM_REF_ID(ref) == EcsWildcard || + ECS_TERM_REF_ID(ref) == EcsAny || + ECS_TERM_REF_ID(ref) == EcsVariable) + { + /* Builtin variable ids default to variable */ + ref->id |= EcsIsVariable; } else { - v->array = ecs_os_realloc(v->array, size * elem_count); + ref->id |= EcsIsEntity; } - v->size = elem_count; } } + + return 0; } -void ecs_vec_set_min_size( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count) +static +bool flecs_identifier_is_0( + const char *id) { - if (elem_count > vec->size) { - ecs_vec_set_size(allocator, vec, size, elem_count); - } + return id[0] == '#' && id[1] == '0' && !id[2]; } -void ecs_vec_set_min_count( - struct ecs_allocator_t *allocator, - ecs_vec_t *vec, - ecs_size_t size, - int32_t elem_count) +static +int flecs_term_ref_lookup( + const ecs_world_t *world, + ecs_entity_t scope, + ecs_term_ref_t *ref, + ecs_query_validator_ctx_t *ctx) { - ecs_vec_set_min_size(allocator, vec, size, elem_count); - if (vec->count < elem_count) { - vec->count = elem_count; + const char *name = ref->name; + if (!name) { + return 0; } -} -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)); + if (ref->id & EcsIsVariable) { + if (!ecs_os_strcmp(name, "this")) { + ref->id = EcsThis | ECS_TERM_REF_FLAGS(ref); + ref->name = NULL; + return 0; + } + return 0; + } else if (ref->id & EcsIsName) { + if (ref->name == NULL) { + flecs_query_validator_error(ctx, "IsName flag specified without name"); + return -1; + } + return 0; } -} -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); + ecs_assert(ref->id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + + if (flecs_identifier_is_0(name)) { + if (ECS_TERM_REF_ID(ref)) { + flecs_query_validator_error( + ctx, "name '0' does not match entity id"); + return -1; } + ref->name = NULL; + return 0; + } - v->count = elem_count; + ecs_entity_t e = 0; + if (scope) { + e = ecs_lookup_child(world, scope, name); } -} -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 (!e) { + e = ecs_lookup(world, name); + } -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); + if (!e) { + if (ctx->query && (ctx->query->flags & EcsQueryAllowUnresolvedByName)) { + ref->id |= EcsIsName; + ref->id &= ~EcsIsEntity; + return 0; + } else { + flecs_query_validator_error(ctx, "unresolved identifier '%s'", name); + return -1; + } } - v->count = count + 1; - return ECS_ELEM(v->array, size, count); -} -void ecs_vec_remove( - ecs_vec_t *v, - ecs_size_t size, - int32_t index) -{ - ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); - ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); - if (index == --v->count) { - return; + ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); + if (ref_id && ref_id != e) { + char *e_str = ecs_get_path(world, ref_id); + flecs_query_validator_error(ctx, "name '%s' does not match term.id '%s'", + name, e_str); + ecs_os_free(e_str); + return -1; } - ecs_os_memcpy( - ECS_ELEM(v->array, size, index), - ECS_ELEM(v->array, size, v->count), - size); -} + ref->id = e | ECS_TERM_REF_FLAGS(ref); + ref_id = ECS_TERM_REF_ID(ref); -void ecs_vec_remove_last( - ecs_vec_t *v) -{ - v->count --; -} + if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || + !ecs_os_strcmp(name, "$")) + { + ref->id &= ~EcsIsEntity; + ref->id |= EcsIsVariable; + } -int32_t ecs_vec_count( - const ecs_vec_t *v) -{ - return v->count; -} + /* Check if looked up id is alive (relevant for numerical ids) */ + if (!(ref->id & EcsIsName) && ref_id) { + if (!ecs_is_alive(world, ref_id)) { + flecs_query_validator_error(ctx, "identifier '%s' is not alive", ref->name); + return -1; + } -int32_t ecs_vec_size( - const ecs_vec_t *v) -{ - return v->size; -} + ref->name = NULL; + return 0; + } -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 >= 0, ECS_OUT_OF_RANGE, NULL); - ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); - return ECS_ELEM(v->array, size, index); + return 0; } -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); +static +ecs_id_t flecs_wildcard_to_any(ecs_id_t id) { + ecs_id_t flags = id & EcsTermRefFlags; + + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); + if (first == EcsWildcard) id = ecs_pair(EcsAny, second); + if (second == EcsWildcard) id = ecs_pair(ECS_PAIR_FIRST(id), EcsAny); + } else if ((id & ~EcsTermRefFlags) == EcsWildcard) { + id = EcsAny; + } + + return id | flags; } -void* ecs_vec_first( - const ecs_vec_t *v) +static +int flecs_term_refs_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) { - return v->array; -} + ecs_term_ref_t *src = &term->src; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; - /** - * @file queries/api.c - * @brief User facing API for rules. - */ + /* Include subsets for component by default, to support inheritance */ + if (!(first->id & EcsTraverseFlags)) { + first->id |= EcsSelf; + } -#include + /* Traverse Self by default for pair target */ + if (!(second->id & EcsTraverseFlags)) { + if (ECS_TERM_REF_ID(second) || second->name || (second->id & EcsIsEntity)) { + second->id |= EcsSelf; + } + } -/* Placeholder arrays for queries that only have $this variable */ -ecs_query_var_t flecs_this_array = { - .kind = EcsVarTable, - .table_id = EcsVarNone -}; -char *flecs_this_name_array = NULL; + /* Source defaults to This */ + if (!ECS_TERM_REF_ID(src) && (src->name == NULL) && !(src->id & EcsIsEntity)) { + src->id = EcsThis | ECS_TERM_REF_FLAGS(src); + src->id |= EcsIsVariable; + } -ecs_mixins_t ecs_query_t_mixins = { - .type_name = "ecs_query_t", - .elems = { - [EcsMixinWorld] = offsetof(ecs_query_impl_t, pub.real_world), - [EcsMixinEntity] = offsetof(ecs_query_impl_t, pub.entity), - [EcsMixinDtor] = offsetof(ecs_query_impl_t, dtor) + /* Initialize term identifier flags */ + if (flecs_term_ref_finalize_flags(src, ctx)) { + return -1; } -}; -int32_t ecs_query_find_var( - const ecs_query_t *q, - const char *name) -{ - flecs_poly_assert(q, ecs_query_t); + if (flecs_term_ref_finalize_flags(first, ctx)) { + return -1; + } - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_var_id_t var_id = flecs_query_find_var_id(impl, name, EcsVarEntity); - if (var_id == EcsVarNone) { - if (q->flags & EcsQueryMatchThis) { - if (!ecs_os_strcmp(name, EcsThisName)) { - var_id = 0; - } - } - if (var_id == EcsVarNone) { + if (flecs_term_ref_finalize_flags(second, ctx)) { + return -1; + } + + /* Lookup term identifiers by name */ + if (flecs_term_ref_lookup(world, 0, src, ctx)) { + return -1; + } + if (flecs_term_ref_lookup(world, 0, first, ctx)) { + return -1; + } + + ecs_entity_t first_id = 0; + ecs_entity_t oneof = 0; + if (first->id & EcsIsEntity) { + first_id = ECS_TERM_REF_ID(first); + + if (!first_id) { + flecs_query_validator_error(ctx, "invalid \"0\" for first.name"); return -1; } + + /* 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); } - return (int32_t)var_id; + + if (flecs_term_ref_lookup(world, oneof, &term->second, ctx)) { + return -1; + } + + /* If source is 0, reset traversal flags */ + if (ECS_TERM_REF_ID(src) == 0 && src->id & EcsIsEntity) { + src->id &= ~EcsTraverseFlags; + term->trav = 0; + } + + /* If source is wildcard, term won't return any data */ + if ((src->id & EcsIsVariable) && ecs_id_is_wildcard(ECS_TERM_REF_ID(src))) { + term->inout = EcsInOutNone; + } + + /* If operator is Not, automatically convert wildcard queries to any */ + if (term->oper == EcsNot) { + if (ECS_TERM_REF_ID(first) == EcsWildcard) { + first->id = EcsAny | ECS_TERM_REF_FLAGS(first); + } + + if (ECS_TERM_REF_ID(second) == EcsWildcard) { + second->id = EcsAny | ECS_TERM_REF_FLAGS(second); + } + + term->id = flecs_wildcard_to_any(term->id); + } + + return 0; } -const char* ecs_query_var_name( - const ecs_query_t *q, - int32_t var_id) +static +ecs_entity_t flecs_term_ref_get_entity( + const ecs_term_ref_t *ref) { - flecs_poly_assert(q, ecs_query_t); - - if (var_id) { - ecs_assert(var_id < flecs_query_impl(q)->var_count, - ECS_INVALID_PARAMETER, NULL); - return flecs_query_impl(q)->vars[var_id].name; + if (ref->id & EcsIsEntity) { + return ECS_TERM_REF_ID(ref); /* Id is known */ + } else if (ref->id & EcsIsVariable) { + /* Return wildcard for variables, as they aren't known yet */ + if (ECS_TERM_REF_ID(ref) != EcsAny) { + /* Any variable should not use wildcard, as this would return all + * ids matching a wildcard, whereas Any returns the first match */ + return EcsWildcard; + } else { + return EcsAny; + } } else { - return EcsThisName; + return 0; /* Term id is uninitialized */ } } -bool ecs_query_var_is_entity( - const ecs_query_t *q, - int32_t var_id) +static +int flecs_term_populate_id( + ecs_term_t *term) { - flecs_poly_assert(q, ecs_query_t); + ecs_entity_t first = flecs_term_ref_get_entity(&term->first); + ecs_entity_t second = flecs_term_ref_get_entity(&term->second); + ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; - return flecs_query_impl(q)->vars[var_id].kind == EcsVarEntity; + if (first & ECS_ID_FLAGS_MASK) { + return -1; + } + if (second & ECS_ID_FLAGS_MASK) { + return -1; + } + + if ((second || (term->second.id & EcsIsEntity))) { + flags |= ECS_PAIR; + } + + if (!second && !ECS_HAS_ID_FLAG(flags, PAIR)) { + term->id = first | flags; + } else { + term->id = ecs_pair(first, second) | flags; + } + + return 0; } static -int flecs_query_set_caching_policy( - ecs_query_impl_t *impl, - const ecs_query_desc_t *desc) +int flecs_term_populate_from_id( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) { - ecs_query_cache_kind_t kind = desc->cache_kind; - bool group_order_by = desc->group_by || desc->group_by_callback || - desc->order_by || desc->order_by_callback; + ecs_entity_t first = 0; + ecs_entity_t second = 0; + bool pair_w_0_tgt = false; - /* If caching policy is default, try to pick a policy that does the right - * thing in most cases. */ - if (kind == EcsQueryCacheDefault) { - if (desc->entity || group_order_by) { - /* If the query is created with an entity handle (typically - * indicating that the query is named or belongs to a system) the - * chance is very high that the query will be reused, so enable - * caching. - * Additionally, if the query uses features that require a cache - * such as group_by/order_by, also enable caching. */ - kind = EcsQueryCacheAuto; - } else { - /* Be conservative in other scenario's, as caching adds significant - * overhead to the cost of query creation which doesn't offset the - * benefit of faster iteration if it's only used once. */ - kind = EcsQueryCacheNone; + if (ECS_HAS_ID_FLAG(term->id, PAIR)) { + first = ECS_PAIR_FIRST(term->id); + second = ECS_PAIR_SECOND(term->id); + + if (!first) { + flecs_query_validator_error(ctx, "missing first element in term.id"); + return -1; + } + if (!second) { + if (first != EcsChildOf) { + flecs_query_validator_error(ctx, "missing second element in term.id"); + return -1; + } else { + /* Exception is made for ChildOf so we can use (ChildOf, 0) to match + * all entities in the root */ + pair_w_0_tgt = true; + } + } + } else { + first = term->id & ECS_COMPONENT_MASK; + if (!first) { + flecs_query_validator_error(ctx, "missing first element in term.id"); + return -1; } } - /* Don't cache query, even if it has cacheable terms */ - if (kind == EcsQueryCacheNone) { - impl->pub.cache_kind = EcsQueryCacheNone; - if (desc->group_by || desc->order_by) { - ecs_err("cannot create uncached query with group_by/order_by"); + ecs_entity_t term_first = flecs_term_ref_get_entity(&term->first); + if (term_first) { + if ((uint32_t)term_first != (uint32_t)first) { + flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); return -1; } - return 0; + } else { + ecs_entity_t first_id = ecs_get_alive(world, first); + if (!first_id) { + term->first.id = first | ECS_TERM_REF_FLAGS(&term->first); + } else { + term->first.id = first_id | ECS_TERM_REF_FLAGS(&term->first); + } } - /* Entire query must be cached */ - if (desc->cache_kind == EcsQueryCacheAll) { - if (impl->pub.flags & EcsQueryIsCacheable) { - impl->pub.cache_kind = EcsQueryCacheAll; - return 0; - } else { - ecs_err("cannot enforce QueryCacheAll, " - "query contains uncacheable terms"); + ecs_entity_t term_second = flecs_term_ref_get_entity(&term->second); + if (term_second) { + if ((uint32_t)term_second != second) { + flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); return -1; } + } else if (second) { + ecs_entity_t second_id = ecs_get_alive(world, second); + if (!second_id) { + term->second.id = second | ECS_TERM_REF_FLAGS(&term->second); + } else { + term->second.id = second_id | ECS_TERM_REF_FLAGS(&term->second); + } + } else if (pair_w_0_tgt) { + term->second.id = EcsIsEntity; } - /* Only cache terms that are cacheable */ - if (kind == EcsQueryCacheAuto) { - if (impl->pub.flags & EcsQueryIsCacheable) { - /* If all terms of the query are cacheable, just set the policy to - * All which simplifies work for the compiler. */ - impl->pub.cache_kind = EcsQueryCacheAll; - } else if (!(impl->pub.flags & EcsQueryHasCacheable)) { - /* Same for when the query has no cacheable terms */ - impl->pub.cache_kind = EcsQueryCacheNone; - } else { - /* Part of the query is cacheable. Make sure to only create a cache - * if the cacheable part of the query contains not just not/optional - * terms, as this would build a cache that contains all tables. */ - int32_t not_optional_terms = 0, cacheable_terms = 0; - if (!group_order_by) { - int32_t i, term_count = impl->pub.term_count; - const ecs_term_t *terms = impl->pub.terms; - for (i = 0; i < term_count; i ++) { - const ecs_term_t *term = &terms[i]; - if (term->flags_ & EcsTermIsCacheable) { - cacheable_terms ++; - if (term->oper == EcsNot || term->oper == EcsOptional) { - not_optional_terms ++; - } - } - } - } + return 0; +} + +static +int flecs_term_verify_eq_pred( + const ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) +{ + const ecs_term_ref_t *second = &term->second; + const ecs_term_ref_t *src = &term->src; + ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); + ecs_entity_t second_id = ECS_TERM_REF_ID(&term->second); + ecs_entity_t src_id = ECS_TERM_REF_ID(&term->src); + + if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { + flecs_query_validator_error(ctx, "invalid operator combination"); + goto error; + } - if (group_order_by || cacheable_terms != not_optional_terms) { - impl->pub.cache_kind = EcsQueryCacheAuto; - } else { - impl->pub.cache_kind = EcsQueryCacheNone; - } - } + if ((src->id & EcsIsName) && (second->id & EcsIsName)) { + flecs_query_validator_error(ctx, "both sides of operator cannot be a name"); + goto error; } - return 0; -} + if ((src->id & EcsIsEntity) && (second->id & EcsIsEntity)) { + flecs_query_validator_error(ctx, "both sides of operator cannot be an entity"); + goto error; + } -static -int flecs_query_create_cache( - ecs_query_impl_t *impl, - ecs_query_desc_t *desc) -{ - ecs_query_t *q = &impl->pub; - if (flecs_query_set_caching_policy(impl, desc)) { - return -1; + if (!(src->id & EcsIsVariable)) { + flecs_query_validator_error(ctx, "left-hand of operator must be a variable"); + goto error; } - if ((q->cache_kind != EcsQueryCacheNone) && !q->entity) { - /* Cached queries need an entity handle for observer components */ - q->entity = ecs_new(q->world); - desc->entity = q->entity; + if (first_id == EcsPredMatch && !(second->id & EcsIsName)) { + flecs_query_validator_error(ctx, "right-hand of match operator must be a string"); + goto error; } - if (q->cache_kind == EcsQueryCacheAll) { - /* Create query cache for all terms */ - if (!flecs_query_cache_init(impl, desc)) { + if ((src->id & EcsIsVariable) && (second->id & EcsIsVariable)) { + if (src_id && src_id == second_id) { + flecs_query_validator_error(ctx, "both sides of operator are equal"); goto error; } - } else if (q->cache_kind == EcsQueryCacheAuto) { - /* Query is partially cached */ - ecs_query_desc_t cache_desc = *desc; - ecs_os_memset_n(&cache_desc.terms, 0, ecs_term_t, FLECS_TERM_COUNT_MAX); - cache_desc.expr = NULL; - - /* Maps field indices from cache to query */ - int8_t field_map[FLECS_TERM_COUNT_MAX]; - - int32_t i, count = q->term_count, dst_count = 0, dst_field = 0; - ecs_term_t *terms = q->terms; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->flags_ & EcsTermIsCacheable) { - cache_desc.terms[dst_count] = *term; - field_map[dst_field] = flecs_ito(int8_t, term->field_index); - dst_count ++; - if (i) { - dst_field += term->field_index != term[-1].field_index; - } else { - dst_field ++; - } - } - } - - if (dst_count) { - if (!flecs_query_cache_init(impl, &cache_desc)) { - goto error; - } - - impl->cache->field_map = flecs_alloc_n(&impl->stage->allocator, - int8_t, FLECS_TERM_COUNT_MAX); - - ecs_os_memcpy_n(impl->cache->field_map, field_map, int8_t, dst_count); + if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { + flecs_query_validator_error(ctx, "both sides of operator are equal"); + goto error; } - } else { - /* Check if query has features that are unsupported for uncached */ - ecs_assert(q->cache_kind == EcsQueryCacheNone, ECS_INTERNAL_ERROR, NULL); + } - if (!(q->flags & EcsQueryNested)) { - /* If uncached query is not create to populate a cached query, it - * should not have cascade modifiers */ - int32_t i, count = q->term_count; - ecs_term_t *terms = q->terms; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->src.id & EcsCascade) { - char *query_str = ecs_query_str(q); - ecs_err( - "cascade is unsupported for uncached query\n %s", - query_str); - ecs_os_free(query_str); - goto error; - } - } + if (first_id == EcsPredEq) { + if (second_id == EcsPredEq || second_id == EcsPredMatch) { + flecs_query_validator_error(ctx, + "invalid right-hand side for equality operator"); + goto error; } } @@ -33312,9194 +34127,8866 @@ int flecs_query_create_cache( } static -void flecs_query_fini( - ecs_query_impl_t *impl) +int flecs_term_verify( + const ecs_world_t *world, + const ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) { - ecs_stage_t *stage = impl->stage; - ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_allocator_t *a = &stage->allocator; + const ecs_term_ref_t *first = &term->first; + const ecs_term_ref_t *second = &term->second; + const ecs_term_ref_t *src = &term->src; + ecs_entity_t first_id = 0, second_id = 0; + ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; + ecs_id_t id = term->id; - if (impl->ctx_free) { - impl->ctx_free(impl->pub.ctx); + if ((src->id & EcsIsName) && (second->id & EcsIsName)) { + flecs_query_validator_error(ctx, "mismatch between term.id_flags & term.id"); + return -1; } - if (impl->binding_ctx_free) { - impl->binding_ctx_free(impl->pub.binding_ctx); + ecs_entity_t src_id = ECS_TERM_REF_ID(src); + if (first->id & EcsIsEntity) { + first_id = ECS_TERM_REF_ID(first); } - if (impl->vars != &flecs_this_array) { - flecs_free(a, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * - impl->var_size, impl->vars); - flecs_name_index_fini(&impl->tvar_index); - flecs_name_index_fini(&impl->evar_index); + if (second->id & EcsIsEntity) { + second_id = ECS_TERM_REF_ID(second); } - flecs_free_n(a, ecs_query_op_t, impl->op_count, impl->ops); - flecs_free_n(a, ecs_var_id_t, impl->pub.field_count, impl->src_vars); - flecs_free_n(a, int32_t, impl->pub.field_count, impl->monitor); + if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { + return flecs_term_verify_eq_pred(term, ctx); + } - ecs_query_t *q = &impl->pub; - int i, count = q->term_count; - for (i = 0; i < count; i ++) { - ecs_term_t *term = &q->terms[i]; - if (!(term->flags_ & EcsTermKeepAlive)) { - continue; + if (ecs_term_ref_is_set(second) && !ECS_HAS_ID_FLAG(flags, PAIR)) { + flecs_query_validator_error(ctx, "expected PAIR flag for term with pair"); + return -1; + } else if (!ecs_term_ref_is_set(second) && ECS_HAS_ID_FLAG(flags, PAIR)) { + if (first_id != EcsChildOf) { + flecs_query_validator_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 */ } + } - ecs_id_record_t *idr = flecs_id_record_get(q->real_world, term->id); - if (idr) { - if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { - if (ecs_os_has_threading()) { - int32_t idr_keep_alive = ecs_os_adec(&idr->keep_alive); - ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); - (void)idr_keep_alive; - } else { - idr->keep_alive --; - ecs_assert(idr->keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); + if (!ecs_term_ref_is_set(src)) { + flecs_query_validator_error(ctx, "term.src is not initialized"); + return -1; + } + + if (!ecs_term_ref_is_set(first)) { + flecs_query_validator_error(ctx, "term.first is not initialized"); + return -1; + } + + if (ECS_HAS_ID_FLAG(flags, PAIR)) { + if (!ECS_PAIR_FIRST(id)) { + flecs_query_validator_error(ctx, "invalid 0 for first element in pair id"); + return -1; + } + if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { + flecs_query_validator_error(ctx, "invalid 0 for second element in pair id"); + return -1; + } + + if ((first->id & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) + { + flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->id & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_query_validator_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + + if ((second->id & EcsIsEntity) && + (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) + { + flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); + return -1; + } + if ((second->id & EcsIsVariable) && + !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) + { + char *id_str = ecs_id_str(world, id); + flecs_query_validator_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_query_validator_error(ctx, "missing component id"); + return -1; + } + if ((first->id & EcsIsEntity) && + (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) + { + flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); + return -1; + } + if ((first->id & EcsIsVariable) && !ecs_id_is_wildcard(component)) { + char *id_str = ecs_id_str(world, id); + flecs_query_validator_error(ctx, + "expected wildcard for variable term.first (got %s)", id_str); + ecs_os_free(id_str); + return -1; + } + } + + if (first_id) { + if (ecs_term_ref_is_set(second)) { + ecs_flags64_t mask = EcsIsEntity | EcsIsVariable; + if ((src->id & mask) == (second->id & mask)) { + bool is_same = false; + if (src->id & 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_path(world, term->first.id); + flecs_query_validator_error(ctx, "term with acyclic relationship" + " '%s' cannot have same subject and object", pred_str); + ecs_os_free(pred_str); + return -1; } } } - } - if (impl->tokens) { - flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); + 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_path(world, second_id); + char *oneof_str = ecs_get_path(world, oneof); + char *id_str = ecs_id_str(world, term->id); + flecs_query_validator_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 (impl->cache) { - flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->cache->field_map); - flecs_query_cache_fini(impl); + if (term->trav) { + if (!ecs_has_id(world, term->trav, EcsTraversable)) { + char *r_str = ecs_get_path(world, term->trav); + flecs_query_validator_error(ctx, + "cannot traverse non-traversable relationship '%s'", r_str); + ecs_os_free(r_str); + return -1; + } } - flecs_poly_fini(impl, ecs_query_t); - flecs_bfree(&stage->allocators.query_impl, impl); -} - -static -void flecs_query_poly_fini(void *ptr) { - flecs_query_fini(ptr); + return 0; } static -void flecs_query_add_self_ref( - ecs_query_t *q) +int flecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term, + ecs_query_validator_ctx_t *ctx) { - if (q->entity) { - int32_t t, term_count = q->term_count; - ecs_term_t *terms = q->terms; + ctx->term = term; - for (t = 0; t < term_count; t ++) { - ecs_term_t *term = &terms[t]; - if (ECS_TERM_REF_ID(&term->src) == q->entity) { - ecs_add_id(q->world, q->entity, term->id); - } - } + ecs_term_ref_t *src = &term->src; + ecs_term_ref_t *first = &term->first; + ecs_term_ref_t *second = &term->second; + ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); + ecs_flags64_t second_flags = ECS_TERM_REF_FLAGS(second); + + if (first->name && (first->id & ~EcsTermRefFlags)) { + flecs_query_validator_error(ctx, + "first.name and first.id have competing values"); + return -1; + } + if (src->name && (src->id & ~EcsTermRefFlags)) { + flecs_query_validator_error(ctx, + "src.name and src.id have competing values"); + return -1; + } + if (second->name && (second->id & ~EcsTermRefFlags)) { + flecs_query_validator_error(ctx, + "second.name and second.id have competing values"); + return -1; } -} -void ecs_query_fini( - ecs_query_t *q) -{ - flecs_poly_assert(q, ecs_query_t); + if (term->id & ~ECS_ID_FLAGS_MASK) { + if (flecs_term_populate_from_id(world, term, ctx)) { + return -1; + } + } - if (q->entity) { - /* If query is associated with entity, use poly dtor path */ - ecs_delete(q->world, q->entity); - } else { - flecs_query_fini(flecs_query_impl(q)); + if (flecs_term_refs_finalize(world, term, ctx)) { + return -1; } -} -ecs_query_t* ecs_query_init( - ecs_world_t *world, - const ecs_query_desc_t *const_desc) -{ - ecs_world_t *world_arg = world; - ecs_stage_t *stage = flecs_stage_from_world(&world); - ecs_query_impl_t *result = flecs_bcalloc(&stage->allocators.query_impl); - flecs_poly_init(result, ecs_query_t); - - ecs_query_desc_t desc = *const_desc; - ecs_entity_t entity = const_desc->entity; + ecs_entity_t first_id = ECS_TERM_REF_ID(first); + ecs_entity_t second_id = ECS_TERM_REF_ID(second); + ecs_entity_t src_id = ECS_TERM_REF_ID(src); - if (entity) { - /* Remove existing query if entity has one */ - bool deferred = false; - if (ecs_is_deferred(world)) { - deferred = true; - /* Ensures that remove operation doesn't get applied after bind */ - ecs_defer_suspend(world); - } - ecs_remove_pair(world, entity, ecs_id(EcsPoly), EcsQuery); - if (deferred) { - ecs_defer_resume(world); - } + if ((first->id & EcsIsVariable) && (first_id == EcsAny)) { + term->flags_ |= EcsTermMatchAny; } - /* Initialize the query */ - result->pub.entity = entity; - result->pub.real_world = world; - result->pub.world = world_arg; - result->stage = stage; + if ((second->id & EcsIsVariable) && (second_id == EcsAny)) { + term->flags_ |= EcsTermMatchAny; + } - ecs_assert(flecs_poly_is(result->pub.real_world, ecs_world_t), - ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_poly_is(result->stage, ecs_stage_t), - ECS_INTERNAL_ERROR, NULL); + if ((src->id & EcsIsVariable) && (src_id == EcsAny)) { + term->flags_ |= EcsTermMatchAnySrc; + } - /* Validate input, translate to canonical query representation */ - if (flecs_query_finalize_query(world, &result->pub, &desc)) { - goto error; + ecs_flags64_t ent_var_mask = EcsIsEntity | EcsIsVariable; + + /* If EcsVariable is used by itself, assign to predicate (singleton) */ + if ((ECS_TERM_REF_ID(src) == EcsVariable) && (src->id & EcsIsVariable)) { + src->id = first->id | ECS_TERM_REF_FLAGS(src); + src->id &= ~ent_var_mask; + src->id |= first->id & ent_var_mask; + src->name = first->name; } - /* If query terms have itself as source, add term ids to self. This makes it - * easy to attach components to queries, which is one of the ways - * applications can attach data to systems. */ - flecs_query_add_self_ref(&result->pub); + if ((ECS_TERM_REF_ID(second) == EcsVariable) && (second->id & EcsIsVariable)) { + second->id = first->id | ECS_TERM_REF_FLAGS(second); + second->id &= ~ent_var_mask; + second->id |= first->id & ent_var_mask; + second->name = first->name; + } - /* Initialize static context */ - result->pub.ctx = const_desc->ctx; - result->pub.binding_ctx = const_desc->binding_ctx; - result->ctx_free = const_desc->ctx_free; - result->binding_ctx_free = const_desc->binding_ctx_free; - result->dtor = flecs_query_poly_fini; - result->cache = NULL; + if (!(term->id & ~ECS_ID_FLAGS_MASK)) { + if (flecs_term_populate_id(term)) { + return -1; + } + } - /* Initialize query cache if necessary */ - if (flecs_query_create_cache(result, &desc)) { - goto error; + /* If term queries for !(ChildOf, _), translate it to the builtin + * (ChildOf, 0) index which is a cheaper way to find root entities */ + if (term->oper == EcsNot && term->id == ecs_pair(EcsChildOf, EcsAny)) { + /* Only if the source is not EcsAny */ + if (!(ECS_TERM_REF_ID(&term->src) == EcsAny && (term->src.id & EcsIsVariable))) { + term->oper = EcsAnd; + term->id = ecs_pair(EcsChildOf, 0); + second->id = 0; + second->id |= EcsSelf|EcsIsEntity; + } } - if (flecs_query_compile(world, stage, result)) { - goto error; + if (term->id == ecs_pair(EcsChildOf, 0)) { + /* Ensure same behavior for (ChildOf, #0) and !(ChildOf, _) terms */ + term->flags_ |= EcsTermMatchAny; } - /* Entity could've been set by finalize query if query is cached */ - entity = result->pub.entity; - if (entity) { - EcsPoly *poly = flecs_poly_bind(world, entity, ecs_query_t); - poly->poly = result; - flecs_poly_modified(world, entity, ecs_query_t); + ecs_entity_t first_entity = 0; + if ((first->id & EcsIsEntity)) { + first_entity = first_id; } - return &result->pub; -error: - result->pub.entity = 0; - ecs_query_fini(&result->pub); - return NULL; -} + ecs_component_record_t *cdr = flecs_components_get(world, term->id); + ecs_flags32_t id_flags = 0; + if (cdr) { + id_flags = cdr->flags; + } else if (ECS_IS_PAIR(term->id)) { + ecs_component_record_t *wc_idr = flecs_components_get( + world, ecs_pair(ECS_PAIR_FIRST(term->id), EcsWildcard)); + if (wc_idr) { + id_flags = wc_idr->flags; + } + } -bool ecs_query_has( - ecs_query_t *q, - ecs_entity_t entity, - ecs_iter_t *it) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); + if (src_id || src->name) { + if (!(term->src.id & EcsTraverseFlags)) { + if (id_flags & EcsIdOnInstantiateInherit) { + term->src.id |= EcsSelf|EcsUp; + if (!term->trav) { + term->trav = EcsIsA; + } + } else { + term->src.id |= EcsSelf; - *it = ecs_query_iter(q->world, q); - ecs_iter_set_var(it, 0, entity); - return ecs_query_next(it); -error: - return false; -} + if (term->trav) { + char *idstr = ecs_id_str(world, term->id); + flecs_query_validator_error(ctx, ".trav specified for " + "'%s' which can't be inherited", idstr); + ecs_os_free(idstr); + return -1; + } + } + } -bool ecs_query_has_table( - ecs_query_t *q, - ecs_table_t *table, - ecs_iter_t *it) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); + if (term->first.id & EcsCascade) { + flecs_query_validator_error(ctx, + "cascade modifier invalid for term.first"); + return -1; + } - *it = ecs_query_iter(q->world, q); - ecs_iter_set_var_as_table(it, 0, table); - return ecs_query_next(it); -error: - return false; -} + if (term->second.id & EcsCascade) { + flecs_query_validator_error(ctx, + "cascade modifier invalid for term.second"); + return -1; + } -bool ecs_query_has_range( - ecs_query_t *q, - ecs_table_range_t *range, - ecs_iter_t *it) -{ - flecs_poly_assert(q, ecs_query_t); + if (term->first.id & EcsDesc) { + flecs_query_validator_error(ctx, + "desc modifier invalid for term.first"); + return -1; + } - if (q->flags & EcsQueryMatchThis) { - if (range->table) { - if ((range->offset + range->count) > ecs_table_count(range->table)) { - return false; - } + if (term->second.id & EcsDesc) { + flecs_query_validator_error(ctx, + "desc modifier invalid for term.second"); + return -1; } - } - *it = ecs_query_iter(q->world, q); - if (q->flags & EcsQueryMatchThis) { - ecs_iter_set_var_as_range(it, 0, range); - } + if (term->src.id & EcsDesc && !(term->src.id & EcsCascade)) { + flecs_query_validator_error(ctx, + "desc modifier invalid without cascade"); + return -1; + } - return ecs_query_next(it); -} + if (term->src.id & EcsCascade) { + /* Cascade always implies up traversal */ + term->src.id |= EcsUp; + } -ecs_query_count_t ecs_query_count( - const ecs_query_t *q) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_query_count_t result = {0}; + if ((src->id & EcsUp) && !term->trav) { + /* When traversal flags are specified but no traversal relationship, + * default to ChildOf, which is the most common kind of traversal. */ + term->trav = EcsChildOf; + } + } - if (!(q->flags & EcsQueryMatchThis)) { - return result; + if (!(id_flags & EcsIdOnInstantiateInherit) && (term->trav == EcsIsA)) { + if (src->id & EcsUp) { + char *idstr = ecs_id_str(world, term->id); + flecs_query_validator_error(ctx, "IsA traversal not allowed " + "for '%s', add the (OnInstantiate, Inherit) trait", idstr); + ecs_os_free(idstr); + return -1; + } } - ecs_iter_t it = flecs_query_iter(q->world, q); - it.flags |= EcsIterNoData; + if (first_entity && !ecs_term_match_0(term)) { + bool first_is_self = (first_flags & EcsTraverseFlags) == EcsSelf; + ecs_record_t *first_record = flecs_entities_get(world, first_entity); + ecs_table_t *first_table = first_record ? first_record->table : NULL; + + bool first_can_isa = false; + if (first_table) { + first_can_isa = (first_table->flags & EcsTableHasIsA) != 0; + if (first_can_isa) { + first_can_isa = !ecs_table_has_id(world, first_table, EcsFinal); + } + } - while (ecs_query_next(&it)) { - result.results ++; - result.entities += it.count; - ecs_iter_skip(&it); - } + /* Only enable inheritance for ids which are inherited from at the time + * of query creation. To force component inheritance to be evaluated, + * an application can explicitly set traversal flags. */ + if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || + (id_flags & EcsIdIsInheritable) || first_can_isa) + { + if (!first_is_self) { + term->flags_ |= EcsTermIdInherited; + } + } else { +#ifdef FLECS_DEBUG + if (!first_is_self) { + ecs_query_impl_t *q = flecs_query_impl(ctx->query); + if (q) { + ECS_TERMSET_SET(q->final_terms, 1u << ctx->term_index); + } + } +#endif + } - return result; -} + if (first_table) { + if (ecs_term_ref_is_set(second)) { + /* Add traversal flags for transitive relationships */ + if (!((second_flags & EcsTraverseFlags) == EcsSelf)) { + if (!((src->id & EcsIsVariable) && (src_id == EcsAny))) { + if (!((second->id & EcsIsVariable) && (second_id == EcsAny))) { + if (ecs_table_has_id(world, first_table, EcsTransitive)) { + term->flags_ |= EcsTermTransitive; + } -bool ecs_query_is_true( - const ecs_query_t *q) -{ - flecs_poly_assert(q, ecs_query_t); + if (ecs_table_has_id(world, first_table, EcsReflexive)) { + term->flags_ |= EcsTermReflexive; + } + } + } + } - ecs_iter_t it = flecs_query_iter(q->world, q); - return ecs_iter_is_true(&it); -} + /* Check if term is union */ + if (ecs_table_has_id(world, first_table, EcsUnion)) { + /* Any wildcards don't need special handling as they just return + * (Rel, *). */ + if (ECS_IS_PAIR(term->id) && ECS_PAIR_SECOND(term->id) != EcsAny) { + term->flags_ |= EcsTermIsUnion; + } + } + } + } -int32_t ecs_query_match_count( - const ecs_query_t *q) -{ - flecs_poly_assert(q, ecs_query_t); + /* Check if term has toggleable component */ + if (id_flags & EcsIdCanToggle) { + /* If the term isn't matched on a #0 source */ + if (term->src.id != EcsIsEntity) { + term->flags_ |= EcsTermIsToggle; - ecs_query_impl_t *impl = flecs_query_impl(q); - if (!impl->cache) { - return 0; - } else { - return impl->cache->match_count; - } -} + } + } -const ecs_query_t* ecs_query_get_cache_query( - const ecs_query_t *q) -{ - ecs_query_impl_t *impl = flecs_query_impl(q); - if (!impl->cache) { - return NULL; - } else { - return impl->cache->query; + /* Check if this is a member query */ +#ifdef FLECS_META + if (ecs_id(EcsMember) != 0) { + if (first_entity) { + if (ecs_has(world, first_entity, EcsMember)) { + term->flags_ |= EcsTermIsMember; + } + } + } +#endif } -} -const ecs_query_t* ecs_query_get( - const ecs_world_t *world, - ecs_entity_t query) -{ - const EcsPoly *poly_comp = ecs_get_pair(world, query, EcsPoly, EcsQuery); - if (!poly_comp) { - return NULL; - } else { - flecs_poly_assert(poly_comp->poly, ecs_query_t); - return poly_comp->poly; + if (ECS_TERM_REF_ID(first) == EcsVariable) { + flecs_query_validator_error(ctx, "invalid $ for term.first"); + return -1; } -} - - /** - * @file query/util.c - * @brief Query utilities. - */ + if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { + if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { + flecs_query_validator_error(ctx, + "invalid inout value for AndFrom/OrFrom/NotFrom term"); + return -1; + } + } -ecs_query_lbl_t flecs_itolbl(int64_t val) { - return flecs_ito(int16_t, val); -} + /* Is term trivial/cacheable */ + bool cacheable_term = true; + bool trivial_term = true; + if (term->oper != EcsAnd || term->flags_ & EcsTermIsOr) { + trivial_term = false; + } -ecs_var_id_t flecs_itovar(int64_t val) { - return flecs_ito(uint8_t, val); -} + if (ecs_id_is_wildcard(term->id)) { + if (!(id_flags & EcsIdExclusive)) { + trivial_term = false; + } -ecs_var_id_t flecs_utovar(uint64_t val) { - return flecs_uto(uint8_t, val); -} + if (first->id & EcsIsVariable) { + if (!ecs_id_is_wildcard(first_id) || first_id == EcsAny) { + trivial_term = false; + cacheable_term = false; + } + } -bool flecs_term_is_builtin_pred( - ecs_term_t *term) -{ - if (term->first.id & EcsIsEntity) { - ecs_entity_t id = ECS_TERM_REF_ID(&term->first); - if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { - return true; + if (second->id & EcsIsVariable) { + if (!ecs_id_is_wildcard(second_id) || second_id == EcsAny) { + trivial_term = false; + cacheable_term = false; + } } } - return false; -} -const char* flecs_term_ref_var_name( - ecs_term_ref_t *ref) -{ - if (!(ref->id & EcsIsVariable)) { - return NULL; + if (!ecs_term_match_this(term)) { + trivial_term = false; } - if (ECS_TERM_REF_ID(ref) == EcsThis) { - return EcsThisName; + if (term->flags_ & EcsTermTransitive) { + trivial_term = false; + cacheable_term = false; } - return ref->name; -} + if (term->flags_ & EcsTermIdInherited) { + trivial_term = false; + cacheable_term = false; + } -bool flecs_term_ref_is_wildcard( - ecs_term_ref_t *ref) -{ - if ((ref->id & EcsIsVariable) && - ((ECS_TERM_REF_ID(ref) == EcsWildcard) || (ECS_TERM_REF_ID(ref) == EcsAny))) - { - return true; + if (term->flags_ & EcsTermReflexive) { + trivial_term = false; + cacheable_term = false; } - return false; -} -bool flecs_term_is_fixed_id( - ecs_query_t *q, - ecs_term_t *term) -{ - /* Transitive/inherited terms have variable ids */ - if (term->flags_ & (EcsTermTransitive|EcsTermIdInherited)) { - return false; + if (term->trav && term->trav != EcsIsA) { + trivial_term = false; } - /* Or terms can match different ids */ - if (term->oper == EcsOr) { - return false; + if (!(src->id & EcsSelf)) { + trivial_term = false; } - if ((term != q->terms) && term[-1].oper == EcsOr) { - return false; + + if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || + (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) || + (ECS_TERM_REF_ID(&term->first) == EcsPredLookup)) && + (term->first.id & EcsIsEntity)) + { + trivial_term = false; + cacheable_term = false; } - /* Wildcards can assume different ids */ - if (ecs_id_is_wildcard(term->id)) { - return false; + if (ECS_TERM_REF_ID(src) != EcsThis) { + cacheable_term = false; } - /* Any terms can have fixed ids, but they require special handling */ - if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { - return false; + if (term->id == ecs_childof(0)) { + cacheable_term = false; } - /* First terms that are Not or Optional require special handling */ - if (term->oper == EcsNot || term->oper == EcsOptional) { - if (term == q->terms) { - return false; - } + if (term->flags_ & EcsTermIsMember) { + trivial_term = false; + cacheable_term = false; } - return true; -} + if (term->flags_ & EcsTermIsToggle) { + trivial_term = false; + } -bool flecs_term_is_or( - const ecs_query_t *q, - const ecs_term_t *term) -{ - bool first_term = term == q->terms; - return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); -} + if (term->flags_ & EcsTermIsUnion) { + trivial_term = false; + cacheable_term = false; + } -ecs_flags16_t flecs_query_ref_flags( - ecs_flags16_t flags, - ecs_flags16_t kind) -{ - return (flags >> kind) & (EcsQueryIsVar | EcsQueryIsEntity); -} + ECS_BIT_COND16(term->flags_, EcsTermIsTrivial, trivial_term); + ECS_BIT_COND16(term->flags_, EcsTermIsCacheable, cacheable_term); -bool flecs_query_is_written( - ecs_var_id_t var_id, - uint64_t written) -{ - if (var_id == EcsVarNone) { - return true; + if (flecs_term_verify(world, term, ctx)) { + return -1; } - ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); - return (written & (1ull << var_id)) != 0; + return 0; } -void flecs_query_write( - ecs_var_id_t var_id, - uint64_t *written) +bool ecs_term_ref_is_set( + const ecs_term_ref_t *ref) { - ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); - *written |= (1ull << var_id); + return ECS_TERM_REF_ID(ref) != 0 || ref->name != NULL || (ref->id & EcsIsEntity) != 0; } -void flecs_query_write_ctx( - ecs_var_id_t var_id, - ecs_query_compile_ctx_t *ctx, - bool cond_write) +bool ecs_term_is_initialized( + const ecs_term_t *term) { - bool is_written = flecs_query_is_written(var_id, ctx->written); - flecs_query_write(var_id, &ctx->written); - if (!is_written) { - if (cond_write) { - flecs_query_write(var_id, &ctx->cond_written); - } - } + return term->id != 0 || ecs_term_ref_is_set(&term->first); } -bool flecs_ref_is_written( - const ecs_query_op_t *op, - const ecs_query_ref_t *ref, - ecs_flags16_t kind, - uint64_t written) +bool ecs_term_match_this( + const ecs_term_t *term) { - ecs_flags16_t flags = flecs_query_ref_flags(op->flags, kind); - if (flags & EcsQueryIsEntity) { - ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); - if (ref->entity) { - return true; - } - } else if (flags & EcsQueryIsVar) { - return flecs_query_is_written(ref->var, written); - } - return false; + return (term->src.id & EcsIsVariable) && + (ECS_TERM_REF_ID(&term->src) == EcsThis); } -ecs_allocator_t* flecs_query_get_allocator( - const ecs_iter_t *it) +bool ecs_term_match_0( + const ecs_term_t *term) { - ecs_world_t *world = it->world; - if (flecs_poly_is(world, ecs_world_t)) { - return &world->allocator; - } else { - ecs_assert(flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); - return &((ecs_stage_t*)world)->allocator; - } + return (!ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)); } -const char* flecs_query_op_str( - uint16_t kind) +int ecs_term_finalize( + const ecs_world_t *world, + ecs_term_t *term) { - switch(kind) { - case EcsQueryAnd: return "and "; - case EcsQueryAndAny: return "andany "; - case EcsQueryTriv: return "triv "; - case EcsQueryCache: return "cache "; - case EcsQueryIsCache: return "xcache "; - case EcsQueryOnlyAny: return "any "; - case EcsQueryUp: return "up "; - case EcsQuerySelfUp: return "selfup "; - case EcsQueryWith: return "with "; - case EcsQueryTrav: return "trav "; - case EcsQueryAndFrom: return "andfrom "; - case EcsQueryOrFrom: return "orfrom "; - case EcsQueryNotFrom: return "notfrom "; - case EcsQueryIds: return "ids "; - case EcsQueryIdsRight: return "idsr "; - case EcsQueryIdsLeft: return "idsl "; - case EcsQueryEach: return "each "; - case EcsQueryStore: return "store "; - case EcsQueryReset: return "reset "; - case EcsQueryOr: return "or "; - case EcsQueryOptional: return "option "; - case EcsQueryIfVar: return "ifvar "; - case EcsQueryIfSet: return "ifset "; - case EcsQueryEnd: return "end "; - case EcsQueryNot: return "not "; - case EcsQueryPredEq: return "eq "; - case EcsQueryPredNeq: return "neq "; - case EcsQueryPredEqName: return "eq_nm "; - case EcsQueryPredNeqName: return "neq_nm "; - case EcsQueryPredEqMatch: return "eq_m "; - case EcsQueryPredNeqMatch: return "neq_m "; - case EcsQueryMemberEq: return "membereq "; - case EcsQueryMemberNeq: return "memberneq "; - case EcsQueryToggle: return "toggle "; - case EcsQueryToggleOption: return "togglopt "; - case EcsQueryUnionEq: return "union "; - case EcsQueryUnionEqWith: return "union_w "; - case EcsQueryUnionNeq: return "unionneq "; - case EcsQueryUnionEqUp: return "union_up "; - case EcsQueryUnionEqSelfUp: return "union_sup "; - case EcsQueryLookup: return "lookup "; - case EcsQuerySetVars: return "setvars "; - case EcsQuerySetThis: return "setthis "; - case EcsQuerySetFixed: return "setfix "; - case EcsQuerySetIds: return "setids "; - case EcsQuerySetId: return "setid "; - case EcsQueryContain: return "contain "; - case EcsQueryPairEq: return "pair_eq "; - case EcsQueryYield: return "yield "; - case EcsQueryNothing: return "nothing "; - default: return "!invalid "; - } + ecs_query_validator_ctx_t ctx = {0}; + ctx.world = world; + ctx.term = term; + return flecs_term_finalize(world, term, &ctx); } static -int32_t flecs_query_op_ref_str( - const ecs_query_impl_t *query, - ecs_query_ref_t *ref, - ecs_flags16_t flags, - ecs_strbuf_t *buf) +ecs_term_t* flecs_query_or_other_type( + ecs_query_t *q, + int32_t t) { - int32_t color_chars = 0; - if (flags & EcsQueryIsVar) { - ecs_assert(ref->var < query->var_count, ECS_INTERNAL_ERROR, NULL); - ecs_query_var_t *var = &query->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_term_t *term = &q->terms[t]; + ecs_term_t *first = NULL; + while (t--) { + if (q->terms[t].oper != EcsOr) { + break; } - ecs_strbuf_appendlit(buf, "#[reset]"); - if (var->kind == EcsVarTable) { - ecs_strbuf_appendch(buf, ']'); + first = &q->terms[t]; + } + + if (first) { + ecs_world_t *world = q->world; + const ecs_type_info_t *first_type = ecs_get_type_info(world, first->id); + const ecs_type_info_t *term_type = ecs_get_type_info(world, term->id); + + if (first_type == term_type) { + return NULL; } - color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); - } else if (flags & EcsQueryIsEntity) { - char *path = ecs_get_path(query->pub.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 first; + } else { + return NULL; } - return color_chars; } static -void flecs_query_str_append_bitset( - ecs_strbuf_t *buf, - ecs_flags64_t bitset) +void flecs_normalize_term_name( + ecs_term_ref_t *ref) { - ecs_strbuf_list_push(buf, "{", ","); - int8_t b; - for (b = 0; b < 64; b ++) { - if (bitset & (1llu << b)) { - ecs_strbuf_list_append(buf, "%d", b); + if (ref->name && ref->name[0] == '$' && ref->name[1]) { + ecs_assert(ref->id & EcsIsVariable, ECS_INTERNAL_ERROR, NULL); + const char *old = ref->name; + ref->name = &old[1]; + + if (!ecs_os_strcmp(ref->name, "this")) { + ref->name = NULL; + ref->id |= EcsThis; } } - ecs_strbuf_list_pop(buf, "}"); } static -void flecs_query_plan_w_profile( - const ecs_query_t *q, - const ecs_iter_t *it, - ecs_strbuf_t *buf) +int flecs_query_finalize_terms( + const ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc) { - ecs_query_impl_t *impl = flecs_query_impl(q); - ecs_query_op_t *ops = impl->ops; - int32_t i, count = impl->op_count, indent = 0; - if (!count) { - ecs_strbuf_append(buf, ""); - return; /* No plan */ - } - - for (i = 0; i < count; i ++) { - ecs_query_op_t *op = &ops[i]; - ecs_flags16_t flags = op->flags; - ecs_flags16_t src_flags = flecs_query_ref_flags(flags, EcsQuerySrc); - ecs_flags16_t first_flags = flecs_query_ref_flags(flags, EcsQueryFirst); - ecs_flags16_t second_flags = flecs_query_ref_flags(flags, EcsQuerySecond); - - if (it) { -#ifdef FLECS_DEBUG - const ecs_query_iter_t *rit = &it->priv_.iter.query; - ecs_strbuf_append(buf, - "#[green]%4d -> #[red]%4d <- #[grey] | ", - rit->profile[i].count[0], - rit->profile[i].count[1]); -#endif - } + int8_t i, term_count = q->term_count, field_count = 0; + ecs_term_t *terms = q->terms; + int32_t scope_nesting = 0, cacheable_terms = 0; + bool cond_set = false; - 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 == EcsQueryEnd) { - indent --; - } + ecs_query_validator_ctx_t ctx = {0}; + ctx.world = world; + ctx.query = q; + ctx.desc = desc; - ecs_strbuf_append(buf, "%*s", indent, ""); - ecs_strbuf_appendstr(buf, flecs_query_op_str(op->kind)); - ecs_strbuf_appendstr(buf, " "); + q->flags |= EcsQueryMatchOnlyThis; - int32_t written = ecs_strbuf_written(buf); - for (int32_t j = 0; j < (12 - (written - start)); j ++) { - ecs_strbuf_appendch(buf, ' '); + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + if (term->oper == EcsOr) { + term->flags_ |= EcsTermIsOr; + if (i != (term_count - 1)) { + term[1].flags_ |= EcsTermIsOr; + } } - - hidden_chars = flecs_query_op_ref_str(impl, &op->src, src_flags, buf); + } - if (op->kind == EcsQueryNot || - op->kind == EcsQueryOr || - op->kind == EcsQueryOptional || - op->kind == EcsQueryIfVar || - op->kind == EcsQueryIfSet) - { - indent ++; - } + bool cacheable = true; + bool match_nothing = true; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + bool prev_is_or = i && term[-1].oper == EcsOr; + bool nodata_term = false; + ctx.term_index = i; - if (op->kind == EcsQueryTriv) { - flecs_query_str_append_bitset(buf, op->src.entity); + if (flecs_term_finalize(world, term, &ctx)) { + return -1; } - if (op->kind == EcsQueryIfSet) { - ecs_strbuf_append(buf, "[%d]\n", op->other); - continue; + if (term->src.id != EcsIsEntity) { + /* If term doesn't match 0 entity, query doesn't match nothing */ + match_nothing = false; } - bool is_toggle = op->kind == EcsQueryToggle || - op->kind == EcsQueryToggleOption; - - if (!first_flags && !second_flags && !is_toggle) { - ecs_strbuf_appendstr(buf, "\n"); - continue; + if (scope_nesting) { + /* Terms inside a scope are not cacheable */ + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } - written = ecs_strbuf_written(buf) - hidden_chars; - for (int32_t j = 0; j < (30 - (written - start)); j ++) { - ecs_strbuf_appendch(buf, ' '); - } + /* If one of the terms in an OR chain isn't cacheable, none are */ + if (term->flags_ & EcsTermIsCacheable) { + /* Current term is marked as cacheable. Check if it is part of an OR + * chain, and if so, the previous term was also cacheable. */ + if (prev_is_or) { + if (term[-1].flags_ & EcsTermIsCacheable) { + cacheable_terms ++; + } else { + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } + } else { + cacheable_terms ++; + } - if (is_toggle) { - if (op->first.entity) { - flecs_query_str_append_bitset(buf, op->first.entity); + /* Toggle terms may be cacheable for fetching the initial component, + * but require an additional toggle instruction for evaluation. */ + if (term->flags_ & EcsTermIsToggle) { + cacheable = false; } - if (op->second.entity) { - if (op->first.entity) { - ecs_strbuf_appendlit(buf, ", !"); + } else if (prev_is_or) { + /* Current term is not cacheable. If it is part of an OR chain, mark + * previous terms in the chain as also not cacheable. */ + int32_t j; + for (j = i - 1; j >= 0; j --) { + if (terms[j].oper != EcsOr) { + break; + } + if (terms[j].flags_ & EcsTermIsCacheable) { + cacheable_terms --; + ECS_BIT_CLEAR16(terms[j].flags_, EcsTermIsCacheable); } - flecs_query_str_append_bitset(buf, op->second.entity); } - ecs_strbuf_appendstr(buf, "\n"); - continue; - } - - ecs_strbuf_appendstr(buf, "("); - if (op->kind == EcsQueryMemberEq || op->kind == EcsQueryMemberNeq) { - uint32_t offset = (uint32_t)op->first.entity; - uint32_t size = (uint32_t)(op->first.entity >> 32); - ecs_strbuf_append(buf, "#[yellow]elem#[reset]([%d], 0x%x, 0x%x)", - op->field_index, size, offset); - } else { - flecs_query_op_ref_str(impl, &op->first, first_flags, buf); } - if (second_flags) { - ecs_strbuf_appendstr(buf, ", "); - flecs_query_op_ref_str(impl, &op->second, second_flags, buf); - } else { - switch (op->kind) { - case EcsQueryPredEqName: - case EcsQueryPredNeqName: - case EcsQueryPredEqMatch: - case EcsQueryPredNeqMatch: { - int8_t term_index = op->term_index; - ecs_strbuf_appendstr(buf, ", #[yellow]\""); - ecs_strbuf_appendstr(buf, q->terms[term_index].second.name); - ecs_strbuf_appendstr(buf, "\"#[reset]"); - break; - } - case EcsQueryLookup: { - ecs_var_id_t src_id = op->src.var; - ecs_strbuf_appendstr(buf, ", #[yellow]\""); - ecs_strbuf_appendstr(buf, impl->vars[src_id].lookup); - ecs_strbuf_appendstr(buf, "\"#[reset]"); - break; + if (prev_is_or) { + if (ECS_TERM_REF_ID(&term[-1].src) != ECS_TERM_REF_ID(&term->src)) { + flecs_query_validator_error(&ctx, "mismatching src.id for OR terms"); + return -1; } - default: - break; + if (term->oper != EcsOr && term->oper != EcsAnd) { + flecs_query_validator_error(&ctx, + "term after OR operator must use AND operator"); + return -1; } + } else { + field_count ++; } - ecs_strbuf_appendch(buf, ')'); - - ecs_strbuf_appendch(buf, '\n'); - } -} - -char* ecs_query_plan_w_profile( - const ecs_query_t *q, - const ecs_iter_t *it) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_strbuf_t buf = ECS_STRBUF_INIT; - - flecs_query_plan_w_profile(q, it, &buf); - // ecs_query_impl_t *impl = flecs_query_impl(q); - // if (impl->cache) { - // ecs_strbuf_appendch(&buf, '\n'); - // flecs_query_plan_w_profile(impl->cache->query, it, &buf); - // } - -#ifdef FLECS_LOG - char *str = ecs_strbuf_get(&buf); - flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); - ecs_os_free(str); -#endif - - return ecs_strbuf_get(&buf); -} + term->field_index = flecs_ito(int8_t, field_count - 1); -char* ecs_query_plan( - const ecs_query_t *q) -{ - return ecs_query_plan_w_profile(q, NULL); -} + if (ecs_id_is_wildcard(term->id)) { + q->flags |= EcsQueryMatchWildcards; + } else if (!(term->flags_ & EcsTermIsOr)) { + ECS_TERMSET_SET(q->static_id_fields, 1u << term->field_index); + } -static -void flecs_query_str_add_id( - const ecs_world_t *world, - ecs_strbuf_t *buf, - const ecs_term_t *term, - const ecs_term_ref_t *ref, - bool is_src) -{ - bool is_added = false; - ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); - if (ref->id & EcsIsVariable && !ecs_id_is_wildcard(ref_id)){ - ecs_strbuf_appendlit(buf, "$"); - } + if (ecs_term_match_this(term)) { + ECS_BIT_SET(q->flags, EcsQueryMatchThis); + } else { + ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlyThis); + } - if (ref_id) { - char *path = ecs_get_path(world, ref_id); - ecs_strbuf_appendstr(buf, path); - ecs_os_free(path); - } else if (ref->name) { - ecs_strbuf_appendstr(buf, ref->name); - } else { - ecs_strbuf_appendlit(buf, "0"); - } - is_added = true; + if (ECS_TERM_REF_ID(term) == EcsPrefab) { + ECS_BIT_SET(q->flags, EcsQueryMatchPrefab); + } + if (ECS_TERM_REF_ID(term) == EcsDisabled && (term->src.id & EcsSelf)) { + ECS_BIT_SET(q->flags, EcsQueryMatchDisabled); + } + + if (term->oper == EcsNot && term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } - ecs_flags64_t flags = ECS_TERM_REF_FLAGS(ref); - 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 |= EcsSelf; - } + if ((term->id == EcsWildcard) || (term->id == + ecs_pair(EcsWildcard, EcsWildcard))) + { + /* If term type is unknown beforehand, default the inout type to + * none. This prevents accidentally requesting lots of components, + * which can put stress on serializer code. */ + if (term->inout == EcsInOutDefault) { + term->inout = EcsInOutNone; + } + } - if ((flags & EcsTraverseFlags) != EcsSelf) { - if (is_added) { - ecs_strbuf_list_push(buf, "|", "|"); + if (term->src.id == EcsIsEntity) { + nodata_term = true; + } else if (term->inout == EcsInOutNone) { + nodata_term = true; + } else if (!ecs_get_type_info(world, term->id)) { + nodata_term = true; + } else if (term->flags_ & EcsTermIsUnion) { + nodata_term = true; + } else if (term->flags_ & EcsTermIsMember) { + nodata_term = true; + } else if (scope_nesting) { + nodata_term = true; } else { - ecs_strbuf_list_push(buf, "", "|"); - } - if (is_src) { - if (flags & EcsSelf) { - ecs_strbuf_list_appendstr(buf, "self"); + if (ecs_id_is_tag(world, term->id)) { + nodata_term = true; + } else if ((ECS_PAIR_SECOND(term->id) == EcsWildcard) || + (ECS_PAIR_SECOND(term->id) == EcsAny)) + { + /* If the second element of a pair is a wildcard and the first + * element is not a type, we can't know in advance what the + * type of the term is, so it can't provide data. */ + if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { + nodata_term = true; + } } + } - if (flags & EcsCascade) { - ecs_strbuf_list_appendstr(buf, "cascade"); - } else if (flags & EcsUp) { - ecs_strbuf_list_appendstr(buf, "up"); + if (!nodata_term && term->inout != EcsIn && term->inout != EcsInOutNone) { + /* Non-this terms default to EcsIn */ + if (ecs_term_match_this(term) || term->inout != EcsInOutDefault) { + q->flags |= EcsQueryHasOutTerms; } - if (flags & EcsDesc) { - ecs_strbuf_list_appendstr(buf, "desc"); + bool match_non_this = !ecs_term_match_this(term) || + (term->src.id & EcsUp); + if (match_non_this && term->inout != EcsInOutDefault) { + q->flags |= EcsQueryHasNonThisOutTerms; } + } - if (term->trav) { - char *rel_path = ecs_get_path(world, term->trav); - ecs_strbuf_appendlit(buf, " "); - ecs_strbuf_appendstr(buf, rel_path); - ecs_os_free(rel_path); + if (!nodata_term) { + /* If terms in an OR chain do not all return the same type, the + * field will not provide any data */ + if (term->flags_ & EcsTermIsOr) { + ecs_term_t *first = flecs_query_or_other_type(q, i); + if (first) { + nodata_term = true; + } + q->data_fields &= (ecs_termset_t)~(1llu << term->field_index); } } - ecs_strbuf_list_pop(buf, ""); - } -} - -void flecs_term_to_buf( - const ecs_world_t *world, - const ecs_term_t *term, - ecs_strbuf_t *buf, - int32_t t) -{ - const ecs_term_ref_t *src = &term->src; - const ecs_term_ref_t *first = &term->first; - const ecs_term_ref_t *second = &term->second; + if (term->flags_ & EcsTermIsMember) { + nodata_term = false; + } - ecs_entity_t src_id = ECS_TERM_REF_ID(src); - ecs_entity_t first_id = ECS_TERM_REF_ID(first); + if (!nodata_term && term->oper != EcsNot) { + ECS_TERMSET_SET(q->data_fields, 1u << term->field_index); - bool src_set = !ecs_term_match_0(term); - bool second_set = ecs_term_ref_is_set(second); + if (term->inout != EcsIn) { + ECS_TERMSET_SET(q->write_fields, 1u << term->field_index); + } + if (term->inout != EcsOut) { + ECS_TERMSET_SET(q->read_fields, 1u << term->field_index); + } + if (term->inout == EcsInOutDefault) { + ECS_TERMSET_SET(q->shared_readonly_fields, + 1u << term->field_index); + } + } - if (first_id == EcsScopeOpen) { - ecs_strbuf_appendlit(buf, "{"); - return; - } else if (first_id == EcsScopeClose) { - ecs_strbuf_appendlit(buf, "}"); - return; - } + if (ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)) { + ECS_TERMSET_SET(q->fixed_fields, 1u << term->field_index); + } - if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || - (ECS_TERM_REF_ID(&term->first) == EcsPredMatch)) && - (term->first.id & EcsIsEntity)) - { - ecs_strbuf_appendlit(buf, "$"); - if (ECS_TERM_REF_ID(&term->src) == EcsThis && - (term->src.id & EcsIsVariable)) + if ((term->src.id & EcsIsVariable) && + (ECS_TERM_REF_ID(&term->src) != EcsThis)) { - ecs_strbuf_appendlit(buf, "this"); - } else if (term->src.id & EcsIsVariable) { - if (term->src.name) { - ecs_strbuf_appendstr(buf, term->src.name); - } else { - ecs_strbuf_appendstr(buf, "<>"); - } - } else { - /* Shouldn't happen */ + ECS_TERMSET_SET(q->var_fields, 1u << term->field_index); } - if (ECS_TERM_REF_ID(&term->first) == EcsPredEq) { - if (term->oper == EcsNot) { - ecs_strbuf_appendlit(buf, " != "); + bool is_sparse = false; + + ecs_component_record_t *cdr = flecs_components_get(world, term->id); + if (cdr) { + if (ecs_os_has_threading()) { + ecs_os_ainc(&cdr->keep_alive); } else { - ecs_strbuf_appendlit(buf, " == "); + cdr->keep_alive ++; } - } else if (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) { - ecs_strbuf_appendlit(buf, " ~= "); - } - if (term->second.id & EcsIsEntity) { - if (term->second.id != 0) { - ecs_get_path_w_sep_buf(world, 0, ECS_TERM_REF_ID(&term->second), - ".", NULL, buf, false); + term->flags_ |= EcsTermKeepAlive; + + if (cdr->flags & EcsIdIsSparse) { + is_sparse = true; } } else { - if (term->second.id & EcsIsVariable) { - ecs_strbuf_appendlit(buf, "$"); - if (term->second.name) { - ecs_strbuf_appendstr(buf, term->second.name); - } else if (ECS_TERM_REF_ID(&term->second) == EcsThis) { - ecs_strbuf_appendlit(buf, "this"); - } - } else if (term->second.id & EcsIsName) { - ecs_strbuf_appendlit(buf, "\""); - if ((ECS_TERM_REF_ID(&term->first) == EcsPredMatch) && - (term->oper == EcsNot)) - { - ecs_strbuf_appendlit(buf, "!"); - } - ecs_strbuf_appendstr(buf, term->second.name); - ecs_strbuf_appendlit(buf, "\""); + ecs_entity_t type = ecs_get_typeid(world, term->id); + if (type && ecs_has_id(world, type, EcsSparse)) { + is_sparse = true; } } - return; - } + if (is_sparse) { + term->flags_ |= EcsTermIsSparse; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); + if (term->flags_ & EcsTermIsCacheable) { + cacheable_terms --; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + } - 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] "); + /* Sparse component fields must be accessed with ecs_field_at */ + if (!nodata_term) { + q->row_fields |= flecs_uto(uint32_t, 1llu << i); + } } - } - if (term->oper == EcsNot) { - ecs_strbuf_appendlit(buf, "!"); - } else if (term->oper == EcsOptional) { - ecs_strbuf_appendlit(buf, "?"); - } + if (term->oper == EcsOptional || term->oper == EcsNot) { + cond_set = true; + } - if (!src_set) { - flecs_query_str_add_id(world, buf, term, &term->first, false); - if (!second_set) { - ecs_strbuf_appendlit(buf, "()"); + ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); + if (first_id == EcsPredEq || first_id == EcsPredMatch || + first_id == EcsPredLookup) + { + q->flags |= EcsQueryHasPred; + term->src.id = (term->src.id & ~EcsTraverseFlags) | EcsSelf; + term->inout = EcsInOutNone; } else { - ecs_strbuf_appendlit(buf, "(#0,"); - flecs_query_str_add_id(world, buf, term, &term->second, false); - ecs_strbuf_appendlit(buf, ")"); + if (!ecs_term_match_0(term) && term->oper != EcsNot && + term->oper != EcsNotFrom) + { + ECS_TERMSET_SET(q->set_fields, 1u << term->field_index); + } } - } else { - ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; - if (flags && !ECS_HAS_ID_FLAG(flags, PAIR)) { - ecs_strbuf_appendstr(buf, ecs_id_flag_str(flags)); - ecs_strbuf_appendch(buf, '|'); + + if (first_id == EcsScopeOpen) { + q->flags |= EcsQueryHasScopes; + scope_nesting ++; + } + + if (scope_nesting) { + term->flags_ |= EcsTermIsScope; + ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); + ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); + cacheable_terms --; } - flecs_query_str_add_id(world, buf, term, &term->first, false); - ecs_strbuf_appendlit(buf, "("); - if (term->src.id & EcsIsEntity && src_id == first_id) { - ecs_strbuf_appendlit(buf, "$"); - } else { - flecs_query_str_add_id(world, buf, term, &term->src, true); + if (first_id == EcsScopeClose) { + if (i && ECS_TERM_REF_ID(&terms[i - 1].first) == EcsScopeOpen) { + flecs_query_validator_error(&ctx, "invalid empty scope"); + return -1; + } + + q->flags |= EcsQueryHasScopes; + scope_nesting --; } - if (second_set) { - ecs_strbuf_appendlit(buf, ","); - flecs_query_str_add_id(world, buf, term, &term->second, false); + + if (scope_nesting < 0) { + flecs_query_validator_error(&ctx, "'}' without matching '{'"); + return -1; } - 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_to_buf(world, term, &buf, 0); - return ecs_strbuf_get(&buf); -} + if (scope_nesting != 0) { + flecs_query_validator_error(&ctx, "missing '}'"); + return -1; + } -char* ecs_query_str( - const ecs_query_t *q) -{ - ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_world_t *world = q->world; + if (term_count && (terms[term_count - 1].oper == EcsOr)) { + flecs_query_validator_error(&ctx, + "last term of query can't have OR operator"); + return -1; + } - ecs_strbuf_t buf = ECS_STRBUF_INIT; - const ecs_term_t *terms = q->terms; - int32_t i, count = q->term_count; + q->field_count = flecs_ito(int8_t, field_count); - for (i = 0; i < count; i ++) { - const ecs_term_t *term = &terms[i]; + if (field_count) { + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + int32_t field = term->field_index; + q->ids[field] = term->id; - flecs_term_to_buf(world, term, &buf, i); + if (term->flags_ & EcsTermIsOr) { + if (flecs_query_or_other_type(q, i)) { + q->sizes[field] = 0; + q->ids[field] = 0; + continue; + } + } - if (i != (count - 1)) { - if (term->oper == EcsOr) { - ecs_strbuf_appendlit(&buf, " || "); - } else { - if (ECS_TERM_REF_ID(&term->first) != EcsScopeOpen) { - if (ECS_TERM_REF_ID(&term[1].first) != EcsScopeClose) { - ecs_strbuf_appendlit(&buf, ", "); + ecs_component_record_t *cdr = flecs_components_get(world, term->id); + if (cdr) { + if (!ECS_IS_PAIR(cdr->id) || ECS_PAIR_FIRST(cdr->id) != EcsWildcard) { + if (cdr->type_info) { + q->sizes[field] = cdr->type_info->size; + q->ids[field] = cdr->id; } } + } else { + const ecs_type_info_t *ti = ecs_get_type_info( + world, term->id); + if (ti) { + q->sizes[field] = ti->size; + q->ids[field] = term->id; + } } } } - return ecs_strbuf_get(&buf); -error: - return NULL; -} - -int32_t flecs_query_pivot_term( - const ecs_world_t *world, - const ecs_query_t *query) -{ - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); - - const ecs_term_t *terms = query->terms; - int32_t i, term_count = query->term_count; - int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; + ECS_BIT_COND(q->flags, EcsQueryHasCondSet, cond_set); - for (i = 0; i < term_count; i ++) { - const ecs_term_t *term = &terms[i]; - ecs_id_t id = term->id; + /* Check if this is a trivial query */ + if ((q->flags & EcsQueryMatchOnlyThis)) { + if (!(q->flags & + (EcsQueryHasPred|EcsQueryMatchDisabled|EcsQueryMatchPrefab))) + { + ECS_BIT_SET(q->flags, EcsQueryMatchOnlySelf); - if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { - continue; - } + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_term_ref_t *src = &term->src; - if (!ecs_term_match_this(term)) { - continue; - } + if (src->id & EcsUp) { + ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlySelf); + } - ecs_id_record_t *idr = flecs_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 query doesn't match anything */ - } + if (!(term->flags_ & EcsTermIsTrivial)) { + break; + } + } - 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.id & EcsTraverseFlags) == EcsSelf) { - self_pivot_term = i; + if (term_count && (i == term_count)) { + ECS_BIT_SET(q->flags, EcsQueryIsTrivial); } } } - if (self_pivot_term != -1) { - pivot_term = self_pivot_term; - } - - return pivot_term; -error: - return -2; -} + /* Set cacheable flags */ + ECS_BIT_COND(q->flags, EcsQueryHasCacheable, + cacheable_terms != 0); -void flecs_query_apply_iter_flags( - ecs_iter_t *it, - const ecs_query_t *query) -{ - ECS_BIT_COND(it->flags, EcsIterHasCondSet, - ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); - ECS_BIT_COND(it->flags, EcsIterNoData, query->data_fields == 0); -} + /* Exclude queries with order_by from setting the IsCacheable flag. This + * allows the routine that evaluates entirely cached queries to use more + * optimized logic as it doesn't have to deal with order_by edge cases */ + ECS_BIT_COND(q->flags, EcsQueryIsCacheable, + cacheable && (cacheable_terms == term_count) && + !desc->order_by_callback); -/** - * @file query/validator.c - * @brief Validate and finalize queries. - */ + /* If none of the terms match a source, the query matches nothing */ + ECS_BIT_COND(q->flags, EcsQueryMatchNothing, match_nothing); -#include + for (i = 0; i < q->term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + /* Post process term names in case they were used to create variables */ + flecs_normalize_term_name(&term->first); + flecs_normalize_term_name(&term->second); + flecs_normalize_term_name(&term->src); + } -#ifdef FLECS_SCRIPT -#endif + return 0; +} static -void flecs_query_validator_error( - const ecs_query_validator_ctx_t *ctx, - const char *fmt, - ...) +int flecs_query_query_populate_terms( + ecs_world_t *world, + ecs_stage_t *stage, + ecs_query_t *q, + const ecs_query_desc_t *desc) { - ecs_strbuf_t buf = ECS_STRBUF_INIT; + /* Count number of initialized terms in desc->terms */ + int32_t i, term_count = 0; + for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { + if (!ecs_term_is_initialized(&desc->terms[i])) { + break; + } + term_count ++; + } - if (ctx->desc && ctx->desc->expr) { - ecs_strbuf_appendlit(&buf, "expr: "); - ecs_strbuf_appendstr(&buf, ctx->desc->expr); - ecs_strbuf_appendlit(&buf, "\n"); + /* Copy terms from array to query */ + if (term_count) { + ecs_os_memcpy_n(&q->terms, desc->terms, ecs_term_t, term_count); } - if (ctx->query) { - ecs_query_t *query = ctx->query; - const ecs_term_t *terms = query->terms; - int32_t i, count = query->term_count; + /* Parse query expression if set */ + const char *expr = desc->expr; + if (expr && expr[0]) { + #ifdef FLECS_QUERY_DSL + const char *name = desc->entity ? + ecs_get_name(world, desc->entity) : NULL; + + /* Allocate buffer that's large enough to tokenize the query string */ + ecs_size_t token_buffer_size = ecs_os_strlen(expr) * 2 + 1; + char *token_buffer = flecs_alloc( + &flecs_query_impl(q)->stage->allocator, token_buffer_size); - for (i = 0; i < count; i ++) { - const ecs_term_t *term = &terms[i]; - if (ctx->term_index == i) { - ecs_strbuf_appendlit(&buf, " > "); - } else { - ecs_strbuf_appendlit(&buf, " "); - } - flecs_term_to_buf(ctx->world, term, &buf, i); - if (term->oper == EcsOr) { - ecs_strbuf_appendlit(&buf, " ||"); - } else if (i != (count - 1)) { - ecs_strbuf_appendlit(&buf, ","); - } - ecs_strbuf_appendlit(&buf, "\n"); + if (flecs_terms_parse(world, name, expr, token_buffer, &q->terms[term_count], + &term_count)) + { + flecs_free(&stage->allocator, token_buffer_size, token_buffer); + goto error; } - } else { - ecs_strbuf_appendlit(&buf, " > "); - flecs_term_to_buf(ctx->world, ctx->term, &buf, 0); - ecs_strbuf_appendlit(&buf, "\n"); - } - char *expr = ecs_strbuf_get(&buf); - const char *name = NULL; - if (ctx->query && ctx->query->entity) { - name = ecs_get_name(ctx->query->world, ctx->query->entity); + /* Store on query object so we can free later */ + flecs_query_impl(q)->tokens = token_buffer; + flecs_query_impl(q)->tokens_len = flecs_ito(int16_t, token_buffer_size); + #else + (void)world; + (void)stage; + ecs_err("parsing query expressions requires the FLECS_QUERY_DSL addon"); + goto error; + #endif } - va_list args; - va_start(args, fmt); - char *msg = flecs_vasprintf(fmt, args); - ecs_parser_error(name, NULL, 0, "%s\n%s", msg, expr); - ecs_os_free(msg); - ecs_os_free(expr); + q->term_count = flecs_ito(int8_t, term_count); - va_end(args); + return 0; +error: + return -1; } +#ifndef FLECS_SANITIZE static -int flecs_term_ref_finalize_flags( - ecs_term_ref_t *ref, - ecs_query_validator_ctx_t *ctx) +bool flecs_query_finalize_simple( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_desc_t *desc) { - if ((ref->id & EcsIsEntity) && (ref->id & EcsIsVariable)) { - flecs_query_validator_error(ctx, "cannot set both IsEntity and IsVariable"); - return -1; + /* Filter out queries that aren't simple enough */ + if (desc->expr) { + return false; } - if (ref->name && ref->name[0] == '$') { - if (!ref->name[1]) { - if (!(ref->id & EcsIsName)) { - if (ref->id & ~EcsTermRefFlags) { - flecs_query_validator_error(ctx, - "conflicting values for .name and .id"); - return -1; - } + if (desc->order_by_callback || desc->group_by_callback) { + return false; + } - ref->id |= EcsVariable; - ref->id |= EcsIsVariable; - } - } else { - ref->name = &ref->name[1]; - ref->id |= EcsIsVariable; + int8_t i, term_count; + for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { + if (!ecs_term_is_initialized(&desc->terms[i])) { + break; } - } - if (!(ref->id & (EcsIsEntity|EcsIsVariable|EcsIsName))) { - if (ECS_TERM_REF_ID(ref) || ref->name) { - if (ECS_TERM_REF_ID(ref) == EcsThis || - ECS_TERM_REF_ID(ref) == EcsWildcard || - ECS_TERM_REF_ID(ref) == EcsAny || - ECS_TERM_REF_ID(ref) == EcsVariable) - { - /* Builtin variable ids default to variable */ - ref->id |= EcsIsVariable; - } else { - ref->id |= EcsIsEntity; - } + ecs_id_t id = desc->terms[i].id; + if (ecs_id_is_wildcard(id)) { + return false; } - } - return 0; -} + if (id == EcsThis || ECS_PAIR_FIRST(id) == EcsThis || + ECS_PAIR_SECOND(id) == EcsThis) + { + return false; + } -static -int flecs_term_ref_lookup( - const ecs_world_t *world, - ecs_entity_t scope, - ecs_term_ref_t *ref, - ecs_query_validator_ctx_t *ctx) -{ - const char *name = ref->name; - if (!name) { - return 0; - } + if (id == EcsVariable || ECS_PAIR_FIRST(id) == EcsVariable || + ECS_PAIR_SECOND(id) == EcsVariable) + { + return false; + } - if (ref->id & EcsIsVariable) { - if (!ecs_os_strcmp(name, "this")) { - ref->id = EcsThis | ECS_TERM_REF_FLAGS(ref); - ref->name = NULL; - return 0; + if (id == EcsPrefab || id == EcsDisabled) { + return false; } - return 0; - } else if (ref->id & EcsIsName) { - if (ref->name == NULL) { - flecs_query_validator_error(ctx, "IsName flag specified without name"); - return -1; + + ecs_term_t term = { .id = desc->terms[i].id }; + if (ecs_os_memcmp_t(&term, &desc->terms[i], ecs_term_t)) { + return false; } - return 0; } - ecs_assert(ref->id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); + if (!i) { + return false; /* No terms */ + } - if (flecs_identifier_is_0(name)) { - if (ECS_TERM_REF_ID(ref)) { - flecs_query_validator_error( - ctx, "name '0' does not match entity id"); - return -1; + term_count = i; + ecs_os_memcpy_n(&q->terms, desc->terms, ecs_term_t, term_count); + + /* Simple query that only queries for component ids */ + + /* Populate terms */ + int8_t cacheable_count = 0, trivial_count = 0, up_count = 0; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + ecs_id_t id = term->id; + + ecs_entity_t first = id; + if (ECS_IS_PAIR(id)) { + ecs_entity_t second = flecs_entities_get_alive(world, + ECS_PAIR_SECOND(id)); + first = flecs_entities_get_alive(world, ECS_PAIR_FIRST(id)); + term->second.id = second | EcsIsEntity | EcsSelf; } - ref->name = NULL; - return 0; - } - ecs_entity_t e = 0; - if (scope) { - e = ecs_lookup_child(world, scope, name); - } + term->field_index = i; + term->first.id = first | EcsIsEntity | EcsSelf; + term->src.id = EcsThis | EcsIsVariable | EcsSelf; - if (!e) { - e = ecs_lookup(world, name); - } + q->ids[i] = id; - if (!e) { - if (ctx->query && (ctx->query->flags & EcsQueryAllowUnresolvedByName)) { - ref->id |= EcsIsName; - ref->id &= ~EcsIsEntity; - return 0; - } else { - flecs_query_validator_error(ctx, "unresolved identifier '%s'", name); - return -1; + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + cdr->keep_alive ++; + term->flags_ |= EcsTermKeepAlive; } - } - ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); - if (ref_id && ref_id != e) { - char *e_str = ecs_get_path(world, ref_id); - flecs_query_validator_error(ctx, "name '%s' does not match term.id '%s'", - name, e_str); - ecs_os_free(e_str); - return -1; - } + if (!cdr && ECS_IS_PAIR(id)) { + cdr = flecs_components_get(world, + ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); + } - ref->id = e | ECS_TERM_REF_FLAGS(ref); - ref_id = ECS_TERM_REF_ID(ref); + bool cacheable = true, trivial = true; + if (cdr) { + if (cdr->type_info) { + q->sizes[i] = cdr->type_info->size; + q->flags |= EcsQueryHasOutTerms; + q->data_fields |= (ecs_termset_t)(1llu << i); + } - if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || - !ecs_os_strcmp(name, "$")) - { - ref->id &= ~EcsIsEntity; - ref->id |= EcsIsVariable; - } + if (cdr->flags & EcsIdOnInstantiateInherit) { + term->src.id |= EcsUp; + term->trav = EcsIsA; + up_count ++; + } - /* Check if looked up id is alive (relevant for numerical ids) */ - if (!(ref->id & EcsIsName) && ref_id) { - if (!ecs_is_alive(world, ref_id)) { - flecs_query_validator_error(ctx, "identifier '%s' is not alive", ref->name); - return -1; + if (cdr->flags & EcsIdCanToggle) { + term->flags_ |= EcsTermIsToggle; + trivial = false; + } + + if (ECS_IS_PAIR(id)) { + if (cdr->flags & EcsIdIsUnion) { + term->flags_ |= EcsTermIsUnion; + trivial = false; + cacheable = false; + } + } + + if (cdr->flags & EcsIdIsSparse) { + term->flags_ |= EcsTermIsSparse; + cacheable = false; trivial = false; + q->row_fields |= flecs_uto(uint32_t, 1llu << i); + } } - ref->name = NULL; - return 0; + if (ECS_IS_PAIR(id)) { + if (ecs_has_id(world, first, EcsTransitive)) { + term->flags_ |= EcsTermTransitive; + trivial = false; + cacheable = false; + } + if (ecs_has_id(world, first, EcsReflexive)) { + term->flags_ |= EcsTermReflexive; + trivial = false; + cacheable = false; + } + } + + if (flecs_components_get(world, ecs_pair(EcsIsA, first)) != NULL) { + term->flags_ |= EcsTermIdInherited; + cacheable = false; trivial = false; + } + + if (cacheable) { + term->flags_ |= EcsTermIsCacheable; + cacheable_count ++; + } + + if (trivial) { + term->flags_ |= EcsTermIsTrivial; + trivial_count ++; + } } - return 0; -} + /* Initialize static data */ + q->term_count = term_count; + q->field_count = term_count; + q->set_fields = (ecs_termset_t)((1llu << i) - 1); + q->static_id_fields = (ecs_termset_t)((1llu << i) - 1); + q->flags |= EcsQueryMatchThis|EcsQueryMatchOnlyThis|EcsQueryHasTableThisVar; -static -ecs_id_t flecs_wildcard_to_any(ecs_id_t id) { - ecs_id_t flags = id & EcsTermRefFlags; - - if (ECS_IS_PAIR(id)) { - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); - if (first == EcsWildcard) id = ecs_pair(EcsAny, second); - if (second == EcsWildcard) id = ecs_pair(ECS_PAIR_FIRST(id), EcsAny); - } else if ((id & ~EcsTermRefFlags) == EcsWildcard) { - id = EcsAny; + if (cacheable_count) { + q->flags |= EcsQueryHasCacheable; } - return id | flags; + if (cacheable_count == term_count && trivial_count == term_count) { + q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; + } + + if (!up_count) { + q->flags |= EcsQueryMatchOnlySelf; + } + + return true; } +#endif static -int flecs_term_refs_finalize( - const ecs_world_t *world, - ecs_term_t *term, - ecs_query_validator_ctx_t *ctx) +char* flecs_query_append_token( + char *dst, + const char *src) { - ecs_term_ref_t *src = &term->src; - ecs_term_ref_t *first = &term->first; - ecs_term_ref_t *second = &term->second; - - /* Include subsets for component by default, to support inheritance */ - if (!(first->id & EcsTraverseFlags)) { - first->id |= EcsSelf; - } + int32_t len = ecs_os_strlen(src); + ecs_os_memcpy(dst, src, len + 1); + return dst + len + 1; +} - /* Traverse Self by default for pair target */ - if (!(second->id & EcsTraverseFlags)) { - if (ECS_TERM_REF_ID(second) || second->name || (second->id & EcsIsEntity)) { - second->id |= EcsSelf; - } - } +static +void flecs_query_populate_tokens( + ecs_query_impl_t *impl) +{ + ecs_query_t *q = &impl->pub; + int32_t i, term_count = q->term_count; - /* Source defaults to This */ - if (!ECS_TERM_REF_ID(src) && (src->name == NULL) && !(src->id & EcsIsEntity)) { - src->id = EcsThis | ECS_TERM_REF_FLAGS(src); - src->id |= EcsIsVariable; - } + char *old_tokens = impl->tokens; + int32_t old_tokens_len = impl->tokens_len; + impl->tokens = NULL; + impl->tokens_len = 0; - /* Initialize term identifier flags */ - if (flecs_term_ref_finalize_flags(src, ctx)) { - return -1; + /* Step 1: determine size of token buffer */ + int32_t len = 0; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + + if (term->first.name) { + len += ecs_os_strlen(term->first.name) + 1; + } + if (term->second.name) { + len += ecs_os_strlen(term->second.name) + 1; + } + if (term->src.name) { + len += ecs_os_strlen(term->src.name) + 1; + } } - if (flecs_term_ref_finalize_flags(first, ctx)) { - return -1; - } + /* Step 2: reassign term tokens to buffer */ + if (len) { + impl->tokens = flecs_alloc(&impl->stage->allocator, len); + impl->tokens_len = flecs_ito(int16_t, len); + char *token = impl->tokens, *next; - if (flecs_term_ref_finalize_flags(second, ctx)) { - return -1; + for (i = 0; i < term_count; i ++) { + ecs_term_t *term = &q->terms[i]; + if (term->first.name) { + next = flecs_query_append_token(token, term->first.name); + term->first.name = token; + token = next; + } + if (term->second.name) { + next = flecs_query_append_token(token, term->second.name); + term->second.name = token; + token = next; + } + if (term->src.name) { + next = flecs_query_append_token(token, term->src.name); + term->src.name = token; + token = next; + } + } } - /* Lookup term identifiers by name */ - if (flecs_term_ref_lookup(world, 0, src, ctx)) { - return -1; - } - if (flecs_term_ref_lookup(world, 0, first, ctx)) { - return -1; + if (old_tokens) { + flecs_free(&impl->stage->allocator, old_tokens_len, old_tokens); } +} - ecs_entity_t first_id = 0; - ecs_entity_t oneof = 0; - if (first->id & EcsIsEntity) { - first_id = ECS_TERM_REF_ID(first); +int flecs_query_finalize_query( + ecs_world_t *world, + ecs_query_t *q, + const ecs_query_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, + "ecs_query_desc_t was not initialized to zero"); + ecs_stage_t *stage = flecs_stage_from_world(&world); - if (!first_id) { - flecs_query_validator_error(ctx, "invalid \"0\" for first.name"); - return -1; - } + q->flags |= desc->flags | world->default_query_flags; - /* 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); + /* Fast routine that initializes simple queries and skips complex validation + * logic if it's not needed. When running in sanitized mode, always take the + * slow path. This in combination with the test suite ensures that the + * result of the fast & slow code is the same. */ + #ifndef FLECS_SANITIZE + if (flecs_query_finalize_simple(world, q, desc)) { + return 0; } + #endif - if (flecs_term_ref_lookup(world, oneof, &term->second, ctx)) { - return -1; + /* Populate term array from desc terms & DSL expression */ + if (flecs_query_query_populate_terms(world, stage, q, desc)) { + goto error; } - /* If source is 0, reset traversal flags */ - if (ECS_TERM_REF_ID(src) == 0 && src->id & EcsIsEntity) { - src->id &= ~EcsTraverseFlags; - term->trav = 0; + /* Ensure all fields are consistent and properly filled out */ + if (flecs_query_finalize_terms(world, q, desc)) { + goto error; } - /* If source is wildcard, term won't return any data */ - if ((src->id & EcsIsVariable) && ecs_id_is_wildcard(ECS_TERM_REF_ID(src))) { - term->inout = EcsInOutNone; - } + /* Store remaining string tokens in terms (after entity lookups) in single + * token buffer which simplifies memory management & reduces allocations. */ + flecs_query_populate_tokens(flecs_query_impl(q)); - /* If operator is Not, automatically convert wildcard queries to any */ - if (term->oper == EcsNot) { - if (ECS_TERM_REF_ID(first) == EcsWildcard) { - first->id = EcsAny | ECS_TERM_REF_FLAGS(first); - } + return 0; +error: + return -1; +} - if (ECS_TERM_REF_ID(second) == EcsWildcard) { - second->id = EcsAny | ECS_TERM_REF_FLAGS(second); - } +/** + * @file storage/component_index.c + * @brief Index for looking up tables by component id. + * + * An component record stores the administration for an in use (component) id, that is + * an id that has been used in tables. + * + * An component 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. + */ - term->id = flecs_wildcard_to_any(term->id); - } - return 0; +static +ecs_id_record_elem_t* flecs_component_elem( + ecs_component_record_t *head, + ecs_id_record_elem_t *list, + ecs_component_record_t *cdr) +{ + return ECS_OFFSET(cdr->pair, (uintptr_t)list - (uintptr_t)head->pair); } static -ecs_entity_t flecs_term_ref_get_entity( - const ecs_term_ref_t *ref) +void flecs_component_elem_insert( + ecs_component_record_t *head, + ecs_component_record_t *cdr, + ecs_id_record_elem_t *elem) { - if (ref->id & EcsIsEntity) { - return ECS_TERM_REF_ID(ref); /* Id is known */ - } else if (ref->id & EcsIsVariable) { - /* Return wildcard for variables, as they aren't known yet */ - if (ECS_TERM_REF_ID(ref) != EcsAny) { - /* Any variable should not use wildcard, as this would return all - * ids matching a wildcard, whereas Any returns the first match */ - return EcsWildcard; - } else { - return EcsAny; - } - } else { - return 0; /* Term id is uninitialized */ + ecs_id_record_elem_t *head_elem = flecs_component_elem(cdr, elem, head); + ecs_component_record_t *cur = head_elem->next; + elem->next = cur; + elem->prev = head; + if (cur) { + ecs_id_record_elem_t *cur_elem = flecs_component_elem(cdr, elem, cur); + cur_elem->prev = cdr; } + head_elem->next = cdr; } static -int flecs_term_populate_id( - ecs_term_t *term) +void flecs_component_elem_remove( + ecs_component_record_t *cdr, + ecs_id_record_elem_t *elem) { - ecs_entity_t first = flecs_term_ref_get_entity(&term->first); - ecs_entity_t second = flecs_term_ref_get_entity(&term->second); - ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; + ecs_component_record_t *prev = elem->prev; + ecs_component_record_t *next = elem->next; + ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); - if (first & ECS_ID_FLAGS_MASK) { - return -1; - } - if (second & ECS_ID_FLAGS_MASK) { - return -1; + ecs_id_record_elem_t *prev_elem = flecs_component_elem(cdr, elem, prev); + prev_elem->next = next; + if (next) { + ecs_id_record_elem_t *next_elem = flecs_component_elem(cdr, elem, next); + next_elem->prev = prev; } +} - if ((second || (term->second.id & EcsIsEntity))) { - flags |= ECS_PAIR; +static +void flecs_insert_id_elem( + ecs_world_t *world, + ecs_component_record_t *cdr, + ecs_id_t wildcard, + ecs_component_record_t *widr) +{ + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); + if (!widr) { + widr = flecs_components_ensure(world, wildcard); } + ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); - if (!second && !ECS_HAS_ID_FLAG(flags, PAIR)) { - term->id = first | flags; + ecs_pair_id_record_t *pair = cdr->pair; + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_insert(widr, cdr, &pair->first); } else { - term->id = ecs_pair(first, second) | flags; - } + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_insert(widr, cdr, &pair->second); - return 0; + if (cdr->flags & EcsIdTraversable) { + flecs_component_elem_insert(widr, cdr, &pair->trav); + } + } } static -int flecs_term_populate_from_id( - const ecs_world_t *world, - ecs_term_t *term, - ecs_query_validator_ctx_t *ctx) +void flecs_remove_id_elem( + ecs_component_record_t *cdr, + ecs_id_t wildcard) { - ecs_entity_t first = 0; - ecs_entity_t second = 0; + ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - if (ECS_HAS_ID_FLAG(term->id, PAIR)) { - first = ECS_PAIR_FIRST(term->id); - second = ECS_PAIR_SECOND(term->id); + ecs_pair_id_record_t *pair = cdr->pair; + ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); - if (!first) { - flecs_query_validator_error(ctx, "missing first element in term.id"); - return -1; - } - if (!second) { - if (first != EcsChildOf) { - flecs_query_validator_error(ctx, "missing second element in term.id"); - return -1; - } else { - /* (ChildOf, 0) is allowed so query can be used to efficiently - * query for root entities */ - } - } + if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { + ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_remove(cdr, &pair->first); } else { - first = term->id & ECS_COMPONENT_MASK; - if (!first) { - flecs_query_validator_error(ctx, "missing first element in term.id"); - return -1; - } - } + ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + flecs_component_elem_remove(cdr, &pair->second); - ecs_entity_t term_first = flecs_term_ref_get_entity(&term->first); - if (term_first) { - if ((uint32_t)term_first != (uint32_t)first) { - flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); - return -1; - } - } else { - ecs_entity_t first_id = ecs_get_alive(world, first); - if (!first_id) { - term->first.id = first | ECS_TERM_REF_FLAGS(&term->first); - } else { - term->first.id = first_id | ECS_TERM_REF_FLAGS(&term->first); + if (cdr->flags & EcsIdTraversable) { + flecs_component_elem_remove(cdr, &pair->trav); } } +} - ecs_entity_t term_second = flecs_term_ref_get_entity(&term->second); - if (term_second) { - if ((uint32_t)term_second != second) { - flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); - return -1; +static +ecs_id_t flecs_component_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; } - } else if (second) { - ecs_entity_t second_id = ecs_get_alive(world, second); - if (!second_id) { - term->second.id = second | ECS_TERM_REF_FLAGS(&term->second); - } else { - term->second.id = second_id | ECS_TERM_REF_FLAGS(&term->second); + if (o == EcsAny) { + o = EcsWildcard; } + id = ecs_pair(r, o); } - - return 0; + return id; } -static -int flecs_term_verify_eq_pred( - const ecs_term_t *term, - ecs_query_validator_ctx_t *ctx) +void flecs_component_init_sparse( + ecs_world_t *world, + ecs_component_record_t *cdr) { - const ecs_term_ref_t *second = &term->second; - const ecs_term_ref_t *src = &term->src; - ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); - ecs_entity_t second_id = ECS_TERM_REF_ID(&term->second); - ecs_entity_t src_id = ECS_TERM_REF_ID(&term->src); - - if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { - flecs_query_validator_error(ctx, "invalid operator combination"); - goto error; - } - - if ((src->id & EcsIsName) && (second->id & EcsIsName)) { - flecs_query_validator_error(ctx, "both sides of operator cannot be a name"); - goto error; - } - - if ((src->id & EcsIsEntity) && (second->id & EcsIsEntity)) { - flecs_query_validator_error(ctx, "both sides of operator cannot be an entity"); - goto error; - } - - if (!(src->id & EcsIsVariable)) { - flecs_query_validator_error(ctx, "left-hand of operator must be a variable"); - goto error; - } - - if (first_id == EcsPredMatch && !(second->id & EcsIsName)) { - flecs_query_validator_error(ctx, "right-hand of match operator must be a string"); - goto error; - } - - if ((src->id & EcsIsVariable) && (second->id & EcsIsVariable)) { - if (src_id && src_id == second_id) { - flecs_query_validator_error(ctx, "both sides of operator are equal"); - goto error; - } - if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { - flecs_query_validator_error(ctx, "both sides of operator are equal"); - goto error; + if (!cdr->sparse) { + if (cdr->flags & EcsIdIsSparse) { + ecs_assert(!(cdr->flags & EcsIdIsUnion), ECS_CONSTRAINT_VIOLATED, + "cannot mix union and sparse traits"); + ecs_assert(cdr->type_info != NULL, ECS_INVALID_OPERATION, + "only components can be marked as sparse"); + cdr->sparse = flecs_walloc_t(world, ecs_sparse_t); + flecs_sparse_init(cdr->sparse, NULL, NULL, cdr->type_info->size); + } else + if (cdr->flags & EcsIdIsUnion) { + cdr->sparse = flecs_walloc_t(world, ecs_switch_t); + flecs_switch_init(cdr->sparse, &world->allocator); } } +} - if (first_id == EcsPredEq) { - if (second_id == EcsPredEq || second_id == EcsPredMatch) { - flecs_query_validator_error(ctx, - "invalid right-hand side for equality operator"); - goto error; +static +void flecs_component_fini_sparse( + ecs_world_t *world, + ecs_component_record_t *cdr) +{ + if (cdr->sparse) { + if (cdr->flags & EcsIdIsSparse) { + ecs_assert(flecs_sparse_count(cdr->sparse) == 0, + ECS_INTERNAL_ERROR, NULL); + flecs_sparse_fini(cdr->sparse); + flecs_wfree_t(world, ecs_sparse_t, cdr->sparse); + } else + if (cdr->flags & EcsIdIsUnion) { + flecs_switch_fini(cdr->sparse); + flecs_wfree_t(world, ecs_switch_t, cdr->sparse); + } else { + ecs_abort(ECS_INTERNAL_ERROR, "unknown sparse storage"); } } +} - return 0; -error: - return -1; +static +ecs_flags32_t flecs_component_event_flags( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_observable_t *o = &world->observable; + ecs_flags32_t result = 0; + result |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; + result |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; + result |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; + result |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; + result |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; + return result; } static -int flecs_term_verify( - const ecs_world_t *world, - const ecs_term_t *term, - ecs_query_validator_ctx_t *ctx) +ecs_component_record_t* flecs_component_new( + ecs_world_t *world, + ecs_id_t id) { - const ecs_term_ref_t *first = &term->first; - const ecs_term_ref_t *second = &term->second; - const ecs_term_ref_t *src = &term->src; - ecs_entity_t first_id = 0, second_id = 0; - ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; - ecs_id_t id = term->id; + ecs_component_record_t *cdr, *idr_t = NULL; + ecs_id_t hash = flecs_component_hash(id); + cdr = flecs_bcalloc_w_dbg_info( + &world->allocators.id_record, "ecs_component_record_t"); - if ((src->id & EcsIsName) && (second->id & EcsIsName)) { - flecs_query_validator_error(ctx, "mismatch between term.id_flags & term.id"); - return -1; + if (hash >= FLECS_HI_ID_RECORD_ID) { + ecs_map_insert_ptr(&world->id_index_hi, hash, cdr); + } else { + world->id_index_lo[hash] = cdr; } - ecs_entity_t src_id = ECS_TERM_REF_ID(src); - if (first->id & EcsIsEntity) { - first_id = ECS_TERM_REF_ID(first); - } + ecs_table_cache_init(world, &cdr->cache); - if (second->id & EcsIsEntity) { - second_id = ECS_TERM_REF_ID(second); - } + cdr->id = id; + cdr->refcount = 1; - if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { - return flecs_term_verify_eq_pred(term, ctx); - } + bool is_wildcard = ecs_id_is_wildcard(id); + bool is_pair = ECS_IS_PAIR(id); - if (ecs_term_ref_is_set(second) && !ECS_HAS_ID_FLAG(flags, PAIR)) { - flecs_query_validator_error(ctx, "expected PAIR flag for term with pair"); - return -1; - } else if (!ecs_term_ref_is_set(second) && ECS_HAS_ID_FLAG(flags, PAIR)) { - if (first_id != EcsChildOf) { - flecs_query_validator_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 */ - } - } + ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; + if (is_pair) { + cdr->pair = flecs_bcalloc_w_dbg_info( + &world->allocators.pair_id_record, "ecs_pair_id_record_t"); + cdr->pair->reachable.current = -1; - if (!ecs_term_ref_is_set(src)) { - flecs_query_validator_error(ctx, "term.src is not initialized"); - return -1; - } + rel = ECS_PAIR_FIRST(id); + rel = flecs_entities_get_alive(world, rel); + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - if (!ecs_term_ref_is_set(first)) { - flecs_query_validator_error(ctx, "term.first is not initialized"); - return -1; - } + /* Relationship object can be 0, as tables without a ChildOf + * relationship are added to the (ChildOf, 0) component record */ + tgt = ECS_PAIR_SECOND(id); - if (ECS_HAS_ID_FLAG(flags, PAIR)) { - if (!ECS_PAIR_FIRST(id)) { - flecs_query_validator_error(ctx, "invalid 0 for first element in pair id"); - return -1; - } - if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { - flecs_query_validator_error(ctx, "invalid 0 for second element in pair id"); - return -1; +#ifdef FLECS_DEBUG + /* Check constraints */ + if (tgt) { + tgt = flecs_entities_get_alive(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + + /* Can't use relationship as target */ + if (ecs_has_id(world, tgt, EcsRelationship)) { + if (!ecs_id_is_wildcard(rel) && + !ecs_has_id(world, rel, EcsTrait)) + { + char *idstr = ecs_id_str(world, id); + char *tgtstr = ecs_id_str(world, tgt); + ecs_err("constraint violated: relationship '%s' cannot be used" + " as target in pair '%s'", tgtstr, idstr); + ecs_os_free(tgtstr); + ecs_os_free(idstr); + #ifndef FLECS_SOFT_ASSERT + ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); + #endif + } + } } - if ((first->id & EcsIsEntity) && - (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) - { - flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); - return -1; + if (ecs_has_id(world, rel, EcsTarget)) { + char *idstr = ecs_id_str(world, id); + char *relstr = ecs_id_str(world, rel); + ecs_err("constraint violated: " + "%s: target '%s' cannot be used as relationship", + idstr, relstr); + ecs_os_free(relstr); + ecs_os_free(idstr); + #ifndef FLECS_SOFT_ASSERT + ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); + #endif } - if ((first->id & EcsIsVariable) && - !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) - { - char *id_str = ecs_id_str(world, id); - flecs_query_validator_error(ctx, - "expected wildcard for variable term.first (got %s)", id_str); - ecs_os_free(id_str); - return -1; + + if (tgt && !ecs_id_is_wildcard(tgt) && tgt != EcsUnion) { + /* Check if target of relationship satisfies OneOf property */ + ecs_entity_t oneof = flecs_get_oneof(world, rel); + if (oneof) { + if (!ecs_has_pair(world, tgt, EcsChildOf, oneof)) { + char *idstr = ecs_id_str(world, id); + char *tgtstr = ecs_get_path(world, tgt); + char *oneofstr = ecs_get_path(world, oneof); + ecs_err("OneOf constraint violated: " + "%s: '%s' is not a child of '%s'", + idstr, tgtstr, oneofstr); + ecs_os_free(oneofstr); + ecs_os_free(tgtstr); + ecs_os_free(idstr); + #ifndef FLECS_SOFT_ASSERT + ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); + #endif + } + } + + /* Check if we're not trying to inherit from a final target */ + if (rel == EcsIsA) { + if (ecs_has_id(world, tgt, EcsFinal)) { + char *idstr = ecs_id_str(world, id); + char *tgtstr = ecs_get_path(world, tgt); + ecs_err("Final constraint violated: " + "%s: cannot inherit from final entity '%s'", + idstr, tgtstr); + ecs_os_free(tgtstr); + ecs_os_free(idstr); + #ifndef FLECS_SOFT_ASSERT + ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); + #endif + } + } } +#endif + + if (!is_wildcard && (rel != EcsFlag)) { + /* Inherit flags from (relationship, *) record */ + ecs_component_record_t *idr_r = flecs_components_ensure( + world, ecs_pair(rel, EcsWildcard)); + cdr->pair->parent = idr_r; + cdr->flags = idr_r->flags; + + /* If pair is not a wildcard, append it to wildcard lists. These + * allow for quickly enumerating all relationships for an object, + * or all objects for a relationship. */ + flecs_insert_id_elem(world, cdr, ecs_pair(rel, EcsWildcard), idr_r); - if ((second->id & EcsIsEntity) && - (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) - { - flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); - return -1; - } - if ((second->id & EcsIsVariable) && - !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) - { - char *id_str = ecs_id_str(world, id); - flecs_query_validator_error(ctx, - "expected wildcard for variable term.second (got %s)", id_str); - ecs_os_free(id_str); - return -1; + idr_t = flecs_components_ensure(world, ecs_pair(EcsWildcard, tgt)); + flecs_insert_id_elem(world, cdr, ecs_pair(EcsWildcard, tgt), idr_t); } } else { - ecs_entity_t component = id & ECS_COMPONENT_MASK; - if (!component) { - flecs_query_validator_error(ctx, "missing component id"); - return -1; - } - if ((first->id & EcsIsEntity) && - (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) + rel = id & ECS_COMPONENT_MASK; + ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + + /* Can't use relationship outside of a pair */ +#ifdef FLECS_DEBUG + rel = flecs_entities_get_alive(world, rel); + bool is_tgt = false; + if (ecs_has_id(world, rel, EcsRelationship) || + (is_tgt = ecs_has_id(world, rel, EcsTarget))) { - flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); - return -1; + char *idstr = ecs_id_str(world, id); + char *relstr = ecs_id_str(world, rel); + ecs_err("constraint violated: " + "%s: relationship%s '%s' cannot be used as component", + idstr, is_tgt ? " target" : "", relstr); + ecs_os_free(relstr); + ecs_os_free(idstr); + #ifndef FLECS_SOFT_ASSERT + ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); + #endif } - if ((first->id & EcsIsVariable) && !ecs_id_is_wildcard(component)) { - char *id_str = ecs_id_str(world, id); - flecs_query_validator_error(ctx, - "expected wildcard for variable term.first (got %s)", id_str); - ecs_os_free(id_str); - return -1; +#endif + } + + /* Initialize type info if id is not a tag */ + if (!is_wildcard && (!role || is_pair)) { + if (!(cdr->flags & EcsIdTag)) { + const ecs_type_info_t *ti = flecs_type_info_get(world, rel); + if (!ti && tgt) { + ti = flecs_type_info_get(world, tgt); + } + cdr->type_info = ti; } } - if (first_id) { - if (ecs_term_ref_is_set(second)) { - ecs_flags64_t mask = EcsIsEntity | EcsIsVariable; - if ((src->id & mask) == (second->id & mask)) { - bool is_same = false; - if (src->id & EcsIsEntity) { - is_same = src_id == second_id; - } else if (src->name && second->name) { - is_same = !ecs_os_strcmp(src->name, second->name); - } + /* 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 (is_same && ecs_has_id(world, first_id, EcsAcyclic) - && !(term->flags_ & EcsTermReflexive)) - { - char *pred_str = ecs_get_path(world, term->first.id); - flecs_query_validator_error(ctx, "term with acyclic relationship" - " '%s' cannot have same subject and object", pred_str); - ecs_os_free(pred_str); - return -1; - } - } + /* 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 (cdr->flags & EcsIdTraversable) { + /* Flag used to determine if object should be traversed when + * propagating events or with super/subset queries */ + flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); + + /* Add reference to (*, tgt) component record to entity record */ + tgt_r->cdr = idr_t; } - 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_path(world, second_id); - char *oneof_str = ecs_get_path(world, oneof); - char *id_str = ecs_id_str(world, term->id); - flecs_query_validator_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 second element of pair determines the type, check if the pair + * should be stored as a sparse component. */ + if (cdr->type_info && cdr->type_info->component == tgt) { + if (ecs_has_id(world, tgt, EcsSparse)) { + cdr->flags |= EcsIdIsSparse; } } } - if (term->trav) { - if (!ecs_has_id(world, term->trav, EcsTraversable)) { - char *r_str = ecs_get_path(world, term->trav); - flecs_query_validator_error(ctx, - "cannot traverse non-traversable relationship '%s'", r_str); - ecs_os_free(r_str); - return -1; + cdr->flags |= flecs_component_event_flags(world, id); + + if (cdr->flags & EcsIdIsSparse) { + flecs_component_init_sparse(world, cdr); + } else if (cdr->flags & EcsIdIsUnion) { + if (ECS_IS_PAIR(id) && ECS_PAIR_SECOND(id) == EcsUnion) { + flecs_component_init_sparse(world, cdr); } } - return 0; + 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); + } + +#ifdef FLECS_DEBUG_INFO + cdr->str = ecs_id_str(world, cdr->id); +#endif + + /* Update counters */ + world->info.id_create_total ++; + world->info.component_id_count += cdr->type_info != NULL; + world->info.tag_id_count += cdr->type_info == NULL; + world->info.pair_id_count += is_pair; + + return cdr; } static -int flecs_term_finalize( - const ecs_world_t *world, - ecs_term_t *term, - ecs_query_validator_ctx_t *ctx) +void flecs_component_assert_empty( + ecs_component_record_t *cdr) { - ctx->term = term; + (void)cdr; + ecs_assert(flecs_table_cache_count(&cdr->cache) == 0, + ECS_INTERNAL_ERROR, NULL); +} - ecs_term_ref_t *src = &term->src; - ecs_term_ref_t *first = &term->first; - ecs_term_ref_t *second = &term->second; - ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); - ecs_flags64_t second_flags = ECS_TERM_REF_FLAGS(second); +static +void flecs_component_free( + ecs_world_t *world, + ecs_component_record_t *cdr) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t id = cdr->id; - if (first->name && (first->id & ~EcsTermRefFlags)) { - flecs_query_validator_error(ctx, - "first.name and first.id have competing values"); - return -1; - } - if (src->name && (src->id & ~EcsTermRefFlags)) { - flecs_query_validator_error(ctx, - "src.name and src.id have competing values"); - return -1; - } - if (second->name && (second->id & ~EcsTermRefFlags)) { - flecs_query_validator_error(ctx, - "second.name and second.id have competing values"); - return -1; - } + flecs_component_assert_empty(cdr); - if (term->id & ~ECS_ID_FLAGS_MASK) { - if (flecs_term_populate_from_id(world, term, ctx)) { - return -1; + /* Id is still in use by a query */ + ecs_assert((world->flags & EcsWorldQuit) || (cdr->keep_alive == 0), + ECS_ID_IN_USE, "cannot delete id that is queried for"); + + if (ECS_IS_PAIR(id)) { + ecs_entity_t rel = ECS_PAIR_FIRST(id); + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if (!ecs_id_is_wildcard(id)) { + if (ECS_PAIR_FIRST(id) != EcsFlag) { + /* If id is not a wildcard, remove it from the wildcard lists */ + flecs_remove_id_elem(cdr, ecs_pair(rel, EcsWildcard)); + flecs_remove_id_elem(cdr, ecs_pair(EcsWildcard, tgt)); + } + } else { + ecs_log_push_2(); + + /* If id is a wildcard, it means that all id records that match the + * wildcard are also empty, so release them */ + if (ECS_PAIR_FIRST(id) == EcsWildcard) { + /* Iterate (*, Target) list */ + ecs_component_record_t *cur, *next = cdr->pair->second.next; + while ((cur = next)) { + flecs_component_assert_empty(cur); + next = cur->pair->second.next; + flecs_component_release(world, cur); + } + } else { + /* Iterate (Relationship, *) list */ + ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, + ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cur, *next = cdr->pair->first.next; + while ((cur = next)) { + flecs_component_assert_empty(cur); + next = cur->pair->first.next; + flecs_component_release(world, cur); + } + } + + ecs_log_pop_2(); } } - if (flecs_term_refs_finalize(world, term, ctx)) { - return -1; - } + /* Cleanup sparse storage */ + flecs_component_fini_sparse(world, cdr); - ecs_entity_t first_id = ECS_TERM_REF_ID(first); - ecs_entity_t second_id = ECS_TERM_REF_ID(second); - ecs_entity_t src_id = ECS_TERM_REF_ID(src); + /* Update counters */ + world->info.id_delete_total ++; + world->info.pair_id_count -= ECS_IS_PAIR(id); + world->info.component_id_count -= cdr->type_info != NULL; + world->info.tag_id_count -= cdr->type_info == NULL; - if ((first->id & EcsIsVariable) && (first_id == EcsAny)) { - term->flags_ |= EcsTermMatchAny; - } + /* Unregister the component record from the world & free resources */ + ecs_table_cache_fini(&cdr->cache); - if ((second->id & EcsIsVariable) && (second_id == EcsAny)) { - term->flags_ |= EcsTermMatchAny; + if (cdr->pair) { + flecs_name_index_free(cdr->pair->name_index); + ecs_vec_fini_t(&world->allocator, &cdr->pair->reachable.ids, + ecs_reachable_elem_t); + flecs_bfree_w_dbg_info(&world->allocators.pair_id_record, + cdr->pair, "ecs_pair_id_record_t"); } - if ((src->id & EcsIsVariable) && (src_id == EcsAny)) { - term->flags_ |= EcsTermMatchAnySrc; + ecs_id_t hash = flecs_component_hash(id); + if (hash >= FLECS_HI_ID_RECORD_ID) { + ecs_map_remove(&world->id_index_hi, hash); + } else { + world->id_index_lo[hash] = NULL; } - ecs_flags64_t ent_var_mask = EcsIsEntity | EcsIsVariable; +#ifdef FLECS_DEBUG_INFO + ecs_os_free(cdr->str); +#endif - /* If EcsVariable is used by itself, assign to predicate (singleton) */ - if ((ECS_TERM_REF_ID(src) == EcsVariable) && (src->id & EcsIsVariable)) { - src->id = first->id | ECS_TERM_REF_FLAGS(src); - src->id &= ~ent_var_mask; - src->id |= first->id & ent_var_mask; - src->name = first->name; + flecs_bfree_w_dbg_info(&world->allocators.id_record, + cdr, "ecs_component_record_t"); + + 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); } +} - if ((ECS_TERM_REF_ID(second) == EcsVariable) && (second->id & EcsIsVariable)) { - second->id = first->id | ECS_TERM_REF_FLAGS(second); - second->id &= ~ent_var_mask; - second->id |= first->id & ent_var_mask; - second->name = first->name; +ecs_component_record_t* flecs_components_ensure( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + cdr = flecs_component_new(world, id); } + return cdr; +} - if (!(term->id & ~ECS_ID_FLAGS_MASK)) { - if (flecs_term_populate_id(term)) { - return -1; - } +ecs_component_record_t* flecs_components_get( + const ecs_world_t *world, + ecs_id_t id) +{ + flecs_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; } - /* If term queries for !(ChildOf, _), translate it to the builtin - * (ChildOf, 0) index which is a cheaper way to find root entities */ - if (term->oper == EcsNot && term->id == ecs_pair(EcsChildOf, EcsAny)) { - /* Only if the source is not EcsAny */ - if (!(ECS_TERM_REF_ID(&term->src) == EcsAny && (term->src.id & EcsIsVariable))) { - term->oper = EcsAnd; - term->id = ecs_pair(EcsChildOf, 0); - second->id = 0; - second->id |= EcsSelf|EcsIsEntity; - } + ecs_id_t hash = flecs_component_hash(id); + ecs_component_record_t *cdr = NULL; + if (hash >= FLECS_HI_ID_RECORD_ID) { + cdr = ecs_map_get_deref(&world->id_index_hi, ecs_component_record_t, hash); + } else { + cdr = world->id_index_lo[hash]; } - ecs_entity_t first_entity = 0; - if ((first->id & EcsIsEntity)) { - first_entity = first_id; + return cdr; +} + +void flecs_component_claim( + ecs_world_t *world, + ecs_component_record_t *cdr) +{ + (void)world; + cdr->refcount ++; +} + +int32_t flecs_component_release( + ecs_world_t *world, + ecs_component_record_t *cdr) +{ + int32_t rc = -- cdr->refcount; + ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); + + if (!rc) { + flecs_component_free(world, cdr); } - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - ecs_flags32_t id_flags = 0; - if (idr) { - id_flags = idr->flags; - } else if (ECS_IS_PAIR(term->id)) { - ecs_id_record_t *wc_idr = flecs_id_record_get( - world, ecs_pair(ECS_PAIR_FIRST(term->id), EcsWildcard)); - if (wc_idr) { - id_flags = wc_idr->flags; + return rc; +} + +void flecs_component_release_tables( + ecs_world_t *world, + ecs_component_record_t *cdr) +{ + ecs_table_cache_iter_t it; + if (flecs_table_cache_all_iter(&cdr->cache, &it)) { + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { + /* Release current table */ + flecs_table_fini(world, tr->hdr.table); } } +} - if (src_id || src->name) { - if (!(term->src.id & EcsTraverseFlags)) { - if (id_flags & EcsIdOnInstantiateInherit) { - term->src.id |= EcsSelf|EcsUp; - if (!term->trav) { - term->trav = EcsIsA; - } - } else { - term->src.id |= EcsSelf; - - if (term->trav) { - char *idstr = ecs_id_str(world, term->id); - flecs_query_validator_error(ctx, ".trav specified for " - "'%s' which can't be inherited", idstr); - ecs_os_free(idstr); - return -1; - } +bool flecs_component_set_type_info( + ecs_world_t *world, + ecs_component_record_t *cdr, + const ecs_type_info_t *ti) +{ + bool is_wildcard = ecs_id_is_wildcard(cdr->id); + if (!is_wildcard) { + if (ti) { + if (!cdr->type_info) { + world->info.tag_id_count --; + world->info.component_id_count ++; + } + } else { + if (cdr->type_info) { + world->info.tag_id_count ++; + world->info.component_id_count --; } } + } - if (term->first.id & EcsCascade) { - flecs_query_validator_error(ctx, - "cascade modifier invalid for term.first"); - return -1; - } + bool changed = cdr->type_info != ti; + cdr->type_info = ti; - if (term->second.id & EcsCascade) { - flecs_query_validator_error(ctx, - "cascade modifier invalid for term.second"); - return -1; - } + return changed; +} - if (term->first.id & EcsDesc) { - flecs_query_validator_error(ctx, - "desc modifier invalid for term.first"); - return -1; - } +ecs_hashmap_t* flecs_component_name_index_ensure( + ecs_world_t *world, + ecs_component_record_t *cdr) +{ + ecs_assert(cdr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_hashmap_t *map = cdr->pair->name_index; + if (!map) { + map = cdr->pair->name_index = + flecs_name_index_new(world, &world->allocator); + } - if (term->second.id & EcsDesc) { - flecs_query_validator_error(ctx, - "desc modifier invalid for term.second"); - return -1; - } + return map; +} - if (term->src.id & EcsDesc && !(term->src.id & EcsCascade)) { - flecs_query_validator_error(ctx, - "desc modifier invalid without cascade"); - return -1; - } +ecs_hashmap_t* flecs_component_name_index_get( + const ecs_world_t *world, + ecs_component_record_t *cdr) +{ + flecs_poly_assert(world, ecs_world_t); + (void)world; + return cdr->pair->name_index; +} - if (term->src.id & EcsCascade) { - /* Cascade always implies up traversal */ - term->src.id |= EcsUp; - } +const ecs_table_record_t* flecs_component_get_table( + const ecs_component_record_t *cdr, + const ecs_table_t *table) +{ + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + return ecs_table_cache_get(&cdr->cache, table); +} - if ((src->id & EcsUp) && !term->trav) { - /* When traversal flags are specified but no traversal relationship, - * default to ChildOf, which is the most common kind of traversal. */ - term->trav = EcsChildOf; - } - } +void flecs_components_init( + ecs_world_t *world) +{ + /* Cache often used id records on world */ + world->idr_wildcard = flecs_components_ensure(world, EcsWildcard); + world->idr_wildcard_wildcard = flecs_components_ensure(world, + ecs_pair(EcsWildcard, EcsWildcard)); + world->idr_any = flecs_components_ensure(world, EcsAny); + world->idr_isa_wildcard = flecs_components_ensure(world, + ecs_pair(EcsIsA, EcsWildcard)); +} - if (!(id_flags & EcsIdOnInstantiateInherit) && (term->trav == EcsIsA)) { - if (src->id & EcsUp) { - char *idstr = ecs_id_str(world, term->id); - flecs_query_validator_error(ctx, "IsA traversal not allowed " - "for '%s', add the (OnInstantiate, Inherit) trait", idstr); - ecs_os_free(idstr); - return -1; - } +void flecs_components_fini( + 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_component_release(world, ecs_map_ptr(&it)); } - if (first_entity) { - /* Only enable inheritance for ids which are inherited from at the time - * of query creation. To force component inheritance to be evaluated, - * an application can explicitly set traversal flags. */ - if (flecs_id_record_get(world, ecs_pair(EcsIsA, first->id))) { - if (!((first_flags & EcsTraverseFlags) == EcsSelf)) { - term->flags_ |= EcsTermIdInherited; - } + int32_t i; + for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { + ecs_component_record_t *cdr = world->id_index_lo[i]; + if (cdr) { + flecs_component_release(world, cdr); } + } - /* If component id is final, don't attempt component inheritance */ - ecs_record_t *first_record = flecs_entities_get(world, first_entity); - ecs_table_t *first_table = first_record ? first_record->table : NULL; - if (first_table) { - if (ecs_term_ref_is_set(second)) { - /* Add traversal flags for transitive relationships */ - if (!((second_flags & EcsTraverseFlags) == EcsSelf)) { - if (!((src->id & EcsIsVariable) && (src_id == EcsAny))) { - if (!((second->id & EcsIsVariable) && (second_id == EcsAny))) { - if (ecs_table_has_id(world, first_table, EcsTransitive)) { - term->flags_ |= EcsTermTransitive; - } + ecs_assert(ecs_map_count(&world->id_index_hi) == 0, + ECS_INTERNAL_ERROR, NULL); - if (ecs_table_has_id(world, first_table, EcsReflexive)) { - term->flags_ |= EcsTermReflexive; - } - } - } - } + ecs_map_fini(&world->id_index_hi); + ecs_os_free(world->id_index_lo); +} - /* Check if term is union */ - if (ecs_table_has_id(world, first_table, EcsUnion)) { - /* Any wildcards don't need special handling as they just return - * (Rel, *). */ - if (ECS_IS_PAIR(term->id) && ECS_PAIR_SECOND(term->id) != EcsAny) { - term->flags_ |= EcsTermIsUnion; - } - } - } +static +ecs_flags32_t flecs_id_flags( + ecs_world_t *world, + ecs_id_t id) +{ + const ecs_component_record_t *cdr = flecs_components_get(world, id); + if (cdr) { + ecs_flags32_t extra_flags = 0; + if (cdr->flags & EcsIdOnInstantiateInherit) { + extra_flags |= EcsIdHasOnAdd|EcsIdHasOnRemove; } + return cdr->flags|extra_flags; + } + return flecs_component_event_flags(world, id); +} - /* Check if term has toggleable component */ - if (id_flags & EcsIdCanToggle) { - /* If the term isn't matched on a #0 source */ - if (term->src.id != EcsIsEntity) { - term->flags_ |= EcsTermIsToggle; +ecs_flags32_t flecs_id_flags_get( + ecs_world_t *world, + ecs_id_t id) +{ + ecs_flags32_t result = flecs_id_flags(world, id); - } + if (id != EcsAny) { + result |= flecs_id_flags(world, EcsAny); + } + + if (ECS_IS_PAIR(id)) { + ecs_entity_t first = ECS_PAIR_FIRST(id); + ecs_entity_t second = ECS_PAIR_SECOND(id); + + if (id != ecs_pair(first, EcsWildcard)) { + result |= flecs_id_flags(world, ecs_pair(first, EcsWildcard)); + } + if (id != ecs_pair(EcsWildcard, second)) { + result |= flecs_id_flags(world, ecs_pair(EcsWildcard, second)); + } + if (id != ecs_pair(EcsWildcard, EcsWildcard)) { + result |= flecs_id_flags(world, ecs_pair(EcsWildcard, EcsWildcard)); } - /* Check if this is a member query */ -#ifdef FLECS_META - if (ecs_id(EcsMember) != 0) { - if (first_entity) { - if (ecs_has(world, first_entity, EcsMember)) { - term->flags_ |= EcsTermIsMember; - } - } + if (first == EcsIsA) { + result |= EcsIdHasOnAdd|EcsIdHasOnRemove; } -#endif + } else if (id != EcsWildcard) { + result |= flecs_id_flags(world, EcsWildcard); } - if (ECS_TERM_REF_ID(first) == EcsVariable) { - flecs_query_validator_error(ctx, "invalid $ for term.first"); - return -1; - } + return result; +} - if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { - if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { - flecs_query_validator_error(ctx, - "invalid inout value for AndFrom/OrFrom/NotFrom term"); - return -1; - } - } +ecs_component_record_t* flecs_component_first_next( + ecs_component_record_t *cdr) +{ + ecs_assert(cdr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cdr->pair->first.next; +} - /* Is term trivial/cacheable */ - bool cacheable_term = true; - bool trivial_term = true; - if (term->oper != EcsAnd || term->flags_ & EcsTermIsOr) { - trivial_term = false; - } +ecs_component_record_t* flecs_component_second_next( + ecs_component_record_t *cdr) +{ + ecs_assert(cdr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cdr->pair->second.next; +} + +ecs_component_record_t* flecs_component_trav_next( + ecs_component_record_t *cdr) +{ + ecs_assert(cdr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + return cdr->pair->trav.next; +} - if (ecs_id_is_wildcard(term->id)) { - if (!(id_flags & EcsIdExclusive)) { - trivial_term = false; - } +bool flecs_component_iter( + const ecs_component_record_t *cdr, + ecs_table_cache_iter_t *iter_out) +{ + return flecs_table_cache_all_iter(&cdr->cache, iter_out); +} - if (first->id & EcsIsVariable) { - if (!ecs_id_is_wildcard(first_id) || first_id == EcsAny) { - trivial_term = false; - cacheable_term = false; - } - } +const ecs_table_record_t* flecs_component_next( + ecs_table_cache_iter_t *iter) +{ + return flecs_table_cache_next(iter, ecs_table_record_t); +} - if (second->id & EcsIsVariable) { - if (!ecs_id_is_wildcard(second_id) || second_id == EcsAny) { - trivial_term = false; - cacheable_term = false; - } - } - } - if (!ecs_term_match_this(term)) { - trivial_term = false; +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); } - if (term->flags_ & EcsTermTransitive) { - trivial_term = false; - cacheable_term = false; + 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 (term->flags_ & EcsTermIdInherited) { - trivial_term = false; - cacheable_term = false; - } + return page; +} - if (term->flags_ & EcsTermReflexive) { - trivial_term = false; - cacheable_term = false; - } +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)); +} - if (term->trav && term->trav != EcsIsA) { - trivial_term = false; +void flecs_entity_index_fini( + ecs_entity_index_t *index) +{ + ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); + 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]); } + ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); + flecs_ballocator_fini(&index->page_allocator); +} - if (!(src->id & EcsSelf)) { - trivial_term = false; - } +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, + "entity %u does not exist", (uint32_t)entity); + return r; +} - if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || - (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) || - (ECS_TERM_REF_ID(&term->first) == EcsPredLookup)) && - (term->first.id & EcsIsEntity)) - { - trivial_term = false; - cacheable_term = false; - } +ecs_record_t* flecs_entity_index_get( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_get_any(index, entity); + ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL); + ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, + ECS_INVALID_PARAMETER, "mismatching liveliness generation for entity"); + return r; +} - if (ECS_TERM_REF_ID(src) != EcsThis) { - cacheable_term = false; +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; } - if (term->id == ecs_childof(0)) { - cacheable_term = false; + ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, + ecs_entity_index_page_t*, page_index)[0]; + if (!page) { + return NULL; } - if (term->flags_ & EcsTermIsMember) { - trivial_term = false; - cacheable_term = false; + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + if (!r->dense) { + return NULL; } - if (term->flags_ & EcsTermIsToggle) { - trivial_term = false; - } + return r; +} - if (term->flags_ & EcsTermIsUnion) { - trivial_term = false; - cacheable_term = false; +ecs_record_t* flecs_entity_index_try_get( + const ecs_entity_index_t *index, + uint64_t entity) +{ + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + if (r->dense >= index->alive_count) { + return NULL; + } + if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { + return NULL; + } } + return r; +} - ECS_BIT_COND16(term->flags_, EcsTermIsTrivial, trivial_term); - ECS_BIT_COND16(term->flags_, EcsTermIsCacheable, cacheable_term); +ecs_record_t* flecs_entity_index_ensure( + ecs_entity_index_t *index, + uint64_t entity) +{ + uint32_t id = (uint32_t)entity; + ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); + ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; - if (flecs_term_verify(world, term, ctx)) { - return -1; + 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; } - return 0; + ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + + /* Entity is not alive, swap with first not alive element */ + uint64_t *ids = ecs_vec_first(&index->dense); + uint64_t e_swap = ids[index->alive_count]; + ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); + ecs_assert(r_swap->dense == index->alive_count, + ECS_INTERNAL_ERROR, NULL); + + r_swap->dense = dense; + r->dense = index->alive_count; + ids[dense] = e_swap; + ids[index->alive_count ++] = entity; + + ecs_assert(flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); + + return r; } -bool flecs_identifier_is_0( - const char *id) +void flecs_entity_index_remove( + ecs_entity_index_t *index, + uint64_t entity) { - return id[0] == '#' && id[1] == '0' && !id[2]; + ecs_record_t *r = flecs_entity_index_try_get(index, entity); + if (!r) { + /* Entity is not alive or doesn't exist, nothing to be done */ + return; + } + + int32_t dense = r->dense; + int32_t i_swap = -- index->alive_count; + uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); + uint64_t e_swap = e_swap_ptr[0]; + ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); + ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); + + r_swap->dense = dense; + r->table = NULL; + r->cdr = 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); } -bool ecs_term_ref_is_set( - const ecs_term_ref_t *ref) +void flecs_entity_index_make_alive( + ecs_entity_index_t *index, + uint64_t entity) { - return ECS_TERM_REF_ID(ref) != 0 || ref->name != NULL || ref->id & EcsIsEntity; + 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; + } } -bool ecs_term_is_initialized( - const ecs_term_t *term) +uint64_t flecs_entity_index_get_alive( + const ecs_entity_index_t *index, + uint64_t entity) { - return term->id != 0 || ecs_term_ref_is_set(&term->first); + ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); + if (r) { + return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; + } else { + return 0; + } } -bool ecs_term_match_this( - const ecs_term_t *term) +bool flecs_entity_index_is_alive( + const ecs_entity_index_t *index, + uint64_t entity) { - return (term->src.id & EcsIsVariable) && - (ECS_TERM_REF_ID(&term->src) == EcsThis); + return flecs_entity_index_try_get(index, entity) != NULL; } -bool ecs_term_match_0( - const ecs_term_t *term) +bool flecs_entity_index_is_valid( + const ecs_entity_index_t *index, + uint64_t entity) { - return (!ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)); + uint32_t id = (uint32_t)entity; + ecs_record_t *r = flecs_entity_index_try_get_any(index, id); + if (!r || !r->dense) { + /* Doesn't exist yet, so is valid */ + return true; + } + + /* If the id exists, it must be alive */ + return r->dense < index->alive_count; } -int ecs_term_finalize( - const ecs_world_t *world, - ecs_term_t *term) +bool flecs_entity_index_exists( + const ecs_entity_index_t *index, + uint64_t entity) { - ecs_query_validator_ctx_t ctx = {0}; - ctx.world = world; - ctx.term = term; - return flecs_term_finalize(world, term, &ctx); + return flecs_entity_index_try_get_any(index, entity) != NULL; } -static -ecs_term_t* flecs_query_or_other_type( - ecs_query_t *q, - int32_t t) +uint64_t flecs_entity_index_new_id( + ecs_entity_index_t *index) { - ecs_term_t *term = &q->terms[t]; - ecs_term_t *first = NULL; - while (t--) { - if (q->terms[t].oper != EcsOr) { - break; - } - first = &q->terms[t]; + 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 (first) { - ecs_world_t *world = q->world; - const ecs_type_info_t *first_type = ecs_get_type_info(world, first->id); - const ecs_type_info_t *term_type = ecs_get_type_info(world, term->id); + /* Create new id */ + uint32_t id = (uint32_t)++ index->max_id; - if (first_type == term_type) { - return NULL; - } - return first; - } else { - return NULL; - } -} + ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, + "max id %u exceeds 32 bits", index->max_id); -static -void flecs_normalize_term_name( - ecs_term_ref_t *ref) -{ - if (ref->name && ref->name[0] == '$' && ref->name[1]) { - ecs_assert(ref->id & EcsIsVariable, ECS_INTERNAL_ERROR, NULL); - const char *old = ref->name; - ref->name = &old[1]; + /* Make sure id hasn't been issued before */ + ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, + "new entity %u id already in use (likely due to overlapping ranges)", (uint32_t)id); - if (!ecs_os_strcmp(ref->name, "this")) { - ref->name = NULL; - ref->id |= EcsThis; - } - } -} + ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; -static -int flecs_query_finalize_terms( - const ecs_world_t *world, - ecs_query_t *q, - const ecs_query_desc_t *desc) -{ - int8_t i, term_count = q->term_count, field_count = 0; - ecs_term_t *terms = q->terms; - int32_t scope_nesting = 0, cacheable_terms = 0; - bool cond_set = false; + 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); - ecs_query_validator_ctx_t ctx = {0}; - ctx.world = world; - ctx.query = q; - ctx.desc = desc; + return id; +} - q->flags |= EcsQueryMatchOnlyThis; +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); - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - if (term->oper == EcsOr) { - term->flags_ |= EcsTermIsOr; - if (i != (term_count - 1)) { - term[1].flags_ |= EcsTermIsOr; - } - } + if (new_count < dense_count) { + /* Recycle ids */ + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } - bool cacheable = true; - bool match_nothing = true; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - bool prev_is_or = i && term[-1].oper == EcsOr; - bool nodata_term = false; - ctx.term_index = i; - - if (flecs_term_finalize(world, term, &ctx)) { - return -1; - } - - if (term->src.id != EcsIsEntity) { - /* If term doesn't match 0 entity, query doesn't match nothing */ - match_nothing = false; - } + /* 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; - if (scope_nesting) { - /* Terms inside a scope are not cacheable */ - ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); - } + ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, + "max id %u exceeds 32 bits", index->max_id); - /* If one of the terms in an OR chain isn't cacheable, none are */ - if (term->flags_ & EcsTermIsCacheable) { - /* Current term is marked as cacheable. Check if it is part of an OR - * chain, and if so, the previous term was also cacheable. */ - if (prev_is_or) { - if (term[-1].flags_ & EcsTermIsCacheable) { - cacheable_terms ++; - } else { - ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); - } - } else { - cacheable_terms ++; - } + /* Make sure id hasn't been issued before */ + ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, + "new entity %u id already in use (likely due to overlapping ranges)", (uint32_t)id); - /* Toggle terms may be cacheable for fetching the initial component, - * but require an additional toggle instruction for evaluation. */ - if (term->flags_ & EcsTermIsToggle) { - cacheable = false; - } - } else if (prev_is_or) { - /* Current term is not cacheable. If it is part of an OR chain, mark - * previous terms in the chain as also not cacheable. */ - int32_t j; - for (j = i - 1; j >= 0; j --) { - if (terms[j].oper != EcsOr) { - break; - } - if (terms[j].flags_ & EcsTermIsCacheable) { - cacheable_terms --; - ECS_BIT_CLEAR16(terms[j].flags_, EcsTermIsCacheable); - } - } - } + 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; + } - if (prev_is_or) { - if (ECS_TERM_REF_ID(&term[-1].src) != ECS_TERM_REF_ID(&term->src)) { - flecs_query_validator_error(&ctx, "mismatching src.id for OR terms"); - return -1; - } - if (term->oper != EcsOr && term->oper != EcsAnd) { - flecs_query_validator_error(&ctx, - "term after OR operator must use AND operator"); - return -1; - } - } else { - field_count ++; - } + index->alive_count = new_count; + return ecs_vec_get_t(&index->dense, uint64_t, alive_count); +} - term->field_index = flecs_ito(int8_t, field_count - 1); +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 (ecs_id_is_wildcard(term->id)) { - q->flags |= EcsQueryMatchWildcards; - } else if (!(term->flags_ & EcsTermIsOr)) { - ECS_TERMSET_SET(q->static_id_fields, 1u << term->field_index); - } +int32_t flecs_entity_index_count( + const ecs_entity_index_t *index) +{ + return index->alive_count - 1; +} - if (ecs_term_match_this(term)) { - ECS_BIT_SET(q->flags, EcsQueryMatchThis); - } else { - ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlyThis); - } +int32_t flecs_entity_index_size( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - 1; +} - if (ECS_TERM_REF_ID(term) == EcsPrefab) { - ECS_BIT_SET(q->flags, EcsQueryMatchPrefab); - } - if (ECS_TERM_REF_ID(term) == EcsDisabled && (term->src.id & EcsSelf)) { - ECS_BIT_SET(q->flags, EcsQueryMatchDisabled); - } - - if (term->oper == EcsNot && term->inout == EcsInOutDefault) { - term->inout = EcsInOutNone; - } +int32_t flecs_entity_index_not_alive_count( + const ecs_entity_index_t *index) +{ + return ecs_vec_count(&index->dense) - index->alive_count; +} - 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; - } +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); } + } - if (term->src.id == EcsIsEntity) { - nodata_term = true; - } else if (term->inout == EcsInOutNone) { - nodata_term = true; - } else if (!ecs_get_type_info(world, term->id)) { - nodata_term = true; - } else if (term->flags_ & EcsTermIsUnion) { - nodata_term = true; - } else if (term->flags_ & EcsTermIsMember) { - nodata_term = true; - } else if (scope_nesting) { - nodata_term = true; - } else { - if (ecs_id_is_tag(world, term->id)) { - nodata_term = true; - } else if ((ECS_PAIR_SECOND(term->id) == EcsWildcard) || - (ECS_PAIR_SECOND(term->id) == EcsAny)) - { - /* If the second element of a pair is a wildcard and the first - * element is not a type, we can't know in advance what the - * type of the term is, so it can't provide data. */ - if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { - nodata_term = true; - } - } - } + ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); - if (!nodata_term && term->inout != EcsIn && term->inout != EcsInOutNone) { - /* Non-this terms default to EcsIn */ - if (ecs_term_match_this(term) || term->inout != EcsInOutDefault) { - q->flags |= EcsQueryHasOutTerms; - } + index->alive_count = 1; + index->max_id = 0; +} - bool match_non_this = !ecs_term_match_this(term) || - (term->src.id & EcsUp); - if (match_non_this && term->inout != EcsInOutDefault) { - q->flags |= EcsQueryHasNonThisOutTerms; - } +void flecs_entity_index_shrink( + ecs_entity_index_t *index) +{ + ecs_vec_set_count_t( + index->allocator, &index->dense, uint64_t, index->alive_count); + ecs_vec_reclaim_t(index->allocator, &index->dense, uint64_t); + + int32_t i, e, max_page_index = 0, 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) { + continue; } - if (!nodata_term) { - /* If terms in an OR chain do not all return the same type, the - * field will not provide any data */ - if (term->flags_ & EcsTermIsOr) { - ecs_term_t *first = flecs_query_or_other_type(q, i); - if (first) { - nodata_term = true; + bool has_alive = false; + for (e = 0; e < FLECS_ENTITY_PAGE_SIZE; e ++) { + ecs_record_t *r = &page->records[e]; + ecs_entity_t entity = flecs_ito(uint64_t, (i * 4096) + e); + + if (r->dense) { + ecs_assert(flecs_entity_index_get_any(index, entity) == r, + ECS_INTERNAL_ERROR, NULL); + + if (flecs_entity_index_is_alive(index, entity)) { + ecs_assert(flecs_entity_index_is_alive(index, entity), + ECS_INTERNAL_ERROR, NULL); + has_alive = true; + break; } - q->data_fields &= (ecs_termset_t)~(1llu << term->field_index); } } - if (term->flags_ & EcsTermIsMember) { - nodata_term = false; + if (!has_alive) { + flecs_bfree(&index->page_allocator, page); + pages[i] = NULL; + } else { + max_page_index = i; } + } - if (!nodata_term && term->oper != EcsNot) { - ECS_TERMSET_SET(q->data_fields, 1u << term->field_index); + ecs_vec_set_count_t( + index->allocator, &index->pages, ecs_entity_index_page_t*, + max_page_index + 1); + ecs_vec_reclaim_t(index->allocator, &index->pages, ecs_entity_index_page_t*); +} - if (term->inout != EcsIn) { - ECS_TERMSET_SET(q->write_fields, 1u << term->field_index); - } - if (term->inout != EcsOut) { - ECS_TERMSET_SET(q->read_fields, 1u << term->field_index); - } - if (term->inout == EcsInOutDefault) { - ECS_TERMSET_SET(q->shared_readonly_fields, - 1u << term->field_index); - } - } +const uint64_t* flecs_entity_index_ids( + const ecs_entity_index_t *index) +{ + return ecs_vec_get_t(&index->dense, uint64_t, 1); +} - if (ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)) { - ECS_TERMSET_SET(q->fixed_fields, 1u << term->field_index); - } +/** + * @file storage/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 ((term->src.id & EcsIsVariable) && - (ECS_TERM_REF_ID(&term->src) != EcsThis)) - { - ECS_TERMSET_SET(q->var_fields, 1u << term->field_index); - } - bool is_sparse = false; +/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as + * this can severely slow down many ECS operations. */ +#ifdef FLECS_SANITIZE +static +void flecs_table_check_sanity( + ecs_world_t *world, + ecs_table_t *table) +{ + int32_t i, count = ecs_table_count(table); + int32_t size = ecs_table_size(table); + ecs_assert(count <= size, ECS_INTERNAL_ERROR, NULL); - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (idr) { - if (ecs_os_has_threading()) { - ecs_os_ainc(&idr->keep_alive); - } else { - idr->keep_alive ++; - } + 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; - term->flags_ |= EcsTermKeepAlive; + ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); - if (idr->flags & EcsIdIsSparse) { - is_sparse = true; - } - } else { - ecs_entity_t type = ecs_get_typeid(world, term->id); - if (type && ecs_has_id(world, type, EcsSparse)) { - is_sparse = true; - } - } + if (size) { + ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); + } else { + ecs_assert(table->data.entities == NULL, ECS_INTERNAL_ERROR, NULL); + } - if (is_sparse) { - term->flags_ |= EcsTermIsSparse; - ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); - if (term->flags_ & EcsTermIsCacheable) { - cacheable_terms --; - ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); - } + if (table->column_count) { + int32_t column_count = table->column_count; + ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); - /* Sparse component fields must be accessed with ecs_field_at */ - if (!nodata_term) { - q->row_fields |= flecs_uto(uint32_t, 1llu << i); + int16_t *column_map = table->column_map; + ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); + + for (i = 0; i < column_count; i ++) { + int32_t column_map_id = column_map[i + type_count]; + ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table->data.columns[i].ti != NULL, + ECS_INTERNAL_ERROR, NULL); + if (size) { + ecs_assert(table->data.columns[i].data != NULL, + ECS_INTERNAL_ERROR, NULL); + } else { + ecs_assert(table->data.columns[i].data == NULL, + ECS_INTERNAL_ERROR, NULL); } } + } else { + ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); + } - if (term->oper == EcsOptional || term->oper == EcsNot) { - cond_set = true; + if (bs_count) { + ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &table->_->bs_columns[i]; + ecs_assert(flecs_bitset_count(bs) == count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), + ECS_INTERNAL_ERROR, NULL); } + } - ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); - if (first_id == EcsPredEq || first_id == EcsPredMatch || - first_id == EcsPredLookup) - { - q->flags |= EcsQueryHasPred; - term->src.id = (term->src.id & ~EcsTraverseFlags) | EcsSelf; - term->inout = EcsInOutNone; - } else { - if (!ecs_term_match_0(term) && term->oper != EcsNot && - term->oper != EcsNotFrom) - { - ECS_TERMSET_SET(q->set_fields, 1u << term->field_index); - } - } + ecs_assert((table->_->traversable_count == 0) || + (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); +} +#else +#define flecs_table_check_sanity(world, table) +#endif - if (first_id == EcsScopeOpen) { - q->flags |= EcsQueryHasScopes; - scope_nesting ++; - } +/* Set flags for type hooks so table operations can quickly check whether a + * fast or complex operation that invokes hooks is required. */ +static +ecs_flags32_t flecs_type_info_flags( + const ecs_type_info_t *ti) +{ + ecs_flags32_t flags = 0; - if (scope_nesting) { - term->flags_ |= EcsTermIsScope; - ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); - ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); - cacheable_terms --; - } + if (ti->hooks.ctor) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.on_add) { + flags |= EcsTableHasCtors; + } + if (ti->hooks.dtor) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.on_remove) { + flags |= EcsTableHasDtors; + } + if (ti->hooks.copy) { + flags |= EcsTableHasCopy; + } + if (ti->hooks.move) { + flags |= EcsTableHasMove; + } - if (first_id == EcsScopeClose) { - if (i && ECS_TERM_REF_ID(&terms[i - 1].first) == EcsScopeOpen) { - flecs_query_validator_error(&ctx, "invalid empty scope"); - return -1; - } + return flags; +} - q->flags |= EcsQueryHasScopes; - scope_nesting --; - } +static +void flecs_table_init_columns( + ecs_world_t *world, + ecs_table_t *table, + int32_t column_count) +{ + int16_t i, cur = 0, ids_count = flecs_ito(int16_t, table->type.count); - if (scope_nesting < 0) { - flecs_query_validator_error(&ctx, "'}' without matching '{'"); - return -1; + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = table->type.array[i]; + if (id < FLECS_HI_COMPONENT_ID) { + table->component_map[id] = flecs_ito(int16_t, -(i + 1)); } } - if (scope_nesting != 0) { - flecs_query_validator_error(&ctx, "missing '}'"); - return -1; - } - - if (term_count && (terms[term_count - 1].oper == EcsOr)) { - flecs_query_validator_error(&ctx, - "last term of query can't have OR operator"); - return -1; + if (!column_count) { + return; } - q->field_count = flecs_ito(int8_t, field_count); + ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); + table->data.columns = columns; - if (field_count) { - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - int32_t field = term->field_index; - q->ids[field] = term->id; + ecs_id_t *ids = table->type.array; + ecs_table_record_t *records = table->_->records; + int16_t *t2s = table->column_map; + int16_t *s2t = &table->column_map[ids_count]; - if (term->flags_ & EcsTermIsOr) { - if (flecs_query_or_other_type(q, i)) { - q->sizes[field] = 0; - q->ids[field] = 0; - continue; - } - } + for (i = 0; i < ids_count; i ++) { + ecs_id_t id = ids[i]; + ecs_table_record_t *tr = &records[i]; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + const ecs_type_info_t *ti = cdr->type_info; - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (idr) { - if (!ECS_IS_PAIR(idr->id) || ECS_PAIR_FIRST(idr->id) != EcsWildcard) { - if (idr->type_info) { - q->sizes[field] = idr->type_info->size; - q->ids[field] = idr->id; - } - } - } else { - const ecs_type_info_t *ti = ecs_get_type_info( - world, term->id); - if (ti) { - q->sizes[field] = ti->size; - q->ids[field] = term->id; - } - } + if (!ti || (cdr->flags & EcsIdIsSparse)) { + t2s[i] = -1; + continue; } - } - - ECS_BIT_COND(q->flags, EcsQueryHasCondSet, cond_set); - - /* Check if this is a trivial query */ - if ((q->flags & EcsQueryMatchOnlyThis)) { - if (!(q->flags & - (EcsQueryHasPred|EcsQueryMatchDisabled|EcsQueryMatchPrefab))) - { - ECS_BIT_SET(q->flags, EcsQueryMatchOnlySelf); - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &terms[i]; - ecs_term_ref_t *src = &term->src; + t2s[i] = cur; + s2t[cur] = i; + tr->column = flecs_ito(int16_t, cur); - if (src->id & EcsUp) { - ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlySelf); - } + columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); + + if (id < FLECS_HI_COMPONENT_ID) { + table->component_map[id] = flecs_ito(int16_t, cur + 1); + } - if (!(term->flags_ & EcsTermIsTrivial)) { - break; - } - } + table->flags |= flecs_type_info_flags(ti); + cur ++; + } - if (term_count && (i == term_count)) { - ECS_BIT_SET(q->flags, EcsQueryIsTrivial); - } + int32_t record_count = table->_->record_count; + for (; i < record_count; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + ecs_id_t id = cdr->id; + + if (ecs_id_is_wildcard(id)) { + ecs_table_record_t *first_tr = &records[tr->index]; + tr->column = first_tr->column; } } - /* Set cacheable flags */ - ECS_BIT_COND(q->flags, EcsQueryHasCacheable, - cacheable_terms != 0); + /* For debug visualization */ +#ifdef FLECS_DEBUG_INFO + if (table->_->name_column != -1) { + table->_->name_column = table->column_map[table->_->name_column]; + } + if (table->_->doc_name_column != -1) { + table->_->doc_name_column = table->column_map[table->_->doc_name_column]; + } +#endif +} - /* Exclude queries with order_by from setting the IsCacheable flag. This - * allows the routine that evaluates entirely cached queries to use more - * optimized logic as it doesn't have to deal with order_by edge cases */ - ECS_BIT_COND(q->flags, EcsQueryIsCacheable, - cacheable && (cacheable_terms == term_count) && - !desc->order_by_callback); +/* Initialize table storage */ +void flecs_table_init_data( + ecs_world_t *world, + ecs_table_t *table) +{ + flecs_table_init_columns(world, table, table->column_count); - /* If none of the terms match a source, the query matches nothing */ - ECS_BIT_COND(q->flags, EcsQueryMatchNothing, match_nothing); + ecs_table__t *meta = table->_; + int32_t i, bs_count = meta->bs_count; - for (i = 0; i < q->term_count; i ++) { - ecs_term_t *term = &q->terms[i]; - /* Post process term names in case they were used to create variables */ - flecs_normalize_term_name(&term->first); - flecs_normalize_term_name(&term->second); - flecs_normalize_term_name(&term->src); + 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]); + } } - - return 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 -int flecs_query_query_populate_terms( +void flecs_table_init_flags( ecs_world_t *world, - ecs_stage_t *stage, - ecs_query_t *q, - const ecs_query_desc_t *desc) + ecs_table_t *table) { - /* Count number of initialized terms in desc->terms */ - int32_t i, term_count = 0; - for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { - if (!ecs_term_is_initialized(&desc->terms[i])) { - break; + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; + +#ifdef FLECS_DEBUG_INFO + /* For debug visualization */ + table->_->name_column = -1; + table->_->doc_name_column = -1; + table->_->parent.world = world; + table->_->parent.id = 0; +#endif + + int32_t i; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + + if (id <= EcsLastInternalComponentId) { + table->flags |= EcsTableHasBuiltins; } - term_count ++; - } - /* Copy terms from array to query */ - if (term_count) { - ecs_os_memcpy_n(&q->terms, desc->terms, ecs_term_t, term_count); - } + 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 (id == EcsNotQueryable) { + table->flags |= EcsTableNotQueryable; + } else { + if (ECS_IS_PAIR(id)) { + ecs_entity_t r = ECS_PAIR_FIRST(id); - /* Parse query expression if set */ - const char *expr = desc->expr; - if (expr && expr[0]) { - #ifdef FLECS_SCRIPT - ecs_script_impl_t script = { - .pub.world = world, - .pub.name = desc->entity ? ecs_get_name(world, desc->entity) : NULL, - .pub.code = expr - }; + table->flags |= EcsTableHasPairs; - /* Allocate buffer that's large enough to tokenize the query string */ - script.token_buffer_size = ecs_os_strlen(expr) * 2 + 1; - script.token_buffer = flecs_alloc( - &flecs_query_impl(q)->stage->allocator, script.token_buffer_size); + if (r == EcsIsA) { + table->flags |= EcsTableHasIsA; + } else if (r == EcsChildOf) { + table->flags |= EcsTableHasChildOf; + ecs_entity_t tgt = ecs_pair_second(world, id); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); - if (flecs_terms_parse(&script.pub, &q->terms[term_count], - &term_count)) - { - flecs_free(&stage->allocator, - script.token_buffer_size, script.token_buffer); - goto error; - } + if (tgt == EcsFlecs || tgt == EcsFlecsCore || + ecs_has_id(world, tgt, EcsModule)) + { + /* If table contains entities that are inside one of the + * builtin modules, it contains builtin entities */ + table->flags |= EcsTableHasBuiltins; + table->flags |= EcsTableHasModule; + } - /* Store on query object so we can free later */ - flecs_query_impl(q)->tokens = script.token_buffer; - flecs_query_impl(q)->tokens_len = - flecs_ito(int16_t, script.token_buffer_size); - #else - (void)world; - (void)stage; - ecs_err("cannot parse query expression: script addon required"); - goto error; - #endif +#ifdef FLECS_DEBUG_INFO + table->_->parent.id = tgt; +#endif + } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + table->flags |= EcsTableHasName; +#ifdef FLECS_DEBUG_INFO + table->_->name_column = flecs_ito(int16_t, i); +#endif + } else if (r == ecs_id(EcsPoly)) { + table->flags |= EcsTableHasBuiltins; + } +#if defined(FLECS_DEBUG_INFO) && defined(FLECS_DOC) + else if (id == ecs_pair_t(EcsDocDescription, EcsName)) { + table->_->doc_name_column = flecs_ito(int16_t, i); + } +#endif + } else { + if (ECS_HAS_ID_FLAG(id, TOGGLE)) { + ecs_table__t *meta = table->_; + table->flags |= EcsTableHasToggle; + + if (!meta->bs_count) { + meta->bs_offset = flecs_ito(int16_t, i); + } + meta->bs_count ++; + } + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + table->flags |= EcsTableHasOverrides; + } + } + } } +} + +/* Utility function that appends an element to the table record array */ +static +void flecs_table_append_to_records( + ecs_world_t *world, + ecs_table_t *table, + ecs_vec_t *records, + ecs_id_t id, + int32_t column) +{ + /* To avoid a quadratic search, use the O(1) lookup that the index + * already provides. */ + ecs_component_record_t *cdr = flecs_components_ensure(world, id); + + /* Safe, record is owned by table. */ + ecs_table_record_t *tr = ECS_CONST_CAST(ecs_table_record_t*, + flecs_component_get_table(cdr, 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; - q->term_count = flecs_ito(int8_t, term_count); + ecs_table_cache_insert(&cdr->cache, table, &tr->hdr); + } else { + tr->count ++; + } - return 0; -error: - return -1; + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } -#ifndef FLECS_SANITIZE -static -bool flecs_query_finalize_simple( +void flecs_table_emit( ecs_world_t *world, - ecs_query_t *q, - const ecs_query_desc_t *desc) + ecs_table_t *table, + ecs_entity_t event) { - /* Filter out queries that aren't simple enough */ - if (desc->expr) { - return false; - } + ecs_defer_begin(world); + flecs_emit(world, world, 0, &(ecs_event_desc_t) { + .ids = &table->type, + .event = event, + .table = table, + .flags = EcsEventTableOnly, + .observable = world + }); + ecs_defer_end(world); +} - if (desc->order_by_callback || desc->group_by_callback) { - return false; - } +/* 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); - int8_t i, term_count; - for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { - if (!ecs_term_is_initialized(&desc->terms[i])) { - break; - } + /* 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_t id = desc->terms[i].id; - if (ecs_id_is_wildcard(id)) { - return false; - } + 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; + } - if (id == EcsThis || ECS_PAIR_FIRST(id) == EcsThis || - ECS_PAIR_SECOND(id) == EcsThis) - { - return false; - } + /* 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_component_record_t *cdr, *childof_idr = NULL; - if (id == EcsVariable || ECS_PAIR_FIRST(id) == EcsVariable || - ECS_PAIR_SECOND(id) == EcsVariable) - { - return false; - } + 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 (id == EcsPrefab || id == EcsDisabled) { - return false; + /* 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; } - - ecs_term_t term = { .id = desc->terms[i].id }; - if (ecs_os_memcmp_t(&term, &desc->terms[i], ecs_term_t)) { - return false; + 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; } } - if (!i) { - return false; /* No terms */ - } - - term_count = i; - ecs_os_memcpy_n(&q->terms, desc->terms, ecs_term_t, term_count); - - /* Simple query that only queries for component ids */ + /* 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]; - /* Populate terms */ - int8_t cacheable_count = 0, trivial_count = 0, up_count = 0; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &q->terms[i]; - ecs_id_t id = term->id; + cdr = NULL; - ecs_entity_t first = id; - if (ECS_IS_PAIR(id)) { - ecs_entity_t second = flecs_entities_get_alive(world, - ECS_PAIR_SECOND(id)); - first = flecs_entities_get_alive(world, ECS_PAIR_FIRST(id)); - term->second.id = second | EcsIsEntity | EcsSelf; + if (dst_id == src_id) { + ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); + cdr = (ecs_component_record_t*)src_tr[src_i].hdr.cache; + } else if (dst_id < src_id) { + cdr = flecs_components_ensure(world, dst_id); + } + if (cdr) { + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)cdr; + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 1; } - term->field_index = i; - term->first.id = first | EcsIsEntity | EcsSelf; - term->src.id = EcsThis | EcsIsVariable | EcsSelf; + dst_i += dst_id <= src_id; + src_i += dst_id >= src_id; + } - q->ids[i] = 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); + cdr = flecs_components_ensure(world, dst_id); + tr->hdr.cache = (ecs_table_cache_t*)cdr; + ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 1; + } - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->keep_alive ++; - term->flags_ |= EcsTermKeepAlive; + /* 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; } - if (!idr && ECS_IS_PAIR(id)) { - idr = flecs_id_record_get(world, - ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); - } + /* 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); + } - bool cacheable = true, trivial = true; - if (idr) { - if (idr->type_info) { - q->sizes[i] = idr->type_info->size; - q->flags |= EcsQueryHasOutTerms; - q->data_fields |= (ecs_termset_t)(1llu << i); - } + /* Get records size now so we can check that array did not resize */ + int32_t records_size = ecs_vec_size(records); + (void)records_size; - if (idr->flags & EcsIdOnInstantiateInherit) { - term->src.id |= EcsUp; - term->trav = EcsIsA; - up_count ++; + /* Add records for ids with roles (used by cleanup logic) */ + if (first_role != -1) { + for (dst_i = first_role; dst_i < dst_count; dst_i ++) { + ecs_id_t id = dst_ids[dst_i]; + if (!ECS_IS_PAIR(id)) { + ecs_entity_t first = 0; + ecs_entity_t second = 0; + if (ECS_HAS_ID_FLAG(id, PAIR)) { + first = ECS_PAIR_FIRST(id); + second = ECS_PAIR_SECOND(id); + } else { + first = id & ECS_COMPONENT_MASK; + } + if (first) { + flecs_table_append_to_records(world, table, records, + ecs_pair(EcsFlag, first), dst_i); + } + if (second) { + flecs_table_append_to_records(world, table, records, + ecs_pair(EcsFlag, second), dst_i); + } } + } + } - if (idr->flags & EcsIdCanToggle) { - term->flags_ |= EcsTermIsToggle; - trivial = false; + 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); - if (ECS_IS_PAIR(id)) { - if (idr->flags & EcsIdIsUnion) { - term->flags_ |= EcsTermIsUnion; - trivial = false; - cacheable = false; + ecs_component_record_t *p_idr = (ecs_component_record_t*)tr->hdr.cache; + r = ECS_PAIR_FIRST(dst_id); + if (r == EcsChildOf) { + childof_idr = p_idr; } - } - if (idr->flags & EcsIdIsSparse) { - term->flags_ |= EcsTermIsSparse; - cacheable = false; trivial = false; - q->row_fields |= flecs_uto(uint32_t, 1llu << i); - } - } + ecs_assert(p_idr->pair != NULL, ECS_INTERNAL_ERROR, NULL); + cdr = p_idr->pair->parent; /* (R, *) */ + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); - if (ECS_IS_PAIR(id)) { - if (ecs_has_id(world, first, EcsTransitive)) { - term->flags_ |= EcsTermTransitive; - trivial = false; - cacheable = false; - } - if (ecs_has_id(world, first, EcsReflexive)) { - term->flags_ |= EcsTermReflexive; - trivial = false; - cacheable = false; + tr = ecs_vec_append_t(a, records, ecs_table_record_t); + tr->hdr.cache = (ecs_table_cache_t*)cdr; + tr->index = flecs_ito(int16_t, dst_i); + tr->count = 0; } - } - if (flecs_id_record_get(world, ecs_pair(EcsIsA, first)) != NULL) { - term->flags_ |= EcsTermIdInherited; - cacheable = false; trivial = false; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + tr->count ++; } - if (cacheable) { - term->flags_ |= EcsTermIsCacheable; - cacheable_count ++; - } + last_pair = dst_i; - if (trivial) { - term->flags_ |= EcsTermIsTrivial; - trivial_count ++; + /* 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); } } - /* Initialize static data */ - q->term_count = term_count; - q->field_count = term_count; - q->set_fields = (ecs_termset_t)((1llu << i) - 1); - q->static_id_fields = (ecs_termset_t)((1llu << i) - 1); - q->flags |= EcsQueryMatchThis|EcsQueryMatchOnlyThis|EcsQueryHasTableThisVar; - - if (cacheable_count) { - q->flags |= EcsQueryHasCacheable; + /* 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 (cacheable_count == term_count && trivial_count == term_count) { - q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; + 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 (!up_count) { - q->flags |= EcsQueryMatchOnlySelf; + if (!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 = -1; /* The table doesn't have a (ChildOf, 0) component */ + tr->count = 0; } - return true; -} -#endif - -static -char* flecs_query_append_token( - char *dst, - const char *src) -{ - int32_t len = ecs_os_strlen(src); - ecs_os_memcpy(dst, src, len + 1); - return dst + len + 1; -} - -static -void flecs_query_populate_tokens( - ecs_query_impl_t *impl) -{ - ecs_query_t *q = &impl->pub; - int32_t i, term_count = q->term_count; + /* 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; - char *old_tokens = impl->tokens; - int32_t old_tokens_len = impl->tokens_len; - impl->tokens = NULL; - impl->tokens_len = 0; + /* Register & patch up records */ + for (i = 0; i < dst_record_count; i ++) { + tr = &dst_tr[i]; + cdr = (ecs_component_record_t*)dst_tr[i].hdr.cache; + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); - /* Step 1: determine size of token buffer */ - int32_t len = 0; - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &q->terms[i]; - - if (term->first.name) { - len += ecs_os_strlen(term->first.name) + 1; - } - if (term->second.name) { - len += ecs_os_strlen(term->second.name) + 1; - } - if (term->src.name) { - len += ecs_os_strlen(term->src.name) + 1; - } - } + if (ecs_table_cache_get(&cdr->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 */ - /* Step 2: reassign term tokens to buffer */ - if (len) { - impl->tokens = flecs_alloc(&impl->stage->allocator, len); - impl->tokens_len = flecs_ito(int16_t, len); - char *token = impl->tokens, *next; + /* Ensure that record array hasn't been reallocated */ + ecs_assert(records_size == ecs_vec_size(records), + ECS_INTERNAL_ERROR, NULL); - for (i = 0; i < term_count; i ++) { - ecs_term_t *term = &q->terms[i]; - if (term->first.name) { - next = flecs_query_append_token(token, term->first.name); - term->first.name = token; - token = next; - } - if (term->second.name) { - next = flecs_query_append_token(token, term->second.name); - term->second.name = token; - token = next; - } - if (term->src.name) { - next = flecs_query_append_token(token, term->src.name); - term->src.name = token; - token = next; - } + ecs_table_cache_replace(&cdr->cache, table, &tr->hdr); + } else { + /* Other records are not registered yet */ + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_cache_insert(&cdr->cache, table, &tr->hdr); } - } - - if (old_tokens) { - flecs_free(&impl->stage->allocator, old_tokens_len, old_tokens); - } -} - -int flecs_query_finalize_query( - ecs_world_t *world, - ecs_query_t *q, - const ecs_query_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, - "ecs_query_desc_t was not initialized to zero"); - ecs_stage_t *stage = flecs_stage_from_world(&world); - - q->flags |= desc->flags | world->default_query_flags; - - /* Fast routine that initializes simple queries and skips complex validation - * logic if it's not needed. When running in sanitized mode, always take the - * slow path. This in combination with the test suite ensures that the - * result of the fast & slow code is the same. */ - #ifndef FLECS_SANITIZE - if (flecs_query_finalize_simple(world, q, desc)) { - return 0; - } - #endif - - /* Populate term array from desc terms & DSL expression */ - if (flecs_query_query_populate_terms(world, stage, q, desc)) { - goto error; - } - /* Ensure all fields are consistent and properly filled out */ - if (flecs_query_finalize_terms(world, q, desc)) { - goto error; - } - - /* Store remaining string tokens in terms (after entity lookups) in single - * token buffer which simplifies memory management & reduces allocations. */ - flecs_query_populate_tokens(flecs_query_impl(q)); + /* Claim component record so it stays alive as long as the table exists */ + flecs_component_claim(world, cdr); - return 0; -error: - return -1; -} + /* Initialize event flags */ + table->flags |= cdr->flags & EcsIdEventMask; + /* Initialize column index (will be overwritten by init_columns) */ + tr->column = -1; -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); - } + if (ECS_ID_ON_INSTANTIATE(cdr->flags) == EcsOverride) { + table->flags |= EcsTableHasOverrides; + } - 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 ((i < table->type.count) && (cdr->type_info != NULL)) { + if (!(cdr->flags & EcsIdIsSparse)) { + column_count ++; + } + } } - return page; -} + /* Initialize event flags for any record */ + table->flags |= world->idr_any->flags & EcsIdEventMask; -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)); -} + table->component_map = flecs_wcalloc_n( + world, int16_t, FLECS_HI_COMPONENT_ID); -void flecs_entity_index_fini( - ecs_entity_index_t *index) -{ - ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); -#if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) - int32_t i, count = ecs_vec_count(&index->pages); - ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); - for (i = 0; i < count; i ++) { - flecs_bfree(&index->page_allocator, pages[i]); + if (column_count) { + table->column_map = flecs_walloc_n(world, int16_t, + dst_count + column_count); } -#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, - "entity %u does not exist", (uint32_t)entity); - return r; -} - -ecs_record_t* flecs_entity_index_get( - const ecs_entity_index_t *index, - uint64_t entity) -{ - ecs_record_t *r = flecs_entity_index_get_any(index, entity); - ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL); - ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, - ECS_INVALID_PARAMETER, "mismatching liveliness generation for entity"); - return r; -} -ecs_record_t* flecs_entity_index_try_get_any( - const ecs_entity_index_t *index, - uint64_t entity) -{ - uint32_t id = (uint32_t)entity; - int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); - if (page_index >= ecs_vec_count(&index->pages)) { - return NULL; - } + table->column_count = flecs_ito(int16_t, column_count); + flecs_table_init_data(world, table); - ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, - ecs_entity_index_page_t*, page_index)[0]; - if (!page) { - return NULL; + if (table->flags & EcsTableHasName) { + ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); + table->_->name_index = + flecs_component_name_index_ensure(world, childof_idr); + ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); } - ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; - if (!r->dense) { - return NULL; + if (table->flags & EcsTableHasOnTableCreate) { + flecs_table_emit(world, table, EcsOnTableCreate); } - - return r; } -ecs_record_t* flecs_entity_index_try_get( - const ecs_entity_index_t *index, - uint64_t entity) +/* Unregister table from id records */ +static +void flecs_table_records_unregister( + ecs_world_t *world, + ecs_table_t *table) { - 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; -} + 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_component_record_t*)cache)->id; -ecs_record_t* flecs_entity_index_ensure( - ecs_entity_index_t *index, - uint64_t entity) -{ - uint32_t id = (uint32_t)entity; - ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); - ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; + ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); + ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_components_get(world, id) == (ecs_component_record_t*)cache, + ECS_INTERNAL_ERROR, NULL); + (void)id; - int32_t dense = r->dense; - if (dense) { - /* Entity is already alive, nothing to be done */ - if (dense < index->alive_count) { - ecs_assert( - ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, - ECS_INTERNAL_ERROR, NULL); - return r; - } - } else { - /* Entity doesn't have a dense index yet */ - ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; - r->dense = dense = ecs_vec_count(&index->dense) - 1; - index->max_id = id > index->max_id ? id : index->max_id; + ecs_table_cache_remove(cache, table_id, &tr->hdr); + flecs_component_release(world, (ecs_component_record_t*)cache); } - ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); + flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); +} - /* 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); +/* 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_id_t id, + ecs_entity_t event) +{ + (void)world; - r_swap->dense = dense; - r->dense = index->alive_count; - ids[dense] = e_swap; - ids[index->alive_count ++] = entity; + ecs_flags32_t flags = 0; - ecs_assert(flecs_entity_index_is_alive(index, entity), - ECS_INTERNAL_ERROR, NULL); + if (event == EcsOnAdd) { + flags = EcsTableHasOnAdd; + } else if (event == EcsOnRemove) { + flags = EcsTableHasOnRemove; + } else if (event == EcsOnSet) { + flags = EcsTableHasOnSet; + } else if (event == EcsOnTableCreate) { + flags = EcsTableHasOnTableCreate; + } else if (event == EcsOnTableDelete) { + flags = EcsTableHasOnTableDelete; + } else if (event == EcsWildcard) { + flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| + EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; + } - return r; + table->flags |= flags; + + /* Add observer flags to incoming edges for id */ + if (id && ((flags == EcsTableHasOnAdd) || (flags == EcsTableHasOnRemove))) { + flecs_table_edges_add_flags(world, table, id, flags); + } } -void flecs_entity_index_remove( - ecs_entity_index_t *index, - uint64_t entity) +/* 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_record_t *r = flecs_entity_index_try_get(index, entity); - if (!r) { - /* Entity is not alive or doesn't exist, nothing to be done */ - return; + int32_t count = ecs_table_count(table); + if (count) { + ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; + diff.removed = table->type; + diff.removed_flags = table->flags & EcsTableRemoveEdgeFlags; + flecs_notify_on_remove(world, table, NULL, 0, count, &diff); } - - int32_t dense = r->dense; - int32_t i_swap = -- index->alive_count; - uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); - uint64_t e_swap = e_swap_ptr[0]; - ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); - ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); - - r_swap->dense = dense; - r->table = NULL; - r->idr = NULL; - r->row = 0; - r->dense = i_swap; - ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap; - e_swap_ptr[0] = ECS_GENERATION_INC(entity); - ecs_assert(!flecs_entity_index_is_alive(index, entity), - ECS_INTERNAL_ERROR, NULL); } -void flecs_entity_index_make_alive( - ecs_entity_index_t *index, - uint64_t entity) +/* Invoke type hook for entities in table */ +static +void flecs_table_invoke_hook( + ecs_world_t *world, + ecs_table_t *table, + ecs_iter_action_t callback, + ecs_entity_t event, + ecs_column_t *column, + const ecs_entity_t *entities, + int32_t row, + int32_t count) { - 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; - } + int32_t column_index = flecs_ito(int32_t, column - table->data.columns); + ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); + int32_t type_index = table->column_map[table->type.count + column_index]; + ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = &table->_->records[type_index]; + flecs_invoke_hook(world, table, tr, count, row, entities, + table->type.array[type_index], column->ti, event, callback); } -uint64_t flecs_entity_index_get_alive( - const ecs_entity_index_t *index, - uint64_t entity) +/* Construct components */ +static +void flecs_table_invoke_ctor( + ecs_column_t *column, + int32_t row, + int32_t count) { - 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; + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + 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_ELEM(column->data, ti->size, row); + ctor(ptr, count, ti); } } -bool flecs_entity_index_is_alive( - const ecs_entity_index_t *index, - uint64_t entity) +/* Destruct components */ +static +void flecs_table_invoke_dtor( + ecs_column_t *column, + int32_t row, + int32_t count) { - return flecs_entity_index_try_get(index, entity) != NULL; + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + 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_ELEM(column->data, ti->size, row); + dtor(ptr, count, ti); + } } -bool flecs_entity_index_is_valid( - const ecs_entity_index_t *index, - uint64_t entity) +/* Run hooks that get invoked when component is added to entity */ +static +void flecs_table_invoke_add_hooks( + ecs_world_t *world, + ecs_table_t *table, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count, + bool construct) { - 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_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + if (construct) { + flecs_table_invoke_ctor(column, row, count); } - /* If the id exists, it must be alive */ - return r->dense < index->alive_count; + ecs_iter_action_t on_add = ti->hooks.on_add; + if (on_add) { + flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, + entities, row, count); + } } -bool flecs_entity_index_exists( - const ecs_entity_index_t *index, - uint64_t entity) +/* Run hooks that get invoked when component is removed from entity */ +static +void flecs_table_invoke_remove_hooks( + ecs_world_t *world, + ecs_table_t *table, + ecs_column_t *column, + ecs_entity_t *entities, + int32_t row, + int32_t count, + bool dtor) { - return flecs_entity_index_try_get_any(index, entity) != NULL; + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); + 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); + } } -uint64_t flecs_entity_index_new_id( - ecs_entity_index_t *index) +/* Destruct all components and/or delete all entities in table */ +static +void flecs_table_dtor_all( + ecs_world_t *world, + ecs_table_t *table) { - if (index->alive_count != ecs_vec_count(&index->dense)) { - /* Recycle id */ - return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; + int32_t i, c, count = ecs_table_count(table); + if (!count) { + return; } - /* Create new id */ - uint32_t id = (uint32_t)++ index->max_id; + const ecs_entity_t *entities = ecs_table_entities(table); + int32_t column_count = table->column_count; - ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, - "max id %u exceeds 32 bits", index->max_id); + ecs_assert(!column_count || table->data.columns != NULL, + ECS_INTERNAL_ERROR, NULL); - /* Make sure id hasn't been issued before */ - ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, - "new entity %u id already in use (likely due to overlapping ranges)", (uint32_t)id); + if (table->_->traversable_count) { + /* If table contains monitored entities with traversable relationships, + * make sure to invalidate observer cache */ + flecs_emit_propagate_invalidate(world, table, 0, count); + } - ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; + /* If table has components with destructors, iterate component columns */ + if (table->flags & EcsTableHasDtors) { + /* Run on_remove callbacks first before destructing components */ + for (c = 0; c < column_count; c++) { + ecs_column_t *column = &table->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[0], 0, count); + } + } - 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); + /* Destruct components */ + for (c = 0; c < column_count; c++) { + flecs_table_invoke_dtor(&table->data.columns[c], 0, count); + } - return id; + /* 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 = 0; i < count; i ++) { + /* Update entity index after invoking destructors so that entity can + * be safely used in destructor callbacks. */ + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), + ECS_INTERNAL_ERROR, NULL); + + flecs_entities_remove(world, e); + ecs_assert(ecs_is_valid(world, e) == false, + ECS_INTERNAL_ERROR, NULL); + } + + /* If table does not have destructors, just update entity index */ + } else { + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + flecs_entities_remove(world, e); + ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); + } + } } -uint64_t* flecs_entity_index_new_ids( - ecs_entity_index_t *index, - int32_t count) +#define FLECS_LOCKED_STORAGE_MSG \ + "to fix, defer operations with defer_begin/defer_end" + +/* Cleanup table storage */ +static +void flecs_table_fini_data( + ecs_world_t *world, + ecs_table_t *table, + bool do_on_remove, + bool deallocate) { - int32_t alive_count = index->alive_count; - int32_t new_count = alive_count + count; - int32_t dense_count = ecs_vec_count(&index->dense); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - if (new_count < dense_count) { - /* Recycle ids */ - index->alive_count = new_count; - return ecs_vec_get_t(&index->dense, uint64_t, alive_count); + if (do_on_remove) { + flecs_table_notify_on_remove(world, table); } - /* 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; + flecs_table_dtor_all(world, table); - ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, - "max id %u exceeds 32 bits", index->max_id); + if (deallocate) { + ecs_column_t *columns = table->data.columns; + if (columns) { + int32_t c, column_count = table->column_count; + for (c = 0; c < column_count; c ++) { + ecs_column_t *column = &columns[c]; + ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); + ecs_vec_fini(&world->allocator, &v, column->ti->size); + column->data = NULL; + } - /* Make sure id hasn't been issued before */ - ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, - "new entity %u id already in use (likely due to overlapping ranges)", (uint32_t)id); + flecs_wfree_n(world, ecs_column_t, table->column_count, columns); + table->data.columns = NULL; + } + } - 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; + ecs_table__t *meta = table->_; + ecs_bitset_t *bs_columns = meta->bs_columns; + if (bs_columns) { + int32_t c, column_count = meta->bs_count; + if (deallocate) { + for (c = 0; c < column_count; c ++) { + flecs_bitset_fini(&bs_columns[c]); + } + } + else { + for (c = 0; c < column_count; c++) { + bs_columns[c].count = 0; + } + } + + if (deallocate) { + flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); + meta->bs_columns = NULL; + } } - index->alive_count = new_count; - return ecs_vec_get_t(&index->dense, uint64_t, alive_count); + if (deallocate) { + ecs_vec_t v = ecs_vec_from_entities(table); + ecs_vec_fini_t(&world->allocator, &v, ecs_entity_t); + table->data.entities = NULL; + table->data.size = 0; + } + + table->data.count = 0; + table->_->traversable_count = 0; + table->flags &= ~EcsTableHasTraversable; } -void flecs_entity_index_set_size( - ecs_entity_index_t *index, - int32_t size) +const ecs_entity_t* ecs_table_entities( + const ecs_table_t *table) { - ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); + return table->data.entities; } -int32_t flecs_entity_index_count( - const ecs_entity_index_t *index) +/* Cleanup, no OnRemove, retain allocations */ +void ecs_table_clear_entities( + ecs_world_t* world, + ecs_table_t* table) { - return index->alive_count - 1; + flecs_table_fini_data(world, table, true, false); } -int32_t flecs_entity_index_size( - const ecs_entity_index_t *index) +/* Cleanup, run OnRemove, free allocations */ +void flecs_table_delete_entities( + ecs_world_t *world, + ecs_table_t *table) { - return ecs_vec_count(&index->dense) - 1; + flecs_table_fini_data(world, table, true, true); } -int32_t flecs_entity_index_not_alive_count( - const ecs_entity_index_t *index) +/* Unset all components in table. This function is called before a table is + * deleted, and invokes all OnRemove handlers, if any */ +void flecs_table_remove_actions( + ecs_world_t *world, + ecs_table_t *table) { - return ecs_vec_count(&index->dense) - index->alive_count; + flecs_table_notify_on_remove(world, table); } -void flecs_entity_index_clear( - ecs_entity_index_t *index) +/* Free table resources. */ +void flecs_table_fini( + ecs_world_t *world, + ecs_table_t *table) { - 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); + flecs_poly_assert(world, ecs_world_t); + + flecs_increment_table_version(world, table); + + bool is_root = table == &world->store.root; + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + 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_os_perf_trace_push("flecs.table.free"); + + if (!is_root && !(world->flags & EcsWorldQuit)) { + if (table->flags & EcsTableHasOnTableDelete) { + flecs_table_emit(world, table, EcsOnTableDelete); } } - ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); + 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(); + } + + /* Cleanup data, no OnRemove, free allocations */ + flecs_table_fini_data(world, table, false, true); + flecs_table_clear_edges(world, table); + + if (!is_root) { + ecs_type_t ids = { + .array = table->type.array, + .count = table->type.count + }; + + flecs_hashmap_remove_w_hash( + &world->store.table_map, &ids, ecs_table_t*, table->_->hash); + } + + flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); + flecs_wfree_n(world, int16_t, table->column_count + table->type.count, + table->column_map); + flecs_wfree_n(world, int16_t, FLECS_HI_COMPONENT_ID, table->component_map); + flecs_table_records_unregister(world, table); + + /* Update counters */ + world->info.table_count --; + world->info.table_delete_total ++; + + flecs_free_t(&world->allocator, ecs_table__t, table->_); + + if (!(world->flags & EcsWorldFini)) { + ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); + flecs_table_free_type(world, table); + flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); + } + + ecs_log_pop_2(); - index->alive_count = 1; - index->max_id = 0; + ecs_os_perf_trace_pop("flecs.table.free"); } -const uint64_t* flecs_entity_index_ids( - const ecs_entity_index_t *index) +/* 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) { - return ecs_vec_get_t(&index->dense, uint64_t, 1); + flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } -/** - * @file storage/id_index.c - * @brief Index for looking up tables by (component) id. - * - * An id record stores the administration for an in use (component) id, that is - * an id that has been used in tables. - * - * An id record contains a table cache, which stores the list of tables that - * have the id. Each entry in the cache (a table record) stores the first - * occurrence of the id in the table and the number of occurrences of the id in - * the table (in the case of wildcard ids). - * - * Id records are used in lots of scenarios, like uncached queries, or for - * getting a component array/component for an entity. - */ - - -static -ecs_id_record_elem_t* flecs_id_record_elem( - ecs_id_record_t *head, - ecs_id_record_elem_t *list, - ecs_id_record_t *idr) +/* Reset a table to its initial state. */ +void flecs_table_reset( + ecs_world_t *world, + ecs_table_t *table) { - return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + flecs_table_clear_edges(world, table); } -static -void flecs_id_record_elem_insert( - ecs_id_record_t *head, - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) +/* 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) { - 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; + 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; } - head_elem->next = idr; } +/* 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_id_record_elem_remove( - ecs_id_record_t *idr, - ecs_id_record_elem_t *elem) +void flecs_table_mark_table_dirty( + ecs_world_t *world, + ecs_table_t *table, + int32_t index) { - 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; + if (table->dirty_state) { + table->dirty_state[index] ++; + } + if (!index) { + flecs_increment_table_version(world, table); } } -static -void flecs_insert_id_elem( +/* Mark table component dirty */ +void flecs_table_mark_dirty( ecs_world_t *world, - ecs_id_record_t *idr, - ecs_id_t wildcard, - ecs_id_record_t *widr) + ecs_table_t *table, + ecs_entity_t component) { - 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); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { - ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_insert(widr, idr, &idr->first); - } else { - ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_insert(widr, idr, &idr->second); + if (table->dirty_state) { + int32_t column; + if (component < FLECS_HI_COMPONENT_ID) { + column = table->component_map[component]; + if (column <= 0) { + return; + } + } else { + ecs_component_record_t *cdr = flecs_components_get(world, component); + if (!cdr) { + return; + } - if (idr->flags & EcsIdTraversable) { - flecs_id_record_elem_insert(widr, idr, &idr->trav); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr || tr->column == -1) { + return; + } + + column = tr->column + 1; } + + /* Column is offset by 1, 0 is reserved for entity column. */ + + table->dirty_state[column] ++; } } -static -void flecs_remove_id_elem( - ecs_id_record_t *idr, - ecs_id_t wildcard) +/* Get (or create) dirty state of table. Used by queries for change tracking */ +int32_t* flecs_table_get_dirty_state( + ecs_world_t *world, + ecs_table_t *table) { - ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); - - if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { - ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_remove(idr, &idr->first); - } else { - ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, - ECS_INTERNAL_ERROR, NULL); - flecs_id_record_elem_remove(idr, &idr->second); - - if (idr->flags & EcsIdTraversable) { - flecs_id_record_elem_remove(idr, &idr->trav); + flecs_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 bitset (toggle component) column */ static -ecs_id_t flecs_id_record_hash( - ecs_id_t id) +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) { - 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); + ecs_table__t *dst_meta = dst_table->_; + ecs_table__t *src_meta = src_table->_; + if (!dst_meta && !src_meta) { + return; } - return id; -} -void flecs_id_record_init_sparse( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - if (!idr->sparse) { - if (idr->flags & EcsIdIsSparse) { - ecs_assert(!(idr->flags & EcsIdIsUnion), ECS_CONSTRAINT_VIOLATED, - "cannot mix union and sparse traits"); - ecs_assert(idr->type_info != NULL, ECS_INVALID_OPERATION, - "only components can be marked as sparse"); - idr->sparse = flecs_walloc_t(world, ecs_sparse_t); - flecs_sparse_init(idr->sparse, NULL, NULL, idr->type_info->size); - } else - if (idr->flags & EcsIdIsUnion) { - idr->sparse = flecs_walloc_t(world, ecs_switch_t); - flecs_switch_init(idr->sparse, &world->allocator); + int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; + int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; + + if (!src_column_count && !dst_column_count) { + return; + } + + ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; + ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; + + ecs_type_t dst_type = dst_table->type; + ecs_type_t src_type = src_table->type; + + int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; + int32_t offset_old = src_meta ? src_meta->bs_offset : 0; + + ecs_id_t *dst_ids = dst_type.array; + ecs_id_t *src_ids = src_type.array; + + for (; (i_new < dst_column_count) && (i_old < src_column_count);) { + ecs_id_t dst_id = dst_ids[i_new + offset_new]; + ecs_id_t src_id = src_ids[i_old + offset_old]; + + if (dst_id == src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + ecs_bitset_t *dst_bs = &dst_columns[i_new]; + + flecs_bitset_ensure(dst_bs, dst_index + count); + + int i; + for (i = 0; i < count; i ++) { + uint64_t value = flecs_bitset_get(src_bs, src_index + i); + flecs_bitset_set(dst_bs, dst_index + i, value); + } + + if (clear) { + ecs_assert(count == flecs_bitset_count(src_bs), + ECS_INTERNAL_ERROR, NULL); + flecs_bitset_fini(src_bs); + } + } else if (dst_id > src_id) { + ecs_bitset_t *src_bs = &src_columns[i_old]; + flecs_bitset_fini(src_bs); } + + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; } -} -static -void flecs_id_record_fini_sparse( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - if (idr->sparse) { - if (idr->flags & EcsIdIsSparse) { - ecs_assert(flecs_sparse_count(idr->sparse) == 0, + /* 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_sparse_fini(idr->sparse); - flecs_wfree_t(world, ecs_sparse_t, idr->sparse); - } else - if (idr->flags & EcsIdIsUnion) { - flecs_switch_fini(idr->sparse); - flecs_wfree_t(world, ecs_switch_t, idr->sparse); - } else { - ecs_abort(ECS_INTERNAL_ERROR, "unknown sparse storage"); + 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 -ecs_flags32_t flecs_id_record_event_flags( +void flecs_table_grow_column( ecs_world_t *world, - ecs_id_t id) + ecs_vec_t *column, + const ecs_type_info_t *ti, + int32_t to_add, + int32_t dst_size, + bool construct) { - ecs_observable_t *o = &world->observable; - ecs_flags32_t result = 0; - result |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; - result |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; - result |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; - result |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; - result |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; - return result; -} + ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); -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); - idr = flecs_bcalloc(&world->allocators.id_record); + int32_t count = ecs_vec_count(column); + int32_t size = ecs_vec_size(column); + int32_t elem_size = ti->size; + int32_t dst_count = count + to_add; + bool can_realloc = dst_size != size; + void *result = NULL; - if (hash >= FLECS_HI_ID_RECORD_ID) { - ecs_map_insert_ptr(&world->id_index_hi, hash, idr); - } else { - world->id_index_lo[hash] = idr; - } + ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_init(world, &idr->cache); + /* 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); - idr->id = id; - idr->refcount = 1; - idr->reachable.current = -1; + /* Create vector */ + ecs_vec_t dst; + ecs_vec_init(&world->allocator, &dst, elem_size, dst_size); + dst.count = dst_count; - bool is_wildcard = ecs_id_is_wildcard(id); - bool is_pair = ECS_IS_PAIR(id); + void *src_buffer = column->array; + void *dst_buffer = dst.array; - ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; - if (is_pair) { - // rel = ecs_pair_first(world, id); - rel = ECS_PAIR_FIRST(id); - rel = flecs_entities_get_alive(world, rel); - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + /* Move (and construct) existing elements to new vector */ + move_ctor(dst_buffer, src_buffer, count, ti); - /* Relationship object can be 0, as tables without a ChildOf - * relationship are added to the (ChildOf, 0) id record */ - tgt = ECS_PAIR_SECOND(id); + if (construct) { + /* Construct new element(s) */ + result = ECS_ELEM(dst_buffer, elem_size, count); + ctor(result, to_add, ti); + } -#ifdef FLECS_DEBUG - /* Check constraints */ - if (tgt) { - tgt = flecs_entities_get_alive(world, tgt); - ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + /* Free old vector */ + ecs_vec_fini(&world->allocator, column, elem_size); - /* Can't use relationship as target */ - if (ecs_has_id(world, tgt, EcsRelationship)) { - if (!ecs_id_is_wildcard(rel) && - !ecs_has_id(world, rel, EcsTrait)) - { - char *idstr = ecs_id_str(world, id); - char *tgtstr = ecs_id_str(world, tgt); - ecs_err("constraint violated: relationship '%s' cannot be used" - " as target in pair '%s'", tgtstr, idstr); - ecs_os_free(tgtstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - } + *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, elem_size, dst_size); } - if (ecs_has_id(world, rel, EcsTarget)) { - char *idstr = ecs_id_str(world, id); - char *relstr = ecs_id_str(world, rel); - ecs_err("constraint violated: " - "%s: target '%s' cannot be used as relationship", - idstr, relstr); - ecs_os_free(relstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif + result = ecs_vec_grow(&world->allocator, column, elem_size, to_add); + + ecs_xtor_t ctor; + if (construct && (ctor = ti->hooks.ctor)) { + /* If new elements need to be constructed and component has a + * constructor, construct */ + ctor(result, to_add, ti); } + } - if (tgt && !ecs_id_is_wildcard(tgt) && tgt != EcsUnion) { - /* Check if target of relationship satisfies OneOf property */ - ecs_entity_t oneof = flecs_get_oneof(world, rel); - if (oneof) { - if (!ecs_has_pair(world, tgt, EcsChildOf, oneof)) { - char *idstr = ecs_id_str(world, id); - char *tgtstr = ecs_get_path(world, tgt); - char *oneofstr = ecs_get_path(world, oneof); - ecs_err("OneOf constraint violated: " - "%s: '%s' is not a child of '%s'", - idstr, tgtstr, oneofstr); - ecs_os_free(oneofstr); - ecs_os_free(tgtstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - } + ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); +} - /* Check if we're not trying to inherit from a final target */ - if (rel == EcsIsA) { - if (ecs_has_id(world, tgt, EcsFinal)) { - char *idstr = ecs_id_str(world, id); - char *tgtstr = ecs_get_path(world, tgt); - ecs_err("Final constraint violated: " - "%s: cannot inherit from final entity '%s'", - idstr, tgtstr); - ecs_os_free(tgtstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } - } - } -#endif +/* Grow all data structures in a table */ +static +int32_t flecs_table_grow_data( + ecs_world_t *world, + ecs_table_t *table, + int32_t to_add, + int32_t size, + const ecs_entity_t *ids) +{ + flecs_poly_assert(world, ecs_world_t); - 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; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - /* If pair is not a wildcard, append it to wildcard lists. These - * allow for quickly enumerating all relationships for an object, - * or all objects for a relationship. */ - flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); + int32_t count = ecs_table_count(table); + int32_t column_count = table->column_count; - idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); - flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); - } + /* Add entity to column with entity ids */ + ecs_vec_t v_entities = ecs_vec_from_entities(table); + ecs_vec_set_size_t(&world->allocator, &v_entities, ecs_entity_t, size); + + ecs_entity_t *e = ecs_vec_last_t(&v_entities, ecs_entity_t) + 1; + v_entities.count += to_add; + if (v_entities.size > size) { + size = v_entities.size; + } + + /* Update table entities/count/size */ + int32_t prev_count = table->data.count, prev_size = table->data.size; + table->data.entities = v_entities.array; + table->data.count = v_entities.count; + table->data.size = v_entities.size; + + /* Initialize entity ids and record ptrs */ + int32_t i; + if (ids) { + ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); } else { - rel = id & ECS_COMPONENT_MASK; - ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); + ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); + } - /* Can't use relationship outside of a pair */ -#ifdef FLECS_DEBUG - rel = flecs_entities_get_alive(world, rel); - bool is_tgt = false; - if (ecs_has_id(world, rel, EcsRelationship) || - (is_tgt = ecs_has_id(world, rel, EcsTarget))) - { - char *idstr = ecs_id_str(world, id); - char *relstr = ecs_id_str(world, rel); - ecs_err("constraint violated: " - "%s: relationship%s '%s' cannot be used as component", - idstr, is_tgt ? " target" : "", relstr); - ecs_os_free(relstr); - ecs_os_free(idstr); - #ifndef FLECS_SOFT_ASSERT - ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); - #endif - } -#endif + /* Add elements to each column array */ + ecs_column_t *columns = table->data.columns; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); + flecs_table_grow_column(world, &v_column, ti, to_add, size, true); + ecs_assert(v_column.size == size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v_column.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); + column->data = v_column.array; + flecs_table_invoke_add_hooks(world, table, column, e, + count, to_add, false); + } + + ecs_table__t *meta = table->_; + int32_t bs_count = meta->bs_count; + ecs_bitset_t *bs_columns = meta->bs_columns; + + /* Add elements to each bitset column */ + for (i = 0; i < bs_count; i ++) { + ecs_bitset_t *bs = &bs_columns[i]; + flecs_bitset_addn(bs, to_add); + } + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + + /* Return index of first added entity */ + return count; +} + +/* Append operation for tables that don't have any complex logic */ +static +void flecs_table_fast_append( + ecs_world_t *world, + ecs_table_t *table) +{ + /* Add elements to each column array */ + ecs_column_t *columns = table->data.columns; + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); + ecs_vec_append(&world->allocator, &v, ti->size); + column->data = v.array; } +} + +/* Append entity to table */ +int32_t flecs_table_append( + ecs_world_t *world, + ecs_table_t *table, + ecs_entity_t entity, + bool construct, + bool on_add) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + + flecs_table_check_sanity(world, table); + + /* Get count & size before growing entities array. This tells us whether the + * arrays will realloc */ + int32_t count = ecs_table_count(table); + 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_vec_t v_entities = ecs_vec_from_entities(table); + ecs_entity_t *e = ecs_vec_append_t(&world->allocator, + &v_entities, ecs_entity_t); + + ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t *entities = table->data.entities = v_entities.array; + *e = entity; + + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); + ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); - /* 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; - } + /* Fast path: no switch columns, no lifecycle actions */ + if (!(table->flags & EcsTableIsComplex)) { + flecs_table_fast_append(world, table); + table->data.count = v_entities.count; + table->data.size = v_entities.size; + return count; } - /* Mark entities that are used as component/pair ids. When a tracked - * entity is deleted, cleanup policies are applied so that the store - * won't contain any tables with deleted ids. */ - - /* Flag for OnDelete policies */ - flecs_add_flag(world, rel, EcsEntityIsId); - if (tgt) { - /* Flag for OnDeleteTarget policies */ - ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); - ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_record_add_flag(tgt_r, EcsEntityIsTarget); - if (idr->flags & EcsIdTraversable) { - /* Flag used to determine if object should be traversed when - * propagating events or with super/subset queries */ - flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); + int32_t prev_count = table->data.count; + int32_t prev_size = table->data.size; - /* Add reference to (*, tgt) id record to entity record */ - tgt_r->idr = idr_t; - } + ecs_assert(table->data.count == v_entities.count - 1, + ECS_INTERNAL_ERROR, NULL); + table->data.count = v_entities.count; + table->data.size = v_entities.size; - /* If second element of pair determines the type, check if the pair - * should be stored as a sparse component. */ - if (idr->type_info && idr->type_info->component == tgt) { - if (ecs_has_id(world, tgt, EcsSparse)) { - idr->flags |= EcsIdIsSparse; - } - } - } + /* 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 = v_entities.size; - idr->flags |= flecs_id_record_event_flags(world, id); + /* Grow component arrays with 1 element */ + int32_t i; + for (i = 0; i < column_count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); + flecs_table_grow_column(world, &v_column, ti, 1, size, construct); + column->data = v_column.array; - if (idr->flags & EcsIdIsSparse) { - flecs_id_record_init_sparse(world, idr); - } else if (idr->flags & EcsIdIsUnion) { - if (ECS_IS_PAIR(id) && ECS_PAIR_SECOND(id) == EcsUnion) { - flecs_id_record_init_sparse(world, idr); + ecs_iter_action_t on_add_hook; + if (on_add && (on_add_hook = column->ti->hooks.on_add)) { + flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, + &entities[count], count, 1); } + + ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(v_column.count == v_entities.count, + ECS_INTERNAL_ERROR, NULL); } - 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); + ecs_table__t *meta = table->_; + int32_t bs_count = meta->bs_count; + ecs_bitset_t *bs_columns = meta->bs_columns; + + /* 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); } - /* Update counters */ - world->info.id_create_total ++; - world->info.component_id_count += idr->type_info != NULL; - world->info.tag_id_count += idr->type_info == NULL; - world->info.pair_id_count += is_pair; + flecs_table_check_sanity(world, table); - return idr; + return count; } +/* Delete operation for tables that don't have any complex logic */ static -void flecs_id_record_assert_empty( - ecs_id_record_t *idr) +void flecs_table_fast_delete( + ecs_table_t *table, + int32_t row) { - (void)idr; - ecs_assert(flecs_table_cache_count(&idr->cache) == 0, - ECS_INTERNAL_ERROR, NULL); + ecs_column_t *columns = table->data.columns; + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); + ecs_vec_remove(&v, ti->size, row); + column->data = v.array; + } } -static -void flecs_id_record_free( +/* Delete entity from table */ +void flecs_table_delete( ecs_world_t *world, - ecs_id_record_t *idr) + ecs_table_t *table, + int32_t row, + bool destruct) { - flecs_poly_assert(world, ecs_world_t); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t id = idr->id; - - flecs_id_record_assert_empty(idr); + ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - /* Id is still in use by a query */ - ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), - ECS_ID_IN_USE, "cannot delete id that is queried for"); + flecs_table_check_sanity(world, table); - 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(); + int32_t count = ecs_table_count(table); + ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); + count --; + ecs_assert(row <= count, ECS_INTERNAL_ERROR, NULL); - /* 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); - } - } + /* Move last entity id to row */ + ecs_entity_t *entities = table->data.entities; + ecs_entity_t entity_to_move = entities[count]; + ecs_entity_t entity_to_delete = entities[row]; + entities[row] = entity_to_move; - ecs_log_pop_2(); + /* Update record of moved entity in entity index */ + if (row != count) { + ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); + if (record_to_move) { + uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; + record_to_move->row = ECS_ROW_TO_RECORD(row, row_flags); + ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } - } - - /* Cleanup sparse storage */ - flecs_id_record_fini_sparse(world, idr); - - /* Update counters */ - world->info.id_delete_total ++; - world->info.pair_id_count -= ECS_IS_PAIR(id); - world->info.component_id_count -= idr->type_info != NULL; - world->info.tag_id_count -= idr->type_info == NULL; + } - /* Unregister the id record from the world & free resources */ - ecs_table_cache_fini(&idr->cache); - flecs_name_index_free(idr->name_index); - ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); - ecs_id_t hash = flecs_id_record_hash(id); - if (hash >= FLECS_HI_ID_RECORD_ID) { - ecs_map_remove(&world->id_index_hi, hash); - } else { - world->id_index_lo[hash] = NULL; - } + /* Destruct component data */ + ecs_column_t *columns = table->data.columns; + int32_t column_count = table->column_count; + int32_t i; - flecs_bfree(&world->allocators.id_record, idr); + /* 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 (row != count) { + flecs_table_fast_delete(table, row); + } - 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); - } -} + table->data.count --; -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); + flecs_table_check_sanity(world, table); + return; } - return idr; -} -ecs_id_record_t* flecs_id_record_get( - const ecs_world_t *world, - ecs_id_t id) -{ - flecs_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; - } + /* Last element, destruct & remove */ + if (row == 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, row, 1, true); + } + } - 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); + /* Not last element, move last element to deleted element & destruct */ } else { - idr = world->id_index_lo[hash]; - } + /* 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 = ti->size; + void *dst = ECS_ELEM(column->data, size, row); + void *src = ECS_ELEM(column->data, size, count); - return idr; -} + 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, row, 1); + } -void flecs_id_record_claim( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - (void)world; - idr->refcount ++; -} + ecs_move_t move_dtor = ti->hooks.move_dtor; + + /* If neither move nor move_ctor are set, this indicates that + * non-destructive move semantics are not supported for this + * type. In such cases, we set the move_dtor as ctor_move_dtor, + * which indicates a destructive move operation. This adjustment + * ensures compatibility with different language bindings. */ + if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { + move_dtor = ti->hooks.ctor_move_dtor; + } -int32_t flecs_id_record_release( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - int32_t rc = -- idr->refcount; - ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); + if (move_dtor) { + move_dtor(dst, src, 1, ti); + } else { + ecs_os_memcpy(dst, src, size); + } + } + } else { + flecs_table_fast_delete(table, row); + } + } - if (!rc) { - flecs_id_record_free(world, idr); + /* Remove elements from bitset columns */ + ecs_table__t *meta = table->_; + 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], row); } - return rc; -} + table->data.count --; -void flecs_id_record_release_tables( - ecs_world_t *world, - ecs_id_record_t *idr) -{ - ecs_table_cache_iter_t it; - if (flecs_table_cache_all_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_fini(world, tr->hdr.table); - } - } + flecs_table_check_sanity(world, table); } -bool flecs_id_record_set_type_info( - ecs_world_t *world, - ecs_id_record_t *idr, - const ecs_type_info_t *ti) +/* 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) { - 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 --; - } - } - } + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; - bool changed = idr->type_info != ti; - idr->type_info = ti; + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; - return changed; -} + 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 = flecs_column_id(dst_table, i_new); + ecs_id_t src_id = flecs_column_id(src_table, i_old); -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); - } + if (dst_id == src_id) { + int32_t size = dst_column->ti->size; + void *dst = ECS_ELEM(dst_column->data, size, dst_index); + void *src = ECS_ELEM(src_column->data, size, src_index); + ecs_os_memcpy(dst, src, size); + } - return map; + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; + } } -ecs_hashmap_t* flecs_id_name_index_ensure( +/* Move entity from src to dst table */ +void flecs_table_move( ecs_world_t *world, - ecs_id_t id) + 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) { - flecs_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); + 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, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - return flecs_id_record_name_index_ensure(world, idr); -} + ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); -ecs_hashmap_t* flecs_id_name_index_get( - const ecs_world_t *world, - ecs_id_t id) -{ - flecs_poly_assert(world, ecs_world_t); + flecs_table_check_sanity(world, dst_table); + flecs_table_check_sanity(world, src_table); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; + if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { + flecs_table_fast_move(dst_table, dst_index, src_table, src_index); + flecs_table_check_sanity(world, dst_table); + flecs_table_check_sanity(world, src_table); + return; } - return idr->name_index; -} - -ecs_table_record_t* flecs_table_record_get( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) -{ - flecs_poly_assert(world, ecs_world_t); + flecs_table_move_bitset_columns( + dst_table, dst_index, src_table, src_index, 1, false); - ecs_id_record_t* idr = flecs_id_record_get(world, id); - if (!idr) { - return NULL; - } + /* 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; - return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); -} + /* 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); -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); -} + int32_t i_new = 0, dst_column_count = dst_table->column_count; + int32_t i_old = 0, src_column_count = src_table->column_count; -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)); -} + ecs_column_t *src_columns = src_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; -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)); - } + 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 = flecs_column_id(dst_table, i_new); + ecs_id_t src_id = flecs_column_id(src_table, i_old); - 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) { - flecs_id_record_release(world, idr); - } - } + if (dst_id == src_id) { + ecs_type_info_t *ti = dst_column->ti; + int32_t size = ti->size; - ecs_assert(ecs_map_count(&world->id_index_hi) == 0, - ECS_INTERNAL_ERROR, NULL); + ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); + void *dst = ECS_ELEM(dst_column->data, size, dst_index); + void *src = ECS_ELEM(src_column->data, size, src_index); - ecs_map_fini(&world->id_index_hi); - ecs_os_free(world->id_index_lo); -} + 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; + } -static -ecs_flags32_t flecs_id_flags( - ecs_world_t *world, - ecs_id_t id) -{ - const ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - ecs_flags32_t extra_flags = 0; - if (idr->flags & EcsIdOnInstantiateInherit) { - extra_flags |= EcsIdHasOnAdd|EcsIdHasOnRemove; + 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 if (same_entity) { + flecs_table_invoke_remove_hooks(world, src_table, + src_column, &src_entity, src_index, 1, use_move_dtor); + } } - return idr->flags|extra_flags; - } - return flecs_id_record_event_flags(world, id); -} - -ecs_flags32_t flecs_id_flags_get( - ecs_world_t *world, - ecs_id_t id) -{ - ecs_flags32_t result = flecs_id_flags(world, id); - if (id != EcsAny) { - result |= flecs_id_flags(world, EcsAny); + i_new += dst_id <= src_id; + i_old += dst_id >= src_id; } - if (ECS_IS_PAIR(id)) { - ecs_entity_t first = ECS_PAIR_FIRST(id); - ecs_entity_t second = ECS_PAIR_SECOND(id); - - if (id != ecs_pair(first, EcsWildcard)) { - result |= flecs_id_flags(world, ecs_pair(first, EcsWildcard)); - } - if (id != ecs_pair(EcsWildcard, second)) { - result |= flecs_id_flags(world, ecs_pair(EcsWildcard, second)); - } - if (id != ecs_pair(EcsWildcard, EcsWildcard)) { - result |= flecs_id_flags(world, ecs_pair(EcsWildcard, EcsWildcard)); - } + 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); + } - if (first == EcsIsA) { - result |= EcsIdHasOnAdd|EcsIdHasOnRemove; + if (same_entity) { + 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); } - } else if (id != EcsWildcard) { - result |= flecs_id_flags(world, EcsWildcard); } - return result; + flecs_table_check_sanity(world, dst_table); + flecs_table_check_sanity(world, src_table); } -/** - * @file storage/table.c - * @brief Table storage implementation. - * - * Tables are the data structure that store the component data. Tables have - * columns for each component in the table, and rows for each entity stored in - * the table. Once created, the component list for a table doesn't change, but - * entities can move from one table to another. - * - * Each table has a type, which is a vector with the (component) ids in the - * table. The vector is sorted by id, which ensures that there can be only one - * table for each unique combination of components. - * - * Not all ids in a table have to be components. Tags are ids that have no - * data type associated with them, and as a result don't need to be explicitly - * stored beyond an element in the table type. To save space and speed up table - * creation, each table has a reference to a "storage table", which is a table - * that only includes component ids (so excluding tags). - * - * Note that the actual data is not stored on the storage table. The storage - * table is only used for sharing administration. A column_map member maps - * between column indices of the table and its storage table. Tables are - * refcounted, which ensures that storage tables won't be deleted if other - * tables have references to it. - */ - - -/* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as - * this can severely slow down many ECS operations. */ -#ifdef FLECS_SANITIZE -static -void flecs_table_check_sanity( +/* Append n entities to table */ +int32_t flecs_table_appendn( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *table, + int32_t to_add, + const ecs_entity_t *ids) { - int32_t i, count = ecs_table_count(table); - int32_t size = ecs_table_size(table); - ecs_assert(count <= size, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - 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; + flecs_table_check_sanity(world, table); + int32_t cur_count = ecs_table_count(table); + int32_t result = flecs_table_grow_data( + world, table, to_add, cur_count + to_add, ids); + flecs_table_check_sanity(world, table); - ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); + return result; +} - if (size) { - ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); - } else { - ecs_assert(table->data.entities == NULL, ECS_INTERNAL_ERROR, NULL); - } +/* Shrink table storage to fit number of entities */ +bool flecs_table_shrink( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + (void)world; - if (table->column_count) { - int32_t column_count = table->column_count; - ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); + flecs_table_check_sanity(world, table); - int16_t *column_map = table->column_map; - ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); + bool has_payload = table->data.entities != NULL; + ecs_vec_t v_entities = ecs_vec_from_entities(table); + ecs_vec_reclaim_t(&world->allocator, &v_entities, ecs_entity_t); - for (i = 0; i < column_count; i ++) { - int32_t column_map_id = column_map[i + type_count]; - ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table->data.columns[i].ti != NULL, - ECS_INTERNAL_ERROR, NULL); - if (size) { - ecs_assert(table->data.columns[i].data != NULL, - ECS_INTERNAL_ERROR, NULL); - } else { - ecs_assert(table->data.columns[i].data == NULL, - ECS_INTERNAL_ERROR, NULL); - } - } - } else { - ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = table->column_count; + for (i = 0; i < count; i ++) { + ecs_column_t *column = &table->data.columns[i]; + const ecs_type_info_t *ti = column->ti; + ecs_vec_t v_column = ecs_vec_from_column(column, table, ti->size); + ecs_vec_reclaim(&world->allocator, &v_column, ti->size); + column->data = v_column.array; } - 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); - } - } + table->data.count = v_entities.count; + table->data.size = v_entities.size; + table->data.entities = v_entities.array; - ecs_assert((table->_->traversable_count == 0) || - (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); + flecs_table_check_sanity(world, table); + + return has_payload; } -#else -#define flecs_table_check_sanity(world, table) -#endif -/* Set flags for type hooks so table operations can quickly check whether a - * fast or complex operation that invokes hooks is required. */ +/* Swap operation for bitset (toggle component) columns */ static -ecs_flags32_t flecs_type_info_flags( - const ecs_type_info_t *ti) +void flecs_table_swap_bitset_columns( + ecs_table_t *table, + int32_t row_1, + int32_t row_2) { - ecs_flags32_t flags = 0; - - if (ti->hooks.ctor) { - flags |= EcsTableHasCtors; - } - if (ti->hooks.on_add) { - flags |= EcsTableHasCtors; - } - if (ti->hooks.dtor) { - flags |= EcsTableHasDtors; - } - if (ti->hooks.on_remove) { - flags |= EcsTableHasDtors; - } - if (ti->hooks.copy) { - flags |= EcsTableHasCopy; + int32_t i = 0, column_count = table->_->bs_count; + if (!column_count) { + return; } - if (ti->hooks.move) { - flags |= EcsTableHasMove; - } - return flags; + 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); + } } -static -void flecs_table_init_columns( +/* Swap two rows in a table. Used for table sorting. */ +void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, - int32_t column_count) -{ - int16_t i, cur = 0, ids_count = flecs_ito(int16_t, table->type.count); + int32_t row_1, + int32_t row_2) +{ + (void)world; - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = table->type.array[i]; - if (id < FLECS_HI_COMPONENT_ID) { - table->component_map[id] = flecs_ito(int16_t, -(i + 1)); - } - } + ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); - if (!column_count) { + flecs_table_check_sanity(world, table); + + if (row_1 == row_2) { return; } - ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); - table->data.columns = columns; + /* If the table is monitored indicate that there has been a change */ + flecs_table_mark_table_dirty(world, table, 0); - ecs_id_t *ids = table->type.array; - ecs_table_record_t *records = table->_->records; - int16_t *t2s = table->column_map; - int16_t *s2t = &table->column_map[ids_count]; + ecs_entity_t *entities = table->data.entities; + ecs_entity_t e1 = entities[row_1]; + ecs_entity_t e2 = entities[row_2]; - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids[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; + ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); + ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); - if (!ti || (idr->flags & EcsIdIsSparse)) { - t2s[i] = -1; - continue; - } + ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); - t2s[i] = cur; - s2t[cur] = i; - tr->column = flecs_ito(int16_t, cur); + /* 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); - columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); - - if (id < FLECS_HI_COMPONENT_ID) { - table->component_map[id] = flecs_ito(int16_t, cur + 1); - } + /* 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); - table->flags |= flecs_type_info_flags(ti); - cur ++; + flecs_table_swap_bitset_columns(table, row_1, row_2); + + ecs_column_t *columns = table->data.columns; + if (!columns) { + flecs_table_check_sanity(world, table); + return; } - int32_t record_count = table->_->record_count; - for (; i < record_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 = idr->id; - - if (ecs_id_is_wildcard(id)) { - ecs_table_record_t *first_tr = &records[tr->index]; - tr->column = first_tr->column; + /* 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].ti->size); + } + + void* tmp = ecs_os_alloca(temp_buffer_size); + + /* Swap columns */ + for (i = 0; i < column_count; i ++) { + int32_t size = columns[i].ti->size; + ecs_column_t *column = &columns[i]; + void *ptr = column->data; + const ecs_type_info_t *ti = column->ti; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + + void *el_1 = ECS_ELEM(ptr, size, row_1); + void *el_2 = ECS_ELEM(ptr, size, row_2); + + ecs_move_t move = ti->hooks.move; + if (!move) { + ecs_os_memcpy(tmp, el_1, size); + ecs_os_memcpy(el_1, el_2, size); + ecs_os_memcpy(el_2, tmp, size); + } else { + ecs_move_t move_ctor = ti->hooks.move_ctor; + ecs_move_t move_dtor = ti->hooks.move_dtor; + ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); + move_ctor(tmp, el_1, 1, ti); + move(el_1, el_2, 1, ti); + move_dtor(el_2, tmp, 1, ti); } } + + flecs_table_check_sanity(world, table); } -/* Initialize table storage */ -void flecs_table_init_data( +static +void flecs_table_merge_vec( ecs_world_t *world, - ecs_table_t *table) + ecs_vec_t *dst, + ecs_vec_t *src, + int32_t size, + int32_t elem_size) { - flecs_table_init_columns(world, table, table->column_count); + int32_t dst_count = dst->count; - ecs_table__t *meta = table->_; - int32_t i, bs_count = meta->bs_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 (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]); + if (elem_size) { + ecs_vec_set_size(&world->allocator, + dst, size, elem_size); } + ecs_vec_set_count(&world->allocator, + dst, size, dst_count + src_count); + + void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); + void *src_ptr = src->array; + ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + + ecs_vec_fini(&world->allocator, src, size); } } -/* 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. */ +/* Merge data from one table column into other table column */ static -void flecs_table_init_flags( +void flecs_table_merge_column( ecs_world_t *world, - ecs_table_t *table) + ecs_vec_t *dst_vec, + ecs_vec_t *src_vec, + ecs_column_t *dst, + ecs_column_t *src, + int32_t column_size) { - ecs_id_t *ids = table->type.array; - int32_t count = table->type.count; + const ecs_type_info_t *ti = dst->ti; + ecs_assert(ti == src->ti, ECS_INTERNAL_ERROR, NULL); + ecs_size_t elem_size = ti->size; + int32_t dst_count = ecs_vec_count(dst_vec); - int32_t i; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; + if (!dst_count) { + ecs_vec_fini(&world->allocator, dst_vec, elem_size); + *dst_vec = *src_vec; - if (id <= EcsLastInternalComponentId) { - table->flags |= EcsTableHasBuiltins; - } + /* If the new table is not empty, copy the contents from the + * src into the dst. */ + } else { + int32_t src_count = src_vec->count; - 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 (id == EcsNotQueryable) { - table->flags |= EcsTableNotQueryable; + flecs_table_grow_column(world, dst_vec, ti, src_count, column_size, false); + void *dst_ptr = ECS_ELEM(dst_vec->array, elem_size, dst_count); + void *src_ptr = src_vec->array; + + /* Move values into column */ + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_move_t move = ti->hooks.ctor_move_dtor; + if (move) { + move(dst_ptr, src_ptr, src_count, ti); } else { - if (ECS_IS_PAIR(id)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); + ecs_os_memcpy(dst_ptr, src_ptr, elem_size * src_count); + } - table->flags |= EcsTableHasPairs; + ecs_vec_fini(&world->allocator, src_vec, elem_size); + } - 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); + dst->data = dst_vec->array; + src->data = 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 == ecs_id(EcsPoly)) { - table->flags |= EcsTableHasBuiltins; - } - } else { - if (ECS_HAS_ID_FLAG(id, TOGGLE)) { - ecs_table__t *meta = table->_; - table->flags |= EcsTableHasToggle; +/* Merge storage of two tables. */ +static +void flecs_table_merge_data( + ecs_world_t *world, + ecs_table_t *dst_table, + ecs_table_t *src_table, + int32_t dst_count, + int32_t src_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_table->data.columns; + ecs_column_t *dst_columns = dst_table->data.columns; - if (!meta->bs_count) { - meta->bs_offset = flecs_ito(int16_t, i); - } - meta->bs_count ++; - } - if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { - table->flags |= EcsTableHasOverrides; - } - } - } + ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); + + if (!src_count) { + return; + } + + /* Merge entities */ + ecs_vec_t dst_entities = ecs_vec_from_entities(dst_table); + ecs_vec_t src_entities = ecs_vec_from_entities(src_table); + flecs_table_merge_vec(world, &dst_entities, &src_entities, + ECS_SIZEOF(ecs_entity_t), 0); + ecs_assert(dst_entities.count == src_count + dst_count, + ECS_INTERNAL_ERROR, NULL); + int32_t column_size = dst_entities.size; + ecs_allocator_t *a = &world->allocator; + + for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { + ecs_column_t *dst_column = &dst_columns[i_new]; + ecs_column_t *src_column = &src_columns[i_old]; + ecs_id_t dst_id = flecs_column_id(dst_table, i_new); + ecs_id_t src_id = flecs_column_id(src_table, i_old); + ecs_size_t dst_elem_size = dst_column->ti->size; + ecs_size_t src_elem_size = src_column->ti->size; + + ecs_vec_t dst_vec = ecs_vec_from_column( + dst_column, dst_table, dst_elem_size); + ecs_vec_t src_vec = ecs_vec_from_column( + src_column, src_table, src_elem_size); + + if (dst_id == src_id) { + flecs_table_merge_column(world, &dst_vec, &src_vec, 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_vec_set_size(a, &dst_vec, dst_elem_size, column_size); + dst_column->data = dst_vec.array; + 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_vec, src_elem_size); + src_column->data = NULL; + i_old ++; + } + } + + flecs_table_move_bitset_columns( + dst_table, dst_count, src_table, 0, src_count, true); + + /* Initialize remaining columns */ + for (; i_new < dst_column_count; i_new ++) { + ecs_column_t *column = &dst_columns[i_new]; + int32_t elem_size = column->ti->size; + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t vec = ecs_vec_from_column(column, dst_table, elem_size); + ecs_vec_set_size(a, &vec, elem_size, column_size); + column->data = vec.array; + flecs_table_invoke_ctor(column, dst_count, src_count); } + + /* Destruct remaining columns */ + for (; i_old < src_column_count; i_old ++) { + ecs_column_t *column = &src_columns[i_old]; + int32_t elem_size = column->ti->size; + ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); + flecs_table_invoke_dtor(column, 0, src_count); + ecs_vec_t vec = ecs_vec_from_column(column, src_table, elem_size); + ecs_vec_fini(a, &vec, elem_size); + column->data = vec.array; + } + + /* Mark entity column as dirty */ + flecs_table_mark_table_dirty(world, dst_table, 0); + + dst_table->data.entities = dst_entities.array; + dst_table->data.count = dst_entities.count; + dst_table->data.size = dst_entities.size; + + src_table->data.entities = src_entities.array; + src_table->data.count = src_entities.count; + src_table->data.size = src_entities.size; } -/* Utility function that appends an element to the table record array */ -static -void flecs_table_append_to_records( +/* 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 *table, - ecs_vec_t *records, - ecs_id_t id, - int32_t column) + ecs_table_t *dst_table, + ecs_table_t *src_table) { - /* 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_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - } else { - tr->count ++; + flecs_table_check_sanity(world, src_table); + flecs_table_check_sanity(world, dst_table); + + const ecs_entity_t *src_entities = ecs_table_entities(src_table); + int32_t src_count = ecs_table_count(src_table); + int32_t dst_count = ecs_table_count(dst_table); + + /* First, update entity index so old entities point to new type */ + int32_t i; + for(i = 0; i < src_count; i ++) { + ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); + uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); + record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); + record->table = dst_table; } - ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); -} + /* Merge table columns */ + flecs_table_merge_data(world, dst_table, src_table, dst_count, src_count); + + if (src_count) { + 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); + } -void flecs_table_emit( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t event) -{ - ecs_defer_begin(world); - flecs_emit(world, world, 0, &(ecs_event_desc_t) { - .ids = &table->type, - .event = event, - .table = table, - .flags = EcsEventTableOnly, - .observable = world - }); - ecs_defer_end(world); + flecs_table_check_sanity(world, src_table); + flecs_table_check_sanity(world, dst_table); } -/* Main table initialization function */ -void flecs_table_init( +/* Internal mechanism for propagating information to tables */ +void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, - ecs_table_t *from) + ecs_id_t id, + ecs_table_event_t *event) { - /* Make sure table->flags is initialized */ - flecs_table_init_flags(world, table); - - /* The following code walks the table type to discover which id records the - * table needs to register table records with. - * - * In addition to registering itself with id records for each id in the - * table type, a table also registers itself with wildcard id records. For - * example, if a table contains (Eats, Apples), it will register itself with - * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it - * easier for wildcard queries to find the relevant tables. */ + flecs_poly_assert(world, ecs_world_t); - 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; + if (world->flags & EcsWorldFini) { + 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; + switch(event->kind) { + case EcsTableTriggersForId: + flecs_table_add_trigger_flags(world, table, id, event->event); + break; + case EcsTableNoTriggersForId: + break; /* TODO */ + } +} - 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 */ +int32_t flecs_table_get_toggle_column( + ecs_table_t *table, + ecs_id_t id) +{ + ecs_id_t *ids = table->type.array; + int32_t i = table->_->bs_offset, end = i + table->_->bs_count; - /* 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; + for (; i < end; i ++) { + if (ids[i] == (ECS_TOGGLE | id)) { + return i; } } - /* The easy part: initialize a record for every id in the type */ - for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { - ecs_id_t dst_id = dst_ids[dst_i]; - ecs_id_t src_id = src_ids[src_i]; + return -1; +} - idr = NULL; +ecs_bitset_t* flecs_table_get_toggle( + ecs_table_t *table, + ecs_id_t id) +{ + int32_t toggle_column = flecs_table_get_toggle_column(table, id); + if (toggle_column == -1) { + return NULL; + } - if (dst_id == src_id) { - ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); - idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; - } else if (dst_id < src_id) { - idr = flecs_id_record_ensure(world, dst_id); - } - if (idr) { - tr = ecs_vec_append_t(a, records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)idr; - tr->index = flecs_ito(int16_t, dst_i); - tr->count = 1; - } + toggle_column -= table->_->bs_offset; + ecs_assert(toggle_column >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(toggle_column < table->_->bs_count, ECS_INTERNAL_ERROR, NULL); + return &table->_->bs_columns[toggle_column]; +} - dst_i += dst_id <= src_id; - src_i += dst_id >= src_id; - } +ecs_id_t flecs_column_id( + ecs_table_t *table, + int32_t column_index) +{ + int32_t type_index = table->column_map[table->type.count + column_index]; + return table->type.array[type_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; - } +/* -- Public API -- */ - /* 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; +void ecs_table_lock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock ++; } - - /* Total number of records can never be higher than - * - number of regular (non-pair) ids + - * - three records for pairs: (R,T), (R,*), (*,T) - * - one wildcard (*), one any (_) and one pair wildcard (*,*) record - * - one record for (ChildOf, 0) - */ - int32_t flag_id_count = dst_count - start; - int32_t record_count = start + 3 * flag_id_count + 3 + 1; - ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); } +} - /* Add records for ids with roles (used by cleanup logic) */ - if (first_role != -1) { - for (dst_i = first_role; dst_i < dst_count; dst_i ++) { - ecs_id_t id = dst_ids[dst_i]; - if (!ECS_IS_PAIR(id)) { - ecs_entity_t first = 0; - ecs_entity_t second = 0; - if (ECS_HAS_ID_FLAG(id, PAIR)) { - first = ECS_PAIR_FIRST(id); - second = ECS_PAIR_SECOND(id); - } else { - first = id & ECS_COMPONENT_MASK; - } - if (first) { - flecs_table_append_to_records(world, table, records, - ecs_pair(EcsFlag, first), dst_i); - } - if (second) { - flecs_table_append_to_records(world, table, records, - ecs_pair(EcsFlag, second), dst_i); - } - } +void ecs_table_unlock( + ecs_world_t *world, + ecs_table_t *table) +{ + if (table) { + if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { + table->_->lock --; + ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, + "table_unlock called more often than table_lock"); } } +} - int32_t last_pair = -1; - bool has_childof = table->flags & EcsTableHasChildOf; - if (first_pair != -1) { - /* Add a (Relationship, *) record for each relationship. */ - ecs_entity_t r = 0; - for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { - ecs_id_t dst_id = dst_ids[dst_i]; - if (!ECS_IS_PAIR(dst_id)) { - break; /* no more pairs */ - } - if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ - tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); - - ecs_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; - r = ECS_PAIR_FIRST(dst_id); - if (r == EcsChildOf) { - childof_idr = p_idr; - } - - idr = p_idr->parent; /* (R, *) */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); +const ecs_type_t* ecs_table_get_type( + const ecs_table_t *table) +{ + if (table) { + return &table->type; + } else { + return NULL; + } +} - tr = ecs_vec_append_t(a, records, ecs_table_record_t); - tr->hdr.cache = (ecs_table_cache_t*)idr; - tr->index = flecs_ito(int16_t, dst_i); - tr->count = 0; - } +int32_t ecs_table_get_type_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + flecs_poly_assert(world, ecs_world_t); + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); - tr->count ++; + if (id < FLECS_HI_COMPONENT_ID) { + int16_t res = table->component_map[id]; + if (res > 0) { + return table->column_map[table->type.count + (res - 1)]; } - last_pair = dst_i; - - /* Add a (*, Target) record for each relationship target. Type - * ids are sorted relationship-first, so we can't simply do a single - * linear scan to find all occurrences for a target. */ - for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { - ecs_id_t dst_id = dst_ids[dst_i]; - ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); - - flecs_table_append_to_records( - world, table, records, tgt_id, dst_i); - } + return -res - 1; } - /* 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_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + return -1; } - /* Now that all records have been added, copy them to array */ - int32_t i, dst_record_count = ecs_vec_count(records); - ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, - dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); - table->_->record_count = flecs_ito(int16_t, dst_record_count); - table->_->records = dst_tr; - int32_t column_count = 0; - - /* Register & patch up records */ - for (i = 0; i < dst_record_count; i ++) { - tr = &dst_tr[i]; - idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ecs_table_cache_get(&idr->cache, table)) { - /* If this is a target wildcard record it has already been - * registered, but the record is now at a different location in - * memory. Patch up the linked list with the new address */ - ecs_table_cache_replace(&idr->cache, table, &tr->hdr); - } else { - /* Other records are not registered yet */ - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_table_cache_insert(&idr->cache, table, &tr->hdr); - } - - /* Claim id record so it stays alive as long as the table exists */ - flecs_id_record_claim(world, idr); - - /* Initialize event flags */ - table->flags |= idr->flags & EcsIdEventMask; + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr) { + return -1; + } - /* Initialize column index (will be overwritten by init_columns) */ - tr->column = -1; + return tr->index; +error: + return -1; +} - if (ECS_ID_ON_INSTANTIATE(idr->flags) == EcsOverride) { - table->flags |= EcsTableHasOverrides; - } +int32_t ecs_table_get_column_index( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + flecs_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); - if ((i < table->type.count) && (idr->type_info != NULL)) { - if (!(idr->flags & EcsIdIsSparse)) { - column_count ++; - } + if (id < FLECS_HI_COMPONENT_ID) { + int16_t res = table->component_map[id]; + if (res > 0) { + return res - 1; } + return -1; } - table->component_map = flecs_wcalloc_n( - world, int16_t, FLECS_HI_COMPONENT_ID); - - if (column_count) { - table->column_map = flecs_walloc_n(world, int16_t, - dst_count + column_count); + ecs_component_record_t *cdr = flecs_components_get(world, id); + if (!cdr) { + return -1; } - table->column_count = flecs_ito(int16_t, column_count); - flecs_table_init_data(world, table); - - if (table->flags & EcsTableHasName) { - ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); - table->_->name_index = - flecs_id_record_name_index_ensure(world, childof_idr); - ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); + if (!tr) { + return -1; } - if (table->flags & EcsTableHasOnTableCreate) { - flecs_table_emit(world, table, EcsOnTableCreate); - } + return tr->column; +error: + return -1; } -/* Unregister table from id records */ -static -void flecs_table_records_unregister( - ecs_world_t *world, - ecs_table_t *table) +int32_t ecs_table_column_count( + const ecs_table_t *table) { - uint64_t table_id = table->id; - int32_t i, count = table->_->record_count; - for (i = 0; i < count; i ++) { - ecs_table_record_t *tr = &table->_->records[i]; - ecs_table_cache_t *cache = tr->hdr.cache; - ecs_id_t id = ((ecs_id_record_t*)cache)->id; - - ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); - ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); - ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, - ECS_INTERNAL_ERROR, NULL); - (void)id; + return table->column_count; +} - ecs_table_cache_remove(cache, table_id, &tr->hdr); - flecs_id_record_release(world, (ecs_id_record_t*)cache); +int32_t ecs_table_type_to_column_index( + const ecs_table_t *table, + int32_t index) +{ + ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); + int16_t *column_map = table->column_map; + if (column_map) { + return column_map[index]; } - - flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); +error: + return -1; } -/* 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_id_t id, - ecs_entity_t event) +int32_t ecs_table_column_to_type_index( + const ecs_table_t *table, + int32_t index) { - (void)world; + 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; +} - ecs_flags32_t flags = 0; +void* ecs_table_get_column( + const ecs_table_t *table, + int32_t index, + int32_t offset) +{ + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); - if (event == EcsOnAdd) { - flags = EcsTableHasOnAdd; - } else if (event == EcsOnRemove) { - flags = EcsTableHasOnRemove; - } else if (event == EcsOnSet) { - flags = EcsTableHasOnSet; - } else if (event == EcsOnTableCreate) { - flags = EcsTableHasOnTableCreate; - } else if (event == EcsOnTableDelete) { - flags = EcsTableHasOnTableDelete; - } else if (event == EcsWildcard) { - flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| - EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; + ecs_column_t *column = &table->data.columns[index]; + void *result = column->data; + if (offset) { + result = ECS_ELEM(result, column->ti->size, offset); } - table->flags |= flags; - - /* Add observer flags to incoming edges for id */ - if (id && ((flags == EcsTableHasOnAdd) || (flags == EcsTableHasOnRemove))) { - flecs_table_edges_add_flags(world, table, id, flags); - } + return result; +error: + return NULL; } -/* 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) +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t offset) { - int32_t count = ecs_table_count(table); - if (count) { - ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; - diff.removed = table->type; - diff.removed_flags = table->flags & EcsTableRemoveEdgeFlags; - flecs_notify_on_remove(world, table, NULL, 0, count, &diff); - } -} + 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); -/* Invoke type hook for entities in table */ -static -void flecs_table_invoke_hook( - ecs_world_t *world, - ecs_table_t *table, - ecs_iter_action_t callback, - ecs_entity_t event, - ecs_column_t *column, - const ecs_entity_t *entities, - int32_t row, - int32_t count) -{ - int32_t column_index = flecs_ito(int32_t, column - table->data.columns); - ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); - int32_t type_index = table->column_map[table->type.count + column_index]; - ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); - const ecs_table_record_t *tr = &table->_->records[type_index]; - flecs_invoke_hook(world, table, tr, count, row, entities, - table->type.array[type_index], column->ti, event, callback); -} + world = ecs_get_world(world); -/* Construct components */ -static -void flecs_table_invoke_ctor( - ecs_column_t *column, - int32_t row, - int32_t count) -{ - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); - 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_ELEM(column->data, ti->size, row); - ctor(ptr, count, ti); + int32_t index = ecs_table_get_column_index(world, table, id); + if (index == -1) { + return NULL; } + + return ecs_table_get_column(table, index, offset); +error: + return NULL; } -/* Destruct components */ -static -void flecs_table_invoke_dtor( - ecs_column_t *column, - int32_t row, - int32_t count) +size_t ecs_table_get_column_size( + const ecs_table_t *table, + int32_t column) { - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); - 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_ELEM(column->data, ti->size, row); - dtor(ptr, count, ti); - } + ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); + + return flecs_ito(size_t, table->data.columns[column].ti->size); +error: + return 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_column_t *column, - ecs_entity_t *entities, - int32_t row, - int32_t count, - bool construct) +int32_t ecs_table_count( + const ecs_table_t *table) { - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_type_info_t *ti = column->ti; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.count; +} - if (construct) { - flecs_table_invoke_ctor(column, row, count); - } +int32_t ecs_table_size( + const ecs_table_t *table) +{ + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + return table->data.size; +} - 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); - } +bool ecs_table_has_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id) +{ + return ecs_table_get_type_index(world, table, id) != -1; } -/* Run hooks that get invoked when component is removed from entity */ -static -void flecs_table_invoke_remove_hooks( - ecs_world_t *world, - ecs_table_t *table, - ecs_column_t *column, - ecs_entity_t *entities, - int32_t row, - int32_t count, - bool dtor) +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel) { - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_type_info_t *ti = column->ti; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + 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, + "cannot safely determine depth for relationship that is not acyclic " + "(add Acyclic property to relationship)"); - 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); - } + world = ecs_get_world(world); + + return flecs_relation_depth(world, rel, table); +error: + return -1; } -/* Destruct all components and/or delete all entities in table in range */ -static -void flecs_table_dtor_all( - ecs_world_t *world, +bool ecs_table_has_flags( ecs_table_t *table, - int32_t row, - int32_t count, - bool is_delete) + ecs_flags32_t flags) { - const ecs_entity_t *entities = ecs_table_entities(table); - int32_t column_count = table->column_count; - int32_t i, c, end = row + count; + return (table->flags & flags) == flags; +} - ecs_assert(!column_count || table->data.columns != NULL, - ECS_INTERNAL_ERROR, NULL); +void ecs_table_swap_rows( + ecs_world_t* world, + ecs_table_t* table, + int32_t row_1, + int32_t row_2) +{ + flecs_table_swap(world, table, row_1, row_2); +} - 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); - } +int32_t flecs_table_observed_count( + const ecs_table_t *table) +{ + return table->_->traversable_count; +} - /* If table has components with destructors, iterate component columns */ - if (table->flags & EcsTableHasDtors) { - /* Throw up a lock just to be sure */ - table->_->lock = true; +void* ecs_record_get_by_column( + const ecs_record_t *r, + int32_t index, + size_t c_size) +{ + (void)c_size; + ecs_table_t *table = r->table; - /* Run on_remove callbacks first before destructing components */ - for (c = 0; c < column_count; c++) { - ecs_column_t *column = &table->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); - } - } + ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_column_t *column = &table->data.columns[index]; + ecs_size_t size = column->ti->size; - /* Destruct components */ - for (c = 0; c < column_count; c++) { - flecs_table_invoke_dtor(&table->data.columns[c], row, count); - } + ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, + ECS_INVALID_PARAMETER, NULL); - /* 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. */ - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), - ECS_INTERNAL_ERROR, NULL); + return ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); +error: + return NULL; +} - if (is_delete) { - flecs_entities_remove(world, e); - ecs_assert(ecs_is_valid(world, e) == false, - ECS_INTERNAL_ERROR, NULL); - } else { - // If this is not a delete, clear the entity index record - ecs_record_t *record = flecs_entities_get(world, e); - record->table = NULL; - record->row = 0; - } - } +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); - table->_->lock = false; + world = ecs_get_world(world); - /* If table does not have destructors, just update entity index */ - } else { - if (is_delete) { - for (i = row; i < end; i ++) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - flecs_entities_remove(world, e); - ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - } - } else { - for (i = row; i < end; i ++) { - ecs_entity_t e = entities[i]; - ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); - ecs_record_t *record = flecs_entities_get(world, e); - record->table = NULL; - record->row = record->row & ECS_ROW_FLAGS_MASK; - (void)e; - } - } + ecs_record_t *r = flecs_entities_get(world, entity); + if (r) { + return r; } +error: + return NULL; } -#define FLECS_LOCKED_STORAGE_MSG \ - "to fix, defer operations with defer_begin/defer_end" +ecs_table_records_t flecs_table_records( + ecs_table_t* table) +{ + return (ecs_table_records_t){ + .array = table->_->records, + .count = table->_->record_count + }; +} + +/** + * @file storage/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 component 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. + */ + -/* Cleanup table storage */ static -void flecs_table_fini_data( - ecs_world_t *world, - ecs_table_t *table, - bool do_on_remove, - bool is_delete, - bool deallocate) +void flecs_table_cache_list_remove( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) { - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_table_cache_hdr_t *next = elem->next; + ecs_table_cache_hdr_t *prev = elem->prev; - if (do_on_remove) { - flecs_table_notify_on_remove(world, table); + if (next) { + next->prev = prev; } - - int32_t count = ecs_table_count(table); - if (count) { - flecs_table_dtor_all(world, table, 0, count, is_delete); + if (prev) { + prev->next = next; } - if (deallocate) { - ecs_column_t *columns = table->data.columns; - if (columns) { - int32_t c, column_count = table->column_count; - for (c = 0; c < column_count; c ++) { - ecs_column_t *column = &columns[c]; - ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); - ecs_vec_fini(&world->allocator, &v, column->ti->size); - column->data = NULL; - } - - flecs_wfree_n(world, ecs_column_t, table->column_count, columns); - table->data.columns = NULL; - } - } + cache->tables.count --; - ecs_table__t *meta = table->_; - ecs_bitset_t *bs_columns = meta->bs_columns; - if (bs_columns) { - int32_t c, column_count = meta->bs_count; - if (deallocate) { - for (c = 0; c < column_count; c ++) { - flecs_bitset_fini(&bs_columns[c]); - } - } - else { - for (c = 0; c < column_count; c++) { - bs_columns[c].count = 0; - } - } - - if (deallocate) { - flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); - meta->bs_columns = NULL; - } + if (cache->tables.first == elem) { + cache->tables.first = next; } - - if (deallocate) { - ecs_vec_t v = ecs_vec_from_entities(table); - ecs_vec_fini_t(&world->allocator, &v, ecs_entity_t); - table->data.entities = NULL; - table->data.size = 0; + if (cache->tables.last == elem) { + cache->tables.last = prev; } - table->data.count = 0; - table->_->traversable_count = 0; - table->flags &= ~EcsTableHasTraversable; + ecs_assert(cache->tables.first == NULL || cache->tables.count, + ECS_INTERNAL_ERROR, NULL); + ecs_assert(cache->tables.first == NULL || cache->tables.last != NULL, + ECS_INTERNAL_ERROR, NULL); } -const ecs_entity_t* ecs_table_entities( - const ecs_table_t *table) +static +void flecs_table_cache_list_insert( + ecs_table_cache_t *cache, + ecs_table_cache_hdr_t *elem) { - return table->data.entities; -} + ecs_table_cache_hdr_t *last = cache->tables.last; + cache->tables.last = elem; + if ((++ cache->tables.count) == 1) { + cache->tables.first = elem; + } -/* Cleanup, no OnRemove, delete from entity index, deactivate table, retain allocations */ -void ecs_table_clear_entities( - ecs_world_t* world, - ecs_table_t* table) -{ - flecs_table_fini_data(world, table, true, true, false); -} + elem->next = NULL; + elem->prev = last; -/* Cleanup, no OnRemove, clear entity index, deactivate table, free allocations */ -void flecs_table_clear_entities_silent( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_table_fini_data(world, table, false, false, true); -} + if (last) { + last->next = elem; + } -/* Cleanup, run OnRemove, clear entity index, deactivate table, free allocations */ -void flecs_table_clear_entities( - ecs_world_t *world, - ecs_table_t *table) -{ - flecs_table_fini_data(world, table, true, false, true); + ecs_assert( + cache->tables.count != 1 || cache->tables.first == cache->tables.last, + ECS_INTERNAL_ERROR, NULL); } -/* Cleanup, run OnRemove, delete from entity index, deactivate table, free allocations */ -void flecs_table_delete_entities( +void ecs_table_cache_init( ecs_world_t *world, - ecs_table_t *table) + ecs_table_cache_t *cache) { - flecs_table_fini_data(world, table, true, true, true); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_init_w_params(&cache->index, &world->allocators.ptr); } -/* Unset all components in table. This function is called before a table is - * deleted, and invokes all OnRemove handlers, if any */ -void flecs_table_remove_actions( - ecs_world_t *world, - ecs_table_t *table) +void ecs_table_cache_fini( + ecs_table_cache_t *cache) { - (void)world; - flecs_table_notify_on_remove(world, table); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_map_fini(&cache->index); } -/* Free table resources. */ -void flecs_table_fini( - ecs_world_t *world, - ecs_table_t *table) +void ecs_table_cache_insert( + ecs_table_cache_t *cache, + const ecs_table_t *table, + ecs_table_cache_hdr_t *result) { - flecs_poly_assert(world, ecs_world_t); - - bool is_root = table == &world->store.root; - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - 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_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_table_cache_get(cache, table) == NULL, ECS_INTERNAL_ERROR, NULL); - (void)world; + ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_os_perf_trace_push("flecs.table.free"); + result->cache = cache; + result->table = ECS_CONST_CAST(ecs_table_t*, table); - if (!is_root && !(world->flags & EcsWorldQuit)) { - if (table->flags & EcsTableHasOnTableDelete) { - flecs_table_emit(world, table, EcsOnTableDelete); - } - } + flecs_table_cache_list_insert(cache, result); - 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(); + if (table) { + ecs_map_insert_ptr(&cache->index, table->id, result); } - /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ - flecs_table_fini_data(world, table, false, true, true); - flecs_table_clear_edges(world, table); - - if (!is_root) { - ecs_type_t ids = { - .array = table->type.array, - .count = table->type.count - }; - - flecs_hashmap_remove_w_hash( - &world->store.table_map, &ids, ecs_table_t*, table->_->hash); - } + ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); +} - flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); - flecs_wfree_n(world, int16_t, table->column_count + table->type.count, - table->column_map); - flecs_wfree_n(world, int16_t, FLECS_HI_COMPONENT_ID, table->component_map); - flecs_table_records_unregister(world, table); +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); - /* Update counters */ - world->info.table_count --; - world->info.table_delete_total ++; + ecs_table_cache_hdr_t *old = *r; + ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); - flecs_free_t(&world->allocator, ecs_table__t, table->_); + 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 (!(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); + if (cache->tables.first == old) { + cache->tables.first = elem; + } + if (cache->tables.last == old) { + cache->tables.last = elem; } - ecs_log_pop_2(); + *r = elem; + elem->prev = prev; + elem->next = next; +} - ecs_os_perf_trace_pop("flecs.table.free"); +void* ecs_table_cache_get( + const ecs_table_cache_t *cache, + const ecs_table_t *table) +{ + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + if (table) { + if (ecs_map_is_init(&cache->index)) { + return ecs_map_get_deref(&cache->index, void**, table->id); + } + return NULL; + } else { + ecs_table_cache_hdr_t *elem = cache->tables.first; + ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); + return elem; + } } -/* 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) +void* ecs_table_cache_remove( + ecs_table_cache_t *cache, + uint64_t table_id, + ecs_table_cache_hdr_t *elem) { - flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); + + flecs_table_cache_list_remove(cache, elem); + ecs_map_remove(&cache->index, table_id); + + return elem; } -/* Reset a table to its initial state. */ -void flecs_table_reset( - ecs_world_t *world, - ecs_table_t *table) +bool flecs_table_cache_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) { - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - flecs_table_clear_edges(world, table); + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->cur = NULL; + out->iter_fill = true; + out->iter_empty = false; + return out->next != NULL; } -/* 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) +bool flecs_table_cache_empty_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) { - 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; - } + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->cur = NULL; + out->iter_fill = false; + out->iter_empty = true; + return out->next != NULL; } -/* Mark table column dirty. This usually happens as the result of a set - * operation, or iteration of a query with [out] fields. */ -static -void flecs_table_mark_table_dirty( - ecs_world_t *world, - ecs_table_t *table, - int32_t index) +bool flecs_table_cache_all_iter( + const ecs_table_cache_t *cache, + ecs_table_cache_iter_t *out) { - (void)world; - if (table->dirty_state) { - table->dirty_state[index] ++; - } + ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); + out->next = cache->tables.first; + out->cur = NULL; + out->iter_fill = true; + out->iter_empty = true; + return out->next != NULL; } -/* Mark table component dirty */ -void flecs_table_mark_dirty( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t component) +const ecs_table_cache_hdr_t* flecs_table_cache_next_( + ecs_table_cache_iter_t *it) { - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_cache_hdr_t *next; + +repeat: + next = it->next; + it->cur = next; - if (table->dirty_state) { - int32_t column; - if (component < FLECS_HI_COMPONENT_ID) { - column = table->component_map[component]; - if (column <= 0) { - return; + if (next) { + it->next = next->next; + + if (ecs_table_count(next->table)) { + if (!it->iter_fill) { + goto repeat; } } else { - ecs_id_record_t *idr = flecs_id_record_get(world, component); - if (!idr) { - return; - } - - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr || tr->column == -1) { - return; + if (!it->iter_empty) { + goto repeat; } - - column = tr->column + 1; } - - /* Column is offset by 1, 0 is reserved for entity column. */ - - table->dirty_state[column] ++; } -} -/* 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) -{ - flecs_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; + return next; } -/* 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; - } +/** + * @file storage/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]. + */ - int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; - int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; - if (!src_column_count && !dst_column_count) { - return; - } +/* Id sequence (type) utilities */ - 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 +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)); +} - ecs_type_t dst_type = dst_table->type; - ecs_type_t src_type = src_table->type; +static +int flecs_type_compare(const void *ptr_1, const void *ptr_2) { + const ecs_type_t *type_1 = ptr_1; + const ecs_type_t *type_2 = ptr_2; - int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; - int32_t offset_old = src_meta ? src_meta->bs_offset : 0; + int32_t count_1 = type_1->count; + int32_t count_2 = type_2->count; - ecs_id_t *dst_ids = dst_type.array; - ecs_id_t *src_ids = src_type.array; + if (count_1 != count_2) { + return (count_1 > count_2) - (count_1 < count_2); + } - 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]; + 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 (dst_id == src_id) { - ecs_bitset_t *src_bs = &src_columns[i_old]; - ecs_bitset_t *dst_bs = &dst_columns[i_new]; + return result; +} - flecs_bitset_ensure(dst_bs, dst_index + count); +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); +} - 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); - } +/* 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; - 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); + for (i = offset; i < count; i ++) { + ecs_id_t id = array[i]; + if (id == to_add) { + return -1; } - - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; - } - - /* Clear remaining columns */ - if (clear) { - for (; (i_old < src_column_count); i_old ++) { - ecs_bitset_t *src_bs = &src_columns[i_old]; - ecs_assert(count == flecs_bitset_count(src_bs), - ECS_INTERNAL_ERROR, NULL); - flecs_bitset_fini(src_bs); + if (id > to_add) { + return i; } } + return i; } -/* Grow table column. When a column needs to be reallocated this function takes - * care of correctly invoking ctor/move/dtor hooks. */ +/* Find location of id in type */ static -void flecs_table_grow_column( - ecs_world_t *world, - ecs_vec_t *column, - const ecs_type_info_t *ti, - int32_t to_add, - int32_t dst_size, - bool construct) +int flecs_type_find( + const ecs_type_t *type, + ecs_id_t id) { - ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t count = ecs_vec_count(column); - int32_t size = ecs_vec_size(column); - int32_t elem_size = ti->size; - int32_t dst_count = count + to_add; - bool can_realloc = dst_size != size; - void *result = NULL; - - ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); - - /* If the array could possibly realloc and the component has a move action - * defined, move old elements manually */ - ecs_move_t move_ctor; - if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { - ecs_xtor_t ctor = ti->hooks.ctor; - ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Create vector */ - ecs_vec_t dst; - ecs_vec_init(&world->allocator, &dst, elem_size, dst_size); - dst.count = dst_count; - - void *src_buffer = column->array; - void *dst_buffer = dst.array; - - /* Move (and construct) existing elements to new vector */ - move_ctor(dst_buffer, src_buffer, count, ti); + ecs_id_t *array = type->array; + int32_t i, count = type->count; - if (construct) { - /* Construct new element(s) */ - result = ECS_ELEM(dst_buffer, elem_size, count); - ctor(result, to_add, ti); + for (i = 0; i < count; i ++) { + ecs_id_t cur = array[i]; + if (ecs_id_match(cur, id)) { + return i; } - - /* Free old vector */ - ecs_vec_fini(&world->allocator, column, elem_size); - - *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, elem_size, dst_size); + if (cur > id) { + return -1; } + } - result = ecs_vec_grow(&world->allocator, column, elem_size, to_add); + return -1; +} - 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); +/* Count number of matching ids */ +static +int flecs_type_count_matches( + const ecs_type_t *type, + ecs_id_t wildcard, + int32_t offset) +{ + ecs_id_t *array = type->array; + int32_t i = offset, count = type->count; + + for (; i < count; i ++) { + ecs_id_t cur = array[i]; + if (!ecs_id_match(cur, wildcard)) { + break; } } - ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); + return i - offset; } -/* Grow all data structures in a table */ +/* Create type from source type with id */ static -int32_t flecs_table_grow_data( +int flecs_type_new_with( ecs_world_t *world, - ecs_table_t *table, - int32_t to_add, - int32_t size, - const ecs_entity_t *ids) + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t with) { - flecs_poly_assert(world, ecs_world_t); - - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *src_array = src->array; + int32_t at = flecs_type_find_insert(src, 0, with); + if (at == -1) { + return -1; + } - int32_t count = ecs_table_count(table); - int32_t column_count = table->column_count; + 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; - /* Add entity to column with entity ids */ - ecs_vec_t v_entities = ecs_vec_from_entities(table); - ecs_vec_set_size_t(&world->allocator, &v_entities, ecs_entity_t, size); + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); + } - ecs_entity_t *e = ecs_vec_last_t(&v_entities, ecs_entity_t) + 1; - v_entities.count += to_add; - if (v_entities.size > size) { - size = v_entities.size; + int32_t remain = src->count - at; + if (remain) { + ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } - /* Update table entities/count/size */ - int32_t prev_count = table->data.count, prev_size = table->data.size; - table->data.entities = v_entities.array; - table->data.count = v_entities.count; - table->data.size = v_entities.size; + dst_array[at] = with; - /* 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); - } + return 0; +} - /* Add elements to each column array */ - ecs_column_t *columns = table->data.columns; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - const ecs_type_info_t *ti = column->ti; - ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); - flecs_table_grow_column(world, &v_column, ti, to_add, size, true); - ecs_assert(v_column.size == size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(v_column.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); - column->data = v_column.array; - flecs_table_invoke_add_hooks(world, table, column, e, - count, to_add, false); +/* 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); } - ecs_table__t *meta = table->_; - int32_t bs_count = meta->bs_count; - ecs_bitset_t *bs_columns = meta->bs_columns; - - /* 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); + 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 ++; + } } - /* If the table is monitored indicate that there has been a change */ - flecs_table_mark_table_dirty(world, table, 0); + dst->count = w; + if (w != count) { + dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); + } - /* Return index of first added entity */ - return count; + return 0; } -/* Append operation for tables that don't have any complex logic */ +/* Create type from source type without id */ static -void flecs_table_fast_append( +int flecs_type_new_without( ecs_world_t *world, - ecs_table_t *table) + ecs_type_t *dst, + const ecs_type_t *src, + ecs_id_t without) { - /* Add elements to each column array */ - ecs_column_t *columns = table->data.columns; - int32_t i, count = table->column_count; - for (i = 0; i < count; i ++) { - ecs_column_t *column = &columns[i]; - const ecs_type_info_t *ti = column->ti; - ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); - ecs_vec_append(&world->allocator, &v, ti->size); - column->data = v.array; + ecs_id_t *src_array = src->array; + int32_t count = 1, at = flecs_type_find(src, without); + if (at == -1) { + return -1; } -} -/* Append entity to table */ -int32_t flecs_table_append( - ecs_world_t *world, - ecs_table_t *table, - ecs_entity_t entity, - bool construct, - bool on_add) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + int32_t src_count = src->count; + if (src_count == 1) { + dst->array = NULL; + dst->count = 0; + return 0; + } - flecs_table_check_sanity(world, table); + 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); + } - /* Get count & size before growing entities array. This tells us whether the - * arrays will realloc */ - int32_t count = ecs_table_count(table); - int32_t column_count = table->column_count; - ecs_column_t *columns = table->data.columns; + int32_t dst_count = src_count - count; + dst->count = dst_count; + if (!dst_count) { + dst->array = NULL; + return 0; + } - /* Grow buffer with entity ids, set new element to new entity */ - ecs_vec_t v_entities = ecs_vec_from_entities(table); - ecs_entity_t *e = ecs_vec_append_t(&world->allocator, - &v_entities, ecs_entity_t); - - ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t *entities = table->data.entities = v_entities.array; - *e = entity; - - /* If the table is monitored indicate that there has been a change */ - flecs_table_mark_table_dirty(world, table, 0); - ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); + dst->array = dst_array; - /* Fast path: no switch columns, no lifecycle actions */ - if (!(table->flags & EcsTableIsComplex)) { - flecs_table_fast_append(world, table); - table->data.count = v_entities.count; - table->data.size = v_entities.size; - return count; + if (at) { + ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } - int32_t prev_count = table->data.count; - int32_t prev_size = table->data.size; + int32_t remain = dst_count - at; + if (remain) { + ecs_os_memcpy_n( + &dst_array[at], &src_array[at + count], ecs_id_t, remain); + } - ecs_assert(table->data.count == v_entities.count - 1, - ECS_INTERNAL_ERROR, NULL); - table->data.count = v_entities.count; - table->data.size = v_entities.size; + return 0; +} - /* 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 = v_entities.size; +/* 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 }; + } - /* Grow component arrays with 1 element */ - int32_t i; - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - const ecs_type_info_t *ti = column->ti; - ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); - flecs_table_grow_column(world, &v_column, ti, 1, size, construct); - column->data = v_column.array; + 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 + }; +} - 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); - } +/* 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_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); - ecs_assert(v_column.count == v_entities.count, - 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; } +} - ecs_table__t *meta = table->_; - int32_t bs_count = meta->bs_count; - ecs_bitset_t *bs_columns = meta->bs_columns; +/* Graph edge utilities */ - /* 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); - } +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); + builder->added_flags = 0; + builder->removed_flags = 0; +} - flecs_table_check_sanity(world, table); +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); +} - return count; +void flecs_table_diff_builder_clear( + ecs_table_diff_builder_t *builder) +{ + ecs_vec_clear(&builder->added); + ecs_vec_clear(&builder->removed); } -/* Delete operation for tables that don't have any complex logic */ static -void flecs_table_fast_delete( - ecs_table_t *table, - int32_t row) +void flecs_table_diff_build_type( + ecs_world_t *world, + ecs_vec_t *vec, + ecs_type_t *type, + int32_t offset) { - ecs_column_t *columns = table->data.columns; - int32_t i, count = table->column_count; - for (i = 0; i < count; i ++) { - ecs_column_t *column = &columns[i]; - const ecs_type_info_t *ti = column->ti; - ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); - ecs_vec_remove(&v, ti->size, row); - column->data = v.array; + 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); } } -/* Delete entity from table */ -void flecs_table_delete( +void flecs_table_diff_build( ecs_world_t *world, - ecs_table_t *table, - int32_t row, - bool destruct) + ecs_table_diff_builder_t *builder, + ecs_table_diff_t *diff, + int32_t added_offset, + int32_t removed_offset) { - ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - - flecs_table_check_sanity(world, table); - - int32_t count = ecs_table_count(table); - ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); - count --; - ecs_assert(row <= count, ECS_INTERNAL_ERROR, NULL); - - /* Move last entity id to row */ - ecs_entity_t *entities = table->data.entities; - ecs_entity_t entity_to_move = entities[count]; - ecs_entity_t entity_to_delete = entities[row]; - entities[row] = entity_to_move; - - /* Update record of moved entity in entity index */ - if (row != count) { - ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); - if (record_to_move) { - uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; - record_to_move->row = ECS_ROW_TO_RECORD(row, row_flags); - ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); - } - } - - /* If the table is monitored indicate that there has been a change */ - flecs_table_mark_table_dirty(world, table, 0); - - /* Destruct component data */ - ecs_column_t *columns = table->data.columns; - int32_t column_count = table->column_count; - int32_t i; - - /* If this is a table without lifecycle callbacks or special columns, take - * fast path that just remove an element from the array(s) */ - if (!(table->flags & EcsTableIsComplex)) { - if (row != count) { - flecs_table_fast_delete(table, row); - } + flecs_table_diff_build_type(world, &builder->added, &diff->added, + added_offset); + flecs_table_diff_build_type(world, &builder->removed, &diff->removed, + removed_offset); + diff->added_flags = builder->added_flags; + diff->removed_flags = builder->removed_flags; +} - table->data.count --; +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 }; + diff->added_flags = builder->added_flags; + diff->removed_flags = builder->removed_flags; +} - flecs_table_check_sanity(world, table); +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; } - /* Last element, destruct & remove */ - if (row == 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, row, 1, true); - } - } - - /* Not last element, move last element to deleted element & destruct */ - } else { - /* If table has component destructors, invoke */ - if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { - for (i = 0; i < column_count; i ++) { - ecs_column_t *column = &columns[i]; - ecs_type_info_t *ti = column->ti; - ecs_size_t size = ti->size; - void *dst = ECS_ELEM(column->data, size, row); - void *src = ECS_ELEM(column->data, size, count); + 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); +} - 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, row, 1); - } +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); + dst->added_flags |= src->added_flags; + dst->removed_flags |= src->removed_flags; +} - ecs_move_t move_dtor = ti->hooks.move_dtor; - - /* If neither move nor move_ctor are set, this indicates that - * non-destructive move semantics are not supported for this - * type. In such cases, we set the move_dtor as ctor_move_dtor, - * which indicates a destructive move operation. This adjustment - * ensures compatibility with different language bindings. */ - if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { - move_dtor = ti->hooks.ctor_move_dtor; - } +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); +} - if (move_dtor) { - move_dtor(dst, src, 1, ti); - } else { - ecs_os_memcpy(dst, src, size); - } - } - } else { - flecs_table_fast_delete(table, row); - } +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); } - /* Remove elements from bitset columns */ - ecs_table__t *meta = table->_; - 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], row); + 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; } - table->data.count --; + if (id < FLECS_HI_COMPONENT_ID) { + edge = &edges->lo[id]; + } else { + edge = flecs_bcalloc(&world->allocators.graph_edge); + } - flecs_table_check_sanity(world, table); + r[0] = edge; + return edge; } -/* 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) +ecs_graph_edge_t* flecs_table_ensure_edge( + ecs_world_t *world, + ecs_graph_edges_t *edges, + ecs_id_t id) { - int32_t i_new = 0, dst_column_count = dst_table->column_count; - int32_t i_old = 0, src_column_count = src_table->column_count; - - ecs_column_t *src_columns = src_table->data.columns; - ecs_column_t *dst_columns = dst_table->data.columns; - - for (; (i_new < dst_column_count) && (i_old < src_column_count);) { - ecs_column_t *dst_column = &dst_columns[i_new]; - ecs_column_t *src_column = &src_columns[i_old]; - ecs_id_t dst_id = flecs_column_id(dst_table, i_new); - ecs_id_t src_id = flecs_column_id(src_table, i_old); - - if (dst_id == src_id) { - int32_t size = dst_column->ti->size; - void *dst = ECS_ELEM(dst_column->data, size, dst_index); - void *src = ECS_ELEM(src_column->data, size, src_index); - ecs_os_memcpy(dst, src, size); + ecs_graph_edge_t *edge; + + if (id < FLECS_HI_COMPONENT_ID) { + if (!edges->lo) { + edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); } - - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; + edge = &edges->lo[id]; + } else { + edge = flecs_table_ensure_hi_edge(world, edges, id); } + + return edge; } -/* Move entity from src to dst table */ -void flecs_table_move( +static +void flecs_table_disconnect_edge( ecs_world_t *world, - ecs_entity_t dst_entity, - ecs_entity_t src_entity, - ecs_table_t *dst_table, - int32_t dst_index, - ecs_table_t *src_table, - int32_t src_index, - bool construct) + ecs_id_t id, + ecs_graph_edge_t *edge) { - 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, FLECS_LOCKED_STORAGE_MSG); - ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); + (void)id; - ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); + /* Remove backref from destination table */ + ecs_graph_edge_hdr_t *next = edge->hdr.next; + ecs_graph_edge_hdr_t *prev = edge->hdr.prev; - flecs_table_check_sanity(world, dst_table); - flecs_table_check_sanity(world, src_table); + if (next) { + next->prev = prev; + } + if (prev) { + prev->next = next; + } - if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { - flecs_table_fast_move(dst_table, dst_index, src_table, src_index); - flecs_table_check_sanity(world, dst_table); - flecs_table_check_sanity(world, src_table); - return; + /* Remove data associated with edge */ + ecs_table_diff_t *diff = edge->diff; + if (diff) { + flecs_table_diff_free(world, diff); } - flecs_table_move_bitset_columns( - dst_table, dst_index, src_table, src_index, 1, false); + /* 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); + } +} - /* 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; +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); +} - /* 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); +static +void flecs_table_init_edges( + ecs_graph_edges_t *edges) +{ + edges->lo = NULL; + edges->hi = NULL; +} - 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 +void flecs_table_init_node( + ecs_graph_node_t *node) +{ + flecs_table_init_edges(&node->add); + flecs_table_init_edges(&node->remove); +} - ecs_column_t *src_columns = src_table->data.columns; - ecs_column_t *dst_columns = dst_table->data.columns; +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; - 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 = flecs_column_id(dst_table, i_new); - ecs_id_t src_id = flecs_column_id(src_table, i_old); + flecs_table_init_node(&table->node); - if (dst_id == src_id) { - ecs_type_info_t *ti = dst_column->ti; - int32_t size = ti->size; + flecs_table_init(world, table, prev); +} - ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); - void *dst = ECS_ELEM(dst_column->data, size, dst_index); - void *src = ECS_ELEM(src_column->data, size, src_index); +static +ecs_table_t *flecs_table_new( + ecs_world_t *world, + ecs_type_t *type, + flecs_hashmap_result_t table_elem, + ecs_table_t *prev) +{ + ecs_os_perf_trace_push("flecs.table.create"); - 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_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); - 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); +#ifdef FLECS_SANITIZE + int32_t i, j, count = type->count; + for (i = 0; i < count - 1; i ++) { + if (type->array[i] >= type->array[i + 1]) { + for (j = 0; j < count; j ++) { + char *str = ecs_id_str(world, type->array[j]); + if (i == j) { + ecs_err(" > %d: %s", j, str); } else { - ecs_os_memcpy(dst, src, size); + ecs_err(" %d: %s", j, str); } + ecs_os_free(str); } - } else { - if (dst_id < src_id) { - flecs_table_invoke_add_hooks(world, dst_table, - dst_column, &dst_entity, dst_index, 1, construct); - } else if (same_entity) { - flecs_table_invoke_remove_hooks(world, src_table, - src_column, &src_entity, src_index, 1, use_move_dtor); - } + ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); } - - i_new += dst_id <= src_id; - i_old += dst_id >= src_id; } +#endif - 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); - } + result->id = flecs_sparse_last_id(&world->store.tables); + result->type = *type; - if (same_entity) { - 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); - } + 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); } - flecs_table_check_sanity(world, dst_table); - flecs_table_check_sanity(world, src_table); -} + ecs_log_push_2(); -/* Append n entities to table */ -int32_t flecs_table_appendn( - ecs_world_t *world, - ecs_table_t *table, - int32_t to_add, - const ecs_entity_t *ids) -{ - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); + /* Store table in table hashmap */ + *(ecs_table_t**)table_elem.value = result; - flecs_table_check_sanity(world, table); - int32_t cur_count = ecs_table_count(table); - int32_t result = flecs_table_grow_data( - world, table, to_add, cur_count + to_add, ids); - flecs_table_check_sanity(world, table); + /* Set keyvalue to one that has the same lifecycle as the table */ + *(ecs_type_t*)table_elem.key = result->type; + result->_->hash = table_elem.hash; + + flecs_init_table(world, result, prev); + + /* Update counters */ + world->info.table_count ++; + world->info.table_create_total ++; + + ecs_log_pop_2(); + + ecs_os_perf_trace_pop("flecs.table.create"); return result; } -/* Shrink table storage to fit number of entities */ -bool flecs_table_shrink( +static +ecs_table_t* flecs_table_ensure( ecs_world_t *world, - ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - (void)world; - - flecs_table_check_sanity(world, table); + ecs_type_t *type, + bool own_type, + ecs_table_t *prev) +{ + flecs_poly_assert(world, ecs_world_t); - bool has_payload = table->data.entities != NULL; - ecs_vec_t v_entities = ecs_vec_from_entities(table); - ecs_vec_reclaim_t(&world->allocator, &v_entities, ecs_entity_t); + int32_t id_count = type->count; + if (!id_count) { + return &world->store.root; + } - int32_t i, count = table->column_count; - for (i = 0; i < count; i ++) { - ecs_column_t *column = &table->data.columns[i]; - const ecs_type_info_t *ti = column->ti; - ecs_vec_t v_column = ecs_vec_from_column(column, table, ti->size); - ecs_vec_reclaim(&world->allocator, &v_column, ti->size); - column->data = v_column.array; + 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; } - table->data.count = v_entities.count; - table->data.size = v_entities.size; - table->data.entities = v_entities.array; + /* 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); - flecs_table_check_sanity(world, table); + /* If we get here, the table has not been found, so create it. */ + if (own_type) { + return flecs_table_new(world, type, elem, prev); + } - return has_payload; + ecs_type_t copy = flecs_type_copy(world, type); + return flecs_table_new(world, ©, elem, prev); } -/* 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) +void flecs_diff_insert_added( + ecs_world_t *world, + ecs_table_diff_builder_t *diff, + ecs_id_t id) { - 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); - } + ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; } -/* Swap two rows in a table. Used for table sorting. */ -void flecs_table_swap( +static +void flecs_diff_insert_removed( ecs_world_t *world, - ecs_table_t *table, - int32_t row_1, - int32_t row_2) -{ - (void)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; +} - ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); +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) +{ + ecs_type_t node_type = node->type; + ecs_type_t next_type = next->type; - flecs_table_check_sanity(world, table); - - if (row_1 == row_2) { - return; + if (ECS_IS_PAIR(id)) { + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair( + ECS_PAIR_FIRST(id), EcsWildcard)); + if (cdr->flags & EcsIdIsUnion) { + if (node != next) { + id = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); + } 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); + diff->added_flags = EcsTableHasUnion; + edge->diff = diff; + return; + } + } } - /* If the table is monitored indicate that there has been a change */ - flecs_table_mark_table_dirty(world, table, 0); + 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; + ecs_flags32_t added_flags = 0, removed_flags = 0; + bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); - ecs_entity_t *entities = table->data.entities; - ecs_entity_t e1 = entities[row_1]; - ecs_entity_t e2 = entities[row_2]; + /* 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]; - ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); - ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); + bool added = id_next < id_node; + bool removed = id_node < id_next; - ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); + trivial_edge &= !added || id_next == id; + trivial_edge &= !removed || id_node == id; - /* 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); + if (added) { + added_flags |= flecs_id_flags_get(world, id_next) & + EcsTableAddEdgeFlags; + added_count ++; + } - /* 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); + if (removed) { + removed_flags |= flecs_id_flags_get(world, id_node) & + EcsTableRemoveEdgeFlags; + removed_count ++; + } - flecs_table_swap_bitset_columns(table, row_1, row_2); + i_node += id_node <= id_next; + i_next += id_next <= id_node; + } - ecs_column_t *columns = table->data.columns; - if (!columns) { - flecs_table_check_sanity(world, table); - return; + for (; i_next < next_count; i_next ++) { + added_flags |= flecs_id_flags_get(world, ids_next[i_next]) & + EcsTableAddEdgeFlags; + added_count ++; } - /* 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].ti->size); + for (; i_node < node_count; i_node ++) { + removed_flags |= flecs_id_flags_get(world, ids_node[i_node]) & + EcsTableRemoveEdgeFlags; + removed_count ++; } - void* tmp = ecs_os_alloca(temp_buffer_size); + trivial_edge &= (added_count + removed_count) <= 1 && + !ecs_id_is_wildcard(id) && !(added_flags|removed_flags); - /* Swap columns */ - for (i = 0; i < column_count; i ++) { - int32_t size = columns[i].ti->size; - ecs_column_t *column = &columns[i]; - void *ptr = column->data; - const ecs_type_info_t *ti = column->ti; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + if (trivial_edge) { + /* If edge is trivial there's no need to create a diff element for it */ + return; + } - void *el_1 = ECS_ELEM(ptr, size, row_1); - void *el_2 = ECS_ELEM(ptr, size, row_2); + ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; + int32_t added_offset = builder->added.count; + int32_t removed_offset = builder->removed.count; - ecs_move_t move = ti->hooks.move; - if (!move) { - ecs_os_memcpy(tmp, el_1, size); - ecs_os_memcpy(el_1, el_2, size); - ecs_os_memcpy(el_2, tmp, size); - } else { - ecs_move_t move_ctor = ti->hooks.move_ctor; - ecs_move_t move_dtor = ti->hooks.move_dtor; - ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); - move_ctor(tmp, el_1, 1, ti); - move(el_1, el_2, 1, ti); - move_dtor(el_2, tmp, 1, ti); + for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { + ecs_id_t id_node = ids_node[i_node]; + ecs_id_t id_next = ids_next[i_next]; + + if (id_next < id_node) { + flecs_diff_insert_added(world, builder, id_next); + } else if (id_node < id_next) { + flecs_diff_insert_removed(world, builder, id_node); } + + i_node += id_node <= id_next; + i_next += id_next <= id_node; } - flecs_table_check_sanity(world, table); + for (; i_next < next_count; i_next ++) { + flecs_diff_insert_added(world, builder, ids_next[i_next]); + } + for (; i_node < node_count; i_node ++) { + flecs_diff_insert_removed(world, builder, ids_node[i_node]); + } + + ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff = diff; + flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); + diff->added_flags = added_flags; + diff->removed_flags = removed_flags; + + ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); + ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } static -void flecs_table_merge_vec( +void flecs_add_overrides_for_base( ecs_world_t *world, - ecs_vec_t *dst, - ecs_vec_t *src, - int32_t size, - int32_t elem_size) + ecs_type_t *dst_type, + ecs_id_t pair) { - int32_t dst_count = dst->count; + ecs_entity_t base = ecs_pair_second(world, pair); + ecs_assert(base != 0, ECS_INVALID_PARAMETER, + "target of IsA pair is not alive"); + ecs_table_t *base_table = ecs_get_table(world, base); + if (!base_table) { + return; + } - 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; + ecs_id_t *ids = base_table->type.array; + ecs_flags32_t flags = base_table->flags; + if (flags & EcsTableHasOverrides) { + int32_t i, count = base_table->type.count; + for (i = 0; i < count; i ++) { + ecs_id_t id = ids[i]; + ecs_id_t to_add = 0; + if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { + to_add = id & ~ECS_AUTO_OVERRIDE; + } else { + ecs_table_record_t *tr = &base_table->_->records[i]; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; + if (ECS_ID_ON_INSTANTIATE(cdr->flags) == EcsOverride) { + to_add = id; + } + } - if (elem_size) { - ecs_vec_set_size(&world->allocator, - dst, size, elem_size); + 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_component_record_t *cdr = flecs_components_get(world, wc); + if (cdr) { + exclusive = (cdr->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; + } + } + } } - ecs_vec_set_count(&world->allocator, - dst, size, dst_count + src_count); - - void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); - void *src_ptr = src->array; - ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); + } - ecs_vec_fini(&world->allocator, src, size); + if (flags & EcsTableHasIsA) { + const ecs_table_record_t *tr = flecs_component_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]); + } } } -/* Merge data from one table column into other table column */ static -void flecs_table_merge_column( +void flecs_add_with_property( ecs_world_t *world, - ecs_vec_t *dst_vec, - ecs_vec_t *src_vec, - ecs_column_t *dst, - ecs_column_t *src, - int32_t column_size) + ecs_component_record_t *idr_with_wildcard, + ecs_type_t *dst_type, + ecs_entity_t r, + ecs_entity_t o) { - const ecs_type_info_t *ti = dst->ti; - ecs_assert(ti == src->ti, ECS_INTERNAL_ERROR, NULL); - ecs_size_t elem_size = ti->size; - int32_t dst_count = ecs_vec_count(dst_vec); - - if (!dst_count) { - ecs_vec_fini(&world->allocator, dst_vec, elem_size); - *dst_vec = *src_vec; + r = ecs_get_alive(world, r); - /* If the new table is not empty, copy the contents from the - * src into the dst. */ - } else { - int32_t src_count = src_vec->count; + /* 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); + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); + + const ecs_table_record_t *tr = flecs_component_get_table( + idr_with_wildcard, table); + if (tr) { + int32_t i = tr->index, end = i + tr->count; + ecs_id_t *ids = table->type.array; - flecs_table_grow_column(world, dst_vec, ti, src_count, column_size, false); - void *dst_ptr = ECS_ELEM(dst_vec->array, elem_size, dst_count); - void *src_ptr = src_vec->array; + for (; i < end; i ++) { + ecs_id_t id = ids[i]; + ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); + ecs_id_t ra = ECS_PAIR_SECOND(id); + ecs_id_t a = ra; + if (o) { + a = ecs_pair(ra, o); + } - /* Move values into column */ - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_move_t move = ti->hooks.ctor_move_dtor; - if (move) { - move(dst_ptr, src_ptr, src_count, ti); - } else { - ecs_os_memcpy(dst_ptr, src_ptr, elem_size * src_count); + flecs_type_add(world, dst_type, a); + flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); } - - ecs_vec_fini(&world->allocator, src_vec, elem_size); } - dst->data = dst_vec->array; - src->data = NULL; } -/* Merge storage of two tables. */ static -void flecs_table_merge_data( +ecs_table_t* flecs_find_table_with( ecs_world_t *world, - ecs_table_t *dst_table, - ecs_table_t *src_table, - int32_t dst_count, - int32_t src_count) + ecs_table_t *node, + ecs_id_t with) { - int32_t i_new = 0, dst_column_count = dst_table->column_count; - int32_t i_old = 0, src_column_count = src_table->column_count; - ecs_column_t *src_columns = src_table->data.columns; - ecs_column_t *dst_columns = dst_table->data.columns; - - ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); - - if (!src_count) { - return; - } - - /* Merge entities */ - ecs_vec_t dst_entities = ecs_vec_from_entities(dst_table); - ecs_vec_t src_entities = ecs_vec_from_entities(src_table); - flecs_table_merge_vec(world, &dst_entities, &src_entities, - ECS_SIZEOF(ecs_entity_t), 0); - ecs_assert(dst_entities.count == src_count + dst_count, - ECS_INTERNAL_ERROR, NULL); - int32_t column_size = dst_entities.size; - ecs_allocator_t *a = &world->allocator; + ecs_make_alive_id(world, with); - 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 = flecs_column_id(dst_table, i_new); - ecs_id_t src_id = flecs_column_id(src_table, i_old); - ecs_size_t dst_elem_size = dst_column->ti->size; - ecs_size_t src_elem_size = src_column->ti->size; - - ecs_vec_t dst_vec = ecs_vec_from_column( - dst_column, dst_table, dst_elem_size); - ecs_vec_t src_vec = ecs_vec_from_column( - src_column, src_table, src_elem_size); + ecs_component_record_t *cdr = NULL; + ecs_entity_t r = 0, o = 0; - if (dst_id == src_id) { - flecs_table_merge_column(world, &dst_vec, &src_vec, 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_vec_set_size(a, &dst_vec, dst_elem_size, column_size); - dst_column->data = dst_vec.array; - 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_vec, src_elem_size); - src_column->data = NULL; - i_old ++; + if (ECS_IS_PAIR(with)) { + r = ECS_PAIR_FIRST(with); + o = ECS_PAIR_SECOND(with); + cdr = flecs_components_ensure(world, ecs_pair(r, EcsWildcard)); + if (cdr->flags & EcsIdIsUnion) { + with = ecs_pair(r, EcsUnion); + } else if (cdr->flags & EcsIdExclusive) { + /* Relationship is exclusive, check if table already has it */ + const ecs_table_record_t *tr = flecs_component_get_table(cdr, 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 { + cdr = flecs_components_ensure(world, with); + r = with; } - flecs_table_move_bitset_columns( - dst_table, dst_count, src_table, 0, src_count, true); - - /* Initialize remaining columns */ - for (; i_new < dst_column_count; i_new ++) { - ecs_column_t *column = &dst_columns[i_new]; - int32_t elem_size = column->ti->size; - ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); - ecs_vec_t vec = ecs_vec_from_column(column, dst_table, elem_size); - ecs_vec_set_size(a, &vec, elem_size, column_size); - column->data = vec.array; - flecs_table_invoke_ctor(column, dst_count, src_count); + /* 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 */ } - /* Destruct remaining columns */ - for (; i_old < src_column_count; i_old ++) { - ecs_column_t *column = &src_columns[i_old]; - int32_t elem_size = column->ti->size; - ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); - flecs_table_invoke_dtor(column, 0, src_count); - ecs_vec_t vec = ecs_vec_from_column(column, src_table, elem_size); - ecs_vec_fini(a, &vec, elem_size); - column->data = vec.array; - } - - /* Mark entity column as dirty */ - flecs_table_mark_table_dirty(world, dst_table, 0); - - dst_table->data.entities = dst_entities.array; - dst_table->data.count = dst_entities.count; - dst_table->data.size = dst_entities.size; - - src_table->data.entities = src_entities.array; - src_table->data.count = src_entities.count; - src_table->data.size = src_entities.size; -} - -/* 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_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); - - flecs_table_check_sanity(world, src_table); - flecs_table_check_sanity(world, dst_table); - - const ecs_entity_t *src_entities = ecs_table_entities(src_table); - int32_t src_count = ecs_table_count(src_table); - int32_t dst_count = ecs_table_count(dst_table); - - /* First, update entity index so old entities point to new type */ - int32_t i; - for(i = 0; i < src_count; i ++) { - ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); - record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); - record->table = dst_table; + 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); + } } - - /* Merge table columns */ - flecs_table_merge_data(world, dst_table, src_table, dst_count, src_count); - - if (src_count) { - 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 (cdr->flags & EcsIdWith) { + ecs_component_record_t *idr_with_wildcard = flecs_components_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); } - flecs_table_check_sanity(world, src_table); - flecs_table_check_sanity(world, dst_table); + return flecs_table_ensure(world, &dst_type, true, node); } -/* Internal mechanism for propagating information to tables */ -void flecs_table_notify( +static +ecs_table_t* flecs_find_table_without( ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_table_event_t *event) + ecs_table_t *node, + ecs_id_t without) { - flecs_poly_assert(world, ecs_world_t); - - if (world->flags & EcsWorldFini) { - return; + if (ECS_IS_PAIR(without)) { + ecs_entity_t r = 0; + ecs_component_record_t *cdr = NULL; + r = ECS_PAIR_FIRST(without); + cdr = flecs_components_get(world, ecs_pair(r, EcsWildcard)); + if (cdr && cdr->flags & EcsIdIsUnion) { + without = ecs_pair(r, EcsUnion); + } } - switch(event->kind) { - case EcsTableTriggersForId: - flecs_table_add_trigger_flags(world, table, id, event->event); - break; - case EcsTableNoTriggersForId: - break; /* TODO */ + /* Create sequence with new id */ + ecs_type_t dst_type; + int res = flecs_type_new_without(world, &dst_type, &node->type, without); + if (res == -1) { + return node; /* Current table does not have id */ } + + return flecs_table_ensure(world, &dst_type, true, node); } -int32_t flecs_table_get_toggle_column( +static +void flecs_table_init_edge( ecs_table_t *table, - ecs_id_t id) + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) { - ecs_id_t *ids = table->type.array; - int32_t i = table->_->bs_offset, end = i + table->_->bs_count; - - for (; i < end; i ++) { - if (ids[i] == (ECS_TOGGLE | id)) { - return i; - } - } - - return -1; + 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; } -ecs_bitset_t* flecs_table_get_toggle( +static +void flecs_init_edge_for_add( + ecs_world_t *world, ecs_table_t *table, - ecs_id_t id) + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) { - int32_t toggle_column = flecs_table_get_toggle_column(table, id); - if (toggle_column == -1) { - return NULL; - } + flecs_table_init_edge(table, edge, id, to); - toggle_column -= table->_->bs_offset; - ecs_assert(toggle_column >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(toggle_column < table->_->bs_count, ECS_INTERNAL_ERROR, NULL); - return &table->_->bs_columns[toggle_column]; + flecs_table_ensure_hi_edge(world, &table->node.add, id); + + if ((table != to) || (table->flags & EcsTableHasUnion)) { + /* Add edges are appended to refs.next */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *next = to_refs->next; + + to_refs->next = &edge->hdr; + edge->hdr.prev = to_refs; + + edge->hdr.next = next; + if (next) { + next->prev = &edge->hdr; + } + + flecs_compute_table_diff(world, table, to, edge, id); + } } -ecs_id_t flecs_column_id( +static +void flecs_init_edge_for_remove( + ecs_world_t *world, ecs_table_t *table, - int32_t column_index) + ecs_graph_edge_t *edge, + ecs_id_t id, + ecs_table_t *to) { - int32_t type_index = table->column_map[table->type.count + column_index]; - return table->type.array[type_index]; -} + flecs_table_init_edge(table, edge, id, to); -/* -- Public API -- */ + flecs_table_ensure_hi_edge(world, &table->node.remove, id); -void ecs_table_lock( - ecs_world_t *world, - ecs_table_t *table) -{ - if (table) { - if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { - table->_->lock ++; + if (table != to) { + /* Remove edges are appended to refs.prev */ + ecs_graph_edge_hdr_t *to_refs = &to->node.refs; + ecs_graph_edge_hdr_t *prev = to_refs->prev; + + to_refs->prev = &edge->hdr; + edge->hdr.next = to_refs; + + edge->hdr.prev = prev; + if (prev) { + prev->next = &edge->hdr; } + + flecs_compute_table_diff(world, table, to, edge, id); } } -void ecs_table_unlock( +static +ecs_table_t* flecs_create_edge_for_remove( ecs_world_t *world, - ecs_table_t *table) + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) { - if (table) { - if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { - table->_->lock --; - ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, - "table_unlock called more often than table_lock"); - } - } + ecs_table_t *to = flecs_find_table_without(world, node, id); + flecs_init_edge_for_remove(world, node, edge, id, to); + return to; } -const ecs_type_t* ecs_table_get_type( - const ecs_table_t *table) +static +ecs_table_t* flecs_create_edge_for_add( + ecs_world_t *world, + ecs_table_t *node, + ecs_graph_edge_t *edge, + ecs_id_t id) { - if (table) { - return &table->type; - } else { - return NULL; - } + ecs_table_t *to = flecs_find_table_with(world, node, id); + flecs_init_edge_for_add(world, node, edge, id, to); + return to; } -int32_t ecs_table_get_type_index( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) +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) { flecs_poly_assert(world, ecs_world_t); - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); - if (id < FLECS_HI_COMPONENT_ID) { - int16_t res = table->component_map[id]; - if (res > 0) { - return table->column_map[table->type.count + (res - 1)]; - } + /* 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); - return -res - 1; - } + 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; - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; + 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); } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return -1; + if (node != to) { + if (edge->diff) { + *diff = *edge->diff; + } else { + diff->added.count = 0; + diff->removed.array = id_ptr; + diff->removed.count = 1; + } } - return tr->index; + return to; error: - return -1; + return NULL; } -int32_t ecs_table_get_column_index( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id) +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) { flecs_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_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); - if (id < FLECS_HI_COMPONENT_ID) { - int16_t res = table->component_map[id]; - if (res > 0) { - return res - 1; - } - return -1; - } + /* Adding 0 to an entity is not valid */ + ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (!idr) { - return -1; + ecs_id_t id = id_ptr[0]; + ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); + ecs_table_t *to = edge->to; + + if (!to) { + to = flecs_create_edge_for_add(world, node, edge, id); + ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); - if (!tr) { - return -1; + if (node != to || edge->diff) { + if (edge->diff) { + *diff = *edge->diff; + if (diff->added_flags & EcsIdIsUnion) { + if (diff->added.count == 1) { + diff->added.array = id_ptr; + diff->added.count = 1; + diff->removed.count = 0; + } + } + } else { + diff->added.array = id_ptr; + diff->added.count = 1; + diff->removed.count = 0; + } } - return tr->column; + return to; error: - return -1; + return NULL; } -int32_t ecs_table_column_count( - const ecs_table_t *table) +ecs_table_t* flecs_table_find_or_create( + ecs_world_t *world, + ecs_type_t *type) { - return table->column_count; + flecs_poly_assert(world, ecs_world_t); + return flecs_table_ensure(world, type, false, NULL); } -int32_t ecs_table_type_to_column_index( - const ecs_table_t *table, - int32_t index) +void flecs_init_root_table( + ecs_world_t *world) { - ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); - int16_t *column_map = table->column_map; - if (column_map) { - return column_map[index]; - } -error: - return -1; -} + flecs_poly_assert(world, ecs_world_t); -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; + world->store.root.type = (ecs_type_t){0}; + world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); + flecs_init_table(world, &world->store.root, NULL); + + /* Ensure table indices start at 1, as 0 is reserved for the root */ + uint64_t new_id = flecs_sparse_new_id(&world->store.tables); + ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); + (void)new_id; } -void* ecs_table_get_column( - const ecs_table_t *table, - int32_t index, - int32_t offset) +void flecs_table_edges_add_flags( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id, + ecs_flags32_t flags) { - ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); + ecs_graph_node_t *table_node = &table->node; + ecs_graph_edge_hdr_t *node_refs = &table_node->refs; - ecs_column_t *column = &table->data.columns[index]; - void *result = column->data; - if (offset) { - result = ECS_ELEM(result, column->ti->size, offset); + /* Add flags to incoming matching add edges */ + if (flags == EcsTableHasOnAdd) { + ecs_graph_edge_hdr_t *next, *cur = node_refs->next; + if (cur) { + do { + ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; + if ((id == EcsAny) || ecs_id_match(edge->id, id)) { + if (!edge->diff) { + edge->diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff->added.array = flecs_walloc_t(world, ecs_id_t); + edge->diff->added.count = 1; + edge->diff->added.array[0] = edge->id; + } + edge->diff->added_flags |= EcsTableHasOnAdd; + } + next = cur->next; + } while ((cur = next)); + } } - return result; -error: - return NULL; + /* Add flags to outgoing matching remove edges */ + if (flags == EcsTableHasOnRemove) { + ecs_map_iter_t it = ecs_map_iter(table->node.remove.hi); + while (ecs_map_next(&it)) { + ecs_id_t edge_id = ecs_map_key(&it); + if ((id == EcsAny) || ecs_id_match(edge_id, id)) { + ecs_graph_edge_t *edge = ecs_map_ptr(&it); + if (!edge->diff) { + edge->diff = flecs_bcalloc(&world->allocators.table_diff); + edge->diff->removed.array = flecs_walloc_t(world, ecs_id_t); + edge->diff->removed.count = 1; + edge->diff->removed.array[0] = edge->id; + } + edge->diff->removed_flags |= EcsTableHasOnRemove; + } + } + } } -void* ecs_table_get_id( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - int32_t offset) +void flecs_table_clear_edges( + ecs_world_t *world, + ecs_table_t *table) { - 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); + (void)world; + flecs_poly_assert(world, ecs_world_t); - world = ecs_get_world(world); + ecs_log_push_1(); - int32_t index = ecs_table_get_column_index(world, table, id); - if (index == -1) { - return NULL; + 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)); } - return ecs_table_get_column(table, index, offset); -error: - return NULL; -} + it = ecs_map_iter(remove_hi); + while (ecs_map_next(&it)) { + flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); + } -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); + /* 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)); + } - return flecs_ito(size_t, table->data.columns[column].ti->size); -error: - return 0; -} + /* 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)); + } -int32_t ecs_table_count( - const ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return table->data.count; -} + 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); + } -int32_t ecs_table_size( - const ecs_table_t *table) -{ - ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); - return table->data.size; + 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(); } -bool ecs_table_has_id( - const ecs_world_t *world, - const ecs_table_t *table, +/* 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) { - return ecs_table_get_type_index(world, table, id) != -1; + ecs_table_diff_t diff; + table = table ? table : &world->store.root; + return flecs_table_traverse_add(world, table, &id, &diff); } -int32_t ecs_table_get_depth( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_entity_t rel) +ecs_table_t* ecs_table_remove_id( + ecs_world_t *world, + ecs_table_t *table, + ecs_id_t id) { - 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, - "cannot safely determine depth for relationship that is not acyclic " - "(add Acyclic property to relationship)"); - - world = ecs_get_world(world); - - return flecs_relation_depth(world, rel, table); -error: - return -1; + ecs_table_diff_t diff; + table = table ? table : &world->store.root; + return flecs_table_traverse_remove(world, table, &id, &diff); } -bool ecs_table_has_flags( - ecs_table_t *table, - ecs_flags32_t flags) +ecs_table_t* ecs_table_find( + ecs_world_t *world, + const ecs_id_t *ids, + int32_t id_count) { - return (table->flags & flags) == flags; + ecs_type_t type = { + .array = ECS_CONST_CAST(ecs_id_t*, ids), + .count = id_count + }; + return flecs_table_ensure(world, &type, false, NULL); } -void ecs_table_swap_rows( - ecs_world_t* world, - ecs_table_t* table, - int32_t row_1, - int32_t row_2) -{ - flecs_table_swap(world, table, row_1, row_2); -} +/** + * @file addons/json/deserialize.c + * @brief Deserialize JSON strings into (component) values. + */ + +/** + * @file addons/json/json.h + * @brief Internal functions for JSON addon. + */ + +#ifndef FLECS_JSON_PRIVATE_H +#define FLECS_JSON_PRIVATE_H -int32_t flecs_table_observed_count( - const ecs_table_t *table) -{ - return table->_->traversable_count; -} -void* ecs_record_get_by_column( - const ecs_record_t *r, - int32_t index, - size_t c_size) -{ - (void)c_size; - ecs_table_t *table = r->table; +#ifdef FLECS_JSON - ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); - ecs_column_t *column = &table->data.columns[index]; - ecs_size_t size = column->ti->size; +/* Deserialize from JSON */ +typedef enum ecs_json_token_t { + JsonObjectOpen, + JsonObjectClose, + JsonArrayOpen, + JsonArrayClose, + JsonColon, + JsonComma, + JsonNumber, + JsonString, + JsonBoolean, + JsonTrue, + JsonFalse, + JsonNull, + JsonLargeInt, + JsonLargeString, + JsonInvalid +} ecs_json_token_t; - ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, - ECS_INVALID_PARAMETER, NULL); +typedef struct ecs_json_value_ser_ctx_t { + ecs_entity_t type; + const EcsTypeSerializer *ser; + char *id_label; + bool initialized; +} ecs_json_value_ser_ctx_t; - return ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); -error: - return NULL; -} +/* Cached data for serializer */ +typedef struct ecs_json_ser_ctx_t { + ecs_component_record_t *idr_doc_name; + ecs_component_record_t *idr_doc_color; + ecs_json_value_ser_ctx_t value_ctx[64]; +} ecs_json_ser_ctx_t; -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); +typedef struct ecs_json_this_data_t { + const ecs_entity_t *ids; + const EcsIdentifier *names; + const EcsDocDescription *label; + const EcsDocDescription *brief; + const EcsDocDescription *detail; + const EcsDocDescription *color; + const EcsDocDescription *link; + bool has_alerts; +} ecs_json_this_data_t; - world = ecs_get_world(world); +const char* flecs_json_parse( + const char *json, + ecs_json_token_t *token_kind, + char *token); - ecs_record_t *r = flecs_entities_get(world, entity); - if (r) { - return r; - } -error: - return NULL; -} +const char* flecs_json_parse_large_string( + const char *json, + ecs_strbuf_t *buf); -/** - * @file storage/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. - */ +const char* flecs_json_parse_next_member( + const char *json, + char *token, + ecs_json_token_t *token_kind, + const ecs_from_json_desc_t *desc); +const char* flecs_json_expect( + const char *json, + ecs_json_token_t token_kind, + char *token, + const ecs_from_json_desc_t *desc); -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; +const char* flecs_json_expect_string( + const char *json, + char *token, + char **out, + const ecs_from_json_desc_t *desc); - if (next) { - next->prev = prev; - } - if (prev) { - prev->next = next; - } +const char* flecs_json_expect_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - cache->tables.count --; +const char* flecs_json_expect_next_member( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - if (cache->tables.first == elem) { - cache->tables.first = next; - } - if (cache->tables.last == elem) { - cache->tables.last = prev; - } +const char* flecs_json_expect_member_name( + const char *json, + char *token, + const char *member_name, + const ecs_from_json_desc_t *desc); - ecs_assert(cache->tables.first == NULL || cache->tables.count, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(cache->tables.first == NULL || cache->tables.last != NULL, - ECS_INTERNAL_ERROR, NULL); -} +const char* flecs_json_skip_object( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); -static -void flecs_table_cache_list_insert( - ecs_table_cache_t *cache, - ecs_table_cache_hdr_t *elem) -{ - ecs_table_cache_hdr_t *last = cache->tables.last; - cache->tables.last = elem; - if ((++ cache->tables.count) == 1) { - cache->tables.first = elem; - } +const char* flecs_json_skip_array( + const char *json, + char *token, + const ecs_from_json_desc_t *desc); - elem->next = NULL; - elem->prev = last; +/* Serialize to JSON */ +void flecs_json_next( + ecs_strbuf_t *buf); - if (last) { - last->next = elem; - } +void flecs_json_number( + ecs_strbuf_t *buf, + double value); - ecs_assert( - cache->tables.count != 1 || cache->tables.first == cache->tables.last, - ECS_INTERNAL_ERROR, NULL); -} +void flecs_json_u32( + ecs_strbuf_t *buf, + uint32_t value); -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 flecs_json_true( + ecs_strbuf_t *buf); -void ecs_table_cache_fini( - ecs_table_cache_t *cache) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_map_fini(&cache->index); -} +void flecs_json_false( + ecs_strbuf_t *buf); -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); +void flecs_json_bool( + ecs_strbuf_t *buf, + bool value); - result->cache = cache; - result->table = ECS_CONST_CAST(ecs_table_t*, table); +void flecs_json_null( + ecs_strbuf_t *buf); - flecs_table_cache_list_insert(cache, result); +void flecs_json_array_push( + ecs_strbuf_t *buf); - if (table) { - ecs_map_insert_ptr(&cache->index, table->id, result); - } +void flecs_json_array_pop( + ecs_strbuf_t *buf); - ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); -} +void flecs_json_object_push( + ecs_strbuf_t *buf); -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); +void flecs_json_object_pop( + ecs_strbuf_t *buf); - ecs_table_cache_hdr_t *old = *r; - ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); +void flecs_json_string( + ecs_strbuf_t *buf, + const char *value); - 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; - } +void flecs_json_string_escape( + ecs_strbuf_t *buf, + const char *value); - if (cache->tables.first == old) { - cache->tables.first = elem; - } - if (cache->tables.last == old) { - cache->tables.last = elem; - } +void flecs_json_member( + ecs_strbuf_t *buf, + const char *name); - *r = elem; - elem->prev = prev; - elem->next = next; -} +void flecs_json_membern( + ecs_strbuf_t *buf, + const char *name, + int32_t name_len); -void* ecs_table_cache_get( - const ecs_table_cache_t *cache, - const ecs_table_t *table) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - if (table) { - if (ecs_map_is_init(&cache->index)) { - return ecs_map_get_deref(&cache->index, void**, table->id); - } - return NULL; - } else { - ecs_table_cache_hdr_t *elem = cache->tables.first; - ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); - return elem; - } -} +#define flecs_json_memberl(buf, name)\ + flecs_json_membern(buf, name, sizeof(name) - 1) -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); +void flecs_json_path( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); - ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); +void flecs_json_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); - flecs_table_cache_list_remove(cache, elem); - ecs_map_remove(&cache->index, table_id); +void flecs_json_path_or_label( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e, + bool path); - return elem; -} +void flecs_json_color( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_entity_t e); -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->cur = NULL; - out->iter_fill = true; - out->iter_empty = false; - return out->next != NULL; -} +void flecs_json_id( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id); -bool flecs_table_cache_empty_iter( - ecs_table_cache_t *cache, - ecs_table_cache_iter_t *out) -{ - ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); - out->next = cache->tables.first; - out->cur = NULL; - out->iter_fill = false; - out->iter_empty = true; - return out->next != NULL; -} +void flecs_json_id_member( + ecs_strbuf_t *buf, + const ecs_world_t *world, + ecs_id_t id, + bool fullpath); -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->tables.first; - out->cur = NULL; - out->iter_fill = true; - out->iter_empty = true; - return out->next != NULL; -} +ecs_primitive_kind_t flecs_json_op_to_primitive_kind( + ecs_meta_type_op_kind_t kind); -ecs_table_cache_hdr_t* flecs_table_cache_next_( - ecs_table_cache_iter_t *it) -{ - ecs_table_cache_hdr_t *next; +int flecs_json_serialize_iter_result( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); -repeat: - next = it->next; - it->cur = next; +void flecs_json_serialize_field( + const ecs_world_t *world, + const ecs_iter_t *it, + const ecs_query_t *q, + int field, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ctx); - if (next) { - it->next = next->next; +void flecs_json_serialize_query( + const ecs_world_t *world, + const ecs_query_t *q, + ecs_strbuf_t *buf); - if (ecs_table_count(next->table)) { - if (!it->iter_fill) { - goto repeat; - } - } else { - if (!it->iter_empty) { - goto repeat; - } - } - } +int flecs_json_ser_type( + const ecs_world_t *world, + const ecs_vec_t *ser, + const void *base, + ecs_strbuf_t *str); - return next; -} +int flecs_json_serialize_iter_result_fields( + const ecs_world_t *world, + const ecs_iter_t *it, + int32_t i, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + ecs_json_ser_ctx_t *ser_ctx); -/** - * @file storage/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]. - */ +bool flecs_json_serialize_get_value_ctx( + const ecs_world_t *world, + ecs_id_t id, + ecs_json_value_ser_ctx_t *ctx, + const ecs_iter_to_json_desc_t *desc); + +int flecs_json_serialize_iter_result_table( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc, + int32_t count, + bool has_this, + const char *parent_path, + const ecs_json_this_data_t *this_data); +int flecs_json_serialize_iter_result_query( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + ecs_json_ser_ctx_t *ser_ctx, + const ecs_iter_to_json_desc_t *desc, + int32_t count, + bool has_this, + const char *parent_path, + const ecs_json_this_data_t *this_data); -/* Id sequence (type) utilities */ +void flecs_json_serialize_iter_this( + const ecs_iter_t *it, + const char *parent_path, + const ecs_json_this_data_t *this_data, + int32_t row, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc); -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)); -} +bool flecs_json_serialize_vars( + const ecs_world_t *world, + const ecs_iter_t *it, + ecs_strbuf_t *buf, + const ecs_iter_to_json_desc_t *desc); -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; +int flecs_json_serialize_matches( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity); - int32_t count_1 = type_1->count; - int32_t count_2 = type_2->count; +int flecs_json_serialize_refs( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity, + ecs_entity_t relationship); - if (count_1 != count_2) { - return (count_1 > count_2) - (count_1 < count_2); - } +int flecs_json_serialize_alerts( + const ecs_world_t *world, + ecs_strbuf_t *buf, + ecs_entity_t entity); - 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); - } +bool flecs_json_is_builtin( + ecs_id_t id); - return result; -} +#endif -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); -} +#endif /* FLECS_JSON_PRIVATE_H */ + +#include + +#ifdef FLECS_JSON + +typedef struct { + ecs_allocator_t *a; + ecs_vec_t table_type; + ecs_vec_t remove_ids; + ecs_map_t anonymous_ids; + ecs_map_t missing_reflection; + const char *expr; +} ecs_from_json_ctx_t; -/* 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_from_json_ctx_init( + ecs_allocator_t *a, + ecs_from_json_ctx_t *ctx) { - ecs_id_t *array = type->array; - int32_t i, count = type->count; - - for (i = offset; i < count; i ++) { - ecs_id_t id = array[i]; - if (id == to_add) { - return -1; - } - if (id > to_add) { - return i; - } - } - return i; + ctx->a = a; + ecs_vec_init_t(a, &ctx->table_type, ecs_id_t, 0); + ecs_vec_init_t(a, &ctx->remove_ids, ecs_id_t, 0); + ecs_map_init(&ctx->anonymous_ids, a); + ecs_map_init(&ctx->missing_reflection, a); } -/* Find location of id in type */ static -int flecs_type_find( - const ecs_type_t *type, - ecs_id_t id) +void flecs_from_json_ctx_fini( + ecs_from_json_ctx_t *ctx) { - ecs_id_t *array = type->array; - int32_t i, count = type->count; - - for (i = 0; i < count; i ++) { - ecs_id_t cur = array[i]; - if (ecs_id_match(cur, id)) { - return i; - } - if (cur > id) { - return -1; - } - } - - return -1; + ecs_vec_fini_t(ctx->a, &ctx->table_type, ecs_record_t*); + ecs_vec_fini_t(ctx->a, &ctx->remove_ids, ecs_record_t*); + ecs_map_fini(&ctx->anonymous_ids); + ecs_map_fini(&ctx->missing_reflection); } -/* Count number of matching ids */ static -int flecs_type_count_matches( - const ecs_type_t *type, - ecs_id_t wildcard, - int32_t offset) +ecs_entity_t flecs_json_new_id( + ecs_world_t *world, + ecs_entity_t ser_id) { - 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; - } + /* Try to honor low id requirements */ + if (ser_id < FLECS_HI_COMPONENT_ID) { + return ecs_new_low_id(world); + } else { + return ecs_new(world); } - - return i - offset; } -/* Create type from source type with id */ static -int flecs_type_new_with( +void flecs_json_missing_reflection( ecs_world_t *world, - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t with) + ecs_id_t id, + const char *json, + ecs_from_json_ctx_t *ctx, + const ecs_from_json_desc_t *desc) { - ecs_id_t *src_array = src->array; - int32_t at = flecs_type_find_insert(src, 0, with); - if (at == -1) { - return -1; - } - - int32_t dst_count = src->count + 1; - ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); - dst->count = dst_count; - dst->array = dst_array; - - if (at) { - ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); - } - - int32_t remain = src->count - at; - if (remain) { - ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); + if (!desc->strict || ecs_map_get(&ctx->missing_reflection, id)) { + return; } - dst_array[at] = with; + /* Don't spam log when multiple values of a type can't be deserialized */ + ecs_map_ensure(&ctx->missing_reflection, id); - return 0; + char *id_str = ecs_id_str(world, id); + ecs_parser_error(desc->name, desc->expr, json - desc->expr, + "missing reflection for '%s'", id_str); + ecs_os_free(id_str); } -/* Create type from source type without ids matching wildcard */ static -int flecs_type_new_filtered( +ecs_entity_t flecs_json_lookup( ecs_world_t *world, - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t wildcard, - int32_t at) + ecs_entity_t parent, + const char *name, + const ecs_from_json_desc_t *desc) { - *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_entity_t scope = 0; + if (parent) { + scope = ecs_set_scope(world, parent); } - 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_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); + if (parent) { + ecs_set_scope(world, scope); } - dst->count = w; - if (w != count) { - dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); - } + return result; +} - return 0; +static +void flecs_json_mark_reserved( + ecs_map_t *anonymous_ids, + ecs_entity_t e) +{ + ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); + ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); + reserved[0] = 0; } -/* Create type from source type without id */ static -int flecs_type_new_without( +ecs_entity_t flecs_json_ensure_entity( ecs_world_t *world, - ecs_type_t *dst, - const ecs_type_t *src, - ecs_id_t without) + const char *name, + ecs_map_t *anonymous_ids) { - ecs_id_t *src_array = src->array; - int32_t count = 1, at = flecs_type_find(src, without); - if (at == -1) { - return -1; - } + ecs_entity_t e = 0; - int32_t src_count = src->count; - if (src_count == 1) { - dst->array = NULL; - dst->count = 0; - return 0; - } + if (flecs_name_is_id(name)) { + /* Anonymous entity, find or create mapping to new id */ + ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(&name[1])); + 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); - 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); + /* 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) && !ecs_get_name(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 + * Also don't use existing id if the existing entity is not + * anonymous. */ + deser_id[0] = ser_id; + ecs_make_alive(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]); } } - count += flecs_type_count_matches(src, without, at + 1); - } - int32_t dst_count = src_count - count; - dst->count = dst_count; - if (!dst_count) { - dst->array = NULL; - return 0; + 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_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); - dst->array = dst_array; + return e; +} - if (at) { - ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); +static +bool flecs_json_add_id_to_type( + ecs_id_t id) +{ + if (id == ecs_pair_t(EcsIdentifier, EcsName)) { + return false; } - - int32_t remain = dst_count - at; - if (remain) { - ecs_os_memcpy_n( - &dst_array[at], &src_array[at + count], ecs_id_t, remain); + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf) { + return false; } - - return 0; + return true; } -/* Copy type */ -ecs_type_t flecs_type_copy( +static +const char* flecs_json_deser_tags( ecs_world_t *world, - const ecs_type_t *src) + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) { - int32_t src_count = src->count; - if (!src_count) { - return (ecs_type_t){ 0 }; - } + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; - 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 - }; -} + const char *expr = ctx->expr, *lah; -/* 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); + json = flecs_json_expect(json, JsonArrayOpen, token, desc); + if (!json) { + goto error; } -} -/* 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; + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + json = lah; + goto end; } -} -/* Graph edge utilities */ + do { + char *str = NULL; + json = flecs_json_expect_string(json, token, &str, desc); + if (!json) { + goto error; + } -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); - builder->added_flags = 0; - builder->removed_flags = 0; -} + ecs_entity_t tag = flecs_json_lookup(world, 0, str, desc); + if (flecs_json_add_id_to_type(tag)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = tag; + } -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); -} + ecs_add_id(world, e, tag); -void flecs_table_diff_builder_clear( - ecs_table_diff_builder_t *builder) -{ - ecs_vec_clear(&builder->added); - ecs_vec_clear(&builder->removed); -} + if (str != token) { + ecs_os_free(str); + } -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); + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + break; + } + } while (true); + + if (token_kind != JsonArrayClose) { + ecs_parser_error(NULL, expr, json - expr, "expected }"); + goto error; } -} -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); - diff->added_flags = builder->added_flags; - diff->removed_flags = builder->removed_flags; -} -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 }; - diff->added_flags = builder->added_flags; - diff->removed_flags = builder->removed_flags; +end: + return json; +error: + return NULL; } static -void flecs_table_diff_build_add_type_to_vec( +const char* flecs_json_deser_pairs( ecs_world_t *world, - ecs_vec_t *vec, - ecs_type_t *add) + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) { - if (!add || !add->count) { - return; + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; + + const char *expr = ctx->expr, *lah; + + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + 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); -} + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + json = lah; + goto end; + } -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); - dst->added_flags |= src->added_flags; - dst->removed_flags |= src->removed_flags; -} + do { + json = flecs_json_expect_member(json, token, desc); + if (!json) { + 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); -} + ecs_entity_t rel = flecs_json_lookup(world, 0, token, desc); -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); - } + bool multiple_targets = false; - 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; - } + do { + json = flecs_json_parse(json, &token_kind, token); + + if (token_kind == JsonString) { + ecs_entity_t tgt = flecs_json_lookup(world, 0, token, desc); + ecs_id_t id = ecs_pair(rel, tgt); + ecs_add_id(world, e, id); + if (flecs_json_add_id_to_type(id)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; + } + } else if (token_kind == JsonLargeString) { + ecs_strbuf_t large_token = ECS_STRBUF_INIT; + json = flecs_json_parse_large_string(json, &large_token); + if (!json) { + break; + } + + char *str = ecs_strbuf_get(&large_token); + ecs_entity_t tgt = flecs_json_lookup(world, 0, str, desc); + ecs_os_free(str); + ecs_id_t id = ecs_pair(rel, tgt); + ecs_add_id(world, e, id); + if (flecs_json_add_id_to_type(id)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; + } + } else if (token_kind == JsonArrayOpen) { + if (multiple_targets) { + ecs_parser_error(NULL, expr, json - expr, + "expected string"); + goto error; + } - if (id < FLECS_HI_COMPONENT_ID) { - edge = &edges->lo[id]; - } else { - edge = flecs_bcalloc(&world->allocators.graph_edge); - } + multiple_targets = true; + } else if (token_kind == JsonArrayClose) { + if (!multiple_targets) { + ecs_parser_error(NULL, expr, json - expr, + "unexpected ]"); + goto error; + } - r[0] = edge; - return edge; -} + multiple_targets = false; + } else if (token_kind == JsonComma) { + if (!multiple_targets) { + ecs_parser_error(NULL, expr, json - expr, + "unexpected ,"); + goto error; + } + } else { + ecs_parser_error(NULL, expr, json - expr, + "expected array or string"); + goto error; + } + } while (multiple_targets); -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); + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + break; } - edge = &edges->lo[id]; - } else { - edge = flecs_table_ensure_hi_edge(world, edges, id); + } while (true); + + if (token_kind != JsonObjectClose) { + ecs_parser_error(NULL, expr, json - expr, "expected }"); + goto error; } - return edge; +end: + return json; +error: + return NULL; } static -void flecs_table_disconnect_edge( +const char* flecs_json_deser_components( ecs_world_t *world, - ecs_id_t id, - ecs_graph_edge_t *edge) + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) { - ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); - (void)id; + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; - /* Remove backref from destination table */ - ecs_graph_edge_hdr_t *next = edge->hdr.next; - ecs_graph_edge_hdr_t *prev = edge->hdr.prev; + const char *expr = ctx->expr, *lah; - if (next) { - next->prev = prev; - } - if (prev) { - prev->next = next; + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; } - /* Remove data associated with edge */ - ecs_table_diff_t *diff = edge->diff; - if (diff) { - flecs_table_diff_free(world, diff); + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + json = lah; + goto end; } - /* 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); - } -} + do { + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } -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); -} + ecs_id_t id = 0; -static -void flecs_table_init_edges( - ecs_graph_edges_t *edges) -{ - edges->lo = NULL; - edges->hi = NULL; -} + if (token[0] != '(') { + id = flecs_json_lookup(world, 0, token, desc); + } else { + char token_buffer[256]; + ecs_term_t term = {0}; + if (!flecs_term_parse(world, NULL, token, token_buffer, &term)) { + goto error; + } -static -void flecs_table_init_node( - ecs_graph_node_t *node) -{ - flecs_table_init_edges(&node->add); - flecs_table_init_edges(&node->remove); -} + ecs_assert(term.first.name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(term.second.name != NULL, ECS_INTERNAL_ERROR, NULL); -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; + ecs_entity_t rel = flecs_json_lookup( + world, 0, term.first.name, desc); + ecs_entity_t tgt = flecs_json_lookup( + world, 0, term.second.name, desc); + + id = ecs_pair(rel, tgt); + } - flecs_table_init_node(&table->node); + bool skip = false; + if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == ecs_id(EcsIdentifier)) { + skip = true; + } - flecs_table_init(world, table, prev); -} + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonNull) { + ecs_entity_t type = ecs_get_typeid(world, id); + if (!type) { + flecs_json_missing_reflection(world, id, json, ctx, desc); + if (desc->strict) { + goto error; + } -static -ecs_table_t *flecs_table_new( - ecs_world_t *world, - ecs_type_t *type, - flecs_hashmap_result_t table_elem, - ecs_table_t *prev) -{ - ecs_os_perf_trace_push("flecs.table.create"); + json = flecs_parse_ws_eol(json); - 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); + json = flecs_json_skip_object(json + 1, token, desc); + if (!json) { + goto error; + } + } else { + void *ptr = ecs_ensure_id(world, e, id); -#ifdef FLECS_SANITIZE - int32_t i, j, count = type->count; - for (i = 0; i < count - 1; i ++) { - if (type->array[i] >= type->array[i + 1]) { - for (j = 0; j < count; j ++) { - char *str = ecs_id_str(world, type->array[j]); - if (i == j) { - ecs_err(" > %d: %s", j, str); + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonNull) { + if (!skip) { + const char *next = ecs_ptr_from_json( + world, type, ptr, json, desc); + if (!next) { + flecs_json_missing_reflection( + world, id, json, ctx, desc); + if (desc->strict) { + goto error; + } + + json = flecs_parse_ws_eol(json); + + json = flecs_json_skip_object(json + 1, token, desc); + if (!json) { + goto error; + } + } else { + json = next; + ecs_modified_id(world, e, id); + } + } else { + json = flecs_json_skip_object(json + 1, token, desc); + if (!json) { + goto error; + } + } } else { - ecs_err(" %d: %s", j, str); + json = lah; } - ecs_os_free(str); } - ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); + } else { + ecs_add_id(world, e, id); + json = lah; } - } -#endif - result->id = flecs_sparse_last_id(&world->store.tables); - result->type = *type; + /* Don't add ids that have their own fields in serialized data. */ + if (flecs_json_add_id_to_type(id)) { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; + } - 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); - } + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + break; + } + } while (true); - ecs_log_push_2(); + if (token_kind != JsonObjectClose) { + ecs_parser_error(NULL, expr, json - expr, "expected }"); + goto error; + } - /* Store table in table hashmap */ - *(ecs_table_t**)table_elem.value = result; +end: + return json; +error: + return NULL; +} - /* 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; +static +const char* flecs_entity_from_json( + ecs_world_t *world, + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc, + ecs_from_json_ctx_t *ctx) +{ + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; - flecs_init_table(world, result, prev); + const char *expr = ctx->expr, *lah; - /* Update counters */ - world->info.table_count ++; - world->info.table_create_total ++; + ecs_vec_clear(&ctx->table_type); - ecs_log_pop_2(); + ecs_entity_t parent = 0; - ecs_os_perf_trace_pop("flecs.table.create"); + json = flecs_json_expect(json, JsonObjectOpen, token, desc); + if (!json) { + goto error; + } - return result; -} + lah = flecs_json_parse(json, &token_kind, token); + if (!lah) { + goto error; + } -static -ecs_table_t* flecs_table_ensure( - ecs_world_t *world, - ecs_type_t *type, - bool own_type, - ecs_table_t *prev) -{ - flecs_poly_assert(world, ecs_world_t); + if (token_kind == JsonObjectClose) { + json = lah; + goto end; + } - int32_t id_count = type->count; - if (!id_count) { - return &world->store.root; + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; } - 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); + if (!ecs_os_strcmp(token, "parent")) { + char *str = NULL; + json = flecs_json_expect_string(json, token, &str, desc); + if (!json) { + goto error; } - 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); + parent = flecs_json_lookup(world, 0, str, desc); - /* If we get here, the table has not been found, so create it. */ - if (own_type) { - return flecs_table_new(world, type, elem, prev); - } + if (e) { + ecs_add_pair(world, e, EcsChildOf, parent); + } - ecs_type_t copy = flecs_type_copy(world, type); - return flecs_table_new(world, ©, elem, prev); -} + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = + ecs_pair(EcsChildOf, parent); -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; -} + if (str != token) ecs_os_free(str); -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; -} + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } + } -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) -{ - ecs_type_t node_type = node->type; - ecs_type_t next_type = next->type; + if (!ecs_os_strcmp(token, "name")) { + char *str = NULL; + json = flecs_json_expect_string(json, token, &str, desc); + if (!json) { + goto error; + } - 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 & EcsIdIsUnion) { - if (node != next) { - id = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); - } 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); - diff->added_flags = EcsTableHasUnion; - edge->diff = diff; - return; - } + if (!e) { + e = flecs_json_lookup(world, parent, str, desc); + } else { + ecs_set_name(world, e, str); } - } - 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; - ecs_flags32_t added_flags = 0, removed_flags = 0; - bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); + if (str[0] != '#') { + ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = + ecs_pair_t(EcsIdentifier, EcsName); + } - /* 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]; + if (str != token) ecs_os_free(str); - bool added = id_next < id_node; - bool removed = id_node < id_next; + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } + } - trivial_edge &= !added || id_next == id; - trivial_edge &= !removed || id_node == id; + if (!ecs_os_strcmp(token, "id")) { + json = flecs_json_parse(json, &token_kind, token); + if (!json) { + goto error; + } - if (added) { - added_flags |= flecs_id_flags_get(world, id_next) & - EcsTableAddEdgeFlags; - added_count ++; + uint64_t id; + if (token_kind == JsonNumber || token_kind == JsonLargeInt) { + id = flecs_ito(uint64_t, atoll(token)); + } else { + ecs_parser_error(NULL, expr, json - expr, "expected entity id"); + goto error; } - if (removed) { - removed_flags |= flecs_id_flags_get(world, id_node) & - EcsTableRemoveEdgeFlags; - removed_count ++; + if (!e) { + char name[32]; + ecs_os_snprintf(name, 32, "#%u", (uint32_t)id); + e = flecs_json_lookup(world, 0, name, desc); + } else { + /* If we already have an id, ignore explicit id */ } - i_node += id_node <= id_next; - i_next += id_next <= id_node; + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } } - for (; i_next < next_count; i_next ++) { - added_flags |= flecs_id_flags_get(world, ids_next[i_next]) & - EcsTableAddEdgeFlags; - added_count ++; + if (!e) { + ecs_parser_error(NULL, expr, json - expr, "failed to create entity"); + return NULL; } - for (; i_node < node_count; i_node ++) { - removed_flags |= flecs_id_flags_get(world, ids_node[i_node]) & - EcsTableRemoveEdgeFlags; - removed_count ++; + if (!ecs_os_strcmp(token, "has_alerts")) { + json = flecs_json_expect(json, JsonBoolean, token, desc); + if (!json) { + goto error; + } + + json = flecs_json_parse_next_member(json, token, &token_kind, desc); + if (!json) { + goto error; + } + if (token_kind == JsonObjectClose) { + goto end; + } } - trivial_edge &= (added_count + removed_count) <= 1 && - !ecs_id_is_wildcard(id) && !(added_flags|removed_flags); + if (!ecs_os_strcmp(token, "tags")) { + json = flecs_json_deser_tags(world, e, json, desc, ctx); + if (!json) { + goto error; + } - if (trivial_edge) { - /* If edge is trivial there's no need to create a diff element for it */ - return; - } + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + goto end; + } else if (token_kind != JsonComma) { + ecs_parser_error(NULL, expr, json - expr, "expected ','"); + 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; + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } + } - 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 (!ecs_os_strcmp(token, "pairs")) { + json = flecs_json_deser_pairs(world, e, json, desc, ctx); + if (!json) { + goto error; + } - 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); + json = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonObjectClose) { + goto end; + } else if (token_kind != JsonComma) { + ecs_parser_error(NULL, expr, json - expr, "expected ','"); + goto error; } - i_node += id_node <= id_next; - i_next += id_next <= id_node; + json = flecs_json_expect_member(json, token, desc); + if (!json) { + goto error; + } } - for (; i_next < next_count; i_next ++) { - flecs_diff_insert_added(world, builder, ids_next[i_next]); + if (!ecs_os_strcmp(token, "components")) { + json = flecs_json_deser_components(world, e, json, desc, ctx); + if (!json) { + goto error; + } } - for (; i_node < node_count; i_node ++) { - flecs_diff_insert_removed(world, builder, ids_node[i_node]); + + json = flecs_json_expect(json, JsonObjectClose, token, desc); + if (!json) { + goto error; } - 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); - diff->added_flags = added_flags; - diff->removed_flags = removed_flags; + ecs_record_t *r = flecs_entities_get(world, e); + ecs_table_t *table = r ? r->table : NULL; + if (table) { + ecs_id_t *ids = ecs_vec_first(&ctx->table_type); + int32_t ids_count = ecs_vec_count(&ctx->table_type); + qsort(ids, flecs_itosize(ids_count), sizeof(ecs_id_t), flecs_id_qsort_cmp); - ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); - ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); -} + ecs_table_t *dst_table = ecs_table_find(world, + ecs_vec_first(&ctx->table_type), ecs_vec_count(&ctx->table_type)); + if (dst_table->type.count == 0) { + dst_table = NULL; + } -static -void flecs_add_overrides_for_base( - ecs_world_t *world, - ecs_type_t *dst_type, - ecs_id_t pair) -{ - ecs_entity_t base = ecs_pair_second(world, pair); - ecs_assert(base != 0, ECS_INVALID_PARAMETER, - "target of IsA pair is not alive"); - ecs_table_t *base_table = ecs_get_table(world, base); - if (!base_table) { - return; - } + /* Entity had existing components that weren't in the serialized data */ + if (table != dst_table) { + ecs_assert(ecs_get_target(world, e, EcsChildOf, 0) != EcsFlecsCore, + ECS_INVALID_OPERATION, "%s\n[%s] => \n[%s]", + ecs_get_path(world, e), + ecs_table_str(world, table), + ecs_table_str(world, dst_table)); - ecs_id_t *ids = base_table->type.array; - ecs_flags32_t flags = base_table->flags; - if (flags & EcsTableHasOverrides) { - int32_t i, count = base_table->type.count; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - ecs_id_t to_add = 0; - if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { - to_add = id & ~ECS_AUTO_OVERRIDE; + if (!dst_table) { + ecs_clear(world, e); } else { - ecs_table_record_t *tr = &base_table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (ECS_ID_ON_INSTANTIATE(idr->flags) == EcsOverride) { - to_add = id; - } - } + ecs_vec_clear(&ctx->remove_ids); - 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; + ecs_type_t *type = &table->type, *dst_type = &dst_table->type; + int32_t i = 0, i_dst = 0; + for (; (i_dst < dst_type->count) && (i < type->count); ) { + ecs_id_t id = type->array[i], dst_id = dst_type->array[i_dst]; + + if (dst_id > id) { + ecs_vec_append_t( + ctx->a, &ctx->remove_ids, ecs_id_t)[0] = id; } + + i_dst += dst_id <= id; + i += dst_id >= id; } + + ecs_type_t removed = { + .array = ecs_vec_first(&ctx->remove_ids), + .count = ecs_vec_count(&ctx->remove_ids) + }; + + ecs_commit(world, e, r, dst_table, NULL, &removed); } - } - } - 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]); + ecs_assert(ecs_get_table(world, e) == dst_table, + ECS_INTERNAL_ERROR, NULL); } } + +end: + return json; +error: + return NULL; } -static -void flecs_add_with_property( +const char* ecs_entity_from_json( ecs_world_t *world, - ecs_id_record_t *idr_with_wildcard, - ecs_type_t *dst_type, - ecs_entity_t r, - ecs_entity_t o) + ecs_entity_t e, + const char *json, + const ecs_from_json_desc_t *desc_arg) { - r = ecs_get_alive(world, r); - - /* Check if component/relationship has With pairs, which contain ids - * that need to be added to the table. */ - ecs_table_t *table = ecs_get_table(world, r); - if (!table) { - return; + ecs_from_json_desc_t desc = {0}; + if (desc_arg) { + desc = *desc_arg; } - - ecs_table_record_t *tr = flecs_id_record_get_table( - idr_with_wildcard, table); - if (tr) { - int32_t i = tr->index, end = i + tr->count; - ecs_id_t *ids = table->type.array; - for (; i < end; i ++) { - ecs_id_t id = ids[i]; - ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); - ecs_id_t ra = ECS_PAIR_SECOND(id); - ecs_id_t a = ra; - if (o) { - a = ecs_pair(ra, o); - } + desc.expr = json; - flecs_type_add(world, dst_type, a); - flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); - } + ecs_allocator_t *a = &world->allocator; + ecs_from_json_ctx_t ctx; + flecs_from_json_ctx_init(a, &ctx); + ctx.expr = json; + + if (!desc.lookup_action) { + desc.lookup_action = (ecs_entity_t(*)( + const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; + desc.lookup_ctx = &ctx.anonymous_ids; } + json = flecs_entity_from_json(world, e, json, &desc, &ctx); + + flecs_from_json_ctx_fini(&ctx); + return json; } -static -ecs_table_t* flecs_find_table_with( +const char* ecs_world_from_json( ecs_world_t *world, - ecs_table_t *node, - ecs_id_t with) + const char *json, + const ecs_from_json_desc_t *desc_arg) { - ecs_make_alive_id(world, with); + ecs_json_token_t token_kind; + char token[ECS_MAX_TOKEN_SIZE]; - ecs_id_record_t *idr = NULL; - ecs_entity_t r = 0, o = 0; + ecs_from_json_desc_t desc = {0}; + if (desc_arg) { + desc = *desc_arg; + } - 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 & EcsIdIsUnion) { - with = ecs_pair(r, EcsUnion); - } 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; + desc.expr = json; + + ecs_allocator_t *a = &world->allocator; + ecs_from_json_ctx_t ctx; + flecs_from_json_ctx_init(a, &ctx); + + const char *expr = json, *lah; + ctx.expr = expr; + + if (!desc.lookup_action) { + desc.lookup_action = (ecs_entity_t(*)( + const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; + desc.lookup_ctx = &ctx.anonymous_ids; } - /* 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 */ + json = flecs_json_expect(json, JsonObjectOpen, token, &desc); + if (!json) { + 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); - } + json = flecs_json_expect_member_name(json, token, "results", &desc); + if (!json) { + goto error; } - 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); + json = flecs_json_expect(json, JsonArrayOpen, token, &desc); + if (!json) { + goto error; } - return flecs_table_ensure(world, &dst_type, true, node); -} + lah = flecs_json_parse(json, &token_kind, token); + if (token_kind == JsonArrayClose) { + json = lah; + goto end; + } -static -ecs_table_t* flecs_find_table_without( - ecs_world_t *world, - ecs_table_t *node, - ecs_id_t without) -{ - if (ECS_IS_PAIR(without)) { - ecs_entity_t r = 0; - ecs_id_record_t *idr = NULL; - r = ECS_PAIR_FIRST(without); - idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); - if (idr && idr->flags & EcsIdIsUnion) { - without = ecs_pair(r, EcsUnion); + do { + json = flecs_entity_from_json(world, 0, json, &desc, &ctx); + if (!json) { + goto error; } - } - /* 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 */ - } + json = flecs_json_parse(json, &token_kind, token); + if (token_kind != JsonComma) { + if (token_kind != JsonArrayClose) { + ecs_parser_error(NULL, expr, json - expr, "expected ']'"); + goto error; + } + break; + } + } while (true); - return flecs_table_ensure(world, &dst_type, true, node); -} +end: + json = flecs_json_expect(json, JsonObjectClose, token, &desc); -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; + flecs_from_json_ctx_fini(&ctx); + return json; +error: + flecs_from_json_ctx_fini(&ctx); + return NULL; } -static -void flecs_init_edge_for_add( +const char* ecs_world_from_json_file( ecs_world_t *world, - ecs_table_t *table, - ecs_graph_edge_t *edge, - ecs_id_t id, - ecs_table_t *to) + const char *filename, + const ecs_from_json_desc_t *desc) { - flecs_table_init_edge(table, edge, id, to); - - flecs_table_ensure_hi_edge(world, &table->node.add, id); - - if ((table != to) || (table->flags & EcsTableHasUnion)) { - /* Add edges are appended to refs.next */ - ecs_graph_edge_hdr_t *to_refs = &to->node.refs; - ecs_graph_edge_hdr_t *next = to_refs->next; - - to_refs->next = &edge->hdr; - edge->hdr.prev = to_refs; - - edge->hdr.next = next; - if (next) { - next->prev = &edge->hdr; - } - - flecs_compute_table_diff(world, table, to, edge, id); + char *json = flecs_load_from_file(filename); + if (!json) { + ecs_err("file not found: %s", filename); + return NULL; } + + const char *result = ecs_world_from_json(world, json, desc); + ecs_os_free(json); + return result; } -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); +#endif - flecs_table_ensure_hi_edge(world, &table->node.remove, id); +/** + * @file addons/json/deserialize_value.c + * @brief Deserialize JSON strings into (component) values. + */ - 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; +/** + * @file addons/script/script.h + * @brief Flecs script implementation. + */ - to_refs->prev = &edge->hdr; - edge->hdr.next = to_refs; +#ifndef FLECS_SCRIPT_PRIVATE_H +#define FLECS_SCRIPT_PRIVATE_H - edge->hdr.prev = prev; - if (prev) { - prev->next = &edge->hdr; - } +/** + * @file addons/parser/parser.h + * @brief Parser addon. + */ - flecs_compute_table_diff(world, table, to, edge, id); - } -} +#ifndef FLECS_PARSER_H +#define FLECS_PARSER_H -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; -} +#include -static -ecs_table_t* flecs_create_edge_for_add( - ecs_world_t *world, - ecs_table_t *node, - ecs_graph_edge_t *edge, - ecs_id_t id) -{ - ecs_table_t *to = flecs_find_table_with(world, node, id); - flecs_init_edge_for_add(world, node, edge, id, to); - return to; -} +typedef struct ecs_script_impl_t ecs_script_impl_t; +typedef struct ecs_script_scope_t ecs_script_scope_t; -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) -{ - flecs_poly_assert(world, ecs_world_t); +typedef struct ecs_parser_t { + const char *name; + const char *code; - node = node ? node : &world->store.root; + const char *pos; + char *token_cur; + char *token_keep; + bool significant_newline; + bool merge_variable_members; - /* Removing 0 from an entity is not valid */ - ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); + ecs_world_t *world; - 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; + /* For script parser */ + ecs_script_impl_t *script; + ecs_script_scope_t *scope; - 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); - } + /* For term parser */ + ecs_term_t *term; + ecs_oper_kind_t extra_oper; + ecs_term_ref_t *extra_args; +} ecs_parser_t; - if (node != to) { - if (edge->diff) { - *diff = *edge->diff; - } else { - diff->added.count = 0; - diff->removed.array = id_ptr; - diff->removed.count = 1; - } - } +/** + * @file addons/parser/tokenizer.h + * @brief Parser tokenizer. + */ - return to; -error: - return NULL; -} +#ifndef FLECS_PARSER_TOKENIZER_H +#define FLECS_PARSER_TOKENIZER_H -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) -{ - flecs_poly_assert(world, ecs_world_t); - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); +/* Tokenizer */ +typedef enum ecs_token_kind_t { + EcsTokEnd = '\0', + EcsTokUnknown, + EcsTokScopeOpen = '{', + EcsTokScopeClose = '}', + EcsTokParenOpen = '(', + EcsTokParenClose = ')', + EcsTokBracketOpen = '[', + EcsTokBracketClose = ']', + EcsTokMember = '.', + EcsTokComma = ',', + EcsTokSemiColon = ';', + EcsTokColon = ':', + EcsTokAssign = '=', + EcsTokAdd = '+', + EcsTokSub = '-', + EcsTokMul = '*', + EcsTokDiv = '/', + EcsTokMod = '%', + EcsTokBitwiseOr = '|', + EcsTokBitwiseAnd = '&', + EcsTokNot = '!', + EcsTokOptional = '?', + EcsTokAnnotation = '@', + EcsTokNewline = '\n', + EcsTokEq = 100, + EcsTokNeq = 101, + EcsTokGt = 102, + EcsTokGtEq = 103, + EcsTokLt = 104, + EcsTokLtEq = 105, + EcsTokAnd = 106, + EcsTokOr = 107, + EcsTokMatch = 108, + EcsTokRange = 109, + EcsTokShiftLeft = 110, + EcsTokShiftRight = 111, + EcsTokIdentifier = 112, + EcsTokString = 113, + EcsTokNumber = 114, + EcsTokKeywordModule = 115, + EcsTokKeywordUsing = 116, + EcsTokKeywordWith = 117, + EcsTokKeywordIf = 118, + EcsTokKeywordFor = 119, + EcsTokKeywordIn = 120, + EcsTokKeywordElse = 121, + EcsTokKeywordTemplate = 122, + EcsTokKeywordProp = 130, + EcsTokKeywordConst = 131, + EcsTokKeywordMatch = 132, + EcsTokAddAssign = 133, + EcsTokMulAssign = 134, +} ecs_token_kind_t; - node = node ? node : &world->store.root; +typedef struct ecs_token_t { + const char *value; + ecs_token_kind_t kind; +} ecs_token_t; - /* 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); +typedef struct ecs_tokens_t { + int32_t count; + ecs_token_t tokens[256]; +} ecs_tokens_t; - 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; +typedef struct ecs_tokenizer_t { + ecs_tokens_t stack; + ecs_token_t *tokens; +} ecs_tokenizer_t; - 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); - } +const char* flecs_tokenizer_until( + ecs_parser_t *parser, + const char *ptr, + ecs_token_t *out, + char until); - if (node != to || edge->diff) { - if (edge->diff) { - *diff = *edge->diff; - if (diff->added_flags & EcsIdIsUnion) { - if (diff->added.count == 1) { - diff->added.array = id_ptr; - diff->added.count = 1; - diff->removed.count = 0; - } - } - } else { - diff->added.array = id_ptr; - diff->added.count = 1; - diff->removed.count = 0; - } - } +const char* flecs_token_kind_str( + ecs_token_kind_t kind); - return to; -error: - return NULL; -} +const char* flecs_token_str( + ecs_token_kind_t kind); -ecs_table_t* flecs_table_find_or_create( - ecs_world_t *world, - ecs_type_t *type) -{ - flecs_poly_assert(world, ecs_world_t); - return flecs_table_ensure(world, type, false, NULL); -} +const char* flecs_token( + ecs_parser_t *parser, + const char *ptr, + ecs_token_t *out, + bool is_lookahead); -void flecs_init_root_table( - ecs_world_t *world) -{ - flecs_poly_assert(world, ecs_world_t); +const char* flecs_scan_whitespace( + ecs_parser_t *parser, + const char *pos); - 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); +const char* flecs_tokenizer_identifier( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out); - /* 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; -} +#endif -void flecs_table_edges_add_flags( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id, - ecs_flags32_t flags) -{ - ecs_graph_node_t *table_node = &table->node; - ecs_graph_edge_hdr_t *node_refs = &table_node->refs; - /* Add flags to incoming matching add edges */ - if (flags == EcsTableHasOnAdd) { - ecs_graph_edge_hdr_t *next, *cur = node_refs->next; - if (cur) { - do { - ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; - if ((id == EcsAny) || ecs_id_match(edge->id, id)) { - if (!edge->diff) { - edge->diff = flecs_bcalloc(&world->allocators.table_diff); - edge->diff->added.array = flecs_walloc_t(world, ecs_id_t); - edge->diff->added.count = 1; - edge->diff->added.array[0] = edge->id; - } - edge->diff->added_flags |= EcsTableHasOnAdd; - } - next = cur->next; - } while ((cur = next)); - } - } +#endif - /* Add flags to outgoing matching remove edges */ - if (flags == EcsTableHasOnRemove) { - ecs_map_iter_t it = ecs_map_iter(table->node.remove.hi); - while (ecs_map_next(&it)) { - ecs_id_t edge_id = ecs_map_key(&it); - if ((id == EcsAny) || ecs_id_match(edge_id, id)) { - ecs_graph_edge_t *edge = ecs_map_ptr(&it); - if (!edge->diff) { - edge->diff = flecs_bcalloc(&world->allocators.table_diff); - edge->diff->removed.array = flecs_walloc_t(world, ecs_id_t); - edge->diff->removed.count = 1; - edge->diff->removed.array[0] = edge->id; - } - edge->diff->removed_flags |= EcsTableHasOnRemove; - } - } - } -} -void flecs_table_clear_edges( - ecs_world_t *world, - ecs_table_t *table) -{ - (void)world; - flecs_poly_assert(world, ecs_world_t); +#ifdef FLECS_SCRIPT - ecs_log_push_1(); +typedef struct ecs_script_entity_t ecs_script_entity_t; - 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; +#define flecs_script_impl(script) ((ecs_script_impl_t*)script) - /* 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)); - } +struct ecs_script_impl_t { + ecs_script_t pub; + ecs_allocator_t allocator; + ecs_script_scope_t *root; + ecs_expr_node_t *expr; /* Only set if script is just an expression */ + char *token_buffer; + char *token_remaining; /* Remaining space in token buffer */ + const char *next_token; /* First character after expression */ + int32_t token_buffer_size; + int32_t refcount; +}; - it = ecs_map_iter(remove_hi); - while (ecs_map_next(&it)) { - flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); - } +typedef struct ecs_function_calldata_t { + ecs_entity_t function; + ecs_function_callback_t callback; + void *ctx; +} ecs_function_calldata_t; - /* 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)); - } +/** + * @file addons/script/ast.h + * @brief Script AST. + */ - /* 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_SCRIPT_AST_H +#define FLECS_SCRIPT_AST_H - 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); - } +typedef enum ecs_script_node_kind_t { + EcsAstScope, + EcsAstTag, + EcsAstComponent, + EcsAstDefaultComponent, + EcsAstVarComponent, + EcsAstWithVar, + EcsAstWithTag, + EcsAstWithComponent, + EcsAstWith, + EcsAstUsing, + EcsAstModule, + EcsAstAnnotation, + EcsAstTemplate, + EcsAstProp, + EcsAstConst, + EcsAstEntity, + EcsAstPairScope, + EcsAstIf, + EcsAstFor +} ecs_script_node_kind_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; +typedef struct ecs_script_node_t { + ecs_script_node_kind_t kind; + const char *pos; +} ecs_script_node_t; - ecs_log_pop_1(); -} +struct ecs_script_scope_t { + ecs_script_node_t node; + ecs_vec_t stmts; + ecs_script_scope_t *parent; + ecs_id_t default_component_eval; -/* Public convenience functions for traversing table graph */ -ecs_table_t* ecs_table_add_id( - ecs_world_t *world, - ecs_table_t *table, - ecs_id_t id) -{ - ecs_table_diff_t diff; - return flecs_table_traverse_add(world, table, &id, &diff); -} + /* Array with component ids that are added in scope. Used to limit + * archetype moves. */ + ecs_vec_t components; /* vec */ +}; -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); -} +typedef struct ecs_script_id_t { + const char *first; + const char *second; + ecs_id_t flag; + ecs_id_t eval; -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); -} + /* If first or second refer to a variable, these are the cached variable + * stack pointers so we don't have to lookup variables by name. */ + int32_t first_sp; + int32_t second_sp; -/** - * @file addons/json/deserialize.c - * @brief Deserialize JSON strings into (component) values. - */ + /* If true, the lookup result for this id cannot be cached. This is the case + * for entities that are defined inside of templates, which have different + * values for each instantiation. */ + bool dynamic; +} ecs_script_id_t; -/** - * @file addons/json/json.h - * @brief Internal functions for JSON addon. - */ +typedef struct ecs_script_tag_t { + ecs_script_node_t node; + ecs_script_id_t id; +} ecs_script_tag_t; -#ifndef FLECS_JSON_PRIVATE_H -#define FLECS_JSON_PRIVATE_H +typedef struct ecs_script_component_t { + ecs_script_node_t node; + ecs_script_id_t id; + ecs_expr_node_t *expr; + ecs_value_t eval; + bool is_collection; +} ecs_script_component_t; +typedef struct ecs_script_default_component_t { + ecs_script_node_t node; + ecs_expr_node_t *expr; + ecs_value_t eval; +} ecs_script_default_component_t; -#ifdef FLECS_JSON +typedef struct ecs_script_var_component_t { + ecs_script_node_t node; + const char *name; + int32_t sp; +} ecs_script_var_component_t; -/* Deserialize from JSON */ -typedef enum ecs_json_token_t { - JsonObjectOpen, - JsonObjectClose, - JsonArrayOpen, - JsonArrayClose, - JsonColon, - JsonComma, - JsonNumber, - JsonString, - JsonBoolean, - JsonTrue, - JsonFalse, - JsonNull, - JsonLargeInt, - JsonLargeString, - JsonInvalid -} ecs_json_token_t; +struct ecs_script_entity_t { + ecs_script_node_t node; + const char *kind; + const char *name; + bool name_is_var; + bool kind_w_expr; + ecs_script_scope_t *scope; + ecs_expr_node_t *name_expr; -typedef struct ecs_json_value_ser_ctx_t { - ecs_entity_t type; - const EcsTypeSerializer *ser; - char *id_label; - bool initialized; -} ecs_json_value_ser_ctx_t; + /* Populated during eval */ + ecs_script_entity_t *parent; + ecs_entity_t eval; + ecs_entity_t eval_kind; +}; -/* Cached data for serializer */ -typedef struct ecs_json_ser_ctx_t { - ecs_id_record_t *idr_doc_name; - ecs_id_record_t *idr_doc_color; - ecs_json_value_ser_ctx_t value_ctx[64]; -} ecs_json_ser_ctx_t; +typedef struct ecs_script_with_t { + ecs_script_node_t node; + ecs_script_scope_t *expressions; + ecs_script_scope_t *scope; +} ecs_script_with_t; -typedef struct ecs_json_this_data_t { - const ecs_entity_t *ids; - const EcsIdentifier *names; - const EcsDocDescription *label; - const EcsDocDescription *brief; - const EcsDocDescription *detail; - const EcsDocDescription *color; - const EcsDocDescription *link; - bool has_alerts; -} ecs_json_this_data_t; +typedef struct ecs_script_inherit_t { + ecs_script_node_t node; + ecs_script_scope_t *base_list; +} ecs_script_inherit_t; -const char* flecs_json_parse( - const char *json, - ecs_json_token_t *token_kind, - char *token); +typedef struct ecs_script_pair_scope_t { + ecs_script_node_t node; + ecs_script_id_t id; + ecs_script_scope_t *scope; +} ecs_script_pair_scope_t; -const char* flecs_json_parse_large_string( - const char *json, - ecs_strbuf_t *buf); +typedef struct ecs_script_using_t { + ecs_script_node_t node; + const char *name; +} ecs_script_using_t; -const char* flecs_json_parse_next_member( - const char *json, - char *token, - ecs_json_token_t *token_kind, - const ecs_from_json_desc_t *desc); +typedef struct ecs_script_module_t { + ecs_script_node_t node; + const char *name; +} ecs_script_module_t; -const char* flecs_json_expect( - const char *json, - ecs_json_token_t token_kind, - char *token, - const ecs_from_json_desc_t *desc); +typedef struct ecs_script_annot_t { + ecs_script_node_t node; + const char *name; + const char *expr; +} ecs_script_annot_t; -const char* flecs_json_expect_string( - const char *json, - char *token, - char **out, - const ecs_from_json_desc_t *desc); +typedef struct ecs_script_template_node_t { + ecs_script_node_t node; + const char *name; + ecs_script_scope_t* scope; +} ecs_script_template_node_t; -const char* flecs_json_expect_member( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); +typedef struct ecs_script_var_node_t { + ecs_script_node_t node; + const char *name; + const char *type; + ecs_expr_node_t *expr; +} ecs_script_var_node_t; -const char* flecs_json_expect_next_member( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); +typedef struct ecs_script_if_t { + ecs_script_node_t node; + ecs_script_scope_t *if_true; + ecs_script_scope_t *if_false; + ecs_expr_node_t *expr; +} ecs_script_if_t; -const char* flecs_json_expect_member_name( - const char *json, - char *token, - const char *member_name, - const ecs_from_json_desc_t *desc); +typedef struct ecs_script_for_range_t { + ecs_script_node_t node; + const char *loop_var; + ecs_expr_node_t *from; + ecs_expr_node_t *to; + ecs_script_scope_t *scope; +} ecs_script_for_range_t; -const char* flecs_json_skip_object( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); +#define ecs_script_node(kind, node)\ + ((ecs_script_##kind##_t*)node) -const char* flecs_json_skip_array( - const char *json, - char *token, - const ecs_from_json_desc_t *desc); +bool flecs_scope_is_empty( + ecs_script_scope_t *scope); -/* Serialize to JSON */ -void flecs_json_next( - ecs_strbuf_t *buf); +ecs_script_scope_t* flecs_script_insert_scope( + ecs_parser_t *parser); -void flecs_json_number( - ecs_strbuf_t *buf, - double value); +ecs_script_entity_t* flecs_script_insert_entity( + ecs_parser_t *parser, + const char *name, + bool name_is_expr); -void flecs_json_u32( - ecs_strbuf_t *buf, - uint32_t value); +ecs_script_pair_scope_t* flecs_script_insert_pair_scope( + ecs_parser_t *parser, + const char *first, + const char *second); -void flecs_json_true( - ecs_strbuf_t *buf); +ecs_script_with_t* flecs_script_insert_with( + ecs_parser_t *parser); -void flecs_json_false( - ecs_strbuf_t *buf); +ecs_script_using_t* flecs_script_insert_using( + ecs_parser_t *parser, + const char *name); -void flecs_json_bool( - ecs_strbuf_t *buf, - bool value); +ecs_script_module_t* flecs_script_insert_module( + ecs_parser_t *parser, + const char *name); -void flecs_json_null( - ecs_strbuf_t *buf); +ecs_script_template_node_t* flecs_script_insert_template( + ecs_parser_t *parser, + const char *name); -void flecs_json_array_push( - ecs_strbuf_t *buf); +ecs_script_annot_t* flecs_script_insert_annot( + ecs_parser_t *parser, + const char *name, + const char *expr); -void flecs_json_array_pop( - ecs_strbuf_t *buf); +ecs_script_var_node_t* flecs_script_insert_var( + ecs_parser_t *parser, + const char *name); -void flecs_json_object_push( - ecs_strbuf_t *buf); +ecs_script_tag_t* flecs_script_insert_tag( + ecs_parser_t *parser, + const char *name); -void flecs_json_object_pop( - ecs_strbuf_t *buf); +ecs_script_tag_t* flecs_script_insert_pair_tag( + ecs_parser_t *parser, + const char *first, + const char *second); -void flecs_json_string( - ecs_strbuf_t *buf, - const char *value); +ecs_script_component_t* flecs_script_insert_component( + ecs_parser_t *parser, + const char *name); -void flecs_json_string_escape( - ecs_strbuf_t *buf, - const char *value); +ecs_script_component_t* flecs_script_insert_pair_component( + ecs_parser_t *parser, + const char *first, + const char *second); + +ecs_script_default_component_t* flecs_script_insert_default_component( + ecs_parser_t *parser); -void flecs_json_member( - ecs_strbuf_t *buf, +ecs_script_var_component_t* flecs_script_insert_var_component( + ecs_parser_t *parser, const char *name); -void flecs_json_membern( - ecs_strbuf_t *buf, - const char *name, - int32_t name_len); +ecs_script_if_t* flecs_script_insert_if( + ecs_parser_t *parser); -#define flecs_json_memberl(buf, name)\ - flecs_json_membern(buf, name, sizeof(name) - 1) +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_parser_t *parser); -void flecs_json_path( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); +#endif -void flecs_json_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); +/** + * @file addons/script/expr/expr.h + * @brief Script expression support. + */ -void flecs_json_path_or_label( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e, - bool path); +#ifndef FLECS_EXPR_SCRIPT_H +#define FLECS_EXPR_SCRIPT_H -void flecs_json_color( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_entity_t e); +/** + * @file addons/script/expr/stack.h + * @brief Script expression AST. + */ -void flecs_json_id( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id); +#ifndef FLECS_SCRIPT_EXPR_STACK_H +#define FLECS_SCRIPT_EXPR_STACK_H -void flecs_json_id_member( - ecs_strbuf_t *buf, - const ecs_world_t *world, - ecs_id_t id, - bool fullpath); +#define FLECS_EXPR_STACK_MAX (256) +#define FLECS_EXPR_SMALL_DATA_SIZE (24) -ecs_primitive_kind_t flecs_json_op_to_primitive_kind( - ecs_meta_type_op_kind_t kind); -int flecs_json_serialize_iter_result( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc, - ecs_json_ser_ctx_t *ser_ctx); +typedef union ecs_expr_small_value_t { + bool bool_; + char char_; + ecs_byte_t byte_; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + intptr_t iptr; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uintptr_t uptr; + double f32; + double f64; + char *string; + ecs_entity_t entity; + ecs_id_t id; -void flecs_json_serialize_field( - const ecs_world_t *world, - const ecs_iter_t *it, - const ecs_query_t *q, - int field, - ecs_strbuf_t *buf, - ecs_json_ser_ctx_t *ctx); + /* Avoid allocations for small trivial types */ + char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; +} ecs_expr_small_value_t; -void flecs_json_serialize_query( - const ecs_world_t *world, - const ecs_query_t *q, - ecs_strbuf_t *buf); +typedef struct ecs_expr_value_t { + ecs_value_t value; + const ecs_type_info_t *type_info; + bool owned; /* Is value owned by the runtime */ +} ecs_expr_value_t; -int flecs_json_ser_type( - const ecs_world_t *world, - const ecs_vec_t *ser, - const void *base, - ecs_strbuf_t *str); +typedef struct ecs_expr_stack_frame_t { + ecs_stack_cursor_t *cur; + int32_t sp; +} ecs_expr_stack_frame_t; -int flecs_json_serialize_iter_result_fields( - const ecs_world_t *world, - const ecs_iter_t *it, - int32_t i, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc, - ecs_json_ser_ctx_t *ser_ctx); +typedef struct ecs_expr_stack_t { + ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; + ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; + ecs_stack_t stack; + int32_t frame; +} ecs_expr_stack_t; -bool flecs_json_serialize_get_value_ctx( - const ecs_world_t *world, - ecs_id_t id, - ecs_json_value_ser_ctx_t *ctx, - const ecs_iter_to_json_desc_t *desc); +void flecs_expr_stack_init( + ecs_expr_stack_t *stack); -int flecs_json_serialize_iter_result_table( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc, - int32_t count, - bool has_this, - const char *parent_path, - const ecs_json_this_data_t *this_data); +void flecs_expr_stack_fini( + ecs_expr_stack_t *stack); -int flecs_json_serialize_iter_result_query( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf, - ecs_json_ser_ctx_t *ser_ctx, - const ecs_iter_to_json_desc_t *desc, - int32_t count, - bool has_this, - const char *parent_path, - const ecs_json_this_data_t *this_data); +ecs_expr_value_t* flecs_expr_stack_alloc( + ecs_expr_stack_t *stack, + const ecs_type_info_t *ti); -void flecs_json_serialize_iter_this( - const ecs_iter_t *it, - const char *parent_path, - const ecs_json_this_data_t *this_data, - int32_t row, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc); +ecs_expr_value_t* flecs_expr_stack_result( + ecs_expr_stack_t *stack, + ecs_expr_node_t *node); -bool flecs_json_serialize_vars( - const ecs_world_t *world, - const ecs_iter_t *it, - ecs_strbuf_t *buf, - const ecs_iter_to_json_desc_t *desc); +void flecs_expr_stack_push( + ecs_expr_stack_t *stack); -int flecs_json_serialize_matches( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t entity); +void flecs_expr_stack_pop( + ecs_expr_stack_t *stack); -int flecs_json_serialize_refs( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t entity, - ecs_entity_t relationship); +#endif -int flecs_json_serialize_alerts( - const ecs_world_t *world, - ecs_strbuf_t *buf, - ecs_entity_t entity); +/** + * @file addons/script/expr_ast.h + * @brief Script expression AST. + */ -bool flecs_json_is_builtin( - ecs_id_t id); +#ifndef FLECS_SCRIPT_EXPR_AST_H +#define FLECS_SCRIPT_EXPR_AST_H -#endif +#define FLECS_EXPR_SMALL_DATA_SIZE (24) -#endif /* FLECS_JSON_PRIVATE_H */ +typedef enum ecs_expr_node_kind_t { + EcsExprValue, + EcsExprInterpolatedString, + EcsExprInitializer, + EcsExprEmptyInitializer, + EcsExprUnary, + EcsExprBinary, + EcsExprIdentifier, + EcsExprVariable, + EcsExprGlobalVariable, + EcsExprFunction, + EcsExprMethod, + EcsExprMember, + EcsExprElement, + EcsExprComponent, + EcsExprCast, + EcsExprCastNumber, + EcsExprMatch +} ecs_expr_node_kind_t; -#include +struct ecs_expr_node_t { + ecs_expr_node_kind_t kind; + ecs_entity_t type; + const ecs_type_info_t *type_info; + const char *pos; +}; -#ifdef FLECS_JSON +typedef struct ecs_expr_value_node_t { + ecs_expr_node_t node; + void *ptr; + ecs_expr_small_value_t storage; +} ecs_expr_value_node_t; -typedef struct { - ecs_allocator_t *a; - ecs_vec_t table_type; - ecs_vec_t remove_ids; - ecs_map_t anonymous_ids; - ecs_map_t missing_reflection; - const char *expr; -} ecs_from_json_ctx_t; +typedef struct ecs_expr_interpolated_string_t { + ecs_expr_node_t node; + char *value; /* modified by parser */ + char *buffer; /* for storing expr tokens */ + ecs_size_t buffer_size; + ecs_vec_t fragments; /* vec */ + ecs_vec_t expressions; /* vec */ +} ecs_expr_interpolated_string_t; -static -void flecs_from_json_ctx_init( - ecs_allocator_t *a, - ecs_from_json_ctx_t *ctx) -{ - ctx->a = a; - ecs_vec_init_t(a, &ctx->table_type, ecs_id_t, 0); - ecs_vec_init_t(a, &ctx->remove_ids, ecs_id_t, 0); - ecs_map_init(&ctx->anonymous_ids, a); - ecs_map_init(&ctx->missing_reflection, a); -} +typedef struct ecs_expr_initializer_element_t { + const char *member; + ecs_expr_node_t *value; + uintptr_t offset; + ecs_token_kind_t operator; +} ecs_expr_initializer_element_t; -static -void flecs_from_json_ctx_fini( - ecs_from_json_ctx_t *ctx) -{ - ecs_vec_fini_t(ctx->a, &ctx->table_type, ecs_record_t*); - ecs_vec_fini_t(ctx->a, &ctx->remove_ids, ecs_record_t*); - ecs_map_fini(&ctx->anonymous_ids); - ecs_map_fini(&ctx->missing_reflection); -} +typedef struct ecs_expr_initializer_t { + ecs_expr_node_t node; + ecs_vec_t elements; + const ecs_type_info_t *type_info; + bool is_collection; + bool is_dynamic; +} ecs_expr_initializer_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(world); - } -} +typedef struct ecs_expr_variable_t { + ecs_expr_node_t node; + const char *name; + ecs_value_t global_value; /* Only set for global variables */ + int32_t sp; /* For fast variable lookups */ +} ecs_expr_variable_t; -static -void flecs_json_missing_reflection( - ecs_world_t *world, - ecs_id_t id, - const char *json, - ecs_from_json_ctx_t *ctx, - const ecs_from_json_desc_t *desc) -{ - if (!desc->strict || ecs_map_get(&ctx->missing_reflection, id)) { - return; - } +typedef struct ecs_expr_identifier_t { + ecs_expr_node_t node; + const char *value; + ecs_expr_node_t *expr; +} ecs_expr_identifier_t; - /* Don't spam log when multiple values of a type can't be deserialized */ - ecs_map_ensure(&ctx->missing_reflection, id); +typedef struct ecs_expr_unary_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_token_kind_t operator; +} ecs_expr_unary_t; - char *id_str = ecs_id_str(world, id); - ecs_parser_error(desc->name, desc->expr, json - desc->expr, - "missing reflection for '%s'", id_str); - ecs_os_free(id_str); -} +typedef struct ecs_expr_binary_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *right; + ecs_token_kind_t operator; +} ecs_expr_binary_t; -static -ecs_entity_t flecs_json_lookup( - ecs_world_t *world, - ecs_entity_t parent, - const char *name, - const ecs_from_json_desc_t *desc) -{ - ecs_entity_t scope = 0; - if (parent) { - scope = ecs_set_scope(world, parent); - } +typedef struct ecs_expr_member_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + const char *member_name; + uintptr_t offset; +} ecs_expr_member_t; - ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); - if (parent) { - ecs_set_scope(world, scope); - } +typedef struct ecs_expr_function_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_initializer_t *args; + const char *function_name; + ecs_function_calldata_t calldata; +} ecs_expr_function_t; - return result; -} +typedef struct ecs_expr_element_t { + ecs_expr_node_t node; + ecs_expr_node_t *left; + ecs_expr_node_t *index; + ecs_size_t elem_size; +} ecs_expr_element_t; -static -void flecs_json_mark_reserved( - ecs_map_t *anonymous_ids, - ecs_entity_t e) -{ - ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); - ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); - reserved[0] = 0; -} +typedef struct ecs_expr_component_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_id_t component; +} ecs_expr_component_t; -static -ecs_entity_t flecs_json_ensure_entity( - ecs_world_t *world, - const char *name, - ecs_map_t *anonymous_ids) -{ - ecs_entity_t e = 0; +typedef struct ecs_expr_cast_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; +} ecs_expr_cast_t; - if (flecs_name_is_id(name)) { - /* Anonymous entity, find or create mapping to new id */ - ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(&name[1])); - 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); +typedef struct ecs_expr_match_element_t { + ecs_expr_node_t *compare; + ecs_expr_node_t *expr; +} ecs_expr_match_element_t; - /* 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) && !ecs_get_name(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 - * Also don't use existing id if the existing entity is not - * anonymous. */ - deser_id[0] = ser_id; - ecs_make_alive(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); +typedef struct ecs_expr_match_t { + ecs_expr_node_t node; + ecs_expr_node_t *expr; + ecs_vec_t elements; + ecs_expr_match_element_t any; +} ecs_expr_match_t; - /* Mark new id as reserved */ - flecs_json_mark_reserved(anonymous_ids, deser_id[0]); - } - } +ecs_expr_value_node_t* flecs_expr_value_from( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); - 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_expr_variable_t* flecs_expr_variable_from( + ecs_script_t *script, + ecs_expr_node_t *node, + const char *name); - return e; -} +ecs_expr_value_node_t* flecs_expr_bool( + ecs_parser_t *parser, + bool value); -static -bool flecs_json_add_id_to_type( - ecs_id_t id) -{ - if (id == ecs_pair_t(EcsIdentifier, EcsName)) { - return false; - } - if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf) { - return false; - } - return true; -} +ecs_expr_value_node_t* flecs_expr_int( + ecs_parser_t *parser, + int64_t value); -static -const char* flecs_json_deser_tags( - ecs_world_t *world, - ecs_entity_t e, - const char *json, - const ecs_from_json_desc_t *desc, - ecs_from_json_ctx_t *ctx) -{ - ecs_json_token_t token_kind; - char token[ECS_MAX_TOKEN_SIZE]; +ecs_expr_value_node_t* flecs_expr_uint( + ecs_parser_t *parser, + uint64_t value); - const char *expr = ctx->expr, *lah; +ecs_expr_value_node_t* flecs_expr_float( + ecs_parser_t *parser, + double value); - json = flecs_json_expect(json, JsonArrayOpen, token, desc); - if (!json) { - goto error; - } +ecs_expr_value_node_t* flecs_expr_string( + ecs_parser_t *parser, + const char *value); - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonArrayClose) { - json = lah; - goto end; - } +ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( + ecs_parser_t *parser, + const char *value); - do { - char *str = NULL; - json = flecs_json_expect_string(json, token, &str, desc); - if (!json) { - goto error; - } +ecs_expr_value_node_t* flecs_expr_entity( + ecs_parser_t *parser, + ecs_entity_t value); - ecs_entity_t tag = flecs_json_lookup(world, 0, str, desc); - if (flecs_json_add_id_to_type(tag)) { - ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = tag; - } +ecs_expr_initializer_t* flecs_expr_initializer( + ecs_parser_t *parser); - ecs_add_id(world, e, tag); +ecs_expr_identifier_t* flecs_expr_identifier( + ecs_parser_t *parser, + const char *value); - if (str != token) { - ecs_os_free(str); - } +ecs_expr_variable_t* flecs_expr_variable( + ecs_parser_t *parser, + const char *value); - json = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonComma) { - break; - } - } while (true); +ecs_expr_unary_t* flecs_expr_unary( + ecs_parser_t *parser); - if (token_kind != JsonArrayClose) { - ecs_parser_error(NULL, expr, json - expr, "expected }"); - goto error; - } +ecs_expr_binary_t* flecs_expr_binary( + ecs_parser_t *parser); +ecs_expr_member_t* flecs_expr_member( + ecs_parser_t *parser); -end: - return json; -error: - return NULL; -} +ecs_expr_function_t* flecs_expr_function( + ecs_parser_t *parser); -static -const char* flecs_json_deser_pairs( - ecs_world_t *world, - ecs_entity_t e, - const char *json, - const ecs_from_json_desc_t *desc, - ecs_from_json_ctx_t *ctx) -{ - ecs_json_token_t token_kind; - char token[ECS_MAX_TOKEN_SIZE]; +ecs_expr_element_t* flecs_expr_element( + ecs_parser_t *parser); - const char *expr = ctx->expr, *lah; +ecs_expr_match_t* flecs_expr_match( + ecs_parser_t *parser); - json = flecs_json_expect(json, JsonObjectOpen, token, desc); - if (!json) { - goto error; - } +ecs_expr_cast_t* flecs_expr_cast( + ecs_script_t *script, + ecs_expr_node_t *node, + ecs_entity_t type); - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonObjectClose) { - json = lah; - goto end; - } +#endif - do { - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } +/** + * @file addons/script/exor_visit.h + * @brief Script AST visitor utilities. + */ - ecs_entity_t rel = flecs_json_lookup(world, 0, token, desc); +#ifndef FLECS_EXPR_SCRIPT_VISIT_H +#define FLECS_EXPR_SCRIPT_VISIT_H - bool multiple_targets = false; +#define flecs_expr_visit_error(script, node, ...) \ + ecs_parser_error( \ + script->name, script->code, \ + ((const ecs_expr_node_t*)node)->pos - script->code, \ + __VA_ARGS__); - do { - json = flecs_json_parse(json, &token_kind, token); - - if (token_kind == JsonString) { - ecs_entity_t tgt = flecs_json_lookup(world, 0, token, desc); - ecs_id_t id = ecs_pair(rel, tgt); - ecs_add_id(world, e, id); - if (flecs_json_add_id_to_type(id)) { - ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; - } - } else if (token_kind == JsonLargeString) { - ecs_strbuf_t large_token = ECS_STRBUF_INIT; - json = flecs_json_parse_large_string(json, &large_token); - if (!json) { - break; - } +int flecs_expr_visit_type( + ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_expr_eval_desc_t *desc); - char *str = ecs_strbuf_get(&large_token); - ecs_entity_t tgt = flecs_json_lookup(world, 0, str, desc); - ecs_os_free(str); - ecs_id_t id = ecs_pair(rel, tgt); - ecs_add_id(world, e, id); - if (flecs_json_add_id_to_type(id)) { - ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; - } - } else if (token_kind == JsonArrayOpen) { - if (multiple_targets) { - ecs_parser_error(NULL, expr, json - expr, - "expected string"); - goto error; - } +int flecs_expr_visit_fold( + ecs_script_t *script, + ecs_expr_node_t **node, + const ecs_expr_eval_desc_t *desc); - multiple_targets = true; - } else if (token_kind == JsonArrayClose) { - if (!multiple_targets) { - ecs_parser_error(NULL, expr, json - expr, - "unexpected ]"); - goto error; - } +int flecs_expr_visit_eval( + const ecs_script_t *script, + ecs_expr_node_t *node, + const ecs_expr_eval_desc_t *desc, + ecs_value_t *out); - multiple_targets = false; - } else if (token_kind == JsonComma) { - if (!multiple_targets) { - ecs_parser_error(NULL, expr, json - expr, - "unexpected ,"); - goto error; - } - } else { - ecs_parser_error(NULL, expr, json - expr, - "expected array or string"); - goto error; - } - } while (multiple_targets); +void flecs_expr_visit_free( + ecs_script_t *script, + ecs_expr_node_t *node); - json = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonComma) { - break; - } - } while (true); +ecs_script_var_t flecs_expr_find_var( + ecs_script_t *script, + const char *name); - if (token_kind != JsonObjectClose) { - ecs_parser_error(NULL, expr, json - expr, "expected }"); - goto error; - } +#endif -end: - return json; -error: - return NULL; -} -static -const char* flecs_json_deser_components( +int flecs_value_copy_to( ecs_world_t *world, - ecs_entity_t e, - const char *json, - const ecs_from_json_desc_t *desc, - ecs_from_json_ctx_t *ctx) -{ - ecs_json_token_t token_kind; - char token[ECS_MAX_TOKEN_SIZE]; - - const char *expr = ctx->expr, *lah; + ecs_value_t *dst, + const ecs_expr_value_t *src); - json = flecs_json_expect(json, JsonObjectOpen, token, desc); - if (!json) { - goto error; - } +int flecs_value_move_to( + ecs_world_t *world, + ecs_value_t *dst, + ecs_value_t *src); - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonObjectClose) { - json = lah; - goto end; - } +int flecs_value_binary( + const ecs_script_t *script, + const ecs_value_t *left, + const ecs_value_t *right, + ecs_value_t *out, + ecs_token_kind_t operator); - do { - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } +int flecs_value_unary( + const ecs_script_t *script, + const ecs_value_t *expr, + ecs_value_t *out, + ecs_token_kind_t operator); - ecs_id_t id = 0; +const char* flecs_script_parse_expr( + ecs_parser_t *parser, + const char *pos, + ecs_token_kind_t left_oper, + ecs_expr_node_t **out); - if (token[0] != '(') { - id = flecs_json_lookup(world, 0, token, desc); - } else { - char token_buffer[256]; - ecs_term_t term = {0}; - if (!flecs_term_parse(world, NULL, token, &term, token_buffer)) { - goto error; - } +const char* flecs_script_parse_initializer( + ecs_parser_t *parser, + const char *pos, + char until, + ecs_expr_initializer_t **node_out); - ecs_assert(term.first.name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(term.second.name != NULL, ECS_INTERNAL_ERROR, NULL); +void flecs_expr_to_str_buf( + const ecs_world_t *world, + const ecs_expr_node_t *expr, + ecs_strbuf_t *buf, + bool colors); - ecs_entity_t rel = flecs_json_lookup( - world, 0, term.first.name, desc); - ecs_entity_t tgt = flecs_json_lookup( - world, 0, term.second.name, desc); - - id = ecs_pair(rel, tgt); - } +bool flecs_string_is_interpolated( + const char *str); - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonNull) { - ecs_entity_t type = ecs_get_typeid(world, id); - if (!type) { - flecs_json_missing_reflection(world, id, json, ctx, desc); - if (desc->strict) { - goto error; - } +char* flecs_string_escape( + char *str); - json = flecs_json_skip_object(json + 1, token, desc); - if (!json) { - goto error; - } - } else { - void *ptr = ecs_ensure_id(world, e, id); +bool flecs_value_is_0( + const ecs_value_t *value); - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonNull) { - const char *next = ecs_ptr_from_json( - world, type, ptr, json, desc); - if (!next) { - flecs_json_missing_reflection( - world, id, json, ctx, desc); - if (desc->strict) { - goto error; - } +bool flecs_expr_is_type_integer( + ecs_entity_t type); - json = flecs_json_skip_object(json + 1, token, desc); - if (!json) { - goto error; - } - } else { - json = next; - ecs_modified_id(world, e, id); - } - } else { - json = lah; - } - } - } else { - ecs_add_id(world, e, id); - json = lah; - } +bool flecs_expr_is_type_number( + ecs_entity_t type); - /* Don't add ids that have their own fields in serialized data. */ - if (flecs_json_add_id_to_type(id)) { - ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; - } +#endif - json = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonComma) { - break; - } - } while (true); +/** + * @file addons/script/visit.h + * @brief Script AST visitor utilities. + */ - if (token_kind != JsonObjectClose) { - ecs_parser_error(NULL, expr, json - expr, "expected }"); - goto error; - } +#ifndef FLECS_SCRIPT_VISIT_H +#define FLECS_SCRIPT_VISIT_H -end: - return json; -error: - return NULL; -} +typedef struct ecs_script_visit_t ecs_script_visit_t; -static -const char* flecs_entity_from_json( - ecs_world_t *world, - ecs_entity_t e, - const char *json, - const ecs_from_json_desc_t *desc, - ecs_from_json_ctx_t *ctx) -{ - ecs_json_token_t token_kind; - char token[ECS_MAX_TOKEN_SIZE]; +typedef int (*ecs_visit_action_t)( + ecs_script_visit_t *visitor, + ecs_script_node_t *node); - const char *expr = ctx->expr, *lah; +struct ecs_script_visit_t { + ecs_script_impl_t *script; + ecs_visit_action_t visit; + ecs_script_node_t* nodes[256]; + ecs_script_node_t *prev, *next; + int32_t depth; +}; - ecs_vec_clear(&ctx->table_type); +int ecs_script_visit_( + ecs_script_visit_t *visitor, + ecs_visit_action_t visit, + ecs_script_impl_t *script); - ecs_entity_t parent = 0; +#define ecs_script_visit(script, visitor, visit) \ + ecs_script_visit_((ecs_script_visit_t*)visitor,\ + (ecs_visit_action_t)visit,\ + script) - json = flecs_json_expect(json, JsonObjectOpen, token, desc); - if (!json) { - goto error; - } +int ecs_script_visit_node_( + ecs_script_visit_t *v, + ecs_script_node_t *node); - lah = flecs_json_parse(json, &token_kind, token); - if (!lah) { - goto error; - } +#define ecs_script_visit_node(visitor, node) \ + ecs_script_visit_node_((ecs_script_visit_t*)visitor, \ + (ecs_script_node_t*)node) - if (token_kind == JsonObjectClose) { - json = lah; - goto end; - } +int ecs_script_visit_scope_( + ecs_script_visit_t *v, + ecs_script_scope_t *node); - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } +#define ecs_script_visit_scope(visitor, node) \ + ecs_script_visit_scope_((ecs_script_visit_t*)visitor, node) - if (!ecs_os_strcmp(token, "parent")) { - char *str = NULL; - json = flecs_json_expect_string(json, token, &str, desc); - if (!json) { - goto error; - } +ecs_script_node_t* ecs_script_parent_node_( + ecs_script_visit_t *v); - parent = flecs_json_lookup(world, 0, str, desc); +#define ecs_script_parent_node(visitor) \ + ecs_script_parent_node_((ecs_script_visit_t*)visitor) - if (e) { - ecs_add_pair(world, e, EcsChildOf, parent); - } +ecs_script_scope_t* ecs_script_current_scope_( + ecs_script_visit_t *v); - ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = - ecs_pair(EcsChildOf, parent); +#define ecs_script_current_scope(visitor) \ + ecs_script_current_scope_((ecs_script_visit_t*)visitor) - if (str != token) ecs_os_free(str); +ecs_script_node_t* ecs_script_parent_( + ecs_script_visit_t *v, + ecs_script_node_t *node); - json = flecs_json_parse_next_member(json, token, &token_kind, desc); - if (!json) { - goto error; - } - if (token_kind == JsonObjectClose) { - goto end; - } - } +#define ecs_script_parent(visitor, node) \ + ecs_script_parent_((ecs_script_visit_t*)visitor, (ecs_script_node_t*)node) - if (!ecs_os_strcmp(token, "name")) { - char *str = NULL; - json = flecs_json_expect_string(json, token, &str, desc); - if (!json) { - goto error; - } +ecs_script_node_t* ecs_script_next_node_( + ecs_script_visit_t *v); - if (!e) { - e = flecs_json_lookup(world, parent, str, desc); - } else { - ecs_set_name(world, e, str); - } +#define ecs_script_next_node(visitor) \ + ecs_script_next_node_((ecs_script_visit_t*)visitor) - if (str[0] != '#') { - ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = - ecs_pair_t(EcsIdentifier, EcsName); - } +int32_t ecs_script_node_line_number_( + ecs_script_impl_t *script, + ecs_script_node_t *node); - if (str != token) ecs_os_free(str); +#define ecs_script_node_line_number(script, node) \ + ecs_script_node_line_number_(script, (ecs_script_node_t*)node) - json = flecs_json_parse_next_member(json, token, &token_kind, desc); - if (!json) { - goto error; - } - if (token_kind == JsonObjectClose) { - goto end; - } - } +#endif - if (!ecs_os_strcmp(token, "id")) { - json = flecs_json_parse(json, &token_kind, token); - if (!json) { - goto error; - } +/** + * @file addons/script/visit_eval.h + * @brief Script evaluation visitor. + */ - uint64_t id; - if (token_kind == JsonNumber || token_kind == JsonLargeInt) { - id = flecs_ito(uint64_t, atoll(token)); - } else { - ecs_parser_error(NULL, expr, json - expr, "expected entity id"); - goto error; - } +#ifndef FLECS_SCRIPT_VISIT_EVAL_H +#define FLECS_SCRIPT_VISIT_EVAL_H - if (!e) { - char name[32]; - ecs_os_snprintf(name, 32, "#%u", (uint32_t)id); - e = flecs_json_lookup(world, 0, name, desc); - } else { - /* If we already have an id, ignore explicit id */ - } +typedef struct ecs_script_eval_visitor_t { + ecs_script_visit_t base; + ecs_world_t *world; + ecs_script_runtime_t *r; + ecs_script_template_t *template; /* Set when creating template */ + ecs_entity_t template_entity; /* Set when creating template instance */ + ecs_entity_t module; + ecs_entity_t parent; + ecs_script_entity_t *entity; + ecs_entity_t with_relationship; + int32_t with_relationship_sp; + bool is_with_scope; + bool dynamic_variable_binding; + ecs_script_vars_t *vars; +} ecs_script_eval_visitor_t; - json = flecs_json_parse_next_member(json, token, &token_kind, desc); - if (!json) { - goto error; - } - if (token_kind == JsonObjectClose) { - goto end; - } - } +void flecs_script_eval_error_( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node, + const char *fmt, + ...); - if (!e) { - ecs_parser_error(NULL, expr, json - expr, "failed to create entity"); - return NULL; - } +#define flecs_script_eval_error(v, node, ...)\ + flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) - if (!ecs_os_strcmp(token, "has_alerts")) { - json = flecs_json_expect(json, JsonBoolean, token, desc); - if (!json) { - goto error; - } +int flecs_script_find_entity( + ecs_script_eval_visitor_t *v, + ecs_entity_t from, + const char *path, + int32_t *frame_offset, + ecs_entity_t *out); - json = flecs_json_parse_next_member(json, token, &token_kind, desc); - if (!json) { - goto error; - } - if (token_kind == JsonObjectClose) { - goto end; - } - } +ecs_script_var_t* flecs_script_find_var( + const ecs_script_vars_t *vars, + const char *name, + int32_t *frame_offset); - if (!ecs_os_strcmp(token, "tags")) { - json = flecs_json_deser_tags(world, e, json, desc, ctx); - if (!json) { - goto error; - } +ecs_entity_t flecs_script_create_entity( + ecs_script_eval_visitor_t *v, + const char *name); - json = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonObjectClose) { - goto end; - } else if (token_kind != JsonComma) { - ecs_parser_error(NULL, expr, json - expr, "expected ','"); - goto error; - } +const ecs_type_info_t* flecs_script_get_type_info( + ecs_script_eval_visitor_t *v, + void *node, + ecs_id_t id); - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } - } +int flecs_script_eval_expr( + ecs_script_eval_visitor_t *v, + ecs_expr_node_t **expr_ptr, + ecs_value_t *value); - if (!ecs_os_strcmp(token, "pairs")) { - json = flecs_json_deser_pairs(world, e, json, desc, ctx); - if (!json) { - goto error; - } +void flecs_script_eval_visit_init( + const ecs_script_impl_t *script, + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); - json = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonObjectClose) { - goto end; - } else if (token_kind != JsonComma) { - ecs_parser_error(NULL, expr, json - expr, "expected ','"); - goto error; - } +void flecs_script_eval_visit_fini( + ecs_script_eval_visitor_t *v, + const ecs_script_eval_desc_t *desc); - json = flecs_json_expect_member(json, token, desc); - if (!json) { - goto error; - } - } +int flecs_script_eval_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node); - if (!ecs_os_strcmp(token, "components")) { - json = flecs_json_deser_components(world, e, json, desc, ctx); - if (!json) { - goto error; - } - } +int flecs_script_check_node( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node); - json = flecs_json_expect(json, JsonObjectClose, token, desc); - if (!json) { - goto error; - } +int flecs_script_check_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); - ecs_record_t *r = flecs_entities_get(world, e); - ecs_table_t *table = r ? r->table : NULL; - if (table) { - ecs_id_t *ids = ecs_vec_first(&ctx->table_type); - int32_t ids_count = ecs_vec_count(&ctx->table_type); - qsort(ids, flecs_itosize(ids_count), sizeof(ecs_id_t), flecs_id_qsort_cmp); +/* Functions shared between check and eval visitor */ - ecs_table_t *dst_table = ecs_table_find(world, - ecs_vec_first(&ctx->table_type), ecs_vec_count(&ctx->table_type)); - if (dst_table->type.count == 0) { - dst_table = NULL; - } +int flecs_script_eval_scope( + ecs_script_eval_visitor_t *v, + ecs_script_scope_t *node); - /* Entity had existing components that weren't in the serialized data */ - if (table != dst_table) { - ecs_assert(ecs_get_target(world, e, EcsChildOf, 0) != EcsFlecsCore, - ECS_INVALID_OPERATION, "%s\n[%s] => \n[%s]", - ecs_get_path(world, e), - ecs_table_str(world, table), - ecs_table_str(world, dst_table)); +int flecs_script_eval_id( + ecs_script_eval_visitor_t *v, + void *node, + ecs_script_id_t *id); - if (!dst_table) { - ecs_clear(world, e); - } else { - ecs_vec_clear(&ctx->remove_ids); +int flecs_script_eval_using( + ecs_script_eval_visitor_t *v, + ecs_script_using_t *node); - ecs_type_t *type = &table->type, *dst_type = &dst_table->type; - int32_t i = 0, i_dst = 0; - for (; (i_dst < dst_type->count) && (i < type->count); ) { - ecs_id_t id = type->array[i], dst_id = dst_type->array[i_dst]; +int flecs_script_eval_const( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node); - if (dst_id > id) { - ecs_vec_append_t( - ctx->a, &ctx->remove_ids, ecs_id_t)[0] = id; - } +ecs_entity_t flecs_script_find_entity_action( + const ecs_world_t *world, + const char *path, + void *ctx); - i_dst += dst_id <= id; - i += dst_id >= id; - } +#endif - ecs_type_t removed = { - .array = ecs_vec_first(&ctx->remove_ids), - .count = ecs_vec_count(&ctx->remove_ids) - }; +/** + * @file addons/script/template.h + * @brief Script template implementation. + */ - ecs_commit(world, e, r, dst_table, NULL, &removed); - } +#ifndef FLECS_SCRIPT_TEMPLATE_H +#define FLECS_SCRIPT_TEMPLATE_H - ecs_assert(ecs_get_table(world, e) == dst_table, - ECS_INTERNAL_ERROR, NULL); - } - } +extern ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); -end: - return json; -error: - return NULL; -} +struct ecs_script_template_t { + /* Template handle */ + ecs_entity_t entity; -const char* ecs_entity_from_json( - ecs_world_t *world, - ecs_entity_t e, - const char *json, - const ecs_from_json_desc_t *desc_arg) -{ - ecs_from_json_desc_t desc = {0}; - if (desc_arg) { - desc = *desc_arg; - } + /* Template AST node */ + ecs_script_template_node_t *node; - desc.expr = json; + /* Hoisted using statements */ + ecs_vec_t using_; - ecs_allocator_t *a = &world->allocator; - ecs_from_json_ctx_t ctx; - flecs_from_json_ctx_init(a, &ctx); - ctx.expr = json; + /* Hoisted variables */ + ecs_script_vars_t *vars; - if (!desc.lookup_action) { - desc.lookup_action = (ecs_entity_t(*)( - const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; - desc.lookup_ctx = &ctx.anonymous_ids; - } + /* Default values for props */ + ecs_vec_t prop_defaults; - json = flecs_entity_from_json(world, e, json, &desc, &ctx); + /* Type info for template component */ + const ecs_type_info_t *type_info; +}; - flecs_from_json_ctx_fini(&ctx); - return json; -} +#define ECS_TEMPLATE_SMALL_SIZE (36) -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]; +/* Event used for deferring template instantiation */ +typedef struct EcsScriptTemplateSetEvent { + ecs_entity_t template_entity; + ecs_entity_t *entities; + void *data; + int32_t count; - ecs_from_json_desc_t desc = {0}; - if (desc_arg) { - desc = *desc_arg; - } + /* Storage for small template types */ + int64_t _align; /* Align data storage to 8 bytes */ + char data_storage[ECS_TEMPLATE_SMALL_SIZE]; + ecs_entity_t entity_storage; +} EcsScriptTemplateSetEvent; - desc.expr = json; +int flecs_script_eval_template( + ecs_script_eval_visitor_t *v, + ecs_script_template_node_t *template); - ecs_allocator_t *a = &world->allocator; - ecs_from_json_ctx_t ctx; - flecs_from_json_ctx_init(a, &ctx); +ecs_script_template_t* flecs_script_template_init( + ecs_script_impl_t *script); - const char *expr = json, *lah; - ctx.expr = expr; +void flecs_script_template_fini( + ecs_script_impl_t *script, + ecs_script_template_t *template); - if (!desc.lookup_action) { - desc.lookup_action = (ecs_entity_t(*)( - const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; - desc.lookup_ctx = &ctx.anonymous_ids; - } +void flecs_script_template_import( + ecs_world_t *world); - json = flecs_json_expect(json, JsonObjectOpen, token, &desc); - if (!json) { - goto error; - } +#endif - json = flecs_json_expect_member_name(json, token, "results", &desc); - if (!json) { - goto error; - } - json = flecs_json_expect(json, JsonArrayOpen, token, &desc); - if (!json) { - goto error; - } +struct ecs_script_runtime_t { + ecs_allocator_t allocator; + ecs_expr_stack_t expr_stack; + ecs_stack_t stack; + ecs_vec_t using; + ecs_vec_t with; + ecs_vec_t with_type_info; + ecs_vec_t annot; +}; - lah = flecs_json_parse(json, &token_kind, token); - if (token_kind == JsonArrayClose) { - json = lah; - goto end; - } +ecs_script_t* flecs_script_new( + ecs_world_t *world); - do { - json = flecs_entity_from_json(world, 0, json, &desc, &ctx); - if (!json) { - goto error; - } +ecs_script_scope_t* flecs_script_scope_new( + ecs_parser_t *parser); - json = flecs_json_parse(json, &token_kind, token); - if (token_kind != JsonComma) { - if (token_kind != JsonArrayClose) { - ecs_parser_error(NULL, expr, json - expr, "expected ']'"); - goto error; - } - break; - } - } while (true); +int flecs_script_visit_free( + ecs_script_t *script); -end: - json = flecs_json_expect(json, JsonObjectClose, token, &desc); +ecs_script_vars_t* flecs_script_vars_push( + ecs_script_vars_t *parent, + ecs_stack_t *stack, + ecs_allocator_t *allocator); - flecs_from_json_ctx_fini(&ctx); - return json; -error: - flecs_from_json_ctx_fini(&ctx); - return NULL; -} +ecs_script_runtime_t* flecs_script_runtime_get( + ecs_world_t *world); -const char* ecs_world_from_json_file( - ecs_world_t *world, - const char *filename, - const ecs_from_json_desc_t *desc) -{ - char *json = flecs_load_from_file(filename); - if (!json) { - ecs_err("file not found: %s", filename); - return NULL; - } +void flecs_script_register_builtin_functions( + ecs_world_t *world); - const char *result = ecs_world_from_json(world, json, desc); - ecs_os_free(json); - return result; -} +void flecs_function_import( + ecs_world_t *world); -#endif +int flecs_script_check( + const ecs_script_t *script, + const ecs_script_eval_desc_t *desc); -/** - * @file addons/json/deserialize_value.c - * @brief Deserialize JSON strings into (component) values. - */ +#endif // FLECS_SCRIPT +#endif // FLECS_SCRIPT_PRIVATE_H #include @@ -43397,6 +43884,16 @@ int flecs_expr_ser_primitive( void flecs_rtt_init_default_hooks( ecs_iter_t *it); +int ecs_compare_string( + const void *str_a, + const void *str_b, + const ecs_type_info_t *ti); + +bool ecs_equals_string( + const void *str_a, + const void *str_b, + const ecs_type_info_t *ti); + #endif #endif @@ -43416,13 +43913,13 @@ int ecs_entity_to_json_buf( return -1; } - /* Cache id record for flecs.doc ids */ + /* Cache component record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC - ser_ctx.idr_doc_name = flecs_id_record_get(world, + ser_ctx.idr_doc_name = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsName)); - ser_ctx.idr_doc_color = flecs_id_record_get(world, + ser_ctx.idr_doc_color = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif @@ -43870,13 +44367,13 @@ int ecs_iter_to_json_buf( { ecs_world_t *world = it->real_world; - /* Cache id record for flecs.doc ids */ + /* Cache component record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC - ser_ctx.idr_doc_name = flecs_id_record_get(world, + ser_ctx.idr_doc_name = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsName)); - ser_ctx.idr_doc_color = flecs_id_record_get(world, + ser_ctx.idr_doc_color = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif @@ -44021,12 +44518,12 @@ int flecs_json_serialize_matches( flecs_json_memberl(buf, "matches"); flecs_json_array_push(buf); - ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair_t(EcsPoly, EcsQuery)); - if (idr) { + if (cdr) { ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { + if (cdr && flecs_table_cache_iter((ecs_table_cache_t*)cdr, &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; @@ -44072,9 +44569,9 @@ static int flecs_json_serialize_refs_idr( const ecs_world_t *world, ecs_strbuf_t *buf, - ecs_id_record_t *idr) + ecs_component_record_t *cdr) { - char *id_str = ecs_id_str(world, ecs_pair_first(world, idr->id)); + char *id_str = ecs_id_str(world, ecs_pair_first(world, cdr->id)); flecs_json_member(buf, id_str); ecs_os_free(id_str); @@ -44082,7 +44579,7 @@ int flecs_json_serialize_refs_idr( flecs_json_array_push(buf); ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { + if (cdr && flecs_table_cache_all_iter((ecs_table_cache_t*)cdr, &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; @@ -44110,17 +44607,17 @@ int flecs_json_serialize_refs( flecs_json_memberl(buf, "refs"); flecs_json_object_push(buf); - ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair(relationship, entity)); - if (idr) { + if (cdr) { if (relationship == EcsWildcard) { - ecs_id_record_t *cur = idr; - while ((cur = cur->second.next)) { + ecs_component_record_t *cur = cdr; + while ((cur = flecs_component_second_next(cur))) { flecs_json_serialize_refs_idr(world, buf, cur); } } else { - flecs_json_serialize_refs_idr(world, buf, idr); + flecs_json_serialize_refs_idr(world, buf, cdr); } } @@ -44384,8 +44881,8 @@ int flecs_json_serialize_iter_result( /* Get path to parent once for entire table */ if (table->flags & EcsTableHasChildOf) { - const ecs_table_record_t *tr = flecs_table_record_get( - world, table, ecs_pair(EcsChildOf, EcsWildcard)); + const ecs_table_record_t *tr = flecs_component_get_table( + world->idr_childof_wildcard, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t parent = ecs_pair_second( world, table->type.array[tr->index]); @@ -44785,9 +45282,9 @@ bool flecs_json_serialize_table_type_info( int32_t i, type_count = table->type.count; for (i = 0; i < type_count; i ++) { const ecs_table_record_t *tr = &table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; ecs_id_t id = table->type.array[i]; - if (!(idr->flags & EcsIdIsSparse) && + if (!(cdr->flags & EcsIdIsSparse) && (!table->column_map || (table->column_map[i] == -1))) { continue; @@ -44799,7 +45296,7 @@ bool flecs_json_serialize_table_type_info( } } - const ecs_type_info_t *ti = idr->type_info; + const ecs_type_info_t *ti = cdr->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_next(buf); @@ -44846,14 +45343,14 @@ bool flecs_json_serialize_table_tags( } const ecs_table_record_t *tr = &trs[f]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { + if (!(cdr->flags & EcsIdOnInstantiateInherit)) { continue; } } - if (idr->flags & EcsIdIsSparse) { + if (cdr->flags & EcsIdIsSparse) { continue; } @@ -44913,14 +45410,14 @@ bool flecs_json_serialize_table_pairs( } const ecs_table_record_t *tr = &trs[f]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { + if (!(cdr->flags & EcsIdOnInstantiateInherit)) { continue; } } - if (idr->flags & EcsIdIsSparse) { + if (cdr->flags & EcsIdIsSparse) { continue; } @@ -45011,10 +45508,10 @@ int flecs_json_serialize_table_components( void *ptr; const ecs_table_record_t *tr = &table->_->records[i]; - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_component_record_t *cdr = (ecs_component_record_t*)tr->hdr.cache; if (src_table) { - if (!(idr->flags & EcsIdOnInstantiateInherit)) { + if (!(cdr->flags & EcsIdOnInstantiateInherit)) { continue; } } @@ -45026,12 +45523,12 @@ int flecs_json_serialize_table_components( ti = column->ti; ptr = ECS_ELEM(column->data, ti->size, row); } else { - if (!(idr->flags & EcsIdIsSparse)) { + if (!(cdr->flags & EcsIdIsSparse)) { continue; } ecs_entity_t e = ecs_table_entities(table)[row]; - ptr = flecs_sparse_get_any(idr->sparse, 0, e); - ti = idr->type_info; + ptr = flecs_sparse_get_any(cdr->sparse, 0, e); + ti = cdr->type_info; } if (!ptr) { @@ -45100,7 +45597,7 @@ bool flecs_json_serialize_table_inherited_type( return false; } - const ecs_table_record_t *tr = flecs_id_record_get_table( + const ecs_table_record_t *tr = flecs_component_get_table( world->idr_isa_wildcard, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* Table has IsA flag */ @@ -47040,9 +47537,7 @@ ecs_entity_t ecs_struct_init( goto error; } - ecs_entity_t m = ecs_entity(world, { - .name = m_desc->name - }); + ecs_entity_t m = ecs_new_from_path(world, t, m_desc->name); ecs_set(world, m, EcsMember, { .type = m_desc->type, @@ -48129,7 +48624,7 @@ int ecs_meta_from_desc( #include #ifdef FLECS_META -#ifdef FLECS_SCRIPT +#ifdef FLECS_QUERY_DSL #endif static @@ -49590,7 +50085,7 @@ int ecs_meta_set_string( break; } case EcsOpId: { - #ifdef FLECS_SCRIPT + #ifdef FLECS_QUERY_DSL ecs_id_t id = 0; if (flecs_id_parse(cursor->world, NULL, value, &id) == NULL) { goto error; @@ -50375,6 +50870,17 @@ void flecs_meta_import_core_definitions( } }); + ecs_struct(world, { + .entity = ecs_id(EcsIdentifier), + .members = { + { + .name = "value", + .type = ecs_id(ecs_string_t), + .offset = offsetof(EcsIdentifier, value) + } + } + }); + /* Define const string as an opaque type that maps to string This enables reflection for strings that are in .rodata, (read-only) so that the meta add-on does not try to free them. @@ -50389,7 +50895,11 @@ void flecs_meta_import_core_definitions( }), .type = { .size = ECS_SIZEOF(const char*), - .alignment = ECS_ALIGNOF(const char*) + .alignment = ECS_ALIGNOF(const char*), + .hooks = { + .cmp = ecs_compare_string, + .equals = ecs_equals_string + } } }), .type = { @@ -50670,6 +51180,386 @@ static ECS_DTOR(ecs_string_t, ptr, { *(ecs_string_t*)ptr = NULL; }) +/* Primitive comparers */ + +static +int ecs_compare_bool( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_bool_t*)a_ptr)) - (int)(*((const ecs_bool_t*)b_ptr)); +} + +static +bool ecs_equals_bool( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_bool(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_char( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_char_t*)a_ptr)) - (int)(*((const ecs_char_t*)b_ptr)); +} + +static +bool ecs_equals_char( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_char(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_byte( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_byte_t*)a_ptr)) - (int)(*((const ecs_byte_t*)b_ptr)); +} + +static +bool ecs_equals_byte( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_byte(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_u8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_u8_t*)a_ptr)) - (int)(*((const ecs_u8_t*)b_ptr)); +} + +static +bool ecs_equals_u8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_u8(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_u16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_u16_t*)a_ptr)) - (int)(*((const ecs_u16_t*)b_ptr)); +} + +static +bool ecs_equals_u16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_u16(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_u32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_u32_t a = *((const ecs_u32_t*)a_ptr); + ecs_u32_t b = *((const ecs_u32_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_u32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_u32(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_u64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_u64_t a = *((const ecs_u64_t*)a_ptr); + ecs_u64_t b = *((const ecs_u64_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_u64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_u64(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_uptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_uptr_t a = *((const ecs_uptr_t*)a_ptr); + ecs_uptr_t b = *((const ecs_uptr_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_uptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_uptr(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_i8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_i8_t*)a_ptr)) - + (int)(*((const ecs_i8_t*)b_ptr)); +} + +static +bool ecs_equals_i8( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_i8(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_i16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + return (int)(*((const ecs_i16_t*)a_ptr)) - + (int)(*((const ecs_i16_t*)b_ptr)); +} + +static +bool ecs_equals_i16( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_i16(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_i32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_i32_t a = *((const ecs_i32_t*)a_ptr); + ecs_i32_t b = *((const ecs_i32_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_i32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_i32(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_i64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_i64_t a = *((const ecs_i64_t*)a_ptr); + ecs_i64_t b = *((const ecs_i64_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_i64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_i64(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_iptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_iptr_t a = *((const ecs_iptr_t*)a_ptr); + ecs_iptr_t b = *((const ecs_iptr_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_iptr( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_iptr(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_f32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_f32_t a = *((const ecs_f32_t*)a_ptr); + ecs_f32_t b = *((const ecs_f32_t*)b_ptr); + if (a < b) return -1; + if (a > b) return 1; + return 0; +} + +static +bool ecs_equals_f32( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + /* intentional equal check as if it was an integer */ + return ecs_compare_u32(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_f64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_f64_t a = *((const ecs_f64_t*)a_ptr); + ecs_f64_t b = *((const ecs_f64_t*)b_ptr); + if (a < b) return -1; + if (a > b) return 1; + return 0; +} + +static +bool ecs_equals_f64( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + /* intentional equal check as if it was an integer */ + return ecs_compare_u64(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_entity( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_entity_t a = *((const ecs_entity_t*)a_ptr); + ecs_entity_t b = *((const ecs_entity_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_entity( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_entity(a_ptr, b_ptr, ti) == 0; +} + +static +int ecs_compare_id( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + (void)ti; + ecs_id_t a = *((const ecs_id_t*)a_ptr); + ecs_id_t b = *((const ecs_id_t*)b_ptr); + return (a > b) - (a < b); +} + +static +bool ecs_equals_id( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_id(a_ptr, b_ptr, ti) == 0; +} + +int ecs_compare_string( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) { + (void)ti; + const ecs_string_t str_a = *((const ecs_string_t *) a_ptr); + const ecs_string_t str_b = *((const ecs_string_t *) b_ptr); + if(str_a == str_b) { + return 0; + } + if(str_a == NULL) { + return -1; + } + if(str_b == NULL) { + return 1; + } + return ecs_os_strcmp(str_a, str_b); +} + +bool ecs_equals_string( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *ti) +{ + return ecs_compare_string(a_ptr, b_ptr, ti) == 0; +} + /* EcsTypeSerializer lifecycle */ @@ -50913,8 +51803,24 @@ int flecs_init_type( * serializers on uninitialized values. For runtime types (rtt), the default hooks are set by flecs_meta_rtt_init_default_hooks */ ecs_type_info_t *ti = flecs_type_info_ensure(world, type); - if (meta_type->existing && !ti->hooks.ctor) { - ti->hooks.ctor = flecs_default_ctor; + if (meta_type->existing) { + if(!ti->hooks.ctor) { + ti->hooks.ctor = flecs_default_ctor; + } + if(kind == EcsEnumType) { + /* Generate compare/equals hooks for enums, copying + the underlying type's hooks, which should be + any of the default primitive integral compare hooks, + i.e. ecs_compare_i8, _i16 _32... */ + const EcsEnum* enum_info = ecs_get(world, type, EcsEnum); + ecs_assert(enum_info != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_hooks_t *enum_hooks = ecs_get_hooks_id(world, enum_info->underlying_type); + ecs_assert(!(enum_hooks->flags & (ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)), ECS_INTERNAL_ERROR, NULL); + ti->hooks.cmp = enum_hooks->cmp; + ti->hooks.equals = enum_hooks->equals; + ti->hooks.flags &= ~(ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL); + ti->hooks.flags |= ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS; + } } } else { if (meta_type->kind != kind) { @@ -51902,13 +52808,6 @@ void FlecsMetaImport( .type.alignment = ECS_ALIGNOF(EcsPrimitive) }); - ecs_component(world, { - .entity = ecs_entity(world, { .id = EcsConstant, - .name = "constant", .symbol = "EcsConstant", - .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) - }) - }); - ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsEnum), .name = "enum", .symbol = "EcsEnum", @@ -52158,7 +53057,11 @@ void FlecsMetaImport( .symbol = #type });\ ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ .kind = primitive_kind\ - }); + });\ + ecs_set_hooks(world, ecs_##type##_t, { \ + .cmp = ecs_compare_##type, \ + .equals = ecs_equals_##type \ + }) ECS_PRIMITIVE(world, bool, EcsBool); ECS_PRIMITIVE(world, char, EcsChar); @@ -52181,12 +53084,14 @@ void FlecsMetaImport( #undef ECS_PRIMITIVE - ecs_set_hooks(world, ecs_string_t, { - .ctor = flecs_default_ctor, - .copy = ecs_copy(ecs_string_t), - .move = ecs_move(ecs_string_t), - .dtor = ecs_dtor(ecs_string_t) - }); + ecs_type_hooks_t string_hooks = *ecs_get_hooks(world, ecs_string_t); + string_hooks.ctor = flecs_default_ctor; + string_hooks.copy = ecs_copy(ecs_string_t); + string_hooks.move = ecs_move(ecs_string_t); + string_hooks.dtor = ecs_dtor(ecs_string_t); + string_hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world, ecs_id(ecs_string_t), &string_hooks); + /* Set default child components. Can be used as hint by deserializers */ ecs_set(world, ecs_id(EcsStruct), EcsDefaultChildComponent, {ecs_id(EcsMember)}); @@ -52219,6 +53124,8 @@ typedef struct ecs_rtt_call_data_t { ecs_xtor_t xtor; ecs_move_t move; ecs_copy_t copy; + ecs_cmp_t cmp; + ecs_equals_t equals; } hook; const ecs_type_info_t *type_info; int32_t offset; @@ -52227,10 +53134,13 @@ typedef struct ecs_rtt_call_data_t { /* Lifecycle context for runtime structs */ typedef struct ecs_rtt_struct_ctx_t { - ecs_vec_t vctor; /* vector */ - ecs_vec_t vdtor; /* vector */ - ecs_vec_t vmove; /* vector */ - ecs_vec_t vcopy; /* vector */ + ecs_vec_t vctor; /* vector */ + ecs_vec_t vdtor; /* vector */ + ecs_vec_t vmove; /* vector */ + ecs_vec_t vcopy; /* vector */ + ecs_vec_t vcmp; /* vector */ + ecs_vec_t vequals; /* vector */ + } ecs_rtt_struct_ctx_t; /* Lifecycle context for runtime arrays */ @@ -52386,6 +53296,77 @@ void flecs_rtt_struct_copy( } } +/* Generic compare hook. It will read hook information call data from the + * structs's lifecycle context and call the compare hooks configured when + * the type was created. */ +static +int flecs_rtt_struct_cmp( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return 0; + } + + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + int cb_count = ecs_vec_count(&rtt_ctx->vcmp); + int i; + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_get_t(&rtt_ctx->vcmp, ecs_rtt_call_data_t, i); + int c = comp_data->hook.cmp( + ECS_OFFSET(a_ptr, comp_data->offset), + ECS_OFFSET(b_ptr, comp_data->offset), + comp_data->type_info); + if (c != 0) { + return c; + } + } + return 0; +} + +/* Generic equals hook. It will read hook information call data from the + * structs's lifecycle context and call the equals hooks configured when + * the type was created. */ +static +bool flecs_rtt_struct_equals( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return true; + } + + ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); + + int cb_count = ecs_vec_count(&rtt_ctx->vequals); + int i; + for (i = 0; i < cb_count; i++) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_get_t(&rtt_ctx->vequals, ecs_rtt_call_data_t, i); + bool eq = comp_data->hook.equals( + ECS_OFFSET(a_ptr, comp_data->offset), + ECS_OFFSET(b_ptr, comp_data->offset), + comp_data->type_info); + if (!eq) { + return false; + } + } + return true; +} + +static +void flecs_rtt_free_lifecycle_nop( + void *ctx) +{ + (void)ctx; +} + static void flecs_rtt_free_lifecycle_struct_ctx( void *ctx) @@ -52400,6 +53381,8 @@ void flecs_rtt_free_lifecycle_struct_ctx( ecs_vec_fini_t(NULL, &lifecycle_ctx->vdtor, ecs_rtt_call_data_t); ecs_vec_fini_t(NULL, &lifecycle_ctx->vmove, ecs_rtt_call_data_t); ecs_vec_fini_t(NULL, &lifecycle_ctx->vcopy, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vcmp, ecs_rtt_call_data_t); + ecs_vec_fini_t(NULL, &lifecycle_ctx->vequals, ecs_rtt_call_data_t); ecs_os_free(ctx); } @@ -52412,40 +53395,52 @@ ecs_rtt_struct_ctx_t * flecs_rtt_configure_struct_hooks( bool ctor, bool dtor, bool move, - bool copy) + bool copy, + bool cmp, + bool equals) { ecs_type_hooks_t hooks = ti->hooks; if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); } + hooks.ctor = ctor && !(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ? + flecs_rtt_struct_ctor : NULL; + + hooks.dtor = dtor && !(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ? + flecs_rtt_struct_dtor : NULL; + + hooks.move = move && !(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ? + flecs_rtt_struct_move : NULL; + + hooks.copy = copy && !(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ? + flecs_rtt_struct_copy : NULL; + + hooks.cmp = cmp && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ? + flecs_rtt_struct_cmp : NULL; + + hooks.equals = equals && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ? + flecs_rtt_struct_equals : NULL; + ecs_rtt_struct_ctx_t *rtt_ctx = NULL; - if (ctor || dtor || move || copy) { + if (hooks.ctor || hooks.dtor || hooks.move || hooks.copy + || hooks.cmp || hooks.equals) { rtt_ctx = ecs_os_malloc_t(ecs_rtt_struct_ctx_t); ecs_vec_init_t(NULL, &rtt_ctx->vctor, ecs_rtt_call_data_t, 0); ecs_vec_init_t(NULL, &rtt_ctx->vdtor, ecs_rtt_call_data_t, 0); ecs_vec_init_t(NULL, &rtt_ctx->vmove, ecs_rtt_call_data_t, 0); ecs_vec_init_t(NULL, &rtt_ctx->vcopy, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vcmp, ecs_rtt_call_data_t, 0); + ecs_vec_init_t(NULL, &rtt_ctx->vequals, ecs_rtt_call_data_t, 0); + hooks.lifecycle_ctx = rtt_ctx; hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_struct_ctx; - - if (ctor) { - hooks.ctor = flecs_rtt_struct_ctor; - } - if (dtor) { - hooks.dtor = flecs_rtt_struct_dtor; - } - if (move) { - hooks.move = flecs_rtt_struct_move; - } - if (copy) { - hooks.copy = flecs_rtt_struct_copy; - } } else { hooks.lifecycle_ctx = NULL; - hooks.lifecycle_ctx_free = NULL; + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; } - hooks.flags |= flags; + + hooks.flags = flags; hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, ti->component, &hooks); return rtt_ctx; @@ -52470,6 +53465,8 @@ void flecs_rtt_init_default_hooks_struct( bool dtor_hook_required = false; bool move_hook_required = false; bool copy_hook_required = false; + bool valid_cmp = true; + bool valid_equals = true; /* Iterate all struct members and see if any member type has hooks. If so, * the struct itself will need to have that hook: */ @@ -52484,6 +53481,9 @@ void flecs_rtt_init_default_hooks_struct( dtor_hook_required |= member_ti->hooks.dtor != NULL; move_hook_required |= member_ti->hooks.move != NULL; copy_hook_required |= member_ti->hooks.copy != NULL; + /* A struct has a valid cmp/equals hook if all its members have it: */ + valid_cmp &= member_ti->hooks.cmp != NULL; + valid_equals &= member_ti->hooks.equals != NULL; flags |= member_ti->hooks.flags; } @@ -52496,10 +53496,13 @@ void flecs_rtt_init_default_hooks_struct( ctor_hook_required, dtor_hook_required, move_hook_required, - copy_hook_required); + copy_hook_required, + valid_cmp, + valid_equals + ); if (!rtt_ctx) { - return; /* no hooks required */ + return; /* no hook forwarding required */ } /* At least a hook was configured, therefore examine each struct member to @@ -52552,6 +53555,24 @@ void flecs_rtt_init_default_hooks_struct( copy_data->hook.copy = flecs_rtt_default_copy; } } + if (valid_cmp) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_append_t(NULL, &rtt_ctx->vcmp, ecs_rtt_call_data_t); + comp_data->offset = m->offset; + comp_data->type_info = member_ti; + comp_data->count = 1; + ecs_assert(member_ti->hooks.cmp, ECS_INTERNAL_ERROR, NULL); + comp_data->hook.cmp = member_ti->hooks.cmp; + } + if (valid_equals) { + ecs_rtt_call_data_t *comp_data = + ecs_vec_append_t(NULL, &rtt_ctx->vequals, ecs_rtt_call_data_t); + comp_data->offset = m->offset; + comp_data->type_info = member_ti; + comp_data->count = 1; + ecs_assert(member_ti->hooks.equals, ECS_INTERNAL_ERROR, NULL); + comp_data->hook.equals = member_ti->hooks.equals; + } } } @@ -52648,6 +53669,62 @@ void flecs_rtt_array_copy( } } +/* Generic array compare hook. It will invoke the compare hook of the underlying + * type for each element */ +static +int flecs_rtt_array_cmp( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return 0; + } + + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_cmp_t cmp = rtt_ctx->type_info->hooks.cmp; + ecs_assert(cmp, ECS_INVALID_PARAMETER, NULL); + ecs_size_t element_size = rtt_ctx->type_info->size; + int i; + for (i = 0; i < rtt_ctx->elem_count; i++) { + const void *a_element = ECS_ELEM(a_ptr, element_size, i); + const void *b_element = ECS_ELEM(b_ptr, element_size, i); + int c = cmp(a_element, b_element, rtt_ctx->type_info); + if(c != 0) { + return c; + } + } + return 0; +} + +/* Generic array equals hook. It will invoke the equals hook of the underlying + * type for each element */ +static +bool flecs_rtt_array_equals( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return true; + } + + ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_equals_t equals = rtt_ctx->type_info->hooks.equals; + ecs_assert(equals, ECS_INVALID_PARAMETER, NULL); + ecs_size_t element_size = rtt_ctx->type_info->size; + int i; + for (i = 0; i < rtt_ctx->elem_count; i++) { + const void *a_element = ECS_ELEM(a_ptr, element_size, i); + const void *b_element = ECS_ELEM(b_ptr, element_size, i); + bool eq = equals(a_element, b_element, rtt_ctx->type_info); + if(!eq) { + return false; + } + } + return true; +} + /* Checks if an array's underlying type has hooks installed. If so, it generates * and installs required hooks for the array type itself. These hooks will * invoke the underlying type's hook for each element in the array. */ @@ -52658,26 +53735,44 @@ void flecs_rtt_init_default_hooks_array( { const EcsArray *array_info = ecs_get(world, component, EcsArray); ecs_assert(array_info != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *array_ti = + const ecs_type_info_t *element_ti = ecs_get_type_info(world, array_info->type); + ecs_flags32_t flags = element_ti->hooks.flags; bool ctor_hook_required = - array_ti->hooks.ctor && array_ti->hooks.ctor != flecs_default_ctor; - bool dtor_hook_required = array_ti->hooks.dtor != NULL; - bool move_hook_required = array_ti->hooks.move != NULL; - bool copy_hook_required = array_ti->hooks.copy != NULL; - ecs_flags32_t flags = array_ti->hooks.flags; + element_ti->hooks.ctor && element_ti->hooks.ctor != flecs_default_ctor; + bool dtor_hook_required = element_ti->hooks.dtor != NULL; + bool move_hook_required = element_ti->hooks.move != NULL; + bool copy_hook_required = element_ti->hooks.copy != NULL; + bool valid_cmp = element_ti->hooks.cmp != NULL && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL); + bool valid_equals = element_ti->hooks.equals != NULL && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL); + ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component); + + hooks.ctor = ctor_hook_required && !(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ? + flecs_rtt_array_ctor : NULL; + hooks.dtor = dtor_hook_required && !(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ? + flecs_rtt_array_dtor : NULL; + hooks.move = move_hook_required && !(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ? + flecs_rtt_array_move : NULL; + hooks.copy = copy_hook_required && !(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ? + flecs_rtt_array_copy : NULL; + hooks.cmp = valid_cmp && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ? + flecs_rtt_array_cmp : NULL; + hooks.equals = valid_equals && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ? + flecs_rtt_array_equals : NULL; + if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); - hooks.lifecycle_ctx_free = NULL; + hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; } - if (ctor_hook_required || dtor_hook_required || move_hook_required || - copy_hook_required) { + if (hooks.ctor || hooks.dtor || hooks.move || + hooks.copy || hooks.cmp || hooks.equals) + { ecs_rtt_array_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_array_ctx_t); - rtt_ctx->type_info = array_ti; + rtt_ctx->type_info = element_ti; rtt_ctx->elem_count = array_info->count; if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); @@ -52687,23 +53782,7 @@ void flecs_rtt_init_default_hooks_array( hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_array_ctx; } - if (ctor_hook_required) { - hooks.ctor = flecs_rtt_array_ctor; - } - - if (dtor_hook_required) { - hooks.dtor = flecs_rtt_array_dtor; - } - - if (move_hook_required) { - hooks.move = flecs_rtt_array_move; - } - - if (copy_hook_required) { - hooks.copy = flecs_rtt_array_copy; - } - - hooks.flags |= flags; + hooks.flags = flags; hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, component, &hooks); } @@ -52817,6 +53896,92 @@ void flecs_rtt_vector_copy( } } +/* Generic vector compare hook. */ +static +int flecs_rtt_vector_cmp( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return 0; + } + + const ecs_vec_t *vec_a = a_ptr; + const ecs_vec_t *vec_b = b_ptr; + + ecs_size_t count_a = ecs_vec_count(vec_a); + ecs_size_t count_b = ecs_vec_count(vec_b); + { + int c = count_a - count_b; + if(c != 0) { + return c; + } + } + + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_cmp_t cmp = rtt_ctx->type_info->hooks.cmp; + ecs_assert(cmp, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t element_size = rtt_ctx->type_info->size; + const void *a = ecs_vec_first(vec_a); + const void *b = ecs_vec_first(vec_b); + + int i; + for (i = 0; i < count_a; i++) { + const void *a_element = ECS_ELEM(a, element_size, i); + const void *b_element = ECS_ELEM(b, element_size, i); + int c = cmp(a_element, b_element, rtt_ctx->type_info); + if(c != 0) { + return c; + } + } + return 0; +} + +/* Generic vector equals hook. */ +static +bool flecs_rtt_vector_equals( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info) +{ + if(a_ptr == b_ptr) { + return true; + } + + const ecs_vec_t *vec_a = a_ptr; + const ecs_vec_t *vec_b = b_ptr; + + ecs_size_t count_a = ecs_vec_count(vec_a); + ecs_size_t count_b = ecs_vec_count(vec_b); + { + int c = count_a - count_b; + if(c != 0) { + return c; + } + } + + ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; + ecs_equals_t equals = rtt_ctx->type_info->hooks.equals; + ecs_assert(equals, ECS_INVALID_PARAMETER, NULL); + + ecs_size_t element_size = rtt_ctx->type_info->size; + const void *a = ecs_vec_first(vec_a); + const void *b = ecs_vec_first(vec_b); + + int i; + for (i = 0; i < count_a; i++) { + const void *a_element = ECS_ELEM(a, element_size, i); + const void *b_element = ECS_ELEM(b, element_size, i); + int eq = equals(a_element, b_element, rtt_ctx->type_info); + if(!eq) { + return false; + } + } + return true; +} + /* Generates and installs required hooks for managing the vector and underlying * type lifecycle. Vectors always have hooks because at the very least the * vector structure itself must be initialized/destroyed/copied/moved, even if @@ -52828,20 +53993,41 @@ void flecs_rtt_init_default_hooks_vector( { const EcsVector *vector_info = ecs_get(world, component, EcsVector); ecs_assert(vector_info != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *vector_ti = + const ecs_type_info_t *element_ti = ecs_get_type_info(world, vector_info->type); ecs_rtt_vector_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_vector_ctx_t); - rtt_ctx->type_info = vector_ti; + rtt_ctx->type_info = element_ti; + ecs_flags32_t flags = element_ti->hooks.flags; + ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component); + if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); } hooks.lifecycle_ctx = rtt_ctx; hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_vector_ctx; + hooks.ctor = flecs_rtt_vector_ctor; hooks.dtor = flecs_rtt_vector_dtor; hooks.move = flecs_rtt_vector_move; hooks.copy = flecs_rtt_vector_copy; + + if (element_ti->hooks.cmp != NULL && !(flags & ECS_TYPE_HOOK_CMP_ILLEGAL)) { + hooks.cmp = flecs_rtt_vector_cmp; + } else { + hooks.cmp = NULL; + } + + if (element_ti->hooks.equals != NULL && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL)) { + hooks.equals = flecs_rtt_vector_equals; + } else { + hooks.equals = NULL; + } + + + /* propagate only the compare/equals hook illegal flag, if set */ + hooks.flags |= flags & (ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL); + hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, component, &hooks); } @@ -52872,27 +54058,39 @@ void flecs_rtt_init_default_hooks( * */ ecs_entity_t component = it->entities[i]; + + /* Skip configuring hooks for ids already in use */ + const ecs_world_t* w = ecs_get_world(world); + if(ecs_id_in_use(w, component) || + ecs_id_in_use(w, ecs_pair(component, EcsWildcard))) { + continue; + } + const ecs_type_info_t *ti = ecs_get_type_info(world, component); + ecs_assert(ti,ECS_INTERNAL_ERROR,NULL); - if (ti) { - if (type->kind == EcsStructType) { - flecs_rtt_init_default_hooks_struct(world, component, ti); - } else if (type->kind == EcsArrayType) { - flecs_rtt_init_default_hooks_array(world, component); - } else if (type->kind == EcsVectorType) { - flecs_rtt_init_default_hooks_vector(world, component); - } + if (type->kind == EcsStructType) { + flecs_rtt_init_default_hooks_struct(world, component, ti); + } else if (type->kind == EcsArrayType) { + flecs_rtt_init_default_hooks_array(world, component); + } else if (type->kind == EcsVectorType) { + flecs_rtt_init_default_hooks_vector(world, component); } + ecs_type_hooks_t hooks = ti->hooks; /* Make sure there is at least a 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 (!ti || !ti->hooks.ctor) { - ecs_set_hooks_id(world, component, &(ecs_type_hooks_t){ - .ctor = flecs_default_ctor - }); + if(!ti->hooks.ctor && !(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL)) { + hooks.ctor = flecs_default_ctor; } + + hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id( + world, + component, + &hooks); } } @@ -52913,8 +54111,30 @@ int flecs_meta_serialize_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; +ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind( + ecs_primitive_kind_t kind) +{ + switch(kind) { + case EcsBool: return EcsOpBool; + case EcsChar: return EcsOpChar; + case EcsByte: return EcsOpByte; + case EcsU8: return EcsOpU8; + case EcsU16: return EcsOpU16; + case EcsU32: return EcsOpU32; + case EcsU64: return EcsOpU64; + case EcsI8: return EcsOpI8; + case EcsI16: return EcsOpI16; + case EcsI32: return EcsOpI32; + case EcsI64: return EcsOpI64; + case EcsF32: return EcsOpF32; + case EcsF64: return EcsOpF64; + case EcsUPtr: return EcsOpUPtr; + case EcsIPtr: return EcsOpIPtr; + case EcsString: return EcsOpString; + case EcsEntity: return EcsOpEntity; + case EcsId: return EcsOpId; + default: ecs_abort(ECS_INTERNAL_ERROR, NULL); + } } static @@ -53139,699 +54359,1331 @@ int flecs_meta_serialize_type( return -1; } - switch(ptr->kind) { - case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); - case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); - case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); - case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); - case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); - case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); - case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); + switch(ptr->kind) { + case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); + case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); + case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); + case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); + case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); + case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); + case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); + } + + return 0; +} + +static +int flecs_meta_serialize_component( + ecs_world_t *world, + ecs_entity_t type, + ecs_vec_t *ops) +{ + const EcsType *ptr = ecs_get(world, type, EcsType); + if (!ptr) { + char *path = ecs_get_path(world, type); + ecs_err("missing EcsType for type %s'", path); + ecs_os_free(path); + return -1; + } + + if (ptr->kind == EcsArrayType) { + return flecs_meta_serialize_array_component(world, type, ops); + } else { + return flecs_meta_serialize_type(world, type, 0, ops); + } +} + +void ecs_meta_type_serialized_init( + ecs_iter_t *it) +{ + ecs_world_t *world = it->world; + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + ecs_vec_t ops; + ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); + flecs_meta_serialize_component(world, e, &ops); + + EcsTypeSerializer *ptr = ecs_ensure( + world, e, EcsTypeSerializer); + if (ptr->ops.array) { + ecs_meta_dtor_serialized(ptr); + } + + ptr->ops = ops; + } +} + +#endif + +/** + * @file addons/os_api_impl/os_api_impl.c + * @brief Builtin implementation for OS API. + */ + + +#ifdef FLECS_OS_API_IMPL +#ifdef ECS_TARGET_WINDOWS +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin Windows implementation for OS API. + */ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +typedef struct ecs_win_thread_t { + HANDLE thread; + ecs_os_thread_callback_t callback; + void *arg; +} ecs_win_thread_t; + +static +DWORD flecs_win_thread(void *ptr) { + ecs_win_thread_t *thread = ptr; + thread->callback(thread->arg); + return 0; +} + +static +ecs_os_thread_t win_thread_new( + ecs_os_thread_callback_t callback, + void *arg) +{ + ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t); + thread->arg= arg; + thread->callback = callback; + thread->thread = CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL); + return (ecs_os_thread_t)(uintptr_t)thread; +} + +static +void* win_thread_join( + ecs_os_thread_t thr) +{ + ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr; + DWORD r = WaitForSingleObject(thread->thread, INFINITE); + if (r == WAIT_FAILED) { + ecs_err("win_thread_join: WaitForSingleObject failed"); + } + ecs_os_free(thread); + return NULL; +} + +static +ecs_os_thread_id_t win_thread_self(void) +{ + return (ecs_os_thread_id_t)GetCurrentThreadId(); +} + +static +int32_t win_ainc( + int32_t *count) +{ + return InterlockedIncrement((volatile long*)count); +} + +static +int32_t win_adec( + int32_t *count) +{ + return InterlockedDecrement((volatile long*)count); +} + +static +int64_t win_lainc( + int64_t *count) +{ + return InterlockedIncrement64(count); +} + +static +int64_t win_ladec( + int64_t *count) +{ + return InterlockedDecrement64(count); +} + +static +ecs_os_mutex_t win_mutex_new(void) { + CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); + InitializeCriticalSection(mutex); + return (ecs_os_mutex_t)(uintptr_t)mutex; +} + +static +void win_mutex_free( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + DeleteCriticalSection(mutex); + ecs_os_free(mutex); +} + +static +void win_mutex_lock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + EnterCriticalSection(mutex); +} + +static +void win_mutex_unlock( + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + LeaveCriticalSection(mutex); +} + +static +ecs_os_cond_t win_cond_new(void) { + CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); + InitializeConditionVariable(cond); + return (ecs_os_cond_t)(uintptr_t)cond; +} + +static +void win_cond_free( + ecs_os_cond_t c) +{ + (void)c; +} + +static +void win_cond_signal( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeConditionVariable(cond); +} + +static +void win_cond_broadcast( + ecs_os_cond_t c) +{ + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + WakeAllConditionVariable(cond); +} + +static +void win_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; + CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; + SleepConditionVariableCS(cond, mutex, INFINITE); +} + +static bool win_time_initialized; +static double win_time_freq; +static LARGE_INTEGER win_time_start; +static ULONG win_current_resolution; + +static +void win_time_setup(void) { + if ( win_time_initialized) { + return; + } + + win_time_initialized = true; + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&win_time_start); + win_time_freq = (double)freq.QuadPart / 1000000000.0; +} + +static +void win_sleep( + int32_t sec, + int32_t nanosec) +{ + HANDLE timer; + LARGE_INTEGER ft; + + ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + + timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +} + +static +void win_enable_high_timer_resolution(bool enable) +{ + HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (!hntdll) { + return; + } + + union { + LONG (__stdcall *f)( + ULONG desired, BOOLEAN set, ULONG * current); + FARPROC p; + } func; + + func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); + if(!func.p) { + return; + } + + ULONG current, resolution = 10000; /* 1 ms */ + + if (!enable && win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); + win_current_resolution = 0; + return; + } else if (!enable) { + return; + } + + if (resolution == win_current_resolution) { + return; } - return 0; -} - -static -int flecs_meta_serialize_component( - ecs_world_t *world, - ecs_entity_t type, - ecs_vec_t *ops) -{ - const EcsType *ptr = ecs_get(world, type, EcsType); - if (!ptr) { - char *path = ecs_get_path(world, type); - ecs_err("missing EcsType for type %s'", path); - ecs_os_free(path); - return -1; + if (win_current_resolution) { + func.f(win_current_resolution, 0, ¤t); } - if (ptr->kind == EcsArrayType) { - return flecs_meta_serialize_array_component(world, type, ops); - } else { - return flecs_meta_serialize_type(world, type, 0, ops); + if (func.f(resolution, 1, ¤t)) { + /* Try setting a lower resolution */ + resolution *= 2; + if(func.f(resolution, 1, ¤t)) return; } + + win_current_resolution = resolution; } -void ecs_meta_type_serialized_init( - ecs_iter_t *it) -{ - ecs_world_t *world = it->world; +static +uint64_t win_time_now(void) { + uint64_t now; - 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); + LARGE_INTEGER qpc_t; + QueryPerformanceCounter(&qpc_t); + now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); - EcsTypeSerializer *ptr = ecs_ensure( - world, e, EcsTypeSerializer); - if (ptr->ops.array) { - ecs_meta_dtor_serialized(ptr); - } + return now; +} - ptr->ops = ops; +static +void win_fini(void) { + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(false); } } -#endif +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); -/** - * @file addons/os_api_impl/os_api_impl.c - * @brief Builtin implementation for OS API. - */ + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = win_thread_new; + api.thread_join_ = win_thread_join; + api.thread_self_ = win_thread_self; + api.task_new_ = win_thread_new; + api.task_join_ = win_thread_join; + api.ainc_ = win_ainc; + api.adec_ = win_adec; + api.lainc_ = win_lainc; + api.ladec_ = win_ladec; + api.mutex_new_ = win_mutex_new; + api.mutex_free_ = win_mutex_free; + api.mutex_lock_ = win_mutex_lock; + api.mutex_unlock_ = win_mutex_unlock; + api.cond_new_ = win_cond_new; + api.cond_free_ = win_cond_free; + api.cond_signal_ = win_cond_signal; + api.cond_broadcast_ = win_cond_broadcast; + api.cond_wait_ = win_cond_wait; + api.sleep_ = win_sleep; + api.now_ = win_time_now; + api.fini_ = win_fini; + win_time_setup(); -#ifdef FLECS_OS_API_IMPL -#ifdef ECS_TARGET_WINDOWS + if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { + win_enable_high_timer_resolution(true); + } + + ecs_os_set_api(&api); +} + +#else /** * @file addons/os_api_impl/posix_impl.inl - * @brief Builtin Windows implementation for OS API. + * @brief Builtin POSIX implementation for OS API. */ -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include +#include "pthread.h" -typedef struct ecs_win_thread_t { - HANDLE thread; - ecs_os_thread_callback_t callback; - void *arg; -} ecs_win_thread_t; +#if defined(__APPLE__) && defined(__MACH__) +#include +#elif defined(__EMSCRIPTEN__) +#include +#else +#include +#endif -static -DWORD flecs_win_thread(void *ptr) { - ecs_win_thread_t *thread = ptr; - thread->callback(thread->arg); - return 0; -} +/* This mutex is used to emulate atomic operations when the gnu builtins are + * not supported. This is probably not very fast but if the compiler doesn't + * support the gnu built-ins, then speed is probably not a priority. */ +#ifndef __GNUC__ +static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif static -ecs_os_thread_t win_thread_new( +ecs_os_thread_t posix_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); + pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); + + if (pthread_create (thread, NULL, callback, arg) != 0) { + ecs_os_abort(); + } + return (ecs_os_thread_t)(uintptr_t)thread; } static -void* win_thread_join( - ecs_os_thread_t thr) +void* posix_thread_join( + ecs_os_thread_t thread) { - 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; + void *arg; + pthread_t *thr = (pthread_t*)(uintptr_t)thread; + pthread_join(*thr, &arg); + ecs_os_free(thr); + return arg; } static -ecs_os_thread_id_t win_thread_self(void) +ecs_os_thread_id_t posix_thread_self(void) { - return (ecs_os_thread_id_t)GetCurrentThreadId(); + return (ecs_os_thread_id_t)pthread_self(); } static -int32_t win_ainc( - int32_t *count) +int32_t posix_ainc( + int32_t *count) { - return InterlockedIncrement((volatile long*)count); + int value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif } static -int32_t win_adec( +int32_t posix_adec( int32_t *count) { - return InterlockedDecrement((volatile long*)count); + int32_t value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif } static -int64_t win_lainc( - int64_t *count) +int64_t posix_lainc( + int64_t *count) { - return InterlockedIncrement64(count); + int64_t value; +#ifdef __GNUC__ + value = __sync_add_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) += 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif } static -int64_t win_ladec( +int64_t posix_ladec( int64_t *count) { - return InterlockedDecrement64(count); + int64_t value; +#ifdef __GNUC__ + value = __sync_sub_and_fetch (count, 1); + return value; +#else + if (pthread_mutex_lock(&atomic_mutex)) { + abort(); + } + value = (*count) -= 1; + if (pthread_mutex_unlock(&atomic_mutex)) { + abort(); + } + return value; +#endif } static -ecs_os_mutex_t win_mutex_new(void) { - CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); - InitializeCriticalSection(mutex); +ecs_os_mutex_t posix_mutex_new(void) { + pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); + if (pthread_mutex_init(mutex, NULL)) { + abort(); + } return (ecs_os_mutex_t)(uintptr_t)mutex; } static -void win_mutex_free( +void posix_mutex_free( ecs_os_mutex_t m) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - DeleteCriticalSection(mutex); + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + pthread_mutex_destroy(mutex); ecs_os_free(mutex); } static -void win_mutex_lock( +void posix_mutex_lock( ecs_os_mutex_t m) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - EnterCriticalSection(mutex); + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_lock(mutex)) { + abort(); + } } static -void win_mutex_unlock( +void posix_mutex_unlock( ecs_os_mutex_t m) { - CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; - LeaveCriticalSection(mutex); + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_mutex_unlock(mutex)) { + abort(); + } } static -ecs_os_cond_t win_cond_new(void) { - CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); - InitializeConditionVariable(cond); +ecs_os_cond_t posix_cond_new(void) { + pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); + if (pthread_cond_init(cond, NULL)) { + abort(); + } return (ecs_os_cond_t)(uintptr_t)cond; } static -void win_cond_free( +void posix_cond_free( ecs_os_cond_t c) { - (void)c; + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_destroy(cond)) { + abort(); + } + ecs_os_free(cond); } static -void win_cond_signal( +void posix_cond_signal( ecs_os_cond_t c) { - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeConditionVariable(cond); + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_signal(cond)) { + abort(); + } +} + +static +void posix_cond_broadcast( + ecs_os_cond_t c) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + if (pthread_cond_broadcast(cond)) { + abort(); + } +} + +static +void posix_cond_wait( + ecs_os_cond_t c, + ecs_os_mutex_t m) +{ + pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; + pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; + if (pthread_cond_wait(cond, mutex)) { + abort(); + } +} + +static bool posix_time_initialized; + +#if defined(__APPLE__) && defined(__MACH__) +static mach_timebase_info_data_t posix_osx_timebase; +static uint64_t posix_time_start; +#else +static uint64_t posix_time_start; +#endif + +static +void posix_time_setup(void) { + if (posix_time_initialized) { + return; + } + + posix_time_initialized = true; + + #if defined(__APPLE__) && defined(__MACH__) + mach_timebase_info(&posix_osx_timebase); + posix_time_start = mach_absolute_time(); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; + #endif +} + +static +void posix_sleep( + int32_t sec, + int32_t nanosec) +{ + struct timespec sleepTime; + ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + + sleepTime.tv_sec = sec; + sleepTime.tv_nsec = nanosec; + if (nanosleep(&sleepTime, NULL)) { + ecs_err("nanosleep failed"); + } +} + +/* prevent 64-bit overflow when computing relative timestamp + see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 +*/ +#if defined(ECS_TARGET_DARWIN) +static +int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { + int64_t q = value / denom; + int64_t r = value % denom; + return q * numer + r * numer / denom; +} +#endif + +static +uint64_t posix_time_now(void) { + ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + + uint64_t now; + + #if defined(ECS_TARGET_DARWIN) + now = (uint64_t) posix_int64_muldiv( + (int64_t)mach_absolute_time(), + (int64_t)posix_osx_timebase.numer, + (int64_t)posix_osx_timebase.denom); + #elif defined(__EMSCRIPTEN__) + now = (long long)(emscripten_get_now() * 1000.0 * 1000); + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); + #endif + + return now; +} + +void ecs_set_os_api_impl(void) { + ecs_os_set_api_defaults(); + + ecs_os_api_t api = ecs_os_api; + + api.thread_new_ = posix_thread_new; + api.thread_join_ = posix_thread_join; + api.thread_self_ = posix_thread_self; + api.task_new_ = posix_thread_new; + api.task_join_ = posix_thread_join; + api.ainc_ = posix_ainc; + api.adec_ = posix_adec; + api.lainc_ = posix_lainc; + api.ladec_ = posix_ladec; + api.mutex_new_ = posix_mutex_new; + api.mutex_free_ = posix_mutex_free; + api.mutex_lock_ = posix_mutex_lock; + api.mutex_unlock_ = posix_mutex_unlock; + api.cond_new_ = posix_cond_new; + api.cond_free_ = posix_cond_free; + api.cond_signal_ = posix_cond_signal; + api.cond_broadcast_ = posix_cond_broadcast; + api.cond_wait_ = posix_cond_wait; + api.sleep_ = posix_sleep; + api.now_ = posix_time_now; + + posix_time_setup(); + + ecs_os_set_api(&api); +} + +#endif +#endif + +/** + * @file addons/script/tokenizer.c + * @brief Script tokenizer. + */ + + +#ifdef FLECS_PARSER + + +#define Keyword(keyword, _kind)\ + } else if (!ecs_os_strncmp(pos, keyword " ", ecs_os_strlen(keyword) + 1)) {\ + out->value = keyword;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(keyword);\ + } else if (!ecs_os_strncmp(pos, keyword "\n", ecs_os_strlen(keyword) + 1)) {\ + out->value = keyword;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(keyword); + +#define OperatorMultiChar(oper, _kind)\ + } else if (!ecs_os_strncmp(pos, oper, ecs_os_strlen(oper))) {\ + out->value = oper;\ + out->kind = _kind;\ + return pos + ecs_os_strlen(oper); + +#define Operator(oper, _kind)\ + } else if (pos[0] == oper[0]) {\ + out->value = oper;\ + out->kind = _kind;\ + return pos + 1; + +const char* flecs_token_kind_str( + ecs_token_kind_t kind) +{ + switch(kind) { + case EcsTokUnknown: + return "unknown token "; + case EcsTokColon: + case EcsTokScopeOpen: + case EcsTokScopeClose: + case EcsTokParenOpen: + case EcsTokParenClose: + case EcsTokBracketOpen: + case EcsTokBracketClose: + case EcsTokAnnotation: + case EcsTokComma: + case EcsTokSemiColon: + case EcsTokAssign: + case EcsTokAdd: + case EcsTokSub: + case EcsTokMul: + case EcsTokDiv: + case EcsTokMod: + case EcsTokBitwiseOr: + case EcsTokBitwiseAnd: + case EcsTokNot: + case EcsTokOptional: + case EcsTokEq: + case EcsTokNeq: + case EcsTokGt: + case EcsTokGtEq: + case EcsTokLt: + case EcsTokLtEq: + case EcsTokAnd: + case EcsTokOr: + case EcsTokMatch: + case EcsTokRange: + case EcsTokShiftLeft: + case EcsTokShiftRight: + case EcsTokAddAssign: + case EcsTokMulAssign: + return ""; + case EcsTokKeywordWith: + case EcsTokKeywordUsing: + case EcsTokKeywordProp: + case EcsTokKeywordConst: + case EcsTokKeywordIf: + case EcsTokKeywordElse: + case EcsTokKeywordFor: + case EcsTokKeywordIn: + case EcsTokKeywordTemplate: + case EcsTokKeywordModule: + case EcsTokKeywordMatch: + return "keyword "; + case EcsTokIdentifier: + return "identifier "; + case EcsTokString: + return "string "; + case EcsTokNumber: + return "number "; + case EcsTokNewline: + return "newline"; + case EcsTokMember: + return "member"; + case EcsTokEnd: + return "end of script"; + default: + return ""; + } } -static -void win_cond_broadcast( - ecs_os_cond_t c) +const char* flecs_token_str( + ecs_token_kind_t kind) { - CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; - WakeAllConditionVariable(cond); + switch(kind) { + case EcsTokUnknown: return "unknown token"; + case EcsTokColon: return ":"; + case EcsTokScopeOpen: return "{"; + case EcsTokScopeClose: return "}"; + case EcsTokParenOpen: return "("; + case EcsTokParenClose: return ")"; + case EcsTokBracketOpen: return "["; + case EcsTokBracketClose: return "]"; + case EcsTokAnnotation: return "@"; + case EcsTokComma: return ","; + case EcsTokSemiColon: return ";"; + case EcsTokAssign: return "="; + case EcsTokAdd: return "+"; + case EcsTokSub: return "-"; + case EcsTokMul: return "*"; + case EcsTokDiv: return "/"; + case EcsTokMod: return "%%"; + case EcsTokBitwiseOr: return "|"; + case EcsTokBitwiseAnd: return "&"; + case EcsTokNot: return "!"; + case EcsTokOptional: return "?"; + case EcsTokEq: return "=="; + case EcsTokNeq: return "!="; + case EcsTokGt: return ">"; + case EcsTokGtEq: return ">="; + case EcsTokLt: return "<"; + case EcsTokLtEq: return "<="; + case EcsTokAnd: return "&&"; + case EcsTokOr: return "||"; + case EcsTokMatch: return "~="; + case EcsTokRange: return ".."; + case EcsTokShiftLeft: return "<<"; + case EcsTokShiftRight: return ">>"; + case EcsTokAddAssign: return "+="; + case EcsTokMulAssign: return "*="; + case EcsTokKeywordWith: return "with"; + case EcsTokKeywordUsing: return "using"; + case EcsTokKeywordProp: return "prop"; + case EcsTokKeywordConst: return "const"; + case EcsTokKeywordMatch: return "match"; + case EcsTokKeywordIf: return "if"; + case EcsTokKeywordElse: return "else"; + case EcsTokKeywordFor: return "for"; + case EcsTokKeywordIn: return "in"; + case EcsTokKeywordTemplate: return "template"; + case EcsTokKeywordModule: return "module"; + case EcsTokIdentifier: return "identifier"; + case EcsTokString: return "string"; + case EcsTokNumber: return "number"; + case EcsTokNewline: return "newline"; + case EcsTokMember: return "member"; + case EcsTokEnd: return "end of script"; + default: + return ""; + } } -static -void win_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) +const char* flecs_scan_whitespace( + ecs_parser_t *parser, + const char *pos) { - 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; + (void)parser; -static -void win_time_setup(void) { - if ( win_time_initialized) { - return; + if (parser->significant_newline) { + while (pos[0] && isspace(pos[0]) && pos[0] != '\n') { + pos ++; + } + } else { + while (pos[0] && isspace(pos[0])) { + pos ++; + } } - - win_time_initialized = true; - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - QueryPerformanceCounter(&win_time_start); - win_time_freq = (double)freq.QuadPart / 1000000000.0; + return pos; } static -void win_sleep( - int32_t sec, - int32_t nanosec) +const char* flecs_scan_whitespace_and_comment( + ecs_parser_t *parser, + const char *pos) { - HANDLE timer; - LARGE_INTEGER ft; +repeat_skip_whitespace_comment: + pos = flecs_scan_whitespace(parser, pos); + if (pos[0] == '/') { + if (pos[1] == '/') { + for (pos = pos + 2; pos[0] && pos[0] != '\n'; pos ++) { } + if (pos[0] == '\n') { + pos ++; + goto repeat_skip_whitespace_comment; + } + } else if (pos[1] == '*') { + for (pos = &pos[2]; pos[0] != 0; pos ++) { + if (pos[0] == '*' && pos[1] == '/') { + pos += 2; + goto repeat_skip_whitespace_comment; + } + } - ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "missing */ for multiline comment"); + } + } - timer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); + return pos; } +// Identifier token static -void win_enable_high_timer_resolution(bool enable) +bool flecs_script_is_identifier( + char c) { - HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); - if (!hntdll) { - return; - } - - union { - LONG (__stdcall *f)( - ULONG desired, BOOLEAN set, ULONG * current); - FARPROC p; - } func; - - func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); - if(!func.p) { - return; - } - - ULONG current, resolution = 10000; /* 1 ms */ - - if (!enable && win_current_resolution) { - func.f(win_current_resolution, 0, ¤t); - win_current_resolution = 0; - return; - } else if (!enable) { - return; - } - - if (resolution == win_current_resolution) { - return; - } + return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); +} - if (win_current_resolution) { - func.f(win_current_resolution, 0, ¤t); +const char* flecs_tokenizer_identifier( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) +{ + if (out) { + out->kind = EcsTokIdentifier; + out->value = parser->token_cur; } - if (func.f(resolution, 1, ¤t)) { - /* Try setting a lower resolution */ - resolution *= 2; - if(func.f(resolution, 1, ¤t)) return; + ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); + bool is_var = pos[0] == '$'; + char *outpos = NULL; + const char *start = pos; + if (parser) { + outpos = parser->token_cur; + if (parser->merge_variable_members) { + is_var = false; + } } - win_current_resolution = resolution; -} + do { + char c = pos[0]; + bool is_ident = flecs_script_is_identifier(c) || isdigit(c); -static -uint64_t win_time_now(void) { - uint64_t now; + if (!is_var) { + is_ident = is_ident || (c == '.'); + } - LARGE_INTEGER qpc_t; - QueryPerformanceCounter(&qpc_t); - now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); + /* Retain \. for name lookup operation */ + if (!is_ident && c == '\\' && pos[1] == '.') { + is_ident = true; + } - return now; -} + /* Retain .* for using wildcard expressions */ + if (!is_ident && c == '*') { + if (pos != start && pos[-1] == '.') { + is_ident = true; + } + } -static -void win_fini(void) { - if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { - win_enable_high_timer_resolution(false); - } -} + if (!is_ident) { + if (c == '\\') { + pos ++; + } else if (c == '<') { + int32_t indent = 0; + do { + c = *pos; -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); + if (c == '<') { + indent ++; + } else if (c == '>') { + indent --; + } else if (!c) { + ecs_parser_error(parser->name, + parser->code, + pos - parser->code, + "< without > in identifier"); + return NULL; + } - ecs_os_api_t api = ecs_os_api; + if (outpos) { + *outpos = c; + outpos ++; + } + pos ++; - 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; + if (!indent) { + break; + } + } while (true); - win_time_setup(); + if (outpos && parser) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + } + return pos; + } else if (c == '>') { + ecs_parser_error(parser->name, + parser->code, + pos - parser->code, + "> without < in identifier"); + return NULL; + } else { + if (outpos && parser) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + } + return pos; + } + } - if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { - win_enable_high_timer_resolution(true); - } + if (outpos) { + *outpos = *pos; + outpos ++; + } - ecs_os_set_api(&api); + pos ++; + } while (true); } -#else -/** - * @file addons/os_api_impl/posix_impl.inl - * @brief Builtin POSIX implementation for OS API. - */ - -#include "pthread.h" - -#if defined(__APPLE__) && defined(__MACH__) -#include -#elif defined(__EMSCRIPTEN__) -#include -#else -#include -#endif - -/* This mutex is used to emulate atomic operations when the gnu builtins are - * not supported. This is probably not very fast but if the compiler doesn't - * support the gnu built-ins, then speed is probably not a priority. */ -#ifndef __GNUC__ -static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; -#endif - +// Number token static static -ecs_os_thread_t posix_thread_new( - ecs_os_thread_callback_t callback, - void *arg) +bool flecs_script_is_number( + const char *c) { - pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); - - if (pthread_create (thread, NULL, callback, arg) != 0) { - ecs_os_abort(); - } - - return (ecs_os_thread_t)(uintptr_t)thread; + return isdigit(c[0]) || ((c[0] == '-') && isdigit(c[1])); } static -void* posix_thread_join( - ecs_os_thread_t thread) +const char* flecs_script_number( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) { - void *arg; - pthread_t *thr = (pthread_t*)(uintptr_t)thread; - pthread_join(*thr, &arg); - ecs_os_free(thr); - return arg; -} + out->kind = EcsTokNumber; + out->value = parser->token_cur; + + bool dot_parsed = false; + bool e_parsed = false; + int base = 10; -static -ecs_os_thread_id_t posix_thread_self(void) -{ - return (ecs_os_thread_id_t)pthread_self(); -} + ecs_assert(flecs_script_is_number(pos), ECS_INTERNAL_ERROR, NULL); + char *outpos = parser->token_cur; -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(); + if (pos[0] == '-') { + outpos[0] = pos[0]; + pos ++; + outpos ++; } - return value; -#endif -} -static -int32_t posix_adec( - int32_t *count) -{ - int32_t value; -#ifdef __GNUC__ - value = __sync_sub_and_fetch (count, 1); - return value; -#else - if (pthread_mutex_lock(&atomic_mutex)) { - abort(); - } - value = (*count) -= 1; - if (pthread_mutex_unlock(&atomic_mutex)) { - abort(); + if (pos[0] == '0' && (pos[1] == 'x' || pos[1] == 'X')) { + base = 16; + outpos[0] = pos[0]; + outpos[1] = pos[1]; + outpos += 2; + pos += 2; + } else if (pos[0] == '0' && (pos[1] == 'b' || pos[1] == 'B')) { + base = 2; + outpos[0] = pos[0]; + outpos[1] = pos[1]; + outpos += 2; + pos += 2; } - return value; -#endif -} -static -int64_t posix_lainc( - int64_t *count) -{ - int64_t value; -#ifdef __GNUC__ - value = __sync_add_and_fetch (count, 1); - return value; -#else - if (pthread_mutex_lock(&atomic_mutex)) { - abort(); - } - value = (*count) += 1; - if (pthread_mutex_unlock(&atomic_mutex)) { - abort(); - } - return value; -#endif -} + do { + char c = pos[0]; + bool valid_number = false; -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 -} + if (c == '.') { + if (!dot_parsed && !e_parsed) { + if (isdigit(pos[1])) { + dot_parsed = true; + valid_number = true; + } + } + } else if (c == 'e') { + if (!e_parsed) { + if (isdigit(pos[1])) { + e_parsed = true; + valid_number = true; + } + } + } else if ((base == 10) && isdigit(c)) { + valid_number = true; + } else if ((base == 16) && isxdigit(c)) { + valid_number = true; + } else if ((base == 2) && (c == '0' || c == '1')) { + valid_number = true; + } -static -ecs_os_mutex_t posix_mutex_new(void) { - pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); - if (pthread_mutex_init(mutex, NULL)) { - abort(); - } - return (ecs_os_mutex_t)(uintptr_t)mutex; -} + if (!valid_number) { + *outpos = '\0'; + parser->token_cur = outpos + 1; + break; + } -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); + outpos[0] = pos[0]; + outpos ++; + pos ++; + } while (true); + + return pos; } static -void posix_mutex_lock( - ecs_os_mutex_t m) +const char* flecs_script_skip_string( + ecs_parser_t *parser, + const char *pos, + char delim) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_lock(mutex)) { - abort(); + char ch; + for (; (ch = pos[0]) && pos[0] != delim; pos ++) { + if (ch == '\\') { + pos ++; + } + } + + if (!pos[0]) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "unterminated string"); + return NULL; } + + return pos; } static -void posix_mutex_unlock( - ecs_os_mutex_t m) +const char* flecs_script_string( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) { - pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; - if (pthread_mutex_unlock(mutex)) { - abort(); + const char *end = flecs_script_skip_string(parser, pos + 1, '"'); + if (!end) { + return NULL; } -} -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_assert(end[0] == '"', ECS_INTERNAL_ERROR, NULL); + end --; -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); + int32_t len = flecs_ito(int32_t, end - pos); + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokString; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; } -static -void posix_cond_signal( - ecs_os_cond_t c) +static +const char* flecs_script_multiline_string( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out) { - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_signal(cond)) { - abort(); + char ch; + const char *end = pos + 1; + while ((ch = end[0]) && (ch != '`')) { + if (ch == '\\' && end[1] == '`') { + end ++; + } + end ++; } -} -static -void posix_cond_broadcast( - ecs_os_cond_t c) -{ - pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; - if (pthread_cond_broadcast(cond)) { - abort(); + if (ch != '`') { + return NULL; } + + end --; + + int32_t len = flecs_ito(int32_t, end - pos); + ecs_os_memcpy(parser->token_cur, pos + 1, len); + parser->token_cur[len] = '\0'; + + out->kind = EcsTokString; + out->value = parser->token_cur; + parser->token_cur += len + 1; + return end + 2; } -static -void posix_cond_wait( - ecs_os_cond_t c, - ecs_os_mutex_t m) +const char* flecs_tokenizer_until( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out, + char until) { - 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(); + parser->pos = pos; + + const char *start = pos = flecs_scan_whitespace(parser, pos); + char ch; + + for (; (ch = pos[0]); pos ++) { + if (ch == until) { + break; + } } -} -static bool posix_time_initialized; + if (!pos[0]) { + if (until == '\0') { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "expected end of script"); + return NULL; + } else + if (until == '\n') { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "expected newline"); + return NULL; + } else { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "expected '%c'", until); + return NULL; + } + } -#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 + int32_t len = flecs_ito(int32_t, pos - start); + ecs_os_memcpy(parser->token_cur, start, len); + out->value = parser->token_cur; + parser->token_cur += len; -static -void posix_time_setup(void) { - if (posix_time_initialized) { - return; + while (isspace(parser->token_cur[-1])) { + parser->token_cur --; } - - 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 + parser->token_cur[0] = '\0'; + parser->token_cur ++; + + return pos; } -static -void posix_sleep( - int32_t sec, - int32_t nanosec) +const char* flecs_token( + ecs_parser_t *parser, + const char *pos, + ecs_token_t *out, + bool is_lookahead) { - struct timespec sleepTime; - ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); + parser->pos = pos; - sleepTime.tv_sec = sec; - sleepTime.tv_nsec = nanosec; - if (nanosleep(&sleepTime, NULL)) { - ecs_err("nanosleep failed"); - } -} + // Skip whitespace and comments + pos = flecs_scan_whitespace_and_comment(parser, pos); -/* prevent 64-bit overflow when computing relative timestamp - see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 -*/ -#if defined(ECS_TARGET_DARWIN) -static -int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { - int64_t q = value / denom; - int64_t r = value % denom; - return q * numer + r * numer / denom; -} -#endif + out->kind = EcsTokUnknown; + out->value = NULL; -static -uint64_t posix_time_now(void) { - ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); + if (pos[0] == '\0') { + out->kind = EcsTokEnd; + return pos; + } else if (pos[0] == '\n') { + out->kind = EcsTokNewline; + + // Parse multiple newlines/whitespaces as a single token + pos = flecs_scan_whitespace_and_comment(parser, pos + 1); + if (pos[0] == '\n') { + pos ++; + } + return pos; - uint64_t now; + } else if (flecs_script_is_number(pos)) { + return flecs_script_number(parser, pos, out); - #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 + OperatorMultiChar ("+=", EcsTokAddAssign) + OperatorMultiChar ("*=", EcsTokMulAssign) + Operator (":", EcsTokColon) + Operator ("{", EcsTokScopeOpen) + Operator ("}", EcsTokScopeClose) + Operator ("(", EcsTokParenOpen) + Operator (")", EcsTokParenClose) + Operator ("[", EcsTokBracketOpen) + Operator ("]", EcsTokBracketClose) + Operator ("@", EcsTokAnnotation) + Operator (",", EcsTokComma) + Operator (";", EcsTokSemiColon) + Operator ("+", EcsTokAdd) + Operator ("-", EcsTokSub) + Operator ("*", EcsTokMul) + Operator ("/", EcsTokDiv) + Operator ("%%", EcsTokMod) + Operator ("?", EcsTokOptional) + + OperatorMultiChar ("..", EcsTokRange) + Operator (".", EcsTokMember) - return now; -} + OperatorMultiChar ("==", EcsTokEq) + OperatorMultiChar ("!=", EcsTokNeq) + OperatorMultiChar ("<<", EcsTokShiftLeft) + OperatorMultiChar (">>", EcsTokShiftRight) + OperatorMultiChar (">=", EcsTokGtEq) + OperatorMultiChar ("<=", EcsTokLtEq) + + OperatorMultiChar ("&&", EcsTokAnd) + OperatorMultiChar ("||", EcsTokOr) + OperatorMultiChar ("~=", EcsTokMatch) -void ecs_set_os_api_impl(void) { - ecs_os_set_api_defaults(); + Operator ("!", EcsTokNot) + Operator ("=", EcsTokAssign) + Operator ("&", EcsTokBitwiseAnd) + Operator ("|", EcsTokBitwiseOr) + Operator (">", EcsTokGt) + Operator ("<", EcsTokLt) - ecs_os_api_t api = ecs_os_api; + Keyword ("with", EcsTokKeywordWith) + Keyword ("using", EcsTokKeywordUsing) + Keyword ("template", EcsTokKeywordTemplate) + Keyword ("prop", EcsTokKeywordProp) + Keyword ("const", EcsTokKeywordConst) + Keyword ("if", EcsTokKeywordIf) + Keyword ("else", EcsTokKeywordElse) + Keyword ("for", EcsTokKeywordFor) + Keyword ("in", EcsTokKeywordIn) + Keyword ("match", EcsTokKeywordMatch) + Keyword ("module", EcsTokKeywordModule) - 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; + } else if (pos[0] == '"') { + return flecs_script_string(parser, pos, out); - posix_time_setup(); + } else if (pos[0] == '`') { + return flecs_script_multiline_string(parser, pos, out); - ecs_os_set_api(&api); + } else if (flecs_script_is_identifier(pos[0])) { + return flecs_tokenizer_identifier(parser, pos, out); + } + + if (!is_lookahead) { + ecs_parser_error(parser->name, parser->code, + pos - parser->code, "unknown token '%c'", pos[0]); + } + + return NULL; } -#endif #endif /** @@ -53964,7 +55816,7 @@ bool flecs_pipeline_check_term( (void)world; ecs_term_ref_t *src = &term->src; - if (term->inout == EcsInOutNone || term->inout == EcsInOutFilter) { + if (term->inout == EcsInOutFilter) { return false; } @@ -54517,9 +56369,9 @@ static void flecs_run_startup_systems( ecs_world_t *world) { - ecs_id_record_t *idr = flecs_id_record_get(world, + ecs_component_record_t *cdr = flecs_components_get(world, ecs_dependson(EcsOnStart)); - if (!idr || !flecs_table_cache_count(&idr->cache)) { + if (!cdr || !flecs_table_cache_count(&cdr->cache)) { /* Don't bother creating startup pipeline if no systems exist */ return; } @@ -54675,7 +56527,7 @@ ecs_entity_t ecs_pipeline_init( 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); + pq->idr_inactive = flecs_components_ensure(world, EcsEmpty); ecs_set(world, result, EcsPipeline, { pq }); return result; @@ -55008,3322 +56860,3352 @@ void flecs_workers_progress( ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { - flecs_poly_assert(world, ecs_world_t); - ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, - "cannot call progress while world is deferred"); - - /* Make sure workers are running and ready */ - flecs_wait_for_workers(world); - - /* Run pipeline on main thread */ - ecs_world_t *stage = ecs_get_stage(world, 0); - ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); - flecs_run_pipeline(stage, pq, delta_time); - ecs_set_scope((ecs_world_t*)stage, old_scope); -} - -static -void flecs_set_threads_internal( - ecs_world_t *world, - int32_t threads, - bool use_task_api) -{ - ecs_assert(threads <= 1 || (use_task_api - ? ecs_os_has_task_support() - : ecs_os_has_threading()), - ECS_MISSING_OS_API, NULL); - - int32_t stage_count = ecs_get_stage_count(world); - bool worker_method_changed = (use_task_api != world->workers_use_task_api); - - if ((stage_count != threads) || worker_method_changed) { - /* Stop existing threads */ - if (stage_count > 1) { - flecs_join_worker_threads(world); - ecs_set_stage_count(world, 1); - - if (world->worker_cond) { - ecs_os_cond_free(world->worker_cond); - } - if (world->sync_cond) { - ecs_os_cond_free(world->sync_cond); - } - if (world->sync_mutex) { - ecs_os_mutex_free(world->sync_mutex); - } - } - - world->workers_use_task_api = use_task_api; - - /* Start threads if number of threads > 1 */ - if (threads > 1) { - world->worker_cond = ecs_os_cond_new(); - world->sync_cond = ecs_os_cond_new(); - world->sync_mutex = ecs_os_mutex_new(); - flecs_start_workers(world, threads); - } - } -} - -/* -- Public functions -- */ - -void ecs_set_threads( - ecs_world_t *world, - int32_t threads) -{ - flecs_set_threads_internal(world, threads, false /* use thread API */); -} - -void ecs_set_task_threads( - ecs_world_t *world, - int32_t task_threads) -{ - flecs_set_threads_internal(world, task_threads, true /* use task API */); -} - -bool ecs_using_task_threads( - ecs_world_t *world) -{ - return world->workers_use_task_api; -} - -#endif - -/** - * @file addons/script/ast.c - * @brief Script AST implementation. - */ - - -#ifdef FLECS_SCRIPT - -#define flecs_ast_new(parser, T, kind)\ - (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) -#define flecs_ast_vec(parser, vec, T) \ - ecs_vec_init_t(&parser->script->allocator, &vec, T*, 0) -#define flecs_ast_append(parser, vec, T, node) \ - ecs_vec_append_t(&parser->script->allocator, &vec, T*)[0] = node - -static -void* flecs_ast_new_( - ecs_script_parser_t *parser, - ecs_size_t size, - ecs_script_node_kind_t kind) -{ - ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_allocator_t *a = &parser->script->allocator; - ecs_script_node_t *result = flecs_calloc_w_dbg_info( - a, size, "ecs_script_node_t"); - result->kind = kind; - result->pos = parser->pos; - return result; -} - -ecs_script_scope_t* flecs_script_scope_new( - ecs_script_parser_t *parser) -{ - ecs_script_scope_t *result = flecs_ast_new( - parser, ecs_script_scope_t, EcsAstScope); - flecs_ast_vec(parser, result->stmts, ecs_script_node_t); - ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); - return result; -} - -bool flecs_scope_is_empty( - ecs_script_scope_t *scope) -{ - return ecs_vec_count(&scope->stmts) == 0; -} - -ecs_script_scope_t* flecs_script_insert_scope( - ecs_script_parser_t *parser) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_scope_t *result = flecs_script_scope_new(parser); - flecs_ast_append(parser, scope->stmts, ecs_script_scope_t, result); - ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); - return result; -} - -ecs_script_entity_t* flecs_script_insert_entity( - ecs_script_parser_t *parser, - const char *name, - bool name_is_expr) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_script_entity_t *result = flecs_ast_new( - parser, ecs_script_entity_t, EcsAstEntity); - - if (name && !ecs_os_strcmp(name, "_")) { - name = NULL; - } - - result->name = name; - - if (name_is_expr) { - parser->significant_newline = false; - result->name_expr = (ecs_expr_node_t*) - flecs_expr_interpolated_string(parser, name); - if (!result->name_expr) { - goto error; - } - parser->significant_newline = true; - } - - ecs_script_scope_t *entity_scope = flecs_script_scope_new(parser); - ecs_assert(entity_scope != NULL, ECS_INTERNAL_ERROR, NULL); - result->scope = entity_scope; - - flecs_ast_append(parser, scope->stmts, ecs_script_entity_t, result); - return result; -error: - return NULL; -} - -static -void flecs_script_set_id( - ecs_script_id_t *id, - const char *first, - const char *second) -{ - ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); - id->first = first; - id->second = second; - id->first_sp = -1; - id->second_sp = -1; -} - -ecs_script_pair_scope_t* flecs_script_insert_pair_scope( - ecs_script_parser_t *parser, - const char *first, - const char *second) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_script_pair_scope_t *result = flecs_ast_new( - parser, ecs_script_pair_scope_t, EcsAstPairScope); - flecs_script_set_id(&result->id, first, second); - result->scope = flecs_script_scope_new(parser); - - flecs_ast_append(parser, scope->stmts, ecs_script_pair_scope_t, result); - return result; -} - -ecs_script_tag_t* flecs_script_insert_pair_tag( - ecs_script_parser_t *parser, - const char *first, - const char *second) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_script_tag_t *result = flecs_ast_new( - parser, ecs_script_tag_t, EcsAstTag); - flecs_script_set_id(&result->id, first, second); - - flecs_ast_append(parser, scope->stmts, ecs_script_tag_t, result); - - return result; -} - -ecs_script_tag_t* flecs_script_insert_tag( - ecs_script_parser_t *parser, - const char *name) -{ - return flecs_script_insert_pair_tag(parser, name, NULL); -} - -ecs_script_component_t* flecs_script_insert_pair_component( - ecs_script_parser_t *parser, - const char *first, - const char *second) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_script_component_t *result = flecs_ast_new( - parser, ecs_script_component_t, EcsAstComponent); - flecs_script_set_id(&result->id, first, second); + flecs_poly_assert(world, ecs_world_t); + ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, + "cannot call progress while world is deferred"); - flecs_ast_append(parser, scope->stmts, ecs_script_component_t, result); + /* Make sure workers are running and ready */ + flecs_wait_for_workers(world); - return result; + /* 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); } -ecs_script_component_t* flecs_script_insert_component( - ecs_script_parser_t *parser, - const char *name) +static +void flecs_set_threads_internal( + ecs_world_t *world, + int32_t threads, + bool use_task_api) { - return flecs_script_insert_pair_component(parser, name, NULL); -} + ecs_assert(threads <= 1 || (use_task_api + ? ecs_os_has_task_support() + : ecs_os_has_threading()), + ECS_MISSING_OS_API, NULL); -ecs_script_default_component_t* flecs_script_insert_default_component( - ecs_script_parser_t *parser) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t stage_count = ecs_get_stage_count(world); + bool worker_method_changed = (use_task_api != world->workers_use_task_api); - ecs_script_default_component_t *result = flecs_ast_new( - parser, ecs_script_default_component_t, EcsAstDefaultComponent); + 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); - flecs_ast_append(parser, scope->stmts, - ecs_script_default_component_t, result); + 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); + } + } - return result; -} + world->workers_use_task_api = use_task_api; -ecs_script_var_component_t* flecs_script_insert_var_component( - ecs_script_parser_t *parser, - const char *var_name) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(var_name != NULL, ECS_INTERNAL_ERROR, NULL); + /* 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); + } + } +} - ecs_script_var_component_t *result = flecs_ast_new( - parser, ecs_script_var_component_t, EcsAstVarComponent); - result->name = var_name; - result->sp = -1; +/* -- Public functions -- */ - flecs_ast_append(parser, scope->stmts, - ecs_script_var_component_t, result); - return result; +void ecs_set_threads( + ecs_world_t *world, + int32_t threads) +{ + flecs_set_threads_internal(world, threads, false /* use thread API */); } -ecs_script_with_t* flecs_script_insert_with( - ecs_script_parser_t *parser) +void ecs_set_task_threads( + ecs_world_t *world, + int32_t task_threads) { - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_script_with_t *result = flecs_ast_new( - parser, ecs_script_with_t, EcsAstWith); - - result->expressions = flecs_script_scope_new(parser); - result->scope = flecs_script_scope_new(parser); - - flecs_ast_append(parser, scope->stmts, ecs_script_with_t, result); - return result; + flecs_set_threads_internal(world, task_threads, true /* use task API */); } -ecs_script_using_t* flecs_script_insert_using( - ecs_script_parser_t *parser, - const char *name) +bool ecs_using_task_threads( + ecs_world_t *world) { - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + return world->workers_use_task_api; +} - ecs_script_using_t *result = flecs_ast_new( - parser, ecs_script_using_t, EcsAstUsing); +#endif - result->name = name; +/** + * @file addons/script/query_parser.c + * @brief Script grammar parser. + */ - flecs_ast_append(parser, scope->stmts, ecs_script_using_t, result); - return result; -} +/** + * @file addons/parser/grammar.h + * @brief Grammar parser. + * + * Macro utilities that facilitate a simple recursive descent parser. + */ -ecs_script_module_t* flecs_script_insert_module( - ecs_script_parser_t *parser, - const char *name) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); +#ifndef FLECS_PARSER_GRAMMAR_H +#define FLECS_PARSER_GRAMMAR_H - ecs_script_module_t *result = flecs_ast_new( - parser, ecs_script_module_t, EcsAstModule); - result->name = name; +#if defined(ECS_TARGET_CLANG) +/* Ignore unused enum constants in switch as it would blow up the parser code */ +#pragma clang diagnostic ignored "-Wswitch-enum" +/* To allow for nested Parse statements */ +#pragma clang diagnostic ignored "-Wshadow" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wshadow" +#elif defined(ECS_TARGET_MSVC) +/* Allow for variable shadowing */ +#pragma warning(disable : 4456) +#endif - flecs_ast_append(parser, scope->stmts, ecs_script_module_t, result); - return result; -} +/* Create script & parser structs with static token buffer */ +#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ + ecs_script_impl_t script = {\ + .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ + .pub.name = script_name,\ + .pub.code = expr\ + };\ + ecs_parser_t parser = {\ + .script = flecs_script_impl(&script),\ + .name = script_name,\ + .code = expr,\ + .pos = expr,\ + .token_cur = tokens\ + } -ecs_script_annot_t* flecs_script_insert_annot( - ecs_script_parser_t *parser, - const char *name, - const char *expr) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); +/* Definitions for parser functions */ +#define ParserBegin\ + ecs_tokenizer_t _tokenizer;\ + ecs_os_zeromem(&_tokenizer);\ + _tokenizer.tokens = _tokenizer.stack.tokens;\ + ecs_tokenizer_t *tokenizer = &_tokenizer; - ecs_script_annot_t *result = flecs_ast_new( - parser, ecs_script_annot_t, EcsAstAnnotation); +#define ParserEnd\ + Error("unexpected end of rule (parser error)");\ + error:\ + return NULL - result->name = name; - result->expr = expr; +/* Get token */ +#define Token(n) (tokenizer->tokens[n].value) - flecs_ast_append(parser, scope->stmts, ecs_script_annot_t, result); - return result; -} +/* Push/pop token frame (allows token stack reuse in recursive functions) */ +#define TokenFramePush() \ + tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; -ecs_script_template_node_t* flecs_script_insert_template( - ecs_script_parser_t *parser, - const char *name) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); +#define TokenFramePop() \ + tokenizer->tokens = tokenizer->stack.tokens; - ecs_script_template_node_t *result = flecs_ast_new( - parser, ecs_script_template_node_t, EcsAstTemplate); - result->name = name; - result->scope = flecs_script_scope_new(parser); +/* Error */ +#define Error(...)\ + ecs_parser_error(parser->name, parser->code,\ + (pos - parser->code) - 1, __VA_ARGS__);\ + goto error - flecs_ast_append(parser, scope->stmts, ecs_script_template_node_t, result); - return result; -} +/* Warning */ +#define Warning(...)\ + ecs_parser_warning(parser->name, parser->code,\ + (pos - parser->code) - 1, __VA_ARGS__);\ -ecs_script_var_node_t* flecs_script_insert_var( - ecs_script_parser_t *parser, - const char *name) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); +/* Parse expression */ +#define Expr(until, ...)\ + {\ + ecs_expr_node_t *EXPR = NULL;\ + if (until == '}' || until == ']') {\ + pos --;\ + if (until == '}') {\ + ecs_assert(pos[0] == '{', ECS_INTERNAL_ERROR, NULL);\ + } else if (until == ']') {\ + ecs_assert(pos[0] == '[', ECS_INTERNAL_ERROR, NULL);\ + }\ + }\ + parser->significant_newline = false;\ + if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ + goto error;\ + }\ + parser->significant_newline = true;\ + __VA_ARGS__\ + } - ecs_script_var_node_t *result = flecs_ast_new( - parser, ecs_script_var_node_t, EcsAstConst); - result->name = name; +/* Parse initializer */ +#define Initializer(until, ...)\ + {\ + ecs_expr_node_t *INITIALIZER = NULL;\ + ecs_expr_initializer_t *_initializer = NULL;\ + if (until != '\n') {\ + parser->significant_newline = false;\ + }\ + if (!(pos = flecs_script_parse_initializer(\ + parser, pos, until, &_initializer))) \ + {\ + flecs_expr_visit_free(\ + &parser->script->pub, (ecs_expr_node_t*)_initializer);\ + goto error;\ + }\ + parser->significant_newline = true;\ + if (pos[0] != until) {\ + Error("expected '%c'", until);\ + }\ + INITIALIZER = (ecs_expr_node_t*)_initializer;\ + pos ++;\ + __VA_ARGS__\ + } - flecs_ast_append(parser, scope->stmts, ecs_script_var_node_t, result); - return result; -} +/* Parse token until character */ +#define Until(until, ...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_tokenizer_until(parser, pos, t, until))) {\ + goto error;\ + }\ + }\ + Parse_1(until, __VA_ARGS__) -ecs_script_if_t* flecs_script_insert_if( - ecs_script_parser_t *parser) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); +/* Parse next token */ +#define Parse(...)\ + {\ + ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ + ecs_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ + if (!(pos = flecs_token(parser, pos, t, false))) {\ + goto error;\ + }\ + switch(t->kind) {\ + __VA_ARGS__\ + default:\ + if (t->value) {\ + Error("unexpected %s'%s'", \ + flecs_token_kind_str(t->kind), t->value);\ + } else {\ + Error("unexpected %s", \ + flecs_token_kind_str(t->kind));\ + }\ + }\ + } - ecs_script_if_t *result = flecs_ast_new( - parser, ecs_script_if_t, EcsAstIf); - result->if_true = flecs_script_scope_new(parser); - result->if_false = flecs_script_scope_new(parser); +/* Parse N consecutive tokens */ +#define Parse_1(tok, ...)\ + Parse(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) - flecs_ast_append(parser, scope->stmts, ecs_script_if_t, result); - return result; -} +#define Parse_2(tok1, tok2, ...)\ + Parse_1(tok1, \ + Parse(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + ) -ecs_script_for_range_t* flecs_script_insert_for_range( - ecs_script_parser_t *parser) -{ - ecs_script_scope_t *scope = parser->scope; - ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); +#define Parse_3(tok1, tok2, tok3, ...)\ + Parse_2(tok1, tok2, \ + Parse(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ + ) - ecs_script_for_range_t *result = flecs_ast_new( - parser, ecs_script_for_range_t, EcsAstFor); - result->scope = flecs_script_scope_new(parser); +#define Parse_4(tok1, tok2, tok3, tok4, ...)\ + Parse_3(tok1, tok2, tok3, \ + Parse(\ + case tok4: {\ + __VA_ARGS__\ + }\ + )\ + ) - flecs_ast_append(parser, scope->stmts, ecs_script_for_range_t, result); - return result; -} +#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ + Parse_4(tok1, tok2, tok3, tok4, \ + Parse(\ + case tok5: {\ + __VA_ARGS__\ + }\ + )\ + ) -#endif +#define LookAhead_Keep() \ + pos = lookahead;\ + parser->token_keep = parser->token_cur -/** - * @file addons/script/function.c - * @brief Script function API. - */ +/* Same as Parse, but doesn't error out if token is not in handled cases */ +#define LookAhead(...)\ + const char *lookahead;\ + ecs_token_t lookahead_token;\ + const char *old_lh_token_cur = parser->token_cur;\ + if ((lookahead = flecs_token(parser, pos, &lookahead_token, true))) {\ + tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ + switch(lookahead_token.kind) {\ + __VA_ARGS__\ + default:\ + tokenizer->stack.count --;\ + break;\ + }\ + if (old_lh_token_cur > parser->token_keep) {\ + parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ + } else {\ + parser->token_cur = parser->token_keep;\ + }\ + } +/* Lookahead N consecutive tokens */ +#define LookAhead_1(tok, ...)\ + LookAhead(\ + case tok: {\ + __VA_ARGS__\ + }\ + ) -#ifdef FLECS_SCRIPT +#define LookAhead_2(tok1, tok2, ...)\ + LookAhead_1(tok1, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok2: {\ + __VA_ARGS__\ + }\ + )\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ + ) -static -void ecs_script_params_free(ecs_vec_t *params) { - ecs_script_parameter_t *array = ecs_vec_first(params); - int32_t i, count = ecs_vec_count(params); - for (i = 0; i < count; i ++) { - /* Safe, component owns string */ - ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); - } - ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); -} +#define LookAhead_3(tok1, tok2, tok3, ...)\ + LookAhead_2(tok1, tok2, \ + const char *old_ptr = pos;\ + pos = lookahead;\ + LookAhead(\ + case tok3: {\ + __VA_ARGS__\ + }\ + )\ + if (pos != lookahead) {\ + pos = old_ptr;\ + }\ + ) -static -ECS_MOVE(EcsScriptConstVar, dst, src, { - if (dst->type_info->hooks.dtor) { - dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); +/* Open scope */ +#define Scope(s, ...) {\ + ecs_script_scope_t *old_scope = parser->scope;\ + parser->scope = s;\ + __VA_ARGS__\ + parser->scope = old_scope;\ } - ecs_os_free(dst->value.ptr); - - *dst = *src; +/* Parser loop */ +#define Loop(...)\ + int32_t token_stack_count = tokenizer->stack.count;\ + do {\ + tokenizer->stack.count = token_stack_count;\ + __VA_ARGS__\ + } while (true); - src->value.ptr = NULL; - src->value.type = 0; - src->type_info = NULL; -}) +#define EndOfRule return pos -static -ECS_DTOR(EcsScriptConstVar, ptr, { - if (ptr->type_info->hooks.dtor) { - ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); - } - ecs_os_free(ptr->value.ptr); -}) +#endif -static -ECS_MOVE(EcsScriptFunction, dst, src, { - ecs_script_params_free(&dst->params); - *dst = *src; - ecs_os_zeromem(src); -}) -static -ECS_DTOR(EcsScriptFunction, ptr, { - ecs_script_params_free(&ptr->params); -}) +#ifdef FLECS_QUERY_DSL -static -ECS_MOVE(EcsScriptMethod, dst, src, { - ecs_script_params_free(&dst->params); - *dst = *src; - ecs_os_zeromem(src); -}) -static -ECS_DTOR(EcsScriptMethod, ptr, { - ecs_script_params_free(&ptr->params); -}) +#define EcsTokTermIdentifier\ + EcsTokIdentifier:\ + case EcsTokNumber:\ + case EcsTokMul -ecs_entity_t ecs_const_var_init( - ecs_world_t *world, - ecs_const_var_desc_t *desc) +#define EcsTokEndOfTerm\ + '}':\ + pos --; /* Give token back to parser */\ + case EcsTokOr:\ + if (t->kind == EcsTokOr) {\ + if (parser->term->oper != EcsAnd) {\ + Error("cannot mix operators in || expression");\ + }\ + parser->term->oper = EcsOr;\ + }\ + case ',':\ + case '\n':\ + case '\0' + +// $this == +static +const char* flecs_term_parse_equality_pred( + ecs_parser_t *parser, + const char *pos, + ecs_entity_t pred) { - flecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->type != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->value != NULL, ECS_INVALID_PARAMETER, NULL); + ParserBegin; - const ecs_type_info_t *ti = ecs_get_type_info(world, desc->type); - ecs_check(ti != NULL, ECS_INVALID_PARAMETER, - "ecs_const_var_desc_t::type is not a valid type"); + if (parser->term->oper != EcsAnd) { + Error("cannot mix operator with equality expression"); + } + + parser->term->src = parser->term->first; + parser->term->first = (ecs_term_ref_t){0}; + parser->term->first.id = pred; + + Parse( + // $this == foo + // ^ + case EcsTokTermIdentifier: { + parser->term->second.name = Token(0); + Parse( case EcsTokEndOfTerm: EndOfRule; ) + } - ecs_entity_t result = ecs_entity(world, { - .name = desc->name, - .parent = desc->parent - }); + // $this == "foo" + // ^ + case EcsTokString: { + parser->term->second.name = Token(0); + parser->term->second.id = EcsIsName; - if (!result) { - goto error; - } + if (pred == EcsPredMatch) { + if (Token(0)[0] == '!') { + /* If match expression starts with !, set Not operator. The + * reason the ! is embedded in the expression is because + * there is only a single match (~=) operator. */ + parser->term->second.name ++; + parser->term->oper = EcsNot; + } + } - EcsScriptConstVar *v = ecs_ensure(world, result, EcsScriptConstVar); - v->value.ptr = ecs_os_malloc(ti->size); - v->value.type = desc->type; - v->type_info = ti; - ecs_value_init(world, desc->type, v->value.ptr); - ecs_value_copy(world, desc->type, v->value.ptr, desc->value); - ecs_modified(world, result, EcsScriptConstVar); + Parse( + case EcsTokEndOfTerm: + EndOfRule; + ) + } + ) - return result; -error: - return 0; + ParserEnd; } -ecs_entity_t ecs_function_init( - ecs_world_t *world, - const ecs_function_desc_t *desc) +static +ecs_entity_t flecs_query_parse_trav_flags( + const char *tok) { - flecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); - - ecs_entity_t result = ecs_entity(world, { - .name = desc->name, - .parent = desc->parent - }); + if (!ecs_os_strcmp(tok, "self")) return EcsSelf; + else if (!ecs_os_strcmp(tok, "up")) return EcsUp; + else if (!ecs_os_strcmp(tok, "cascade")) return EcsCascade; + else if (!ecs_os_strcmp(tok, "desc")) return EcsDesc; + else return 0; +} - if (!result) { - goto error; - } +static +const char* flecs_term_parse_trav( + ecs_parser_t *parser, + ecs_term_ref_t *ref, + const char *pos) +{ + ParserBegin; - EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); - f->return_type = desc->return_type; - f->callback = desc->callback; - f->ctx = desc->ctx; + Loop( + // self + Parse_1(EcsTokIdentifier, + ref->id |= flecs_query_parse_trav_flags(Token(0)); - int32_t i; - for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { - if (!desc->params[i].name) { - break; - } + LookAhead( + // self| + case '|': + pos = lookahead; + continue; - if (!i) { - ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); - } + // self IsA + case EcsTokIdentifier: + pos = lookahead; + parser->term->trav = ecs_lookup( + parser->world, Token(1)); + if (!parser->term->trav) { + Error( + "unresolved traversal relationship '%s'", Token(1)); + goto error; + } - ecs_script_parameter_t *p = ecs_vec_append_t( - NULL, &f->params, ecs_script_parameter_t); - p->type = desc->params[i].type; - p->name = ecs_os_strdup(desc->params[i].name); - } + EndOfRule; + ) - ecs_modified(world, result, EcsScriptFunction); + EndOfRule; + ) + ) - return result; -error: - return 0; + ParserEnd; } -ecs_entity_t ecs_method_init( - ecs_world_t *world, - const ecs_function_desc_t *desc) +// Position( +static +const char* flecs_term_parse_arg( + ecs_parser_t *parser, + const char *pos, + int32_t arg) { - flecs_poly_assert(world, ecs_world_t); - ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); - ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); + ParserBegin; - ecs_entity_t result = ecs_entity(world, { - .name = desc->name, - .parent = desc->parent - }); + ecs_term_ref_t *ref = NULL; - if (!result) { - goto error; + // Position(src + if (arg == 0) { + ref = &parser->term->src; + + // Position(src, tgt + } else if (arg == 1) { + ref = &parser->term->second; + } else { + if (arg > FLECS_TERM_ARG_COUNT_MAX) { + Error("too many arguments in term"); + } + ref = &parser->extra_args[arg - 2]; } - EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); - f->return_type = desc->return_type; - f->callback = desc->callback; - f->ctx = desc->ctx; + bool is_trav_flag = false; - int32_t i; - for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { - if (!desc->params[i].name) { - break; - } + LookAhead_1(EcsTokIdentifier, + is_trav_flag = flecs_query_parse_trav_flags(Token(0)) != 0; + ) - if (!i) { - ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + if (is_trav_flag) { + // Position(self|up + // ^ + pos = flecs_term_parse_trav(parser, ref, pos); + if (!pos) { + goto error; } + } else { + // Position(src + // ^ + Parse( + case EcsTokTermIdentifier: { + ref->name = Token(0); - ecs_script_parameter_t *p = ecs_vec_append_t( - NULL, &f->params, ecs_script_parameter_t); - p->type = desc->params[i].type; - p->name = ecs_os_strdup(desc->params[i].name); + // Position(src| + // ^ + { + LookAhead_1('|', + pos = lookahead; + pos = flecs_term_parse_trav(parser, ref, pos); + if (!pos) { + goto error; + } + + // Position(src|up IsA + // ^ + LookAhead_1(EcsTokIdentifier, + pos = lookahead; + parser->term->trav = ecs_lookup( + parser->world, Token(1)); + if (!parser->term->trav) { + Error( + "unresolved trav identifier '%s'", Token(1)); + } + ) + ) + } + + break; + } + ) } - ecs_modified(world, result, EcsScriptMethod); + Parse( + // Position(src, + // ^ + case ',': + if ((arg > 1) && parser->extra_oper != EcsAnd) { + Error("cannot mix operators in extra term arguments"); + } + parser->extra_oper = EcsAnd; + return flecs_term_parse_arg(parser, pos, arg + 1); - return result; -error: - return 0; + // Position(src, second || + // ^ + case EcsTokOr: + if ((arg > 1) && parser->extra_oper != EcsOr) { + Error("cannot mix operators in extra term arguments"); + } + parser->extra_oper = EcsOr; + return flecs_term_parse_arg(parser, pos, arg + 1); + + // Position(src) + // ^ + case ')': + Parse( + case EcsTokEndOfTerm: + EndOfRule; + ) + ) + + ParserEnd; } -void flecs_function_import( - ecs_world_t *world) +// Position +static +const char* flecs_term_parse_id( + ecs_parser_t *parser, + const char *pos) { - ecs_set_name_prefix(world, "EcsScript"); - ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); - ECS_COMPONENT_DEFINE(world, EcsScriptFunction); - ECS_COMPONENT_DEFINE(world, EcsScriptMethod); + ParserBegin; - ecs_struct(world, { - .entity = ecs_id(EcsScriptFunction), - .members = { - { .name = "return_type", .type = ecs_id(ecs_entity_t) } + Parse( + case EcsTokEq: + return flecs_term_parse_equality_pred( + parser, pos, EcsPredEq); + case EcsTokNeq: { + const char *ret = flecs_term_parse_equality_pred( + parser, pos, EcsPredEq); + if (ret) { + parser->term->oper = EcsNot; + } + return ret; } - }); + case EcsTokMatch: + return flecs_term_parse_equality_pred( + parser, pos, EcsPredMatch); - ecs_struct(world, { - .entity = ecs_id(EcsScriptMethod), - .members = { - { .name = "return_type", .type = ecs_id(ecs_entity_t) } + // Position| + case '|': { + pos = flecs_term_parse_trav(parser, &parser->term->first, pos); + if (!pos) { + goto error; + } + + // Position|self( + Parse( + case '(': + return flecs_term_parse_arg(parser, pos, 0); + case EcsTokEndOfTerm: + EndOfRule; + ) } - }); - ecs_set_hooks(world, EcsScriptConstVar, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsScriptConstVar), - .move = ecs_move(EcsScriptConstVar), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); + // Position( + case '(': { + // Position() + LookAhead_1(')', + pos = lookahead; + parser->term->src.id = EcsIsEntity; - ecs_set_hooks(world, EcsScriptFunction, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsScriptFunction), - .move = ecs_move(EcsScriptFunction), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); + Parse( + case EcsTokEndOfTerm: + EndOfRule; + ) + ) - ecs_set_hooks(world, EcsScriptMethod, { - .ctor = flecs_default_ctor, - .dtor = ecs_dtor(EcsScriptMethod), - .move = ecs_move(EcsScriptMethod), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); + return flecs_term_parse_arg(parser, pos, 0); + } - flecs_script_register_builtin_functions(world); + case EcsTokEndOfTerm: + EndOfRule; + ) + + ParserEnd; } -#endif +// ( +static const char* flecs_term_parse_pair( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; -/** - * @file addons/script/builtin_functions.c - * @brief Flecs functions for flecs script. - */ + // (Position + // ^ + Parse( + case EcsTokTermIdentifier: { + parser->term->first.name = Token(0); + LookAhead_1('|', + // (Position|self + pos = lookahead; + pos = flecs_term_parse_trav( + parser, &parser->term->first, pos); + if (!pos) { + goto error; + } + ) -#ifdef FLECS_SCRIPT + // (Position, + Parse_1(',', + return flecs_term_parse_arg(parser, pos, 1); + ) + } + ) -static -void flecs_meta_entity_name( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); + ParserEnd; } +// AND static -void flecs_meta_entity_path( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) +const char* flecs_term_parse_flags( + ecs_parser_t *parser, + const char *token_0, + const char *pos) { - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(char**)result->ptr = ecs_get_path(ctx->world, entity); -} + ecs_assert(token_0 != NULL, ECS_INTERNAL_ERROR, NULL); -static -void flecs_meta_entity_parent( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); -} + ParserBegin; -static -void flecs_meta_entity_has( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; - ecs_id_t id = *(ecs_id_t*)argv[1].ptr; - *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); -} + ecs_id_t flag = 0; + int16_t oper = 0; + ecs_term_t *term = parser->term; -static -void flecs_meta_core_pair( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)argc; - (void)ctx; - ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; - ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; - *(ecs_id_t*)result->ptr = ecs_pair(first, second); -} + // AND + if (!ecs_os_strcmp(token_0, "and")) oper = EcsAndFrom; + else if (!ecs_os_strcmp(token_0, "or")) oper = EcsOrFrom; + else if (!ecs_os_strcmp(token_0, "not")) oper = EcsNotFrom; + else if (!ecs_os_strcmp(token_0, "auto_override")) flag = ECS_AUTO_OVERRIDE; + else if (!ecs_os_strcmp(token_0, "toggle")) flag = ECS_TOGGLE; + else { + // Position + term->first.name = token_0; + return flecs_term_parse_id(parser, pos); + } -#ifdef FLECS_DOC + if (oper || flag) { + // and | + // ^ + Parse_1('|', + Parse( + // and | Position + // ^ + case EcsTokTermIdentifier: { + if (oper) { + term->oper = oper; + } else if (flag) { + term->id = flag; + } -#define FLECS_DOC_FUNC(name)\ - static\ - void flecs_meta_entity_doc_##name(\ - const ecs_function_ctx_t *ctx,\ - int32_t argc,\ - const ecs_value_t *argv,\ - ecs_value_t *result)\ - {\ - (void)argc;\ - ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ - *(char**)result->ptr = \ - ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ - } + term->first.name = Token(1); -FLECS_DOC_FUNC(name) -FLECS_DOC_FUNC(uuid) -FLECS_DOC_FUNC(brief) -FLECS_DOC_FUNC(detail) -FLECS_DOC_FUNC(link) -FLECS_DOC_FUNC(color) + return flecs_term_parse_id(parser, pos); + } -#undef FLECS_DOC_FUNC + // and | ( + // ^ + case '(': { + return flecs_term_parse_pair(parser, pos); + } + ) + ) + } + + ParserEnd; +} +// ! static -void flecs_script_register_builtin_doc_functions( - ecs_world_t *world) +const char* flecs_term_parse_unary( + ecs_parser_t *parser, + const char *pos) { - { - ecs_entity_t m = ecs_method(world, { - .name = "doc_name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_name - }); + ParserBegin; - ecs_doc_set_brief(world, m, "Returns entity doc name"); - } + Parse( + // !( + case '(': { + return flecs_term_parse_pair(parser, pos); + } - { - ecs_entity_t m = ecs_method(world, { - .name = "doc_uuid", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_uuid - }); + // !{ + case '{': { + parser->term->first.id = EcsScopeOpen; + parser->term->src.id = EcsIsEntity; + parser->term->inout = EcsInOutNone; + EndOfRule; + } - ecs_doc_set_brief(world, m, "Returns entity doc uuid"); - } + // !Position + // ^ + case EcsTokTermIdentifier: { + parser->term->first.name = Token(0); + return flecs_term_parse_id(parser, pos); + } + ) - { - ecs_entity_t m = ecs_method(world, { - .name = "doc_brief", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_brief - }); + ParserEnd; +} - ecs_doc_set_brief(world, m, "Returns entity doc brief description"); - } +// [ +static +const char* flecs_term_parse_inout( + ecs_parser_t *parser, + const char *pos) +{ + ParserBegin; - { - ecs_entity_t m = ecs_method(world, { - .name = "doc_detail", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_detail - }); + ecs_term_t *term = parser->term; - ecs_doc_set_brief(world, m, "Returns entity doc detailed description"); - } + // [inout] + // ^ + Parse_2(EcsTokIdentifier, ']', + if (!ecs_os_strcmp(Token(0), "default")) term->inout = EcsInOutDefault; + else if (!ecs_os_strcmp(Token(0), "none")) term->inout = EcsInOutNone; + else if (!ecs_os_strcmp(Token(0), "filter")) term->inout = EcsInOutFilter; + else if (!ecs_os_strcmp(Token(0), "inout")) term->inout = EcsInOut; + else if (!ecs_os_strcmp(Token(0), "in")) term->inout = EcsIn; + else if (!ecs_os_strcmp(Token(0), "out")) term->inout = EcsOut; - { - ecs_entity_t m = ecs_method(world, { - .name = "doc_link", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_link - }); + Parse( + // [inout] Position + // ^ + case EcsTokTermIdentifier: { + return flecs_term_parse_flags(parser, Token(2), pos); + } - ecs_doc_set_brief(world, m, "Returns entity doc link"); - } + // [inout] !Position + // ^ + case '!': + term->oper = EcsNot; + return flecs_term_parse_unary(parser, pos); + case '?': + term->oper = EcsOptional; + return flecs_term_parse_unary(parser, pos); - { - ecs_entity_t m = ecs_method(world, { - .name = "doc_color", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_doc_color - }); + // [inout] ( + // ^ + case '(': { + return flecs_term_parse_pair(parser, pos); + } + ) + ) - ecs_doc_set_brief(world, m, "Returns entity doc color"); - } + ParserEnd; } -#else - static -void flecs_script_register_builtin_doc_functions( - ecs_world_t *world) +const char* flecs_query_term_parse( + ecs_parser_t *parser, + const char *pos) { - (void)world; -} + ParserBegin; -#endif + Parse( + case '[': + return flecs_term_parse_inout(parser, pos); + case EcsTokTermIdentifier: + return flecs_term_parse_flags(parser, Token(0), pos); + case '(': + return flecs_term_parse_pair(parser, pos); + case '!': + parser->term->oper = EcsNot; + return flecs_term_parse_unary(parser, pos); + case '?': + parser->term->oper = EcsOptional; + return flecs_term_parse_unary(parser, pos); + case '{': + parser->term->first.id = EcsScopeOpen; + parser->term->src.id = EcsIsEntity; + parser->term->inout = EcsInOutNone; + EndOfRule; + case '}': + parser->term->first.id = EcsScopeClose; + parser->term->src.id = EcsIsEntity; + parser->term->inout = EcsInOutNone; + LookAhead_1(',', + pos = lookahead; + ) + EndOfRule; + case '\n':\ + case '\0': + EndOfRule; + ); -void flecs_script_register_builtin_functions( - ecs_world_t *world) -{ - { - ecs_entity_t m = ecs_method(world, { - .name = "name", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_name - }); + ParserEnd; +} - ecs_doc_set_brief(world, m, "Returns entity name"); +int flecs_terms_parse( + ecs_world_t *world, + const char *name, + const char *code, + char *token_buffer, + ecs_term_t *terms, + int32_t *term_count_out) +{ + if (!ecs_os_strcmp(code, "0")) { + *term_count_out = 0; + return 0; } - { - ecs_entity_t m = ecs_method(world, { - .name = "path", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_string_t), - .callback = flecs_meta_entity_path - }); + ecs_parser_t parser = { + .name = name, + .code = code, + .world = world, + .pos = code, + .merge_variable_members = true + }; - ecs_doc_set_brief(world, m, "Returns entity path"); - } + parser.token_cur = token_buffer; - { - ecs_entity_t m = ecs_method(world, { - .name = "parent", - .parent = ecs_id(ecs_entity_t), - .return_type = ecs_id(ecs_entity_t), - .callback = flecs_meta_entity_parent - }); + int32_t term_count = 0; + const char *ptr = code; + ecs_term_ref_t extra_args[FLECS_TERM_ARG_COUNT_MAX]; + ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, + FLECS_TERM_ARG_COUNT_MAX); - ecs_doc_set_brief(world, m, "Returns entity parent"); - } + parser.extra_args = extra_args; + parser.extra_oper = 0; - { - ecs_entity_t m = ecs_method(world, { - .name = "has", - .parent = ecs_id(ecs_entity_t), - .params = { - { .name = "component", .type = ecs_id(ecs_id_t) } - }, - .return_type = ecs_id(ecs_bool_t), - .callback = flecs_meta_entity_has - }); + do { + if (term_count == FLECS_TERM_COUNT_MAX) { + ecs_err("max number of terms (%d) reached, increase " + "FLECS_TERM_COUNT_MAX to support more", + FLECS_TERM_COUNT_MAX); + goto error; + } - ecs_doc_set_brief(world, m, "Returns whether entity has component"); - } + /* Parse next term */ + ecs_term_t *term = &terms[term_count]; + parser.term = term; + ecs_os_memset_t(term, 0, ecs_term_t); + ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); + parser.extra_oper = 0; - { - ecs_entity_t m = ecs_function(world, { - .name = "pair", - .parent = ecs_entity(world, { .name = "core"}), - .params = { - { .name = "first", .type = ecs_id(ecs_entity_t) }, - { .name = "second", .type = ecs_id(ecs_entity_t) } - }, - .return_type = ecs_id(ecs_id_t), - .callback = flecs_meta_core_pair - }); + ptr = flecs_query_term_parse(&parser, ptr); + if (!ptr) { + /* Parser error */ + goto error; + } - ecs_doc_set_brief(world, m, "Returns a pair identifier"); - } + if (!ecs_term_is_initialized(term)) { + /* Last term parsed */ + break; + } - flecs_script_register_builtin_doc_functions(world); -} + term_count ++; -#endif + /* Unpack terms with more than two args into multiple terms so that: + * Rel(X, Y, Z) + * becomes: + * Rel(X, Y), Rel(Y, Z) */ + int32_t arg = 0; + while (ecs_term_ref_is_set(&extra_args[arg ++])) { + ecs_assert(arg <= FLECS_TERM_ARG_COUNT_MAX, + ECS_INTERNAL_ERROR, NULL); -/** - * @file addons/script/functions_math.c - * @brief Math functions for flecs script. - */ + if (term_count == FLECS_TERM_COUNT_MAX) { + ecs_err("max number of terms (%d) reached, increase " + "FLECS_TERM_COUNT_MAX to support more", + FLECS_TERM_COUNT_MAX); + goto error; + } + term = &terms[term_count ++]; + *term = term[-1]; -#ifdef FLECS_SCRIPT_MATH -#include + if (parser.extra_oper == EcsAnd) { + term->src = term[-1].second; + term->second = extra_args[arg - 1]; + } else if (parser.extra_oper == EcsOr) { + term->src = term[-1].src; + term->second = extra_args[arg - 1]; + term[-1].oper = EcsOr; + } -typedef struct ecs_script_rng_t { - uint64_t x; /* Current state (initialize with seed) */ - uint64_t w; /* Weyl sequence increment */ - uint64_t s; /* Constant for Weyl sequence */ - int32_t refcount; /* Necessary as flecs script doesn't have ref types */ - bool initialized; -} ecs_script_rng_t; + if (term->first.name != NULL) { + term->first.name = term->first.name; + } + + if (term->src.name != NULL) { + term->src.name = term->src.name; + } + } -static -ecs_script_rng_t* flecs_script_rng_new(void) { - ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); - result->x = 0; - result->w = 0; - result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ - result->refcount = 1; - result->initialized = false; - return result; -} + if (arg) { + ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, + FLECS_TERM_ARG_COUNT_MAX); + } + } while (ptr[0]); -static -void flecs_script_rng_keep(ecs_script_rng_t *rng) { - if (!rng) { - return; - } - rng->refcount ++; -} + (*term_count_out) += term_count; -static -void flecs_script_rng_free(ecs_script_rng_t *rng) { - if (!rng) { - return; - } - ecs_assert(rng->refcount > 0, ECS_INTERNAL_ERROR, NULL); - if (!--rng->refcount) { - ecs_os_free(rng); - } + return 0; +error: + return -1; } -static -uint64_t flecs_script_rng_next(ecs_script_rng_t *rng) { - rng->x *= rng->x; - rng->x += (rng->w += rng->s); - rng->x = (rng->x >> 32) | (rng->x << 32); - return rng->x; -} +const char* flecs_term_parse( + ecs_world_t *world, + const char *name, + const char *expr, + char *token_buffer, + ecs_term_t *term) +{ + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); + ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); -ECS_COMPONENT_DECLARE(EcsScriptRng); + ecs_parser_t parser = { + .name = name, + .code = expr, + .world = world, + .token_cur = token_buffer + }; -static -ECS_CTOR(EcsScriptRng, ptr, { - ptr->seed = 0; - ptr->impl = flecs_script_rng_new(); -}) + parser.term = term; -static -ECS_COPY(EcsScriptRng, dst, src, { - flecs_script_rng_keep(src->impl); - if (dst->impl != src->impl) { - flecs_script_rng_free(dst->impl); + const char *result = flecs_query_term_parse(&parser, expr); + if (!result) { + return NULL; } - dst->seed = src->seed; - dst->impl = src->impl; -}) -static -ECS_MOVE(EcsScriptRng, dst, src, { - flecs_script_rng_free(dst->impl); - dst->seed = src->seed; - dst->impl = src->impl; - src->impl = NULL; -}) + ecs_os_memset_t(term, 0, ecs_term_t); -static -ECS_DTOR(EcsScriptRng, ptr, { - flecs_script_rng_free(ptr->impl); -}) + return flecs_query_term_parse(&parser, expr); +} -void flecs_script_rng_get_float( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) +const char* flecs_id_parse( + const ecs_world_t *world, + const char *name, + const char *expr, + ecs_id_t *id) { - (void)ctx; - (void)argc; - EcsScriptRng *rng = argv[0].ptr; - ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_rng_t *impl = rng->impl; - if (!impl->initialized) { - impl->x = rng->seed; - impl->initialized = true; - } - uint64_t x = flecs_script_rng_next(rng->impl); - double max = *(double*)argv[1].ptr; - double *r = result->ptr; + ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); + ecs_assert(id != NULL, ECS_INVALID_PARAMETER, NULL); - if (ECS_EQZERO(max)) { - ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); - } else { - *r = (double)x / ((double)UINT64_MAX / max); - } -} + char token_buffer[256]; -void flecs_script_rng_get_uint( - const ecs_function_ctx_t *ctx, - int32_t argc, - const ecs_value_t *argv, - ecs_value_t *result) -{ - (void)ctx; - (void)argc; - EcsScriptRng *rng = argv[0].ptr; - ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_script_rng_t *impl = rng->impl; - if (!impl->initialized) { - impl->x = rng->seed; - impl->initialized = true; - } - uint64_t x = flecs_script_rng_next(rng->impl); - uint64_t max = *(uint64_t*)argv[1].ptr; - uint64_t *r = result->ptr; - if (!max) { - ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); - } else { - *r = x % max; + ecs_parser_t parser = { + .name = name, + .code = expr, + .world = ECS_CONST_CAST(ecs_world_t*, world), /* Safe, won't modify */ + .token_cur = token_buffer + }; + + ecs_term_t term = {0}; + parser.term = &term; + + expr = flecs_scan_whitespace(&parser, expr); + if (!ecs_os_strcmp(expr, "#0")) { + *id = 0; + return &expr[1]; } -} -#define FLECS_MATH_FUNC_F64(name, ...)\ - static\ - void flecs_math_##name(\ - const ecs_function_ctx_t *ctx,\ - int32_t argc,\ - const ecs_value_t *argv,\ - ecs_value_t *result)\ - {\ - (void)ctx;\ - (void)argc;\ - ecs_assert(argc == 1, ECS_INTERNAL_ERROR, NULL);\ - double x = *(double*)argv[0].ptr;\ - *(double*)result->ptr = __VA_ARGS__;\ + const char *result = flecs_query_term_parse(&parser, expr); + if (!result) { + return NULL; } -#define FLECS_MATH_FUNC_F64_F64(name, ...)\ - static\ - void flecs_math_##name(\ - const ecs_function_ctx_t *ctx,\ - int32_t argc,\ - const ecs_value_t *argv,\ - ecs_value_t *result)\ - {\ - (void)ctx;\ - (void)argc;\ - ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ - double x = *(double*)argv[0].ptr;\ - double y = *(double*)argv[1].ptr;\ - *(double*)result->ptr = __VA_ARGS__;\ + if (ecs_term_finalize(world, &term)) { + return NULL; } -#define FLECS_MATH_FUNC_F64_I32(name, ...)\ - static\ - void flecs_math_##name(\ - const ecs_function_ctx_t *ctx,\ - int32_t argc,\ - const ecs_value_t *argv,\ - ecs_value_t *result)\ - {\ - (void)ctx;\ - (void)argc;\ - ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ - double x = *(double*)argv[0].ptr;\ - ecs_i32_t y = *(ecs_i32_t*)argv[1].ptr;\ - *(double*)result->ptr = __VA_ARGS__;\ + if (term.oper != EcsAnd) { + ecs_parser_error(name, expr, (result - expr), + "invalid operator for add expression"); + return NULL; } -#define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ - {\ - ecs_entity_t f = ecs_function(world, {\ - .name = #_name,\ - .parent = ecs_id(FlecsScriptMath),\ - .return_type = ecs_id(ecs_f64_t),\ - .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }},\ - .callback = flecs_math_##_name\ - });\ - ecs_doc_set_brief(world, f, brief);\ + if ((term.src.id & ~EcsTraverseFlags) != (EcsThis|EcsIsVariable)) { + ecs_parser_error(name, expr, (result - expr), + "invalid source for add expression (must be $this)"); + return NULL; } -#define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ - {\ - ecs_entity_t f = ecs_function(world, {\ - .name = #_name,\ - .parent = ecs_id(FlecsScriptMath),\ - .return_type = ecs_id(ecs_f64_t),\ - .params = {\ - { .name = "x", .type = ecs_id(ecs_f64_t) },\ - { .name = "y", .type = ecs_id(ecs_f64_t) }\ - },\ - .callback = flecs_math_##_name\ - });\ - ecs_doc_set_brief(world, f, brief);\ - } + *id = term.id; + + return result; +} + +static +const char* flecs_query_arg_parse( + ecs_parser_t *parser, + ecs_query_t *q, + ecs_iter_t *it, + const char *pos) +{ + ParserBegin; + + Parse_3(EcsTokIdentifier, ':', EcsTokIdentifier, { + int var = ecs_query_find_var(q, Token(0)); + if (var == -1) { + Error("unknown variable '%s'", Token(0)); + } + + ecs_entity_t val = ecs_lookup(q->world, Token(2)); + if (!val) { + Error("unresolved entity '%s'", Token(2)); + } + + ecs_iter_set_var(it, var, val); -#define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ - {\ - ecs_entity_t f = ecs_function(world, {\ - .name = #_name,\ - .parent = ecs_id(FlecsScriptMath),\ - .return_type = ecs_id(ecs_f64_t),\ - .params = {\ - { .name = "x", .type = ecs_id(ecs_f64_t) },\ - { .name = "y", .type = ecs_id(ecs_i32_t) }\ - },\ - .callback = flecs_math_##_name\ - });\ - ecs_doc_set_brief(world, f, brief);\ - } + EndOfRule; + }) -/* Trigonometric functions */ -FLECS_MATH_FUNC_F64(cos, cos(x)) -FLECS_MATH_FUNC_F64(sin, sin(x)) -FLECS_MATH_FUNC_F64(tan, tan(x)) -FLECS_MATH_FUNC_F64(acos, acos(x)) -FLECS_MATH_FUNC_F64(asin, asin(x)) -FLECS_MATH_FUNC_F64(atan, atan(x)) -FLECS_MATH_FUNC_F64_F64(atan2, atan2(x, y)) + ParserEnd; +} -/* Hyperbolic functions */ -FLECS_MATH_FUNC_F64(cosh, cosh(x)) -FLECS_MATH_FUNC_F64(sinh, sinh(x)) -FLECS_MATH_FUNC_F64(tanh, tanh(x)) -FLECS_MATH_FUNC_F64(acosh, acosh(x)) -FLECS_MATH_FUNC_F64(asinh, asinh(x)) -FLECS_MATH_FUNC_F64(atanh, atanh(x)) +static +const char* flecs_query_args_parse( + ecs_parser_t *parser, + ecs_query_t *q, + ecs_iter_t *it, + const char *pos) +{ + ParserBegin; -/* Exponential and logarithmic functions */ -FLECS_MATH_FUNC_F64(exp, exp(x)) -FLECS_MATH_FUNC_F64_I32(ldexp, ldexp(x, y)) -FLECS_MATH_FUNC_F64(log, log(x)) -FLECS_MATH_FUNC_F64(log10, log10(x)) -FLECS_MATH_FUNC_F64(exp2, exp2(x)) -FLECS_MATH_FUNC_F64(log2, log2(x)) + bool has_paren = false; + LookAhead( + case '\0': + pos = lookahead; + EndOfRule; + case '(': { + pos = lookahead; + has_paren = true; + LookAhead_1(')', + pos = lookahead; + EndOfRule; + ) + } + ) -/* Power functions */ -FLECS_MATH_FUNC_F64_F64(pow, pow(x, y)) -FLECS_MATH_FUNC_F64(sqrt, sqrt(x)) -FLECS_MATH_FUNC_F64(sqr, x * x) + Loop( + pos = flecs_query_arg_parse(parser, q, it, pos); + if (!pos) { + goto error; + } -/* Rounding functions */ -FLECS_MATH_FUNC_F64(ceil, ceil(x)) -FLECS_MATH_FUNC_F64(floor, floor(x)) -FLECS_MATH_FUNC_F64(round, round(x)) + Parse( + case ',': + continue; + case '\0': + EndOfRule; + case ')': + if (!has_paren) { + Error("unexpected ')' without opening '(')"); + } + EndOfRule; + ) + ) -FLECS_MATH_FUNC_F64(abs, fabs(x)) + ParserEnd; +} -FLECS_API -void FlecsScriptMathImport( - ecs_world_t *world) +const char* ecs_query_args_parse( + ecs_query_t *q, + ecs_iter_t *it, + const char *expr) { - ECS_MODULE(world, FlecsScriptMath); + flecs_poly_assert(q, ecs_query_t); + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL); - ECS_IMPORT(world, FlecsScript); + const char *q_name = q->entity ? ecs_get_name(q->world, q->entity) : NULL; + if (ecs_os_strlen(expr) > 512) { + ecs_parser_error(q_name, expr, 0, "query argument expression too long"); + return NULL; + } - /* Constants */ - double E = 2.71828182845904523536028747135266250; - ecs_const_var(world, { - .name = "E", - .parent = ecs_id(FlecsScriptMath), - .type = ecs_id(ecs_f64_t), - .value = &E - }); + char token_buffer[1024]; + ecs_parser_t parser = { + .name = q_name, + .code = expr, + .world = q->real_world, + .token_cur = token_buffer + }; - double PI = 3.14159265358979323846264338327950288; - ecs_const_var(world, { - .name = "PI", - .parent = ecs_id(FlecsScriptMath), - .type = ecs_id(ecs_f64_t), - .value = &PI - }); + return flecs_query_args_parse(&parser, q, it, expr); +error: + return NULL; +} - /* Trigonometric functions */ - FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); - FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); - FLECS_MATH_FUNC_DEF_F64(tan, "Compute tangent"); - FLECS_MATH_FUNC_DEF_F64(acos, "Compute arc cosine"); - FLECS_MATH_FUNC_DEF_F64(asin, "Compute arc sine"); - FLECS_MATH_FUNC_DEF_F64(atan, "Compute arc tangent"); - FLECS_MATH_FUNC_DEF_F64_F64(atan2, "Compute arc tangent with two parameters"); +#endif - /* Hyperbolic functions */ - FLECS_MATH_FUNC_DEF_F64(cosh, "Compute hyperbolic cosine"); - FLECS_MATH_FUNC_DEF_F64(sinh, "Compute hyperbolic sine"); - FLECS_MATH_FUNC_DEF_F64(tanh, "Compute hyperbolic tangent"); - FLECS_MATH_FUNC_DEF_F64(acosh, "Compute area hyperbolic cosine"); - FLECS_MATH_FUNC_DEF_F64(asinh, "Compute area hyperbolic sine"); - FLECS_MATH_FUNC_DEF_F64(atanh, "Compute area hyperbolic tangent"); +/** + * @file addons/script/ast.c + * @brief Script AST implementation. + */ - /* Exponential and logarithmic functions */ - FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); - FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significant and exponent"); - FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); - FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); - FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); - FLECS_MATH_FUNC_DEF_F64(log2, "Compute binary logarithm"); - /* Power functions */ - FLECS_MATH_FUNC_DEF_F64_F64(pow, "Raise to power"); - FLECS_MATH_FUNC_DEF_F64(sqrt, "Compute square root"); - FLECS_MATH_FUNC_DEF_F64(sqr, "Compute square"); +#ifdef FLECS_SCRIPT - /* Rounding functions */ - FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); - FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); - FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); +#define flecs_ast_new(parser, T, kind)\ + (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) +#define flecs_ast_vec(parser, vec, T) \ + ecs_vec_init_t(&parser->script->allocator, &vec, T*, 0) +#define flecs_ast_append(parser, vec, T, node) \ + ecs_vec_append_t(&parser->script->allocator, &vec, T*)[0] = node - FLECS_MATH_FUNC_DEF_F64(abs, "Compute absolute value"); +static +void* flecs_ast_new_( + ecs_parser_t *parser, + ecs_size_t size, + ecs_script_node_kind_t kind) +{ + ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &parser->script->allocator; + ecs_script_node_t *result = flecs_calloc_w_dbg_info( + a, size, "ecs_script_node_t"); + result->kind = kind; + result->pos = parser->pos; + return result; +} - ecs_set_name_prefix(world, "EcsScript"); +ecs_script_scope_t* flecs_script_scope_new( + ecs_parser_t *parser) +{ + ecs_script_scope_t *result = flecs_ast_new( + parser, ecs_script_scope_t, EcsAstScope); + flecs_ast_vec(parser, result->stmts, ecs_script_node_t); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); + return result; +} - ECS_COMPONENT_DEFINE(world, EcsScriptRng); +bool flecs_scope_is_empty( + ecs_script_scope_t *scope) +{ + return ecs_vec_count(&scope->stmts) == 0; +} - ecs_set_hooks(world, EcsScriptRng, { - .ctor = ecs_ctor(EcsScriptRng), - .move = ecs_move(EcsScriptRng), - .copy = ecs_copy(EcsScriptRng), - .dtor = ecs_dtor(EcsScriptRng), - }); +ecs_script_scope_t* flecs_script_insert_scope( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_scope_t *result = flecs_script_scope_new(parser); + flecs_ast_append(parser, scope->stmts, ecs_script_scope_t, result); + ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); + return result; +} - ecs_struct(world, { - .entity = ecs_id(EcsScriptRng), - .members = { - { .name = "seed", .type = ecs_id(ecs_u64_t) } +ecs_script_entity_t* flecs_script_insert_entity( + ecs_parser_t *parser, + const char *name, + bool name_is_expr) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_entity_t *result = flecs_ast_new( + parser, ecs_script_entity_t, EcsAstEntity); + + if (name && !ecs_os_strcmp(name, "_")) { + name = NULL; + } + + result->name = name; + + if (name_is_expr) { + parser->significant_newline = false; + result->name_expr = (ecs_expr_node_t*) + flecs_expr_interpolated_string(parser, name); + if (!result->name_expr) { + goto error; } - }); + parser->significant_newline = true; + } - ecs_method(world, { - .parent = ecs_id(EcsScriptRng), - .name = "f", - .return_type = ecs_id(ecs_f64_t), - .params = { - { .name = "max", .type = ecs_id(ecs_f64_t) } - }, - .callback = flecs_script_rng_get_float - }); + ecs_script_scope_t *entity_scope = flecs_script_scope_new(parser); + ecs_assert(entity_scope != NULL, ECS_INTERNAL_ERROR, NULL); + result->scope = entity_scope; - ecs_method(world, { - .parent = ecs_id(EcsScriptRng), - .name = "u", - .return_type = ecs_id(ecs_u64_t), - .params = { - { .name = "max", .type = ecs_id(ecs_u64_t) } - }, - .callback = flecs_script_rng_get_uint - }); + flecs_ast_append(parser, scope->stmts, ecs_script_entity_t, result); + return result; +error: + return NULL; } -#endif +static +void flecs_script_set_id( + ecs_script_id_t *id, + const char *first, + const char *second) +{ + ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); + id->first = first; + id->second = second; + id->first_sp = -1; + id->second_sp = -1; +} -/** - * @file addons/script/parser.c - * @brief Script grammar parser. - */ +ecs_script_pair_scope_t* flecs_script_insert_pair_scope( + ecs_parser_t *parser, + const char *first, + const char *second) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_script_pair_scope_t *result = flecs_ast_new( + parser, ecs_script_pair_scope_t, EcsAstPairScope); + flecs_script_set_id(&result->id, first, second); + result->scope = flecs_script_scope_new(parser); + flecs_ast_append(parser, scope->stmts, ecs_script_pair_scope_t, result); + return result; +} -#ifdef FLECS_SCRIPT -/** - * @file addons/script/parser.h - * @brief Script grammar parser. - * - * Macro utilities that facilitate a simple recursive descent parser. - */ +ecs_script_tag_t* flecs_script_insert_pair_tag( + ecs_parser_t *parser, + const char *first, + const char *second) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -#ifndef FLECS_SCRIPT_PARSER_H -#define FLECS_SCRIPT_PARSER_H + ecs_script_tag_t *result = flecs_ast_new( + parser, ecs_script_tag_t, EcsAstTag); + flecs_script_set_id(&result->id, first, second); -#if defined(ECS_TARGET_CLANG) -/* Ignore unused enum constants in switch as it would blow up the parser code */ -#pragma clang diagnostic ignored "-Wswitch-enum" -/* To allow for nested Parse statements */ -#pragma clang diagnostic ignored "-Wshadow" -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#pragma GCC diagnostic ignored "-Wswitch-enum" -#pragma GCC diagnostic ignored "-Wshadow" -#elif defined(ECS_TARGET_MSVC) -/* Allow for variable shadowing */ -#pragma warning(disable : 4456) -#endif + flecs_ast_append(parser, scope->stmts, ecs_script_tag_t, result); -/* Create script & parser structs with static token buffer */ -#define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ - ecs_script_impl_t script = {\ - .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ - .pub.name = script_name,\ - .pub.code = expr\ - };\ - ecs_script_parser_t parser = {\ - .script = flecs_script_impl(&script),\ - .pos = expr,\ - .token_cur = tokens\ - } + return result; +} -/* Definitions for parser functions */ -#define ParserBegin\ - ecs_script_tokenizer_t _tokenizer;\ - ecs_os_zeromem(&_tokenizer);\ - _tokenizer.tokens = _tokenizer.stack.tokens;\ - ecs_script_tokenizer_t *tokenizer = &_tokenizer; +ecs_script_tag_t* flecs_script_insert_tag( + ecs_parser_t *parser, + const char *name) +{ + return flecs_script_insert_pair_tag(parser, name, NULL); +} -#define ParserEnd\ - Error("unexpected end of rule (parser error)");\ - error:\ - return NULL +ecs_script_component_t* flecs_script_insert_pair_component( + ecs_parser_t *parser, + const char *first, + const char *second) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -/* Get token */ -#define Token(n) (tokenizer->tokens[n].value) + ecs_script_component_t *result = flecs_ast_new( + parser, ecs_script_component_t, EcsAstComponent); + flecs_script_set_id(&result->id, first, second); -/* Push/pop token frame (allows token stack reuse in recursive functions) */ -#define TokenFramePush() \ - tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; + flecs_ast_append(parser, scope->stmts, ecs_script_component_t, result); -#define TokenFramePop() \ - tokenizer->tokens = tokenizer->stack.tokens; + return result; +} -/* Error */ -#define Error(...)\ - ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ - (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ - goto error +ecs_script_component_t* flecs_script_insert_component( + ecs_parser_t *parser, + const char *name) +{ + return flecs_script_insert_pair_component(parser, name, NULL); +} -/* Warning */ -#define Warning(...)\ - ecs_parser_warning(parser->script->pub.name, parser->script->pub.code,\ - (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ +ecs_script_default_component_t* flecs_script_insert_default_component( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -/* Parse expression */ -#define Expr(until, ...)\ - {\ - ecs_expr_node_t *EXPR = NULL;\ - if (until == '}' || until == ']') {\ - pos --;\ - if (until == '}') {\ - ecs_assert(pos[0] == '{', ECS_INTERNAL_ERROR, NULL);\ - } else if (until == ']') {\ - ecs_assert(pos[0] == '[', ECS_INTERNAL_ERROR, NULL);\ - }\ - }\ - parser->significant_newline = false;\ - if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ - goto error;\ - }\ - parser->significant_newline = true;\ - __VA_ARGS__\ - } + ecs_script_default_component_t *result = flecs_ast_new( + parser, ecs_script_default_component_t, EcsAstDefaultComponent); -/* Parse initializer */ -#define Initializer(until, ...)\ - {\ - ecs_expr_node_t *INITIALIZER = NULL;\ - ecs_expr_initializer_t *_initializer = NULL;\ - if (until != '\n') {\ - parser->significant_newline = false;\ - }\ - if (!(pos = flecs_script_parse_initializer(\ - parser, pos, until, &_initializer))) \ - {\ - flecs_expr_visit_free(\ - &parser->script->pub, (ecs_expr_node_t*)_initializer);\ - goto error;\ - }\ - parser->significant_newline = true;\ - if (pos[0] != until) {\ - Error("expected '%c'", until);\ - }\ - INITIALIZER = (ecs_expr_node_t*)_initializer;\ - pos ++;\ - __VA_ARGS__\ - } + flecs_ast_append(parser, scope->stmts, + ecs_script_default_component_t, result); -/* Parse token until character */ -#define Until(until, ...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_until(parser, pos, t, until))) {\ - goto error;\ - }\ - }\ - Parse_1(until, __VA_ARGS__) + return result; +} -/* Parse next token */ -#define Parse(...)\ - {\ - ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ - ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->stack.count ++];\ - if (!(pos = flecs_script_token(parser, pos, t, false))) {\ - goto error;\ - }\ - switch(t->kind) {\ - __VA_ARGS__\ - default:\ - if (t->value) {\ - Error("unexpected %s'%s'", \ - flecs_script_token_kind_str(t->kind), t->value);\ - } else {\ - Error("unexpected %s", \ - flecs_script_token_kind_str(t->kind));\ - }\ - }\ - } +ecs_script_var_component_t* flecs_script_insert_var_component( + ecs_parser_t *parser, + const char *var_name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(var_name != NULL, ECS_INTERNAL_ERROR, NULL); -/* Parse N consecutive tokens */ -#define Parse_1(tok, ...)\ - Parse(\ - case tok: {\ - __VA_ARGS__\ - }\ - ) + ecs_script_var_component_t *result = flecs_ast_new( + parser, ecs_script_var_component_t, EcsAstVarComponent); + result->name = var_name; + result->sp = -1; -#define Parse_2(tok1, tok2, ...)\ - Parse_1(tok1, \ - Parse(\ - case tok2: {\ - __VA_ARGS__\ - }\ - )\ - ) + flecs_ast_append(parser, scope->stmts, + ecs_script_var_component_t, result); + return result; +} -#define Parse_3(tok1, tok2, tok3, ...)\ - Parse_2(tok1, tok2, \ - Parse(\ - case tok3: {\ - __VA_ARGS__\ - }\ - )\ - ) +ecs_script_with_t* flecs_script_insert_with( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -#define Parse_4(tok1, tok2, tok3, tok4, ...)\ - Parse_3(tok1, tok2, tok3, \ - Parse(\ - case tok4: {\ - __VA_ARGS__\ - }\ - )\ - ) + ecs_script_with_t *result = flecs_ast_new( + parser, ecs_script_with_t, EcsAstWith); -#define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ - Parse_4(tok1, tok2, tok3, tok4, \ - Parse(\ - case tok5: {\ - __VA_ARGS__\ - }\ - )\ - ) + result->expressions = flecs_script_scope_new(parser); + result->scope = flecs_script_scope_new(parser); -#define LookAhead_Keep() \ - pos = lookahead;\ - parser->token_keep = parser->token_cur + flecs_ast_append(parser, scope->stmts, ecs_script_with_t, result); + return result; +} -/* Same as Parse, but doesn't error out if token is not in handled cases */ -#define LookAhead(...)\ - const char *lookahead;\ - ecs_script_token_t lookahead_token;\ - const char *old_lh_token_cur = parser->token_cur;\ - if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ - tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ - switch(lookahead_token.kind) {\ - __VA_ARGS__\ - default:\ - tokenizer->stack.count --;\ - break;\ - }\ - if (old_lh_token_cur > parser->token_keep) {\ - parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ - } else {\ - parser->token_cur = parser->token_keep;\ - }\ - } +ecs_script_using_t* flecs_script_insert_using( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -/* Lookahead N consecutive tokens */ -#define LookAhead_1(tok, ...)\ - LookAhead(\ - case tok: {\ - __VA_ARGS__\ - }\ - ) + ecs_script_using_t *result = flecs_ast_new( + parser, ecs_script_using_t, EcsAstUsing); -#define LookAhead_2(tok1, tok2, ...)\ - LookAhead_1(tok1, \ - const char *old_ptr = pos;\ - pos = lookahead;\ - LookAhead(\ - case tok2: {\ - __VA_ARGS__\ - }\ - )\ - if (pos != lookahead) {\ - pos = old_ptr;\ - }\ - ) + result->name = name; -#define LookAhead_3(tok1, tok2, tok3, ...)\ - LookAhead_2(tok1, tok2, \ - const char *old_ptr = pos;\ - pos = lookahead;\ - LookAhead(\ - case tok3: {\ - __VA_ARGS__\ - }\ - )\ - if (pos != lookahead) {\ - pos = old_ptr;\ - }\ - ) + flecs_ast_append(parser, scope->stmts, ecs_script_using_t, result); + return result; +} -/* Open scope */ -#define Scope(s, ...) {\ - ecs_script_scope_t *old_scope = parser->scope;\ - parser->scope = s;\ - __VA_ARGS__\ - parser->scope = old_scope;\ - } +ecs_script_module_t* flecs_script_insert_module( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -/* Parser loop */ -#define Loop(...)\ - int32_t token_stack_count = tokenizer->stack.count;\ - do {\ - tokenizer->stack.count = token_stack_count;\ - __VA_ARGS__\ - } while (true); + ecs_script_module_t *result = flecs_ast_new( + parser, ecs_script_module_t, EcsAstModule); -#define EndOfRule return pos + result->name = name; -#endif + flecs_ast_append(parser, scope->stmts, ecs_script_module_t, result); + return result; +} +ecs_script_annot_t* flecs_script_insert_annot( + ecs_parser_t *parser, + const char *name, + const char *expr) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -#define EcsTokEndOfStatement\ - case ';':\ - case '\n':\ - case '\0' + ecs_script_annot_t *result = flecs_ast_new( + parser, ecs_script_annot_t, EcsAstAnnotation); -static -const char* flecs_script_stmt( - ecs_script_parser_t *parser, - const char *pos); + result->name = name; + result->expr = expr; -/* Parse scope (statements inside {}) */ -static -const char* flecs_script_scope( - ecs_script_parser_t *parser, - ecs_script_scope_t *scope, - const char *pos) -{ - ParserBegin; + flecs_ast_append(parser, scope->stmts, ecs_script_annot_t, result); + return result; +} +ecs_script_template_node_t* flecs_script_insert_template( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pos[-1] == '{', ECS_INTERNAL_ERROR, NULL); - ecs_script_scope_t *prev = parser->scope; - parser->scope = scope; + ecs_script_template_node_t *result = flecs_ast_new( + parser, ecs_script_template_node_t, EcsAstTemplate); + result->name = name; + result->scope = flecs_script_scope_new(parser); - Loop( - LookAhead( - case EcsTokScopeClose: - pos = lookahead; - goto scope_close; - case EcsTokEnd: - Error("unexpected end of script"); - goto error; - ) + flecs_ast_append(parser, scope->stmts, ecs_script_template_node_t, result); + return result; +} - pos = flecs_script_stmt(parser, pos); - if (!pos) { - goto error; - } - ) +ecs_script_var_node_t* flecs_script_insert_var( + ecs_parser_t *parser, + const char *name) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); -scope_close: - parser->scope = prev; + ecs_script_var_node_t *result = flecs_ast_new( + parser, ecs_script_var_node_t, EcsAstConst); + result->name = name; - ecs_assert(pos[-1] == '}', ECS_INTERNAL_ERROR, NULL); - return pos; + flecs_ast_append(parser, scope->stmts, ecs_script_var_node_t, result); + return result; +} - ParserEnd; +ecs_script_if_t* flecs_script_insert_if( + ecs_parser_t *parser) +{ + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_script_if_t *result = flecs_ast_new( + parser, ecs_script_if_t, EcsAstIf); + result->if_true = flecs_script_scope_new(parser); + result->if_false = flecs_script_scope_new(parser); + + flecs_ast_append(parser, scope->stmts, ecs_script_if_t, result); + return result; } -/* Parse comma expression (expressions separated by ',') */ -static -const char* flecs_script_comma_expr( - ecs_script_parser_t *parser, - const char *pos, - bool is_base_list) +ecs_script_for_range_t* flecs_script_insert_for_range( + ecs_parser_t *parser) { - ParserBegin; + ecs_script_scope_t *scope = parser->scope; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); - Loop( - LookAhead( - case '\n': - pos = lookahead; - continue; + ecs_script_for_range_t *result = flecs_ast_new( + parser, ecs_script_for_range_t, EcsAstFor); + result->scope = flecs_script_scope_new(parser); - case EcsTokIdentifier: - LookAhead_Keep(); + flecs_ast_append(parser, scope->stmts, ecs_script_for_range_t, result); + return result; +} - if (is_base_list) { - flecs_script_insert_pair_tag(parser, "IsA", Token(0)); - } else { - flecs_script_insert_entity(parser, Token(0), false); - } +#endif - LookAhead_1(',', - pos = lookahead; - continue; - ) - ) +/** + * @file addons/script/function.c + * @brief Script function API. + */ - break; - ) - return pos; -} +#ifdef FLECS_SCRIPT -/* Parse with expression (expression after 'with' keyword) */ static -const char* flecs_script_with_expr( - ecs_script_parser_t *parser, - const char *pos) -{ - ParserBegin; - - Parse( - // Position - case EcsTokIdentifier: { - // Position ( - LookAhead_1('(', - pos = lookahead; +void ecs_script_params_free(ecs_vec_t *params) { + ecs_script_parameter_t *array = ecs_vec_first(params); + int32_t i, count = ecs_vec_count(params); + for (i = 0; i < count; i ++) { + /* Safe, component owns string */ + ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); + } + ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); +} - // Position ( expr ) - Initializer(')', - ecs_script_component_t *component = - flecs_script_insert_component(parser, Token(0)); - component->node.kind = EcsAstWithComponent; - component->expr = INITIALIZER; - EndOfRule; - ) - ) +static +ECS_MOVE(EcsScriptConstVar, dst, src, { + if (dst->type_info->hooks.dtor) { + dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); + } - if (Token(0)[0] == '$') { - ecs_script_var_component_t *var = - flecs_script_insert_var_component(parser, &Token(0)[1]); - var->node.kind = EcsAstWithVar; - } else { - ecs_script_tag_t *tag = - flecs_script_insert_tag(parser, Token(0)); - tag->node.kind = EcsAstWithTag; - } + ecs_os_free(dst->value.ptr); + + *dst = *src; - EndOfRule; - } + src->value.ptr = NULL; + src->value.type = 0; + src->type_info = NULL; +}) - // ( - case '(': - // (Eats, Apples) - Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', - // (Eats, Apples) ( expr - LookAhead_1('(', - pos = lookahead; +static +ECS_DTOR(EcsScriptConstVar, ptr, { + if (ptr->type_info->hooks.dtor) { + ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); + } + ecs_os_free(ptr->value.ptr); +}) - // (Eats, Apples) ( expr ) - Initializer(')', - ecs_script_component_t *component = - flecs_script_insert_pair_component(parser, - Token(1), Token(3)); - component->node.kind = EcsAstWithComponent; - component->expr = INITIALIZER; - EndOfRule; - ) - ) +static +ECS_MOVE(EcsScriptFunction, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) - ecs_script_tag_t *tag = - flecs_script_insert_pair_tag(parser, Token(1), Token(3)); - tag->node.kind = EcsAstWithTag; - EndOfRule; - ) - ) +static +ECS_DTOR(EcsScriptFunction, ptr, { + ecs_script_params_free(&ptr->params); +}) - ParserEnd; -} +static +ECS_MOVE(EcsScriptMethod, dst, src, { + ecs_script_params_free(&dst->params); + *dst = *src; + ecs_os_zeromem(src); +}) -/* Parse with expression list (expression list after 'with' keyword) */ static -const char* flecs_script_with( - ecs_script_parser_t *parser, - ecs_script_with_t *with, - const char *pos) +ECS_DTOR(EcsScriptMethod, ptr, { + ecs_script_params_free(&ptr->params); +}) + +ecs_entity_t ecs_const_var_init( + ecs_world_t *world, + ecs_const_var_desc_t *desc) { - ParserBegin; + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->type != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->value != NULL, ECS_INVALID_PARAMETER, NULL); - bool has_next; - do { - Scope(with->expressions, - pos = flecs_script_with_expr(parser, pos); - ) + const ecs_type_info_t *ti = ecs_get_type_info(world, desc->type); + ecs_check(ti != NULL, ECS_INVALID_PARAMETER, + "ecs_const_var_desc_t::type is not a valid type"); - if (!pos) { - goto error; - } + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); - Parse( - case ',': { - has_next = true; - break; - } - case '{': { - return flecs_script_scope(parser, with->scope, pos); - } - ) - } while (has_next); + if (!result) { + goto error; + } - ParserEnd; + EcsScriptConstVar *v = ecs_ensure(world, result, EcsScriptConstVar); + v->value.ptr = ecs_os_malloc(ti->size); + v->value.type = desc->type; + v->type_info = ti; + ecs_value_init(world, desc->type, v->value.ptr); + ecs_value_copy(world, desc->type, v->value.ptr, desc->value); + ecs_modified(world, result, EcsScriptConstVar); + + return result; +error: + return 0; } -/* Parenthesis expression */ -static -const char* flecs_script_paren_expr( - ecs_script_parser_t *parser, - const char *kind, - ecs_script_entity_t *entity, - const char *pos) +ecs_entity_t ecs_function_init( + ecs_world_t *world, + const ecs_function_desc_t *desc) { - ParserBegin; + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); - Initializer(')', - entity->kind_w_expr = true; + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); - Scope(entity->scope, - ecs_script_component_t *component = - flecs_script_insert_component(parser, kind); - component->expr = INITIALIZER; - ) + if (!result) { + goto error; + } - Parse( - // Position spaceship (expr)\n - EcsTokEndOfStatement: { - EndOfRule; - } + EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); + f->return_type = desc->return_type; + f->callback = desc->callback; + f->ctx = desc->ctx; - // Position spaceship (expr) { - case '{': { - return flecs_script_scope(parser, entity->scope, pos); - } - ) - ) + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } - ParserEnd; + if (!i) { + ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + } + + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, &f->params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } + + ecs_modified(world, result, EcsScriptFunction); + + return result; +error: + return 0; } -/* Parse a single statement */ -static -const char* flecs_script_if_stmt( - ecs_script_parser_t *parser, - const char *pos) +ecs_entity_t ecs_method_init( + ecs_world_t *world, + const ecs_function_desc_t *desc) { - ParserBegin; + flecs_poly_assert(world, ecs_world_t); + ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); + ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); - // if expr - Expr('\0', - // if expr { - Parse_1('{', { - ecs_script_if_t *stmt = flecs_script_insert_if(parser); - stmt->expr = EXPR; - pos = flecs_script_scope(parser, stmt->if_true, pos); - if (!pos) { - goto error; - } + ecs_entity_t result = ecs_entity(world, { + .name = desc->name, + .parent = desc->parent + }); - // if expr { } else - LookAhead_1(EcsTokKeywordElse, - pos = lookahead; + if (!result) { + goto error; + } - Parse( - // if expr { } else if - case EcsTokKeywordIf: { - Scope(stmt->if_false, - return flecs_script_if_stmt(parser, pos); - ) - } + EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); + f->return_type = desc->return_type; + f->callback = desc->callback; + f->ctx = desc->ctx; + + int32_t i; + for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { + if (!desc->params[i].name) { + break; + } - // if expr { } else\n if - case EcsTokNewline: { - Parse_1(EcsTokKeywordIf, - Scope(stmt->if_false, - return flecs_script_if_stmt(parser, pos); - ) - ) - } + if (!i) { + ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); + } - // if expr { } else { - case '{': { - return flecs_script_scope(parser, stmt->if_false, pos); - } - ) - ) + ecs_script_parameter_t *p = ecs_vec_append_t( + NULL, &f->params, ecs_script_parameter_t); + p->type = desc->params[i].type; + p->name = ecs_os_strdup(desc->params[i].name); + } - EndOfRule; - }); - ) + ecs_modified(world, result, EcsScriptMethod); - ParserEnd; + return result; +error: + return 0; } -static -const char* flecs_script_parse_var( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_tokenizer_t *tokenizer, - bool is_prop) +void flecs_function_import( + ecs_world_t *world) { - Parse_1(EcsTokIdentifier, - ecs_script_var_node_t *var = flecs_script_insert_var( - parser, Token(1)); - var->node.kind = is_prop ? EcsAstProp : EcsAstConst; + ecs_set_name_prefix(world, "EcsScript"); + ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); + ECS_COMPONENT_DEFINE(world, EcsScriptFunction); + ECS_COMPONENT_DEFINE(world, EcsScriptMethod); - Parse( - // const color = - case '=': { - // const color = Color : - LookAhead_2(EcsTokIdentifier, ':', - pos = lookahead; + ecs_struct(world, { + .entity = ecs_id(EcsScriptFunction), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); - var->type = Token(3); + ecs_struct(world, { + .entity = ecs_id(EcsScriptMethod), + .members = { + { .name = "return_type", .type = ecs_id(ecs_entity_t) } + } + }); - // const color = Color: { - LookAhead_1('{', - // const color = Color: {expr} - pos = lookahead; - Initializer('}', - var->expr = INITIALIZER; - EndOfRule; - ) - ) + ecs_set_hooks(world, EcsScriptConstVar, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptConstVar), + .move = ecs_move(EcsScriptConstVar), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); - // const color = Color: expr\n - Initializer('\n', - var->expr = INITIALIZER; - EndOfRule; - ) - ) + ecs_set_hooks(world, EcsScriptFunction, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptFunction), + .move = ecs_move(EcsScriptFunction), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); - // const PI = expr\n - Expr('\n', - Warning("'%s var = expr' syntax is deprecated" - ", use '%s var: expr' instead", - is_prop ? "prop" : "const", - is_prop ? "prop" : "const"); - var->expr = EXPR; - EndOfRule; - ) - } + ecs_set_hooks(world, EcsScriptMethod, { + .ctor = flecs_default_ctor, + .dtor = ecs_dtor(EcsScriptMethod), + .move = ecs_move(EcsScriptMethod), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); - case ':': { - // const PI: expr\n - Expr('\n', - var->expr = EXPR; - EndOfRule; - ) - } - ) - ) + flecs_script_register_builtin_functions(world); +} -error: - return NULL; +#endif + +/** + * @file addons/script/builtin_functions.c + * @brief Flecs functions for flecs script. + */ + + +#ifdef FLECS_SCRIPT + +static +void flecs_meta_entity_name( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } -/* Parse a single statement */ static -const char* flecs_script_stmt( - ecs_script_parser_t *parser, - const char *pos) +void flecs_meta_entity_path( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) { - ParserBegin; + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(char**)result->ptr = ecs_get_path(ctx->world, entity); +} - bool name_is_expr_0 = false; - bool name_is_expr_1 = false; +static +void flecs_meta_entity_parent( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); +} - Parse( - case EcsTokIdentifier: goto identifier; - case EcsTokString: goto string_name; - case '{': goto anonymous_entity; - case '(': goto paren; - case '@': goto annotation; - case EcsTokKeywordWith: goto with_stmt; - case EcsTokKeywordModule: goto module_stmt; - case EcsTokKeywordUsing: goto using_stmt; - case EcsTokKeywordTemplate: goto template_stmt; - case EcsTokKeywordProp: goto prop_var; - case EcsTokKeywordConst: goto const_var; - case EcsTokKeywordIf: goto if_stmt; - case EcsTokKeywordFor: goto for_stmt; - EcsTokEndOfStatement: EndOfRule; - ); +static +void flecs_meta_entity_has( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; + ecs_id_t id = *(ecs_id_t*)argv[1].ptr; + *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); +} -anonymous_entity: { - return flecs_script_scope(parser, - flecs_script_insert_entity(parser, "_", false)->scope, pos); +static +void flecs_meta_core_pair( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)argc; + (void)ctx; + ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; + ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; + *(ecs_id_t*)result->ptr = ecs_pair(first, second); } -string_name: - /* If this is an interpolated string, we need to evaluate it as expression - * at evaluation time. Otherwise we can just use the string as name. The - * latter is useful if an entity name contains special characters that are - * not allowed in identifier tokens. */ - if (flecs_string_is_interpolated(Token(0))) { - name_is_expr_0 = true; - } +#ifdef FLECS_DOC -identifier: { - // enterprise } (end of scope) - LookAhead_1('}', - goto insert_tag; - ) +#define FLECS_DOC_FUNC(name)\ + static\ + void flecs_meta_entity_doc_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)argc;\ + ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ + *(char**)result->ptr = \ + ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ + } - Parse( - // enterprise { - case '{': { - return flecs_script_scope(parser, - flecs_script_insert_entity( - parser, Token(0), name_is_expr_0)->scope, pos); - } +FLECS_DOC_FUNC(name) +FLECS_DOC_FUNC(uuid) +FLECS_DOC_FUNC(brief) +FLECS_DOC_FUNC(detail) +FLECS_DOC_FUNC(link) +FLECS_DOC_FUNC(color) - // Red, - case ',': { - if (name_is_expr_0) { - Error("expression not allowed as entity name here"); - } +#undef FLECS_DOC_FUNC - flecs_script_insert_entity(parser, Token(0), false); - pos = flecs_script_comma_expr(parser, pos, false); - EndOfRule; - } +static +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_name + }); - // Npc\n - EcsTokEndOfStatement: { - // Npc\n{ - LookAhead_1('{', - pos = lookahead; - return flecs_script_scope(parser, - flecs_script_insert_entity( - parser, Token(0), name_is_expr_0)->scope, pos); - ) + ecs_doc_set_brief(world, m, "Returns entity doc name"); + } - goto insert_tag; - } + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_uuid", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_uuid + }); - // auto_override | - case '|': { - goto identifier_flag; - } + ecs_doc_set_brief(world, m, "Returns entity doc uuid"); + } - // Position: - case ':': { - goto identifier_colon; - } + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_brief", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_brief + }); - // x = - case '=': { - goto identifier_assign; - } + ecs_doc_set_brief(world, m, "Returns entity doc brief description"); + } - // SpaceShip( - case '(': { - goto identifier_paren; - } + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_detail", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_detail + }); - // Spaceship enterprise - case EcsTokIdentifier: { - goto identifier_identifier; - } + ecs_doc_set_brief(world, m, "Returns entity doc detailed description"); + } - // Spaceship "enterprise" - case EcsTokString: { - goto identifier_string; - } - ) -} + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_link", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_link + }); -insert_tag: { - if (Token(0)[0] == '$') { - if (!flecs_script_insert_var_component(parser, &Token(0)[1])) { - Error( - "invalid context for variable component '%s': must be " - "part of entity", tokenizer->tokens[0].value); - } - } else { - if (!flecs_script_insert_tag(parser, Token(0))) { - Error( - "invalid context for tag '%s': must be part of entity", - tokenizer->tokens[0].value); - } + ecs_doc_set_brief(world, m, "Returns entity doc link"); } - EndOfRule; -} - -// @ -annotation: { - // @brief - Parse_1(EcsTokIdentifier, - // $brief expr - Until('\n', - flecs_script_insert_annot(parser, Token(1), Token(2)); - EndOfRule; - ) - ) -} + { + ecs_entity_t m = ecs_method(world, { + .name = "doc_color", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_doc_color + }); -// with -with_stmt: { - ecs_script_with_t *with = flecs_script_insert_with(parser); - pos = flecs_script_with(parser, with, pos); - EndOfRule; + ecs_doc_set_brief(world, m, "Returns entity doc color"); + } } -// using -using_stmt: { - // using flecs.meta\n - Parse_1(EcsTokIdentifier, - flecs_script_insert_using(parser, Token(1)); +#else - Parse( - EcsTokEndOfStatement: - EndOfRule; - ) - ) +static +void flecs_script_register_builtin_doc_functions( + ecs_world_t *world) +{ + (void)world; } -// module -module_stmt: { - // using flecs.meta\n - Parse_2(EcsTokIdentifier, '\n', - flecs_script_insert_module(parser, Token(1)); - EndOfRule; - ) -} +#endif -// template -template_stmt: { - // template SpaceShip - Parse_1(EcsTokIdentifier, - ecs_script_template_node_t *template = flecs_script_insert_template( - parser, Token(1)); +void flecs_script_register_builtin_functions( + ecs_world_t *world) +{ + { + ecs_entity_t m = ecs_method(world, { + .name = "name", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_name + }); - Parse( - // template SpaceShip { - case '{': - return flecs_script_scope(parser, template->scope, pos); + ecs_doc_set_brief(world, m, "Returns entity name"); + } - // template SpaceShip\n - EcsTokEndOfStatement: - EndOfRule; - ) - ) -} + { + ecs_entity_t m = ecs_method(world, { + .name = "path", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_string_t), + .callback = flecs_meta_entity_path + }); -// prop -prop_var: { - // prop color = Color: - return flecs_script_parse_var(parser, pos, tokenizer, true); -} + ecs_doc_set_brief(world, m, "Returns entity path"); + } -// const -const_var: { - // const color - return flecs_script_parse_var(parser, pos, tokenizer, false); -} + { + ecs_entity_t m = ecs_method(world, { + .name = "parent", + .parent = ecs_id(ecs_entity_t), + .return_type = ecs_id(ecs_entity_t), + .callback = flecs_meta_entity_parent + }); -// if -if_stmt: { - return flecs_script_if_stmt(parser, pos); -} + ecs_doc_set_brief(world, m, "Returns entity parent"); + } -// for -for_stmt: { - // for i - Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { - Expr(0, { - ecs_expr_node_t *from = EXPR; - Parse_1(EcsTokRange, { - Expr(0, { - ecs_expr_node_t *to = EXPR; - ecs_script_for_range_t *stmt = - flecs_script_insert_for_range(parser); - stmt->loop_var = Token(1); - stmt->from = from; - stmt->to = to; + { + ecs_entity_t m = ecs_method(world, { + .name = "has", + .parent = ecs_id(ecs_entity_t), + .params = { + { .name = "component", .type = ecs_id(ecs_id_t) } + }, + .return_type = ecs_id(ecs_bool_t), + .callback = flecs_meta_entity_has + }); - Parse_1('{', { - return flecs_script_scope(parser, stmt->scope, pos); - }); - }); - }); + ecs_doc_set_brief(world, m, "Returns whether entity has component"); + } + + { + ecs_entity_t m = ecs_function(world, { + .name = "pair", + .parent = ecs_entity(world, { .name = "core"}), + .params = { + { .name = "first", .type = ecs_id(ecs_entity_t) }, + { .name = "second", .type = ecs_id(ecs_entity_t) } + }, + .return_type = ecs_id(ecs_id_t), + .callback = flecs_meta_core_pair }); - }); -} + ecs_doc_set_brief(world, m, "Returns a pair identifier"); + } -// ( -paren: { - // (Likes, Apples) - Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', - goto pair; - ) + flecs_script_register_builtin_doc_functions(world); } -// (Likes, Apples) -pair: { - // (Likes, Apples) } (end of scope) - LookAhead_1('}', - flecs_script_insert_pair_tag(parser, Token(1), Token(3)); - EndOfRule; - ) +#endif - Parse( - // (Likes, Apples)\n - EcsTokEndOfStatement: { - flecs_script_insert_pair_tag(parser, Token(1), Token(3)); - EndOfRule; - } +/** + * @file addons/script/functions_math.c + * @brief Math functions for flecs script. + */ - // (Eats, Apples): - case ':': { - // Use lookahead so that expression parser starts at "match" - LookAhead_1(EcsTokKeywordMatch, { - // (Eats, Apples): match expr - Expr('\n', { - ecs_script_component_t *comp = - flecs_script_insert_pair_component( - parser, Token(1), Token(3)); - comp->expr = EXPR; - EndOfRule; - }) - }) - // (Eats, Apples): { - Parse_1('{', { - // (Eats, Apples): { expr } - Initializer('}', - ecs_script_component_t *comp = - flecs_script_insert_pair_component( - parser, Token(1), Token(3)); - comp->expr = INITIALIZER; - EndOfRule; - ) - } - ) - } +#ifdef FLECS_SCRIPT_MATH +#include - // (IsA, Machine) { - case '{': { - ecs_script_pair_scope_t *ps = flecs_script_insert_pair_scope( - parser, Token(1), Token(3)); - return flecs_script_scope(parser, ps->scope, pos); - } - ) +typedef struct ecs_script_rng_t { + uint64_t x; /* Current state (initialize with seed) */ + uint64_t w; /* Weyl sequence increment */ + uint64_t s; /* Constant for Weyl sequence */ + int32_t refcount; /* Necessary as flecs script doesn't have ref types */ + bool initialized; +} ecs_script_rng_t; + +static +ecs_script_rng_t* flecs_script_rng_new(void) { + ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); + result->x = 0; + result->w = 0; + result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ + result->refcount = 1; + result->initialized = false; + return result; } -// auto_override | -identifier_flag: { - ecs_id_t flag; - if (!ecs_os_strcmp(Token(0), "auto_override")) { - flag = ECS_AUTO_OVERRIDE; - } else { - Error("invalid flag '%s'", Token(0)); +static +void flecs_script_rng_keep(ecs_script_rng_t *rng) { + if (!rng) { + return; } + rng->refcount ++; +} - Parse( - // auto_override | ( - case '(': - // auto_override | (Rel, Tgt) - Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', - ecs_script_tag_t *tag = flecs_script_insert_pair_tag( - parser, Token(3), Token(5)); - tag->id.flag = flag; +static +void flecs_script_rng_free(ecs_script_rng_t *rng) { + if (!rng) { + return; + } + ecs_assert(rng->refcount > 0, ECS_INTERNAL_ERROR, NULL); + if (!--rng->refcount) { + ecs_os_free(rng); + } +} - Parse( - // auto_override | (Rel, Tgt)\n - EcsTokEndOfStatement: { - EndOfRule; - } +static +uint64_t flecs_script_rng_next(ecs_script_rng_t *rng) { + rng->x *= rng->x; + rng->x += (rng->w += rng->s); + rng->x = (rng->x >> 32) | (rng->x << 32); + return rng->x; +} - // auto_override | (Rel, Tgt): - case ':': { - Parse_1('{', - // auto_override | (Rel, Tgt): {expr} - Expr('}', { - ecs_script_component_t *comp = - flecs_script_insert_pair_component( - parser, Token(3), Token(5)); - comp->expr = EXPR; - EndOfRule; - }) - ) - } - ) - ) +ECS_COMPONENT_DECLARE(EcsScriptRng); - // auto_override | Position - case EcsTokIdentifier: { - ecs_script_tag_t *tag = flecs_script_insert_tag( - parser, Token(2)); - tag->id.flag = flag; +static +ECS_CTOR(EcsScriptRng, ptr, { + ptr->seed = 0; + ptr->impl = flecs_script_rng_new(); +}) - Parse( - // auto_override | Position\n - EcsTokEndOfStatement: { - EndOfRule; - } +static +ECS_COPY(EcsScriptRng, dst, src, { + flecs_script_rng_keep(src->impl); + if (dst->impl != src->impl) { + flecs_script_rng_free(dst->impl); + } + dst->seed = src->seed; + dst->impl = src->impl; +}) - // auto_override | Position: - case ':': { - Parse_1('{', - // auto_override | Position: {expr} - Expr('}', { - ecs_script_component_t *comp = - flecs_script_insert_component( - parser, Token(2)); - comp->expr = EXPR; - EndOfRule; - }) - ) - } - ) - } - ) -} +static +ECS_MOVE(EcsScriptRng, dst, src, { + flecs_script_rng_free(dst->impl); + dst->seed = src->seed; + dst->impl = src->impl; + src->impl = NULL; +}) -// Position: -identifier_colon: { - { - // Position: { - LookAhead_1('{', - pos = lookahead; - goto component_expr_scope; - ) +static +ECS_DTOR(EcsScriptRng, ptr, { + flecs_script_rng_free(ptr->impl); +}) + +void flecs_script_rng_get_float( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + EcsScriptRng *rng = argv[0].ptr; + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; } + uint64_t x = flecs_script_rng_next(rng->impl); + double max = *(double*)argv[1].ptr; + double *r = result->ptr; - { - // Position: [ - LookAhead_1('[', - pos = lookahead; - goto component_expr_collection; - ) + if (ECS_EQZERO(max)) { + ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); + } else { + *r = (double)x / ((double)UINT64_MAX / max); } +} - { - // Position: match - LookAhead_1(EcsTokKeywordMatch, - goto component_expr_match; - ) +void flecs_script_rng_get_uint( + const ecs_function_ctx_t *ctx, + int32_t argc, + const ecs_value_t *argv, + ecs_value_t *result) +{ + (void)ctx; + (void)argc; + EcsScriptRng *rng = argv[0].ptr; + ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_script_rng_t *impl = rng->impl; + if (!impl->initialized) { + impl->x = rng->seed; + impl->initialized = true; + } + uint64_t x = flecs_script_rng_next(rng->impl); + uint64_t max = *(uint64_t*)argv[1].ptr; + uint64_t *r = result->ptr; + if (!max) { + ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); + } else { + *r = x % max; } +} - // enterprise : SpaceShip - Parse_1(EcsTokIdentifier, { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(0), name_is_expr_0); +#define FLECS_MATH_FUNC_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 1, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } - Scope(entity->scope, - flecs_script_insert_pair_tag(parser, "IsA", Token(2)); +#define FLECS_MATH_FUNC_F64_F64(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + double y = *(double*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } - LookAhead_1(',', { - pos = lookahead; - pos = flecs_script_comma_expr(parser, pos, true); - }) - ) +#define FLECS_MATH_FUNC_F64_I32(name, ...)\ + static\ + void flecs_math_##name(\ + const ecs_function_ctx_t *ctx,\ + int32_t argc,\ + const ecs_value_t *argv,\ + ecs_value_t *result)\ + {\ + (void)ctx;\ + (void)argc;\ + ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ + double x = *(double*)argv[0].ptr;\ + ecs_i32_t y = *(ecs_i32_t*)argv[1].ptr;\ + *(double*)result->ptr = __VA_ARGS__;\ + } - Parse( - // enterprise : SpaceShip\n - EcsTokEndOfStatement: - EndOfRule; +#define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }},\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } - // enterprise : SpaceShip { - case '{': - return flecs_script_scope(parser, entity->scope, pos); - ) - }) -} +#define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ + {\ + ecs_entity_t f = ecs_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_f64_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } -// x = -identifier_assign: { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(0), name_is_expr_0); +#define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ + {\ + ecs_entity_t f = ecs_function(world, {\ + .name = #_name,\ + .parent = ecs_id(FlecsScriptMath),\ + .return_type = ecs_id(ecs_f64_t),\ + .params = {\ + { .name = "x", .type = ecs_id(ecs_f64_t) },\ + { .name = "y", .type = ecs_id(ecs_i32_t) }\ + },\ + .callback = flecs_math_##_name\ + });\ + ecs_doc_set_brief(world, f, brief);\ + } - // x = Position: - LookAhead_2(EcsTokIdentifier, ':', - pos = lookahead; +/* Trigonometric functions */ +FLECS_MATH_FUNC_F64(cos, cos(x)) +FLECS_MATH_FUNC_F64(sin, sin(x)) +FLECS_MATH_FUNC_F64(tan, tan(x)) +FLECS_MATH_FUNC_F64(acos, acos(x)) +FLECS_MATH_FUNC_F64(asin, asin(x)) +FLECS_MATH_FUNC_F64(atan, atan(x)) +FLECS_MATH_FUNC_F64_F64(atan2, atan2(x, y)) - // Use lookahead so that expression parser starts at "match" - LookAhead_1(EcsTokKeywordMatch, { - // (Eats, Apples): match expr - Expr('\n', { - ecs_script_component_t *comp = - flecs_script_insert_pair_component( - parser, Token(1), Token(3)); - comp->expr = EXPR; - EndOfRule; - }) - }) +/* Hyperbolic functions */ +FLECS_MATH_FUNC_F64(cosh, cosh(x)) +FLECS_MATH_FUNC_F64(sinh, sinh(x)) +FLECS_MATH_FUNC_F64(tanh, tanh(x)) +FLECS_MATH_FUNC_F64(acosh, acosh(x)) +FLECS_MATH_FUNC_F64(asinh, asinh(x)) +FLECS_MATH_FUNC_F64(atanh, atanh(x)) - // x = Position: { - Parse_1('{', { - // x = Position: {expr} - Expr('}', - Scope(entity->scope, - ecs_script_component_t *comp = - flecs_script_insert_component(parser, Token(2)); - comp->expr = EXPR; - ) +/* Exponential and logarithmic functions */ +FLECS_MATH_FUNC_F64(exp, exp(x)) +FLECS_MATH_FUNC_F64_I32(ldexp, ldexp(x, y)) +FLECS_MATH_FUNC_F64(log, log(x)) +FLECS_MATH_FUNC_F64(log10, log10(x)) +FLECS_MATH_FUNC_F64(exp2, exp2(x)) +FLECS_MATH_FUNC_F64(log2, log2(x)) - // x = Position: {expr}\n - Parse( - EcsTokEndOfStatement: - EndOfRule; - ) - ) - }) - ) +/* Power functions */ +FLECS_MATH_FUNC_F64_F64(pow, pow(x, y)) +FLECS_MATH_FUNC_F64(sqrt, sqrt(x)) +FLECS_MATH_FUNC_F64(sqr, x * x) - // x = f32\n - Initializer('\n', - Scope(entity->scope, - ecs_script_default_component_t *comp = - flecs_script_insert_default_component(parser); - comp->expr = INITIALIZER; - ) - - EndOfRule; - ) -} +/* Rounding functions */ +FLECS_MATH_FUNC_F64(ceil, ceil(x)) +FLECS_MATH_FUNC_F64(floor, floor(x)) +FLECS_MATH_FUNC_F64(round, round(x)) -// Spaceship enterprise -identifier_string: { - if (flecs_string_is_interpolated(Token(1))) { - name_is_expr_1 = true; - } -} +FLECS_MATH_FUNC_F64(abs, fabs(x)) -identifier_identifier: { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, Token(1), name_is_expr_1); - entity->kind = Token(0); +void FlecsScriptMathImport( + ecs_world_t *world) +{ + ECS_MODULE(world, FlecsScriptMath); - // Spaceship enterprise : - LookAhead_1(':', - pos = lookahead; + ECS_IMPORT(world, FlecsScript); - Parse_1(EcsTokIdentifier, { - Scope(entity->scope, - flecs_script_insert_pair_tag(parser, "IsA", Token(3)); + /* Constants */ + double E = 2.71828182845904523536028747135266250; + ecs_const_var(world, { + .name = "E", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &E + }); - LookAhead_1(',', { - pos = lookahead; - pos = flecs_script_comma_expr(parser, pos, true); - }) - ) + double PI = 3.14159265358979323846264338327950288; + ecs_const_var(world, { + .name = "PI", + .parent = ecs_id(FlecsScriptMath), + .type = ecs_id(ecs_f64_t), + .value = &PI + }); - goto identifier_identifier_x; - }) - ) + /* Trigonometric functions */ + FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); + FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); + FLECS_MATH_FUNC_DEF_F64(tan, "Compute tangent"); + FLECS_MATH_FUNC_DEF_F64(acos, "Compute arc cosine"); + FLECS_MATH_FUNC_DEF_F64(asin, "Compute arc sine"); + FLECS_MATH_FUNC_DEF_F64(atan, "Compute arc tangent"); + FLECS_MATH_FUNC_DEF_F64_F64(atan2, "Compute arc tangent with two parameters"); -identifier_identifier_x: - Parse( - // Spaceship enterprise\n - EcsTokEndOfStatement: { - EndOfRule; - } + /* Hyperbolic functions */ + FLECS_MATH_FUNC_DEF_F64(cosh, "Compute hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(sinh, "Compute hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(tanh, "Compute hyperbolic tangent"); + FLECS_MATH_FUNC_DEF_F64(acosh, "Compute area hyperbolic cosine"); + FLECS_MATH_FUNC_DEF_F64(asinh, "Compute area hyperbolic sine"); + FLECS_MATH_FUNC_DEF_F64(atanh, "Compute area hyperbolic tangent"); - // Spaceship enterprise { - case '{': { - return flecs_script_scope(parser, entity->scope, pos); - } + /* Exponential and logarithmic functions */ + FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); + FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significant and exponent"); + FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); + FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); + FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); + FLECS_MATH_FUNC_DEF_F64(log2, "Compute binary logarithm"); - // Spaceship enterprise( - case '(': { - return flecs_script_paren_expr(parser, Token(0), entity, pos); - } - ) -} + /* Power functions */ + FLECS_MATH_FUNC_DEF_F64_F64(pow, "Raise to power"); + FLECS_MATH_FUNC_DEF_F64(sqrt, "Compute square root"); + FLECS_MATH_FUNC_DEF_F64(sqr, "Compute square"); -// SpaceShip( -identifier_paren: { - // SpaceShip() - Initializer(')', - Parse( - // SpaceShip(expr)\n - EcsTokEndOfStatement: { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL, false); + /* Rounding functions */ + FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); + FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); + FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); - Scope(entity->scope, - ecs_script_component_t *comp = - flecs_script_insert_component(parser, Token(0)); - comp->expr = INITIALIZER; - ) + FLECS_MATH_FUNC_DEF_F64(abs, "Compute absolute value"); - EndOfRule; - } + ecs_set_name_prefix(world, "EcsScript"); - // SpaceShip(expr) { - case '{': { - ecs_script_entity_t *entity = flecs_script_insert_entity( - parser, NULL, false); + ECS_COMPONENT_DEFINE(world, EcsScriptRng); - Scope(entity->scope, - ecs_script_component_t *comp = - flecs_script_insert_component(parser, Token(0)); - comp->expr = INITIALIZER; - ) + ecs_set_hooks(world, EcsScriptRng, { + .ctor = ecs_ctor(EcsScriptRng), + .move = ecs_move(EcsScriptRng), + .copy = ecs_copy(EcsScriptRng), + .dtor = ecs_dtor(EcsScriptRng), + }); - return flecs_script_scope(parser, entity->scope, pos); - } - ) - ) -} + ecs_struct(world, { + .entity = ecs_id(EcsScriptRng), + .members = { + { .name = "seed", .type = ecs_id(ecs_u64_t) } + } + }); -// Position: { -component_expr_scope: { + ecs_method(world, { + .parent = ecs_id(EcsScriptRng), + .name = "f", + .return_type = ecs_id(ecs_f64_t), + .params = { + { .name = "max", .type = ecs_id(ecs_f64_t) } + }, + .callback = flecs_script_rng_get_float + }); - // Position: {expr} - Expr('}', { - ecs_script_component_t *comp = flecs_script_insert_component( - parser, Token(0)); - comp->expr = EXPR; - EndOfRule; - }) + ecs_method(world, { + .parent = ecs_id(EcsScriptRng), + .name = "u", + .return_type = ecs_id(ecs_u64_t), + .params = { + { .name = "max", .type = ecs_id(ecs_u64_t) } + }, + .callback = flecs_script_rng_get_uint + }); } -// Points: [ -component_expr_collection: { - // Position: [expr] - Expr(']', { - ecs_script_component_t *comp = flecs_script_insert_component( - parser, Token(0)); - comp->expr = EXPR; - comp->is_collection = true; - EndOfRule; - }) -} +#endif -// Position: match -component_expr_match: { +/** + * @file addons/script/parser.c + * @brief Script grammar parser. + */ - // Position: match expr - Expr('\n', { - ecs_script_component_t *comp = flecs_script_insert_component( - parser, Token(0)); - comp->expr = EXPR; - EndOfRule; - }) -} - ParserEnd; -} -/* Parse script */ -ecs_script_t* ecs_script_parse( - ecs_world_t *world, - const char *name, - const char *code, - const ecs_script_eval_desc_t *desc) -{ - (void)desc; /* Will be used in future to expand type checking features */ +#ifdef FLECS_SCRIPT - if (!code) { - code = ""; - } +#define EcsTokEndOfStatement\ + case ';':\ + case '\n':\ + case '\0' - ecs_script_t *script = flecs_script_new(world); - script->name = ecs_os_strdup(name); - script->code = ecs_os_strdup(code); +static +const char* flecs_script_stmt( + ecs_parser_t *parser, + const char *pos); - ecs_script_impl_t *impl = flecs_script_impl(script); +/* Parse scope (statements inside {}) */ +static +const char* flecs_script_scope( + ecs_parser_t *parser, + ecs_script_scope_t *scope, + const char *pos) +{ + ParserBegin; - ecs_script_parser_t parser = { - .script = impl, - .scope = impl->root, - .significant_newline = true - }; + ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(pos[-1] == '{', ECS_INTERNAL_ERROR, NULL); - /* Allocate a buffer that is able to store all parsed tokens. Multiply the - * size of the script by two so that there is enough space to add \0 - * terminators and expression deliminators ('""') - * The token buffer will exist for as long as the script object exists, and - * ensures that AST nodes don't need to do separate allocations for the data - * they contain. */ - impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; - impl->token_buffer = flecs_alloc_w_dbg_info( - &impl->allocator, impl->token_buffer_size, "token buffer"); - parser.token_cur = impl->token_buffer; + ecs_script_scope_t *prev = parser->scope; + parser->scope = scope; - /* Start parsing code */ - const char *pos = script->code; + Loop( + LookAhead( + case EcsTokScopeClose: + pos = lookahead; + goto scope_close; + case EcsTokEnd: + Error("unexpected end of script"); + goto error; + ) - do { - pos = flecs_script_stmt(&parser, pos); + pos = flecs_script_stmt(parser, pos); if (!pos) { - /* NULL means error */ goto error; } + ) - if (!pos[0]) { - /* \0 means end of input */ - break; - } - } while (true); +scope_close: + parser->scope = prev; - impl->token_remaining = parser.token_cur; + ecs_assert(pos[-1] == '}', ECS_INTERNAL_ERROR, NULL); + return pos; - return script; -error: - ecs_script_free(script); - return NULL; + ParserEnd; } -#endif +/* Parse comma expression (expressions separated by ',') */ +static +const char* flecs_script_comma_expr( + ecs_parser_t *parser, + const char *pos, + bool is_base_list) +{ + ParserBegin; -/** - * @file addons/script/query_parser.c - * @brief Script grammar parser. - */ + Loop( + LookAhead( + case '\n': + pos = lookahead; + continue; + case EcsTokIdentifier: + LookAhead_Keep(); -#ifdef FLECS_SCRIPT + if (is_base_list) { + flecs_script_insert_pair_tag(parser, "IsA", Token(0)); + } else { + flecs_script_insert_entity(parser, Token(0), false); + } -#define EcsTokTermIdentifier\ - EcsTokIdentifier:\ - case EcsTokNumber:\ - case EcsTokMul + LookAhead_1(',', + pos = lookahead; + continue; + ) + ) -#define EcsTokEndOfTerm\ - '}':\ - pos --; /* Give token back to parser */\ - case EcsTokOr:\ - if (t->kind == EcsTokOr) {\ - if (parser->term->oper != EcsAnd) {\ - Error("cannot mix operators in || expression");\ - }\ - parser->term->oper = EcsOr;\ - }\ - case ',':\ - case '\n':\ - case '\0' + break; + ) -// $this == + return pos; +} + +/* Parse with expression (expression after 'with' keyword) */ static -const char* flecs_term_parse_equality_pred( - ecs_script_parser_t *parser, - const char *pos, - ecs_entity_t pred) +const char* flecs_script_with_expr( + ecs_parser_t *parser, + const char *pos) { ParserBegin; - if (parser->term->oper != EcsAnd) { - Error("cannot mix operator with equality expression"); - } + Parse( + // Position + case EcsTokIdentifier: { + // Position ( + LookAhead_1('(', + pos = lookahead; - parser->term->src = parser->term->first; - parser->term->first = (ecs_term_ref_t){0}; - parser->term->first.id = pred; + // Position ( expr ) + Initializer(')', + ecs_script_component_t *component = + flecs_script_insert_component(parser, Token(0)); + component->node.kind = EcsAstWithComponent; + component->expr = INITIALIZER; + EndOfRule; + ) + ) - Parse( - // $this == foo - // ^ - case EcsTokTermIdentifier: { - parser->term->second.name = Token(0); - Parse( case EcsTokEndOfTerm: EndOfRule; ) + if (Token(0)[0] == '$') { + ecs_script_var_component_t *var = + flecs_script_insert_var_component(parser, &Token(0)[1]); + var->node.kind = EcsAstWithVar; + } else { + ecs_script_tag_t *tag = + flecs_script_insert_tag(parser, Token(0)); + tag->node.kind = EcsAstWithTag; + } + + EndOfRule; } - // $this == "foo" - // ^ - case EcsTokString: { - parser->term->second.name = Token(0); - parser->term->second.id = EcsIsName; + // ( + case '(': + // (Eats, Apples) + Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', + // (Eats, Apples) ( expr + LookAhead_1('(', + pos = lookahead; - if (pred == EcsPredMatch) { - if (Token(0)[0] == '!') { - /* If match expression starts with !, set Not operator. The - * reason the ! is embedded in the expression is because - * there is only a single match (~=) operator. */ - parser->term->second.name ++; - parser->term->oper = EcsNot; - } - } + // (Eats, Apples) ( expr ) + Initializer(')', + ecs_script_component_t *component = + flecs_script_insert_pair_component(parser, + Token(1), Token(3)); + component->node.kind = EcsAstWithComponent; + component->expr = INITIALIZER; + EndOfRule; + ) + ) - Parse( - case EcsTokEndOfTerm: - EndOfRule; + ecs_script_tag_t *tag = + flecs_script_insert_pair_tag(parser, Token(1), Token(3)); + tag->node.kind = EcsAstWithTag; + EndOfRule; ) - } ) ParserEnd; } +/* Parse with expression list (expression list after 'with' keyword) */ static -ecs_entity_t flecs_query_parse_trav_flags( - const char *tok) +const char* flecs_script_with( + ecs_parser_t *parser, + ecs_script_with_t *with, + const char *pos) { - if (!ecs_os_strcmp(tok, "self")) return EcsSelf; - else if (!ecs_os_strcmp(tok, "up")) return EcsUp; - else if (!ecs_os_strcmp(tok, "cascade")) return EcsCascade; - else if (!ecs_os_strcmp(tok, "desc")) return EcsDesc; - else return 0; + ParserBegin; + + bool has_next; + do { + Scope(with->expressions, + pos = flecs_script_with_expr(parser, pos); + ) + + if (!pos) { + goto error; + } + + Parse( + case ',': { + has_next = true; + break; + } + case '{': { + return flecs_script_scope(parser, with->scope, pos); + } + ) + } while (has_next); + + ParserEnd; } +/* Parenthesis expression */ static -const char* flecs_term_parse_trav( - ecs_script_parser_t *parser, - ecs_term_ref_t *ref, - const char *pos) +const char* flecs_script_paren_expr( + ecs_parser_t *parser, + const char *kind, + ecs_script_entity_t *entity, + const char *pos) { ParserBegin; - Loop( - // self - Parse_1(EcsTokIdentifier, - ref->id |= flecs_query_parse_trav_flags(Token(0)); - - LookAhead( - // self| - case '|': - pos = lookahead; - continue; + Initializer(')', + entity->kind_w_expr = true; - // self IsA - case EcsTokIdentifier: - pos = lookahead; - parser->term->trav = ecs_lookup( - parser->script->pub.world, Token(1)); - if (!parser->term->trav) { - Error( - "unresolved traversal relationship '%s'", Token(1)); - goto error; - } + Scope(entity->scope, + ecs_script_component_t *component = + flecs_script_insert_component(parser, kind); + component->expr = INITIALIZER; + ) - EndOfRule; - ) + Parse( + // Position spaceship (expr)\n + EcsTokEndOfStatement: { + EndOfRule; + } - EndOfRule; + // Position spaceship (expr) { + case '{': { + return flecs_script_scope(parser, entity->scope, pos); + } ) ) ParserEnd; } -// Position( +/* Parse a single statement */ static -const char* flecs_term_parse_arg( - ecs_script_parser_t *parser, - const char *pos, - int32_t arg) +const char* flecs_script_if_stmt( + ecs_parser_t *parser, + const char *pos) { ParserBegin; - ecs_term_ref_t *ref = NULL; + // if expr + Expr('\0', + // if expr { + Parse_1('{', { + ecs_script_if_t *stmt = flecs_script_insert_if(parser); + stmt->expr = EXPR; + pos = flecs_script_scope(parser, stmt->if_true, pos); + if (!pos) { + goto error; + } - // Position(src - if (arg == 0) { - ref = &parser->term->src; + // if expr { } else + LookAhead_1(EcsTokKeywordElse, + pos = lookahead; - // Position(src, tgt - } else if (arg == 1) { - ref = &parser->term->second; - } else { - if (arg > FLECS_TERM_ARG_COUNT_MAX) { - Error("too many arguments in term"); - } - ref = &parser->extra_args[arg - 2]; - } + Parse( + // if expr { } else if + case EcsTokKeywordIf: { + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + } - bool is_trav_flag = false; + // if expr { } else\n if + case EcsTokNewline: { + Parse_1(EcsTokKeywordIf, + Scope(stmt->if_false, + return flecs_script_if_stmt(parser, pos); + ) + ) + } - LookAhead_1(EcsTokIdentifier, - is_trav_flag = flecs_query_parse_trav_flags(Token(0)) != 0; + // if expr { } else { + case '{': { + return flecs_script_scope(parser, stmt->if_false, pos); + } + ) + ) + + EndOfRule; + }); ) - if (is_trav_flag) { - // Position(self|up - // ^ - pos = flecs_term_parse_trav(parser, ref, pos); - if (!pos) { - goto error; - } - } else { - // Position(src - // ^ - Parse( - case EcsTokTermIdentifier: { - ref->name = Token(0); + ParserEnd; +} - // Position(src| - // ^ - { - LookAhead_1('|', - pos = lookahead; - pos = flecs_term_parse_trav(parser, ref, pos); - if (!pos) { - goto error; - } +static +const char* flecs_script_parse_var( + ecs_parser_t *parser, + const char *pos, + ecs_tokenizer_t *tokenizer, + bool is_prop) +{ + Parse_1(EcsTokIdentifier, + ecs_script_var_node_t *var = flecs_script_insert_var( + parser, Token(1)); + var->node.kind = is_prop ? EcsAstProp : EcsAstConst; + + Parse( + // const color = + case '=': { + // const color = Color : + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; - // Position(src|up IsA - // ^ - LookAhead_1(EcsTokIdentifier, - pos = lookahead; - parser->term->trav = ecs_lookup( - parser->script->pub.world, Token(1)); - if (!parser->term->trav) { - Error( - "unresolved trav identifier '%s'", Token(1)); - } + var->type = Token(3); + + // const color = Color: { + LookAhead_1('{', + // const color = Color: {expr} + pos = lookahead; + Initializer('}', + var->expr = INITIALIZER; + EndOfRule; ) ) - } - - break; - } - ) - } - Parse( - // Position(src, - // ^ - case ',': - if ((arg > 1) && parser->extra_oper != EcsAnd) { - Error("cannot mix operators in extra term arguments"); - } - parser->extra_oper = EcsAnd; - return flecs_term_parse_arg(parser, pos, arg + 1); + // const color = Color: expr\n + Initializer('\n', + var->expr = INITIALIZER; + EndOfRule; + ) + ) - // Position(src, second || - // ^ - case EcsTokOr: - if ((arg > 1) && parser->extra_oper != EcsOr) { - Error("cannot mix operators in extra term arguments"); + // const PI = expr\n + Expr('\n', + Warning("'%s var = expr' syntax is deprecated" + ", use '%s var: expr' instead", + is_prop ? "prop" : "const", + is_prop ? "prop" : "const"); + var->expr = EXPR; + EndOfRule; + ) } - parser->extra_oper = EcsOr; - return flecs_term_parse_arg(parser, pos, arg + 1); - // Position(src) - // ^ - case ')': - Parse( - case EcsTokEndOfTerm: + case ':': { + // const PI: expr\n + Expr('\n', + var->expr = EXPR; EndOfRule; - ) + ) + } + ) ) - ParserEnd; +error: + return NULL; } -// Position +/* Parse a single statement */ static -const char* flecs_term_parse_id( - ecs_script_parser_t *parser, +const char* flecs_script_stmt( + ecs_parser_t *parser, const char *pos) { ParserBegin; + bool name_is_expr_0 = false; + bool name_is_expr_1 = false; + Parse( - case EcsTokEq: - return flecs_term_parse_equality_pred( - parser, pos, EcsPredEq); - case EcsTokNeq: { - const char *ret = flecs_term_parse_equality_pred( - parser, pos, EcsPredEq); - if (ret) { - parser->term->oper = EcsNot; - } - return ret; + case EcsTokIdentifier: goto identifier; + case EcsTokString: goto string_name; + case '{': goto anonymous_entity; + case '(': goto paren; + case '@': goto annotation; + case EcsTokKeywordWith: goto with_stmt; + case EcsTokKeywordModule: goto module_stmt; + case EcsTokKeywordUsing: goto using_stmt; + case EcsTokKeywordTemplate: goto template_stmt; + case EcsTokKeywordProp: goto prop_var; + case EcsTokKeywordConst: goto const_var; + case EcsTokKeywordIf: goto if_stmt; + case EcsTokKeywordFor: goto for_stmt; + EcsTokEndOfStatement: EndOfRule; + ); + +anonymous_entity: { + return flecs_script_scope(parser, + flecs_script_insert_entity(parser, "_", false)->scope, pos); +} + +string_name: + /* If this is an interpolated string, we need to evaluate it as expression + * at evaluation time. Otherwise we can just use the string as name. The + * latter is useful if an entity name contains special characters that are + * not allowed in identifier tokens. */ + if (flecs_string_is_interpolated(Token(0))) { + name_is_expr_0 = true; + } + +identifier: { + // enterprise } (end of scope) + LookAhead_1('}', + goto insert_tag; + ) + + Parse( + // enterprise { + case '{': { + return flecs_script_scope(parser, + flecs_script_insert_entity( + parser, Token(0), name_is_expr_0)->scope, pos); } - case EcsTokMatch: - return flecs_term_parse_equality_pred( - parser, pos, EcsPredMatch); - // Position| - case '|': { - pos = flecs_term_parse_trav(parser, &parser->term->first, pos); - if (!pos) { - goto error; + // Red, + case ',': { + if (name_is_expr_0) { + Error("expression not allowed as entity name here"); } - // Position|self( - Parse( - case '(': - return flecs_term_parse_arg(parser, pos, 0); - case EcsTokEndOfTerm: - EndOfRule; + flecs_script_insert_entity(parser, Token(0), false); + pos = flecs_script_comma_expr(parser, pos, false); + EndOfRule; + } + + // Npc\n + EcsTokEndOfStatement: { + // Npc\n{ + LookAhead_1('{', + pos = lookahead; + return flecs_script_scope(parser, + flecs_script_insert_entity( + parser, Token(0), name_is_expr_0)->scope, pos); ) + + goto insert_tag; } - // Position( + // auto_override | + case '|': { + goto identifier_flag; + } + + // Position: + case ':': { + goto identifier_colon; + } + + // x = + case '=': { + goto identifier_assign; + } + + // SpaceShip( case '(': { - // Position() - LookAhead_1(')', - pos = lookahead; - parser->term->src.id = EcsIsEntity; + goto identifier_paren; + } - Parse( - case EcsTokEndOfTerm: - EndOfRule; - ) - ) + // Spaceship enterprise + case EcsTokIdentifier: { + goto identifier_identifier; + } - return flecs_term_parse_arg(parser, pos, 0); + // Spaceship "enterprise" + case EcsTokString: { + goto identifier_string; } + ) +} - case EcsTokEndOfTerm: +insert_tag: { + if (Token(0)[0] == '$') { + if (!flecs_script_insert_var_component(parser, &Token(0)[1])) { + Error( + "invalid context for variable component '%s': must be " + "part of entity", tokenizer->tokens[0].value); + } + } else { + if (!flecs_script_insert_tag(parser, Token(0))) { + Error( + "invalid context for tag '%s': must be part of entity", + tokenizer->tokens[0].value); + } + } + + EndOfRule; +} + +// @ +annotation: { + // @brief + Parse_1(EcsTokIdentifier, + // $brief expr + Until('\n', + flecs_script_insert_annot(parser, Token(1), Token(2)); EndOfRule; + ) ) +} - ParserEnd; +// with +with_stmt: { + ecs_script_with_t *with = flecs_script_insert_with(parser); + pos = flecs_script_with(parser, with, pos); + EndOfRule; } -// ( -static const char* flecs_term_parse_pair( - ecs_script_parser_t *parser, - const char *pos) -{ - ParserBegin; +// using +using_stmt: { + // using flecs.meta\n + Parse_1(EcsTokIdentifier, + flecs_script_insert_using(parser, Token(1)); - // (Position - // ^ - Parse( - case EcsTokTermIdentifier: { - parser->term->first.name = Token(0); + Parse( + EcsTokEndOfStatement: + EndOfRule; + ) + ) +} - LookAhead_1('|', - // (Position|self - pos = lookahead; - pos = flecs_term_parse_trav( - parser, &parser->term->first, pos); - if (!pos) { - goto error; - } - ) +// module +module_stmt: { + // using flecs.meta\n + Parse_2(EcsTokIdentifier, '\n', + flecs_script_insert_module(parser, Token(1)); + EndOfRule; + ) +} - // (Position, - Parse_1(',', - return flecs_term_parse_arg(parser, pos, 1); - ) - } +// template +template_stmt: { + // template SpaceShip + Parse_1(EcsTokIdentifier, + ecs_script_template_node_t *template = flecs_script_insert_template( + parser, Token(1)); + + Parse( + // template SpaceShip { + case '{': + return flecs_script_scope(parser, template->scope, pos); + + // template SpaceShip\n + EcsTokEndOfStatement: + EndOfRule; + ) ) +} - ParserEnd; +// prop +prop_var: { + // prop color = Color: + return flecs_script_parse_var(parser, pos, tokenizer, true); } -// AND -static -const char* flecs_term_parse_flags( - ecs_script_parser_t *parser, - const char *token_0, - const char *pos) -{ - ecs_assert(token_0 != NULL, ECS_INTERNAL_ERROR, NULL); +// const +const_var: { + // const color + return flecs_script_parse_var(parser, pos, tokenizer, false); +} - ParserBegin; +// if +if_stmt: { + return flecs_script_if_stmt(parser, pos); +} - ecs_id_t flag = 0; - int16_t oper = 0; - ecs_term_t *term = parser->term; +// for +for_stmt: { + // for i + Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { + Expr(0, { + ecs_expr_node_t *from = EXPR; + Parse_1(EcsTokRange, { + Expr(0, { + ecs_expr_node_t *to = EXPR; + ecs_script_for_range_t *stmt = + flecs_script_insert_for_range(parser); + stmt->loop_var = Token(1); + stmt->from = from; + stmt->to = to; - // AND - if (!ecs_os_strcmp(token_0, "and")) oper = EcsAndFrom; - else if (!ecs_os_strcmp(token_0, "or")) oper = EcsOrFrom; - else if (!ecs_os_strcmp(token_0, "not")) oper = EcsNotFrom; - else if (!ecs_os_strcmp(token_0, "auto_override")) flag = ECS_AUTO_OVERRIDE; - else if (!ecs_os_strcmp(token_0, "toggle")) flag = ECS_TOGGLE; - else { - // Position - term->first.name = token_0; - return flecs_term_parse_id(parser, pos); - } + Parse_1('{', { + return flecs_script_scope(parser, stmt->scope, pos); + }); + }); + }); + }); - if (oper || flag) { - // and | - // ^ - Parse_1('|', - Parse( - // and | Position - // ^ - case EcsTokTermIdentifier: { - if (oper) { - term->oper = oper; - } else if (flag) { - term->id = flag; - } + }); +} - term->first.name = Token(1); +// ( +paren: { + // (Likes, Apples) + Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', + goto pair; + ) +} - return flecs_term_parse_id(parser, pos); - } +// (Likes, Apples) +pair: { + // (Likes, Apples) } (end of scope) + LookAhead_1('}', + flecs_script_insert_pair_tag(parser, Token(1), Token(3)); + EndOfRule; + ) - // and | ( - // ^ - case '(': { - return flecs_term_parse_pair(parser, pos); + Parse( + // (Likes, Apples)\n + EcsTokEndOfStatement: { + flecs_script_insert_pair_tag(parser, Token(1), Token(3)); + EndOfRule; + } + + // (Eats, Apples): + case ':': { + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = EXPR; + EndOfRule; + }) + }) + + // (Eats, Apples): { + Parse_1('{', { + // (Eats, Apples): { expr } + Initializer('}', + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = INITIALIZER; + EndOfRule; + ) } ) - ) - } + } - ParserEnd; + // (IsA, Machine) { + case '{': { + ecs_script_pair_scope_t *ps = flecs_script_insert_pair_scope( + parser, Token(1), Token(3)); + return flecs_script_scope(parser, ps->scope, pos); + } + ) } -// ! -static -const char* flecs_term_parse_unary( - ecs_script_parser_t *parser, - const char *pos) -{ - ParserBegin; +// auto_override | +identifier_flag: { + ecs_id_t flag; + if (!ecs_os_strcmp(Token(0), "auto_override")) { + flag = ECS_AUTO_OVERRIDE; + } else { + Error("invalid flag '%s'", Token(0)); + } Parse( - // !( - case '(': { - return flecs_term_parse_pair(parser, pos); - } + // auto_override | ( + case '(': + // auto_override | (Rel, Tgt) + Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', + ecs_script_tag_t *tag = flecs_script_insert_pair_tag( + parser, Token(3), Token(5)); + tag->id.flag = flag; - // !{ - case '{': { - parser->term->first.id = EcsScopeOpen; - parser->term->src.id = EcsIsEntity; - parser->term->inout = EcsInOutNone; - EndOfRule; - } + Parse( + // auto_override | (Rel, Tgt)\n + EcsTokEndOfStatement: { + EndOfRule; + } - // !Position - // ^ - case EcsTokTermIdentifier: { - parser->term->first.name = Token(0); - return flecs_term_parse_id(parser, pos); + // auto_override | (Rel, Tgt): + case ':': { + Parse_1('{', + // auto_override | (Rel, Tgt): {expr} + Expr('}', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(3), Token(5)); + comp->expr = EXPR; + EndOfRule; + }) + ) + } + ) + ) + + // auto_override | Position + case EcsTokIdentifier: { + ecs_script_tag_t *tag = flecs_script_insert_tag( + parser, Token(2)); + tag->id.flag = flag; + + Parse( + // auto_override | Position\n + EcsTokEndOfStatement: { + EndOfRule; + } + + // auto_override | Position: + case ':': { + Parse_1('{', + // auto_override | Position: {expr} + Expr('}', { + ecs_script_component_t *comp = + flecs_script_insert_component( + parser, Token(2)); + comp->expr = EXPR; + EndOfRule; + }) + ) + } + ) } ) - - ParserEnd; } -// [ -static -const char* flecs_term_parse_inout( - ecs_script_parser_t *parser, - const char *pos) -{ - ParserBegin; +// Position: +identifier_colon: { + { + // Position: { + LookAhead_1('{', + pos = lookahead; + goto component_expr_scope; + ) + } - ecs_term_t *term = parser->term; + { + // Position: [ + LookAhead_1('[', + pos = lookahead; + goto component_expr_collection; + ) + } - // [inout] - // ^ - Parse_2(EcsTokIdentifier, ']', - if (!ecs_os_strcmp(Token(0), "default")) term->inout = EcsInOutDefault; - else if (!ecs_os_strcmp(Token(0), "none")) term->inout = EcsInOutNone; - else if (!ecs_os_strcmp(Token(0), "filter")) term->inout = EcsInOutFilter; - else if (!ecs_os_strcmp(Token(0), "inout")) term->inout = EcsInOut; - else if (!ecs_os_strcmp(Token(0), "in")) term->inout = EcsIn; - else if (!ecs_os_strcmp(Token(0), "out")) term->inout = EcsOut; + { + // Position: match + LookAhead_1(EcsTokKeywordMatch, + goto component_expr_match; + ) + } - Parse( - // [inout] Position - // ^ - case EcsTokTermIdentifier: { - return flecs_term_parse_flags(parser, Token(2), pos); - } + // enterprise : SpaceShip + Parse_1(EcsTokIdentifier, { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(0), name_is_expr_0); - // [inout] !Position - // ^ - case '!': - term->oper = EcsNot; - return flecs_term_parse_unary(parser, pos); - case '?': - term->oper = EcsOptional; - return flecs_term_parse_unary(parser, pos); + Scope(entity->scope, + flecs_script_insert_pair_tag(parser, "IsA", Token(2)); - // [inout] ( - // ^ - case '(': { - return flecs_term_parse_pair(parser, pos); - } + LookAhead_1(',', { + pos = lookahead; + pos = flecs_script_comma_expr(parser, pos, true); + }) ) - ) - ParserEnd; + Parse( + // enterprise : SpaceShip\n + EcsTokEndOfStatement: + EndOfRule; + + // enterprise : SpaceShip { + case '{': + return flecs_script_scope(parser, entity->scope, pos); + ) + }) } -static -const char* flecs_query_term_parse( - ecs_script_parser_t *parser, - const char *pos) -{ - ParserBegin; +// x = +identifier_assign: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(0), name_is_expr_0); - Parse( - case '[': - return flecs_term_parse_inout(parser, pos); - case EcsTokTermIdentifier: - return flecs_term_parse_flags(parser, Token(0), pos); - case '(': - return flecs_term_parse_pair(parser, pos); - case '!': - parser->term->oper = EcsNot; - return flecs_term_parse_unary(parser, pos); - case '?': - parser->term->oper = EcsOptional; - return flecs_term_parse_unary(parser, pos); - case '{': - parser->term->first.id = EcsScopeOpen; - parser->term->src.id = EcsIsEntity; - parser->term->inout = EcsInOutNone; - EndOfRule; - case '}': - parser->term->first.id = EcsScopeClose; - parser->term->src.id = EcsIsEntity; - parser->term->inout = EcsInOutNone; - LookAhead_1(',', - pos = lookahead; + // x = Position: + LookAhead_2(EcsTokIdentifier, ':', + pos = lookahead; + + // Use lookahead so that expression parser starts at "match" + LookAhead_1(EcsTokKeywordMatch, { + // (Eats, Apples): match expr + Expr('\n', { + ecs_script_component_t *comp = + flecs_script_insert_pair_component( + parser, Token(1), Token(3)); + comp->expr = EXPR; + EndOfRule; + }) + }) + + // x = Position: { + Parse_1('{', { + // x = Position: {expr} + Expr('}', + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(2)); + comp->expr = EXPR; + ) + + // x = Position: {expr}\n + Parse( + EcsTokEndOfStatement: + EndOfRule; + ) ) - EndOfRule; - case '\n':\ - case '\0': - EndOfRule; - ); + }) + ) - ParserEnd; + // x = f32\n + Initializer('\n', + Scope(entity->scope, + ecs_script_default_component_t *comp = + flecs_script_insert_default_component(parser); + comp->expr = INITIALIZER; + ) + + EndOfRule; + ) } -int flecs_terms_parse( - ecs_script_t *script, - ecs_term_t *terms, - int32_t *term_count_out) -{ - if (!ecs_os_strcmp(script->code, "0")) { - *term_count_out = 0; - return 0; +// Spaceship enterprise +identifier_string: { + if (flecs_string_is_interpolated(Token(1))) { + name_is_expr_1 = true; } +} - ecs_script_parser_t parser = { - .script = flecs_script_impl(script), - .pos = script->code, - .merge_variable_members = true - }; +identifier_identifier: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, Token(1), name_is_expr_1); + entity->kind = Token(0); - parser.token_cur = flecs_script_impl(script)->token_buffer; + // Spaceship enterprise : + LookAhead_1(':', + pos = lookahead; - int32_t term_count = 0; - const char *ptr = script->code; - ecs_term_ref_t extra_args[FLECS_TERM_ARG_COUNT_MAX]; - ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, - FLECS_TERM_ARG_COUNT_MAX); + Parse_1(EcsTokIdentifier, { + Scope(entity->scope, + flecs_script_insert_pair_tag(parser, "IsA", Token(3)); - parser.extra_args = extra_args; - parser.extra_oper = 0; + LookAhead_1(',', { + pos = lookahead; + pos = flecs_script_comma_expr(parser, pos, true); + }) + ) - do { - if (term_count == FLECS_TERM_COUNT_MAX) { - ecs_err("max number of terms (%d) reached, increase " - "FLECS_TERM_COUNT_MAX to support more", - FLECS_TERM_COUNT_MAX); - goto error; - } + goto identifier_identifier_x; + }) + ) - /* Parse next term */ - ecs_term_t *term = &terms[term_count]; - parser.term = term; - ecs_os_memset_t(term, 0, ecs_term_t); - ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); - parser.extra_oper = 0; +identifier_identifier_x: + Parse( + // Spaceship enterprise\n + EcsTokEndOfStatement: { + EndOfRule; + } - ptr = flecs_query_term_parse(&parser, ptr); - if (!ptr) { - /* Parser error */ - goto error; + // Spaceship enterprise { + case '{': { + return flecs_script_scope(parser, entity->scope, pos); } - if (!ecs_term_is_initialized(term)) { - /* Last term parsed */ - break; + // Spaceship enterprise( + case '(': { + return flecs_script_paren_expr(parser, Token(0), entity, pos); } + ) +} - term_count ++; +// SpaceShip( +identifier_paren: { + // SpaceShip() + Initializer(')', + Parse( + // SpaceShip(expr)\n + EcsTokEndOfStatement: { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, NULL, false); - /* Unpack terms with more than two args into multiple terms so that: - * Rel(X, Y, Z) - * becomes: - * Rel(X, Y), Rel(Y, Z) */ - int32_t arg = 0; - while (ecs_term_ref_is_set(&extra_args[arg ++])) { - ecs_assert(arg <= FLECS_TERM_ARG_COUNT_MAX, - ECS_INTERNAL_ERROR, NULL); + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(0)); + comp->expr = INITIALIZER; + ) - if (term_count == FLECS_TERM_COUNT_MAX) { - ecs_err("max number of terms (%d) reached, increase " - "FLECS_TERM_COUNT_MAX to support more", - FLECS_TERM_COUNT_MAX); - goto error; + EndOfRule; } - term = &terms[term_count ++]; - *term = term[-1]; + // SpaceShip(expr) { + case '{': { + ecs_script_entity_t *entity = flecs_script_insert_entity( + parser, NULL, false); - if (parser.extra_oper == EcsAnd) { - term->src = term[-1].second; - term->second = extra_args[arg - 1]; - } else if (parser.extra_oper == EcsOr) { - term->src = term[-1].src; - term->second = extra_args[arg - 1]; - term[-1].oper = EcsOr; - } + Scope(entity->scope, + ecs_script_component_t *comp = + flecs_script_insert_component(parser, Token(0)); + comp->expr = INITIALIZER; + ) - if (term->first.name != NULL) { - term->first.name = term->first.name; - } - - if (term->src.name != NULL) { - term->src.name = term->src.name; + return flecs_script_scope(parser, entity->scope, pos); } - } - - if (arg) { - ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, - FLECS_TERM_ARG_COUNT_MAX); - } - } while (ptr[0]); + ) + ) +} - (*term_count_out) += term_count; +// Position: { +component_expr_scope: { - return 0; -error: - return -1; + // Position: {expr} + Expr('}', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + EndOfRule; + }) } -const char* flecs_term_parse( - ecs_world_t *world, - const char *name, - const char *expr, - ecs_term_t *term, - char *token_buffer) -{ - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); - ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); - - EcsParserFixedBuffer(world, name, expr, token_buffer, 256); - parser.term = term; +// Points: [ +component_expr_collection: { + // Position: [expr] + Expr(']', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + comp->is_collection = true; + EndOfRule; + }) +} - const char *result = flecs_query_term_parse(&parser, expr); - if (!result) { - return NULL; - } +// Position: match +component_expr_match: { - ecs_os_memset_t(term, 0, ecs_term_t); + // Position: match expr + Expr('\n', { + ecs_script_component_t *comp = flecs_script_insert_component( + parser, Token(0)); + comp->expr = EXPR; + EndOfRule; + }) +} - return flecs_query_term_parse(&parser, expr); + ParserEnd; } -const char* flecs_id_parse( - const ecs_world_t *world, +/* Parse script */ +ecs_script_t* ecs_script_parse( + ecs_world_t *world, const char *name, - const char *expr, - ecs_id_t *id) + const char *code, + const ecs_script_eval_desc_t *desc) { - ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); - ecs_assert(id != NULL, ECS_INVALID_PARAMETER, NULL); - - char token_buffer[256]; - ecs_term_t term = {0}; - EcsParserFixedBuffer(world, name, expr, token_buffer, 256); - parser.term = &term; - - expr = flecs_scan_whitespace(&parser, expr); - if (!ecs_os_strcmp(expr, "#0")) { - *id = 0; - return &expr[1]; - } - - const char *result = flecs_query_term_parse(&parser, expr); - if (!result) { - return NULL; - } - - if (ecs_term_finalize(world, &term)) { - return NULL; - } - - if (term.oper != EcsAnd) { - ecs_parser_error(name, expr, (result - expr), - "invalid operator for add expression"); - return NULL; - } + (void)desc; /* Will be used in future to expand type checking features */ - if ((term.src.id & ~EcsTraverseFlags) != (EcsThis|EcsIsVariable)) { - ecs_parser_error(name, expr, (result - expr), - "invalid source for add expression (must be $this)"); - return NULL; + if (!code) { + code = ""; } - *id = term.id; - - return result; -} - -static -const char* flecs_query_arg_parse( - ecs_script_parser_t *parser, - ecs_query_t *q, - ecs_iter_t *it, - const char *pos) -{ - ParserBegin; - - Parse_3(EcsTokIdentifier, ':', EcsTokIdentifier, { - int var = ecs_query_find_var(q, Token(0)); - if (var == -1) { - Error("unknown variable '%s'", Token(0)); - } - - ecs_entity_t val = ecs_lookup(q->world, Token(2)); - if (!val) { - Error("unresolved entity '%s'", Token(2)); - } - - ecs_iter_set_var(it, var, val); + ecs_script_t *script = flecs_script_new(world); + script->name = ecs_os_strdup(name); + script->code = ecs_os_strdup(code); - EndOfRule; - }) + ecs_script_impl_t *impl = flecs_script_impl(script); - ParserEnd; -} + ecs_parser_t parser = { + .name = script->name, + .code = script->code, + .script = impl, + .scope = impl->root, + .significant_newline = true + }; -static -const char* flecs_query_args_parse( - ecs_script_parser_t *parser, - ecs_query_t *q, - ecs_iter_t *it, - const char *pos) -{ - ParserBegin; + /* Allocate a buffer that is able to store all parsed tokens. Multiply the + * size of the script by two so that there is enough space to add \0 + * terminators and expression deliminators ('""') + * The token buffer will exist for as long as the script object exists, and + * ensures that AST nodes don't need to do separate allocations for the data + * they contain. */ + impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; + impl->token_buffer = flecs_alloc_w_dbg_info( + &impl->allocator, impl->token_buffer_size, "token buffer"); + parser.token_cur = impl->token_buffer; - bool has_paren = false; - LookAhead( - case '\0': - pos = lookahead; - EndOfRule; - case '(': { - pos = lookahead; - has_paren = true; - LookAhead_1(')', - pos = lookahead; - EndOfRule; - ) - } - ) + /* Start parsing code */ + const char *pos = script->code; - Loop( - pos = flecs_query_arg_parse(parser, q, it, pos); + do { + pos = flecs_script_stmt(&parser, pos); if (!pos) { + /* NULL means error */ goto error; } - Parse( - case ',': - continue; - case '\0': - EndOfRule; - case ')': - if (!has_paren) { - Error("unexpected ')' without opening '(')"); - } - EndOfRule; - ) - ) - - ParserEnd; -} - -const char* ecs_query_args_parse( - ecs_query_t *q, - ecs_iter_t *it, - const char *expr) -{ - flecs_poly_assert(q, ecs_query_t); - ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL); + if (!pos[0]) { + /* \0 means end of input */ + break; + } + } while (true); - const char *q_name = q->entity ? ecs_get_name(q->world, q->entity) : NULL; - if (ecs_os_strlen(expr) > 512) { - ecs_parser_error(q_name, expr, 0, "query argument expression too long"); - return NULL; - } + impl->token_remaining = parser.token_cur; - char token_buffer[1024]; - EcsParserFixedBuffer(q->world, q_name, expr, token_buffer, 256); - return flecs_query_args_parse(&parser, q, it, expr); + return script; error: + ecs_script_free(script); return NULL; } @@ -58385,7 +60267,7 @@ ecs_script_t* flecs_script_new( { ecs_script_impl_t *result = ecs_os_calloc_t(ecs_script_impl_t); flecs_allocator_init(&result->allocator); - ecs_script_parser_t parser = { .script = result }; + ecs_parser_t parser = { .script = result }; result->root = flecs_script_scope_new(&parser); result->pub.world = world; result->refcount = 1; @@ -59307,1101 +61189,489 @@ void flecs_script_template_instantiate( ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = template->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - const EcsStruct *st = ecs_record_get(world, r, EcsStruct); - - ecs_script_eval_visitor_t v; - ecs_script_eval_desc_t desc = { - .runtime = flecs_script_runtime_get(world) - }; - - flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); - ecs_vec_t prev_using = v.r->using; - ecs_vec_t prev_with = desc.runtime->with; - ecs_vec_t prev_with_type_info = desc.runtime->with_type_info; - v.r->using = template->using_; - v.template_entity = template_entity; - ecs_vec_init_t(NULL, &desc.runtime->with, ecs_value_t, 0); - ecs_vec_init_t(NULL, &desc.runtime->with_type_info, ecs_type_info_t*, 0); - - ecs_script_scope_t *scope = template->node->scope; - - /* Dummy entity node for instance */ - ecs_script_entity_t instance_node = { - .node = { - .kind = EcsAstEntity, - .pos = template->node->node.pos - }, - .scope = scope - }; - - v.entity = &instance_node; - - int32_t i, m; - for (i = 0; i < count; i ++) { - v.parent = entities[i]; - ecs_assert(ecs_is_alive(world, v.parent), ECS_INTERNAL_ERROR, NULL); - - instance_node.eval = entities[i]; - - /* Create variables to hold template properties */ - ecs_script_vars_t *vars = flecs_script_vars_push( - NULL, &v.r->stack, &v.r->allocator); - vars->parent = template->vars; /* Include hoisted variables */ - vars->sp = ecs_vec_count(&template->vars->vars); - - /* Allocate enough space for variables */ - ecs_script_vars_set_size(vars, (st ? st->members.count : 0) + 1); - - /* Populate $this variable with instance entity */ - ecs_entity_t instance = entities[i]; - ecs_script_var_t *this_var = ecs_script_vars_declare( - vars, NULL /* $this */); - this_var->value.type = ecs_id(ecs_entity_t); - this_var->value.ptr = &instance; - - /* Populate properties from template members */ - if (st) { - const ecs_member_t *members = st->members.array; - for (m = 0; m < st->members.count; m ++) { - const ecs_member_t *member = &members[m]; - - /* Assign template property from template instance. Don't - * set name as variables will be resolved by frame offset. */ - ecs_script_var_t *var = ecs_script_vars_declare( - vars, NULL /* member->name */); - var->value.type = member->type; - var->value.ptr = ECS_OFFSET(data, member->offset); - } - } - - ecs_script_clear(world, template_entity, instance); - - /* Run template code */ - v.vars = vars; - - flecs_script_eval_scope(&v, scope); - - /* Pop variable scope */ - ecs_script_vars_pop(vars); - - data = ECS_OFFSET(data, ti->size); - } - - ecs_vec_fini_t(&desc.runtime->allocator, - &desc.runtime->with, ecs_value_t); - ecs_vec_fini_t(&desc.runtime->allocator, - &desc.runtime->with_type_info, ecs_type_info_t*); - - v.r->with = prev_with; - v.r->with_type_info = prev_with_type_info; - v.r->using = prev_using; - flecs_script_eval_visit_fini(&v, &desc); -} - -static -void flecs_on_template_set_event( - ecs_iter_t *it) -{ - ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); - - EcsScriptTemplateSetEvent *evt = it->param; - ecs_world_t *world = it->real_world; - ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); - - ecs_defer_suspend(world); - - flecs_script_template_instantiate( - world, evt->template_entity, evt->entities, evt->data, evt->count); - - ecs_defer_resume(world); -} - -/* Template on_set handler to update contents for new property values */ -static -void flecs_script_template_on_set( - ecs_iter_t *it) -{ - if (it->table->flags & EcsTableIsPrefab) { - /* Don't instantiate templates for prefabs */ - return; - } - - ecs_world_t *world = it->world; - ecs_entity_t template_entity = ecs_field_id(it, 0); - ecs_record_t *r = ecs_record_find(world, template_entity); - if (!r) { - ecs_err("template entity is empty (should never happen)"); - return; - } - - const EcsScript *script = ecs_record_get(world, r, EcsScript); - if (!script) { - ecs_err("template is missing script component"); - return; - } - - ecs_script_template_t *template = script->template_; - ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); - const ecs_type_info_t *ti = template->type_info; - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); - - if (ecs_is_deferred(it->world)) { - flecs_script_template_defer_on_set(it, template_entity, ti, data); - return; - } - - flecs_script_template_instantiate( - world, template_entity, it->entities, data, it->count); - return; -} - -static -int flecs_script_template_eval_prop( - ecs_script_eval_visitor_t *v, - ecs_script_var_node_t *node) -{ - ecs_script_template_t *template = v->template; - if (ecs_vec_count(&v->vars->vars) > - ecs_vec_count(&template->prop_defaults)) - { - flecs_script_eval_error(v, node, - "const variables declared before prop '%s' (props must come first)", - node->name); - return -1; - } - - ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); - if (!var) { - flecs_script_eval_error(v, node, - "variable '%s' redeclared", node->name); - return -1; - } - - ecs_entity_t type; - const ecs_type_info_t *ti; - - if (node->type) { - if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { - flecs_script_eval_error(v, node, - "unresolved type '%s' for prop '%s'", - node->type, node->name); - return -1; - } - - ti = flecs_script_get_type_info(v, node, type); - if (!ti) { - return -1; - } - - var->value.type = type; - var->value.ptr = flecs_stack_alloc( - &v->r->stack, ti->size, ti->alignment); - var->type_info = ti; - - if (flecs_script_eval_expr(v, &node->expr, &var->value)) { - return -1; - } - } else { - /* We don't know the type yet, so we can't create a storage for it yet. - * Run the expression first to deduce the type. */ - ecs_value_t value = {0}; - if (flecs_script_eval_expr(v, &node->expr, &value)) { - flecs_script_eval_error(v, node, - "failed to evaluate expression for const variable '%s'", - node->name); - return -1; - } - - ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); - ti = ecs_get_type_info(v->world, value.type); - ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - - var->value.ptr = flecs_stack_calloc( - &v->r->stack, ti->size, ti->alignment); - type = var->value.type = value.type; - var->type_info = ti; - - if (ti->hooks.ctor) { - ti->hooks.ctor(var->value.ptr, 1, ti); - } - - ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); - ecs_value_fini_w_type_info(v->world, ti, value.ptr); - flecs_free(&v->world->allocator, ti->size, value.ptr); - } - - ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, - &template->prop_defaults, ecs_script_var_t); - value->value.ptr = flecs_calloc_w_dbg_info( - &v->base.script->allocator, ti->size, ti->name); - value->value.type = type; - value->type_info = ti; - ecs_value_copy_w_type_info( - v->world, ti, value->value.ptr, var->value.ptr); - - ecs_entity_t mbr = ecs_entity(v->world, { - .name = node->name, - .parent = template->entity - }); - - ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); - - return 0; -} - -static -int flecs_script_template_eval( - ecs_script_eval_visitor_t *v, - ecs_script_node_t *node) -{ - switch(node->kind) { - case EcsAstTag: - case EcsAstComponent: - case EcsAstVarComponent: - case EcsAstEntity: - case EcsAstScope: - case EcsAstDefaultComponent: - case EcsAstWithVar: - case EcsAstWithTag: - case EcsAstWithComponent: - case EcsAstUsing: - case EcsAstModule: - case EcsAstAnnotation: - case EcsAstConst: - case EcsAstPairScope: - case EcsAstWith: - case EcsAstIf: - case EcsAstFor: - break; - case EcsAstTemplate: - flecs_script_eval_error(v, node, "nested templates are not allowed"); - return -1; - case EcsAstProp: - return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); - } - - return flecs_script_check_node(v, node); -} - -static -int flecs_script_template_preprocess( - ecs_script_eval_visitor_t *v, - ecs_script_template_t *template) -{ - ecs_visit_action_t prev_visit = v->base.visit; - v->template = template; - - /* Dummy entity node for instance */ - ecs_script_entity_t instance_node = { - .node = { - .kind = EcsAstEntity, - .pos = template->node->node.pos - } - }; - - v->entity = &instance_node; - - v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; - v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); - ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); - var->value.type = ecs_id(ecs_entity_t); - int result = flecs_script_check_scope(v, template->node->scope); - v->vars = ecs_script_vars_pop(v->vars); - v->base.visit = prev_visit; - v->template = NULL; - v->entity = NULL; - - return result; -} - -static -int flecs_script_template_hoist_using( - ecs_script_eval_visitor_t *v, - ecs_script_template_t *template) -{ - ecs_allocator_t *a = &v->base.script->allocator; - if (v->module) { - ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; - } - - int i, count = ecs_vec_count(&v->r->using); - for (i = 0; i < count; i ++) { - ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = - ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; - } - - return 0; -} - -static -int flecs_script_template_hoist_vars( - ecs_script_eval_visitor_t *v, - ecs_script_template_t *template, - ecs_script_vars_t *vars) -{ - int32_t i, count = ecs_vec_count(&vars->vars); - ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); - for (i = 0; i < count; i ++) { - ecs_script_var_t *src = &src_vars[i]; - if (ecs_script_vars_lookup(template->vars, src->name)) { - /* If variable is masked, don't declare it twice */ - continue; - } - ecs_script_var_t *dst = ecs_script_vars_define_id( - template->vars, src->name, src->value.type); - ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_value_copy(v->world, - src->value.type, dst->value.ptr, src->value.ptr); - } - - if (vars->parent) { - flecs_script_template_hoist_vars(v, template, vars->parent); - } - - return 0; -} - -ecs_script_template_t* flecs_script_template_init( - ecs_script_impl_t *script) -{ - ecs_allocator_t *a = &script->allocator; - ecs_script_template_t *result = flecs_alloc_t(a, ecs_script_template_t); - ecs_vec_init_t(NULL, &result->prop_defaults, ecs_script_var_t, 0); - ecs_vec_init_t(NULL, &result->using_, ecs_entity_t, 0); - result->vars = ecs_script_vars_init(script->pub.world); - return result; -} - -void flecs_script_template_fini( - ecs_script_impl_t *script, - ecs_script_template_t *template) -{ - ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_allocator_t *a = &script->allocator; - - int32_t i, count = ecs_vec_count(&template->prop_defaults); - ecs_script_var_t *values = ecs_vec_first(&template->prop_defaults); - for (i = 0; i < count; i ++) { - ecs_script_var_t *value = &values[i]; - const ecs_type_info_t *ti = value->type_info; - if (ti->hooks.dtor) { - ti->hooks.dtor(value->value.ptr, 1, ti); - } - flecs_free(a, ti->size, value->value.ptr); - } - - ecs_vec_fini_t(a, &template->prop_defaults, ecs_script_var_t); - - ecs_vec_fini_t(a, &template->using_, ecs_entity_t); - ecs_script_vars_fini(template->vars); - flecs_free_t(a, ecs_script_template_t, template); -} - -/* Create new template */ -int flecs_script_eval_template( - ecs_script_eval_visitor_t *v, - ecs_script_template_node_t *node) -{ - ecs_entity_t template_entity = flecs_script_create_entity(v, node->name); - if (!template_entity) { - return -1; - } - - ecs_script_template_t *template = flecs_script_template_init(v->base.script); - template->entity = template_entity; - template->node = node; - - /* Variables are always presented to a template in a well defined order, so - * we don't need dynamic variable binding. */ - bool old_dynamic_variable_binding = v->dynamic_variable_binding; - v->dynamic_variable_binding = false; + const EcsStruct *st = ecs_record_get(world, r, EcsStruct); - if (flecs_script_template_preprocess(v, template)) { - goto error; - } + ecs_script_eval_visitor_t v; + ecs_script_eval_desc_t desc = { + .runtime = flecs_script_runtime_get(world) + }; - if (flecs_script_template_hoist_using(v, template)) { - goto error; - } + flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); + ecs_vec_t prev_using = v.r->using; + ecs_vec_t prev_with = desc.runtime->with; + ecs_vec_t prev_with_type_info = desc.runtime->with_type_info; + v.r->using = template->using_; + v.template_entity = template_entity; + ecs_vec_init_t(NULL, &desc.runtime->with, ecs_value_t, 0); + ecs_vec_init_t(NULL, &desc.runtime->with_type_info, ecs_type_info_t*, 0); - if (flecs_script_template_hoist_vars(v, template, v->vars)) { - goto error; - } + ecs_script_scope_t *scope = template->node->scope; - v->dynamic_variable_binding = old_dynamic_variable_binding; + /* Dummy entity node for instance */ + ecs_script_entity_t instance_node = { + .node = { + .kind = EcsAstEntity, + .pos = template->node->node.pos + }, + .scope = scope + }; - /* If template has no props, give template dummy size so we can register - * hooks for it. */ - if (!ecs_has(v->world, template_entity, EcsComponent)) { - ecs_set(v->world, template_entity, EcsComponent, {1, 1}); - } + v.entity = &instance_node; - template->type_info = ecs_get_type_info(v->world, template_entity); + int32_t i, m; + for (i = 0; i < count; i ++) { + v.parent = entities[i]; + ecs_assert(ecs_is_alive(world, v.parent), ECS_INTERNAL_ERROR, NULL); - ecs_add_pair(v->world, template_entity, EcsOnInstantiate, EcsOverride); + instance_node.eval = entities[i]; - EcsScript *script = ecs_ensure(v->world, template_entity, EcsScript); - if (script->script) { - if (script->template_) { - flecs_script_template_fini( - flecs_script_impl(script->script), script->template_); - } - ecs_script_free(script->script); - } + /* Create variables to hold template properties */ + ecs_script_vars_t *vars = flecs_script_vars_push( + NULL, &v.r->stack, &v.r->allocator); + vars->parent = template->vars; /* Include hoisted variables */ + vars->sp = ecs_vec_count(&template->vars->vars); - script->script = &v->base.script->pub; - script->template_ = template; - ecs_modified(v->world, template_entity, EcsScript); + /* Allocate enough space for variables */ + ecs_script_vars_set_size(vars, (st ? st->members.count : 0) + 1); - ecs_set_hooks_id(v->world, template_entity, &(ecs_type_hooks_t) { - .ctor = flecs_script_template_ctor, - .on_set = flecs_script_template_on_set, - .ctx = v->world - }); + /* Populate $this variable with instance entity */ + ecs_entity_t instance = entities[i]; + ecs_script_var_t *this_var = ecs_script_vars_declare( + vars, NULL /* $this */); + this_var->value.type = ecs_id(ecs_entity_t); + this_var->value.ptr = &instance; - /* Keep script alive for as long as template is alive */ - v->base.script->refcount ++; + /* Populate properties from template members */ + if (st) { + const ecs_member_t *members = st->members.array; + for (m = 0; m < st->members.count; m ++) { + const ecs_member_t *member = &members[m]; - return 0; -error: - flecs_script_template_fini(v->base.script, template); - return -1; -} + /* Assign template property from template instance. Don't + * set name as variables will be resolved by frame offset. */ + ecs_script_var_t *var = ecs_script_vars_declare( + vars, NULL /* member->name */); + var->value.type = member->type; + var->value.ptr = ECS_OFFSET(data, member->offset); + } + } -void flecs_script_template_import( - ecs_world_t *world) -{ - ECS_COMPONENT_DEFINE(world, EcsScriptTemplateSetEvent); - ECS_TAG_DEFINE(world, EcsScriptTemplate); + ecs_script_clear(world, template_entity, instance); - ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); + /* Run template code */ + v.vars = vars; - ecs_set_hooks(world, EcsScriptTemplateSetEvent, { - .ctor = flecs_default_ctor, - .move = ecs_move(EcsScriptTemplateSetEvent), - .dtor = ecs_dtor(EcsScriptTemplateSetEvent), - .flags = ECS_TYPE_HOOK_COPY_ILLEGAL - }); + flecs_script_eval_scope(&v, scope); - ecs_observer(world, { - .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), - .query.terms = {{ .id = EcsAny }}, - .events = { ecs_id(EcsScriptTemplateSetEvent) }, - .callback = flecs_on_template_set_event - }); -} + /* Pop variable scope */ + ecs_script_vars_pop(vars); -#endif + data = ECS_OFFSET(data, ti->size); + } -/** - * @file addons/script/tokenizer.c - * @brief Script tokenizer. - */ + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with, ecs_value_t); + ecs_vec_fini_t(&desc.runtime->allocator, + &desc.runtime->with_type_info, ecs_type_info_t*); + v.r->with = prev_with; + v.r->with_type_info = prev_with_type_info; + v.r->using = prev_using; + flecs_script_eval_visit_fini(&v, &desc); +} -#ifdef FLECS_SCRIPT +static +void flecs_on_template_set_event( + ecs_iter_t *it) +{ + ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); -#define Keyword(keyword, _kind)\ - } else if (!ecs_os_strncmp(pos, keyword " ", ecs_os_strlen(keyword) + 1)) {\ - out->value = keyword;\ - out->kind = _kind;\ - return pos + ecs_os_strlen(keyword);\ - } else if (!ecs_os_strncmp(pos, keyword "\n", ecs_os_strlen(keyword) + 1)) {\ - out->value = keyword;\ - out->kind = _kind;\ - return pos + ecs_os_strlen(keyword); + EcsScriptTemplateSetEvent *evt = it->param; + ecs_world_t *world = it->real_world; + ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); -#define OperatorMultiChar(oper, _kind)\ - } else if (!ecs_os_strncmp(pos, oper, ecs_os_strlen(oper))) {\ - out->value = oper;\ - out->kind = _kind;\ - return pos + ecs_os_strlen(oper); + ecs_defer_suspend(world); -#define Operator(oper, _kind)\ - } else if (pos[0] == oper[0]) {\ - out->value = oper;\ - out->kind = _kind;\ - return pos + 1; + flecs_script_template_instantiate( + world, evt->template_entity, evt->entities, evt->data, evt->count); -const char* flecs_script_token_kind_str( - ecs_script_token_kind_t kind) -{ - switch(kind) { - case EcsTokUnknown: - return "unknown token "; - case EcsTokColon: - case EcsTokScopeOpen: - case EcsTokScopeClose: - case EcsTokParenOpen: - case EcsTokParenClose: - case EcsTokBracketOpen: - case EcsTokBracketClose: - case EcsTokAnnotation: - case EcsTokComma: - case EcsTokSemiColon: - case EcsTokAssign: - case EcsTokAdd: - case EcsTokSub: - case EcsTokMul: - case EcsTokDiv: - case EcsTokMod: - case EcsTokBitwiseOr: - case EcsTokBitwiseAnd: - case EcsTokNot: - case EcsTokOptional: - case EcsTokEq: - case EcsTokNeq: - case EcsTokGt: - case EcsTokGtEq: - case EcsTokLt: - case EcsTokLtEq: - case EcsTokAnd: - case EcsTokOr: - case EcsTokMatch: - case EcsTokRange: - case EcsTokShiftLeft: - case EcsTokShiftRight: - case EcsTokAddAssign: - case EcsTokMulAssign: - return ""; - case EcsTokKeywordWith: - case EcsTokKeywordUsing: - case EcsTokKeywordProp: - case EcsTokKeywordConst: - case EcsTokKeywordIf: - case EcsTokKeywordElse: - case EcsTokKeywordFor: - case EcsTokKeywordIn: - case EcsTokKeywordTemplate: - case EcsTokKeywordModule: - case EcsTokKeywordMatch: - return "keyword "; - case EcsTokIdentifier: - return "identifier "; - case EcsTokString: - return "string "; - case EcsTokNumber: - return "number "; - case EcsTokNewline: - return "newline"; - case EcsTokMember: - return "member"; - case EcsTokEnd: - return "end of script"; - default: - return ""; - } + ecs_defer_resume(world); } -const char* flecs_script_token_str( - ecs_script_token_kind_t kind) +/* Template on_set handler to update contents for new property values */ +static +void flecs_script_template_on_set( + ecs_iter_t *it) { - switch(kind) { - case EcsTokUnknown: return "unknown token"; - case EcsTokColon: return ":"; - case EcsTokScopeOpen: return "{"; - case EcsTokScopeClose: return "}"; - case EcsTokParenOpen: return "("; - case EcsTokParenClose: return ")"; - case EcsTokBracketOpen: return "["; - case EcsTokBracketClose: return "]"; - case EcsTokAnnotation: return "@"; - case EcsTokComma: return ","; - case EcsTokSemiColon: return ";"; - case EcsTokAssign: return "="; - case EcsTokAdd: return "+"; - case EcsTokSub: return "-"; - case EcsTokMul: return "*"; - case EcsTokDiv: return "/"; - case EcsTokMod: return "%%"; - case EcsTokBitwiseOr: return "|"; - case EcsTokBitwiseAnd: return "&"; - case EcsTokNot: return "!"; - case EcsTokOptional: return "?"; - case EcsTokEq: return "=="; - case EcsTokNeq: return "!="; - case EcsTokGt: return ">"; - case EcsTokGtEq: return ">="; - case EcsTokLt: return "<"; - case EcsTokLtEq: return "<="; - case EcsTokAnd: return "&&"; - case EcsTokOr: return "||"; - case EcsTokMatch: return "~="; - case EcsTokRange: return ".."; - case EcsTokShiftLeft: return "<<"; - case EcsTokShiftRight: return ">>"; - case EcsTokAddAssign: return "+="; - case EcsTokMulAssign: return "*="; - case EcsTokKeywordWith: return "with"; - case EcsTokKeywordUsing: return "using"; - case EcsTokKeywordProp: return "prop"; - case EcsTokKeywordConst: return "const"; - case EcsTokKeywordMatch: return "match"; - case EcsTokKeywordIf: return "if"; - case EcsTokKeywordElse: return "else"; - case EcsTokKeywordFor: return "for"; - case EcsTokKeywordIn: return "in"; - case EcsTokKeywordTemplate: return "template"; - case EcsTokKeywordModule: return "module"; - case EcsTokIdentifier: return "identifier"; - case EcsTokString: return "string"; - case EcsTokNumber: return "number"; - case EcsTokNewline: return "newline"; - case EcsTokMember: return "member"; - case EcsTokEnd: return "end of script"; - default: - return ""; + if (it->table->flags & EcsTableIsPrefab) { + /* Don't instantiate templates for prefabs */ + return; } -} -const char* flecs_scan_whitespace( - ecs_script_parser_t *parser, - const char *pos) -{ - (void)parser; - - if (parser->significant_newline) { - while (pos[0] && isspace(pos[0]) && pos[0] != '\n') { - pos ++; - } - } else { - while (pos[0] && isspace(pos[0])) { - pos ++; - } + ecs_world_t *world = it->world; + ecs_entity_t template_entity = ecs_field_id(it, 0); + ecs_record_t *r = ecs_record_find(world, template_entity); + if (!r) { + ecs_err("template entity is empty (should never happen)"); + return; } - return pos; -} + const EcsScript *script = ecs_record_get(world, r, EcsScript); + if (!script) { + ecs_err("template is missing script component"); + return; + } -static -const char* flecs_scan_whitespace_and_comment( - ecs_script_parser_t *parser, - const char *pos) -{ -repeat_skip_whitespace_comment: - pos = flecs_scan_whitespace(parser, pos); - if (pos[0] == '/') { - if (pos[1] == '/') { - for (pos = pos + 2; pos[0] && pos[0] != '\n'; pos ++) { } - if (pos[0] == '\n') { - pos ++; - goto repeat_skip_whitespace_comment; - } - } else if (pos[1] == '*') { - for (pos = &pos[2]; pos[0] != 0; pos ++) { - if (pos[0] == '*' && pos[1] == '/') { - pos += 2; - goto repeat_skip_whitespace_comment; - } - } + ecs_script_template_t *template = script->template_; + ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_type_info_t *ti = template->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "missing */ for multiline comment"); - } + if (ecs_is_deferred(it->world)) { + flecs_script_template_defer_on_set(it, template_entity, ti, data); + return; } - return pos; + flecs_script_template_instantiate( + world, template_entity, it->entities, data, it->count); + return; } -// Identifier token static -bool flecs_script_is_identifier( - char c) -{ - return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); -} - -const char* flecs_script_identifier( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) +int flecs_script_template_eval_prop( + ecs_script_eval_visitor_t *v, + ecs_script_var_node_t *node) { - if (out) { - out->kind = EcsTokIdentifier; - out->value = parser->token_cur; + ecs_script_template_t *template = v->template; + if (ecs_vec_count(&v->vars->vars) > + ecs_vec_count(&template->prop_defaults)) + { + flecs_script_eval_error(v, node, + "const variables declared before prop '%s' (props must come first)", + node->name); + return -1; } - ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); - bool is_var = pos[0] == '$'; - char *outpos = NULL; - const char *start = pos; - if (parser) { - outpos = parser->token_cur; - if (parser->merge_variable_members) { - is_var = false; - } + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); + if (!var) { + flecs_script_eval_error(v, node, + "variable '%s' redeclared", node->name); + return -1; } - do { - char c = pos[0]; - bool is_ident = flecs_script_is_identifier(c) || isdigit(c); + ecs_entity_t type; + const ecs_type_info_t *ti; - if (!is_var) { - is_ident = is_ident || (c == '.'); + if (node->type) { + if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { + flecs_script_eval_error(v, node, + "unresolved type '%s' for prop '%s'", + node->type, node->name); + return -1; } - /* Retain \. for name lookup operation */ - if (!is_ident && c == '\\' && pos[1] == '.') { - is_ident = true; + ti = flecs_script_get_type_info(v, node, type); + if (!ti) { + return -1; } - /* Retain .* for using wildcard expressions */ - if (!is_ident && c == '*') { - if (pos != start && pos[-1] == '.') { - is_ident = true; - } + var->value.type = type; + var->value.ptr = flecs_stack_alloc( + &v->r->stack, ti->size, ti->alignment); + var->type_info = ti; + + if (flecs_script_eval_expr(v, &node->expr, &var->value)) { + return -1; + } + } else { + /* We don't know the type yet, so we can't create a storage for it yet. + * Run the expression first to deduce the type. */ + ecs_value_t value = {0}; + if (flecs_script_eval_expr(v, &node->expr, &value)) { + flecs_script_eval_error(v, node, + "failed to evaluate expression for const variable '%s'", + node->name); + return -1; } - if (!is_ident) { - if (c == '\\') { - pos ++; - } else if (c == '<') { - int32_t indent = 0; - do { - c = *pos; + ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); + ti = ecs_get_type_info(v->world, value.type); + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); - if (c == '<') { - indent ++; - } else if (c == '>') { - indent --; - } else if (!c) { - ecs_parser_error(parser->script->pub.name, - parser->script->pub.code, - pos - parser->script->pub.code, - "< without > in identifier"); - return NULL; - } + var->value.ptr = flecs_stack_calloc( + &v->r->stack, ti->size, ti->alignment); + type = var->value.type = value.type; + var->type_info = ti; - if (outpos) { - *outpos = c; - outpos ++; - } - pos ++; + if (ti->hooks.ctor) { + ti->hooks.ctor(var->value.ptr, 1, ti); + } - if (!indent) { - break; - } - } while (true); + ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); + ecs_value_fini_w_type_info(v->world, ti, value.ptr); + flecs_free(&v->world->allocator, ti->size, value.ptr); + } - if (outpos && parser) { - *outpos = '\0'; - parser->token_cur = outpos + 1; - } - return pos; - } else if (c == '>') { - ecs_parser_error(parser->script->pub.name, - parser->script->pub.code, - pos - parser->script->pub.code, - "> without < in identifier"); - return NULL; - } else { - if (outpos && parser) { - *outpos = '\0'; - parser->token_cur = outpos + 1; - } - return pos; - } - } + ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, + &template->prop_defaults, ecs_script_var_t); + value->value.ptr = flecs_calloc_w_dbg_info( + &v->base.script->allocator, ti->size, ti->name); + value->value.type = type; + value->type_info = ti; + ecs_value_copy_w_type_info( + v->world, ti, value->value.ptr, var->value.ptr); - if (outpos) { - *outpos = *pos; - outpos ++; - } + ecs_entity_t mbr = ecs_entity(v->world, { + .name = node->name, + .parent = template->entity + }); - pos ++; - } while (true); + ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); + + return 0; } -// Number token static static -bool flecs_script_is_number( - const char *c) +int flecs_script_template_eval( + ecs_script_eval_visitor_t *v, + ecs_script_node_t *node) { - return isdigit(c[0]) || ((c[0] == '-') && isdigit(c[1])); + switch(node->kind) { + case EcsAstTag: + case EcsAstComponent: + case EcsAstVarComponent: + case EcsAstEntity: + case EcsAstScope: + case EcsAstDefaultComponent: + case EcsAstWithVar: + case EcsAstWithTag: + case EcsAstWithComponent: + case EcsAstUsing: + case EcsAstModule: + case EcsAstAnnotation: + case EcsAstConst: + case EcsAstPairScope: + case EcsAstWith: + case EcsAstIf: + case EcsAstFor: + break; + case EcsAstTemplate: + flecs_script_eval_error(v, node, "nested templates are not allowed"); + return -1; + case EcsAstProp: + return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); + } + + return flecs_script_check_node(v, node); } static -const char* flecs_script_number( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) +int flecs_script_template_preprocess( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template) { - out->kind = EcsTokNumber; - out->value = parser->token_cur; - - bool dot_parsed = false; - bool e_parsed = false; - - ecs_assert(flecs_script_is_number(pos), ECS_INTERNAL_ERROR, NULL); - char *outpos = parser->token_cur; - - if (pos[0] == '-') { - outpos[0] = pos[0]; - pos ++; - outpos ++; - } - - do { - char c = pos[0]; - bool valid_number = false; + ecs_visit_action_t prev_visit = v->base.visit; + v->template = template; - if (c == '.') { - if (!dot_parsed && !e_parsed) { - if (isdigit(pos[1])) { - dot_parsed = true; - valid_number = true; - } - } - } else if (c == 'e') { - if (!e_parsed) { - if (isdigit(pos[1])) { - e_parsed = true; - valid_number = true; - } - } - } else if (isdigit(c)) { - valid_number = true; + /* Dummy entity node for instance */ + ecs_script_entity_t instance_node = { + .node = { + .kind = EcsAstEntity, + .pos = template->node->node.pos } + }; - if (!valid_number) { - *outpos = '\0'; - parser->token_cur = outpos + 1; - break; - } + v->entity = &instance_node; - outpos[0] = pos[0]; - outpos ++; - pos ++; - } while (true); + v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; + v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); + ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); + var->value.type = ecs_id(ecs_entity_t); + int result = flecs_script_check_scope(v, template->node->scope); + v->vars = ecs_script_vars_pop(v->vars); + v->base.visit = prev_visit; + v->template = NULL; + v->entity = NULL; - return pos; + return result; } static -const char* flecs_script_skip_string( - ecs_script_parser_t *parser, - const char *pos, - char delim) +int flecs_script_template_hoist_using( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template) { - char ch; - for (; (ch = pos[0]) && pos[0] != delim; pos ++) { - if (ch == '\\') { - pos ++; - } + ecs_allocator_t *a = &v->base.script->allocator; + if (v->module) { + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; } - if (!pos[0]) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "unterminated string"); - return NULL; + int i, count = ecs_vec_count(&v->r->using); + for (i = 0; i < count; i ++) { + ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = + ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } - return pos; + return 0; } static -const char* flecs_script_string( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) +int flecs_script_template_hoist_vars( + ecs_script_eval_visitor_t *v, + ecs_script_template_t *template, + ecs_script_vars_t *vars) { - const char *end = flecs_script_skip_string(parser, pos + 1, '"'); - if (!end) { - return NULL; + int32_t i, count = ecs_vec_count(&vars->vars); + ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); + for (i = 0; i < count; i ++) { + ecs_script_var_t *src = &src_vars[i]; + if (ecs_script_vars_lookup(template->vars, src->name)) { + /* If variable is masked, don't declare it twice */ + continue; + } + ecs_script_var_t *dst = ecs_script_vars_define_id( + template->vars, src->name, src->value.type); + ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_value_copy(v->world, + src->value.type, dst->value.ptr, src->value.ptr); } - ecs_assert(end[0] == '"', ECS_INTERNAL_ERROR, NULL); - end --; + if (vars->parent) { + flecs_script_template_hoist_vars(v, template, vars->parent); + } - int32_t len = flecs_ito(int32_t, end - pos); - ecs_os_memcpy(parser->token_cur, pos + 1, len); - parser->token_cur[len] = '\0'; + return 0; +} - out->kind = EcsTokString; - out->value = parser->token_cur; - parser->token_cur += len + 1; - return end + 2; +ecs_script_template_t* flecs_script_template_init( + ecs_script_impl_t *script) +{ + ecs_allocator_t *a = &script->allocator; + ecs_script_template_t *result = flecs_alloc_t(a, ecs_script_template_t); + ecs_vec_init_t(NULL, &result->prop_defaults, ecs_script_var_t, 0); + ecs_vec_init_t(NULL, &result->using_, ecs_entity_t, 0); + result->vars = ecs_script_vars_init(script->pub.world); + return result; } -static -const char* flecs_script_multiline_string( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out) +void flecs_script_template_fini( + ecs_script_impl_t *script, + ecs_script_template_t *template) { - char ch; - const char *end = pos + 1; - while ((ch = end[0]) && (ch != '`')) { - if (ch == '\\' && end[1] == '`') { - end ++; - } - end ++; - } + ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_allocator_t *a = &script->allocator; - if (ch != '`') { - return NULL; + int32_t i, count = ecs_vec_count(&template->prop_defaults); + ecs_script_var_t *values = ecs_vec_first(&template->prop_defaults); + for (i = 0; i < count; i ++) { + ecs_script_var_t *value = &values[i]; + const ecs_type_info_t *ti = value->type_info; + if (ti->hooks.dtor) { + ti->hooks.dtor(value->value.ptr, 1, ti); + } + flecs_free(a, ti->size, value->value.ptr); } - end --; - - int32_t len = flecs_ito(int32_t, end - pos); - ecs_os_memcpy(parser->token_cur, pos + 1, len); - parser->token_cur[len] = '\0'; + ecs_vec_fini_t(a, &template->prop_defaults, ecs_script_var_t); - out->kind = EcsTokString; - out->value = parser->token_cur; - parser->token_cur += len + 1; - return end + 2; + ecs_vec_fini_t(a, &template->using_, ecs_entity_t); + ecs_script_vars_fini(template->vars); + flecs_free_t(a, ecs_script_template_t, template); } -const char* flecs_script_until( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out, - char until) +/* Create new template */ +int flecs_script_eval_template( + ecs_script_eval_visitor_t *v, + ecs_script_template_node_t *node) { - parser->pos = pos; - - const char *start = pos = flecs_scan_whitespace(parser, pos); - char ch; - - for (; (ch = pos[0]); pos ++) { - if (ch == until) { - break; - } + ecs_entity_t template_entity = flecs_script_create_entity(v, node->name); + if (!template_entity) { + return -1; } - if (!pos[0]) { - if (until == '\0') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected end of script"); - return NULL; - } else - if (until == '\n') { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected newline"); - return NULL; - } else { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "expected '%c'", until); - return NULL; - } - } + ecs_script_template_t *template = flecs_script_template_init(v->base.script); + template->entity = template_entity; + template->node = node; - int32_t len = flecs_ito(int32_t, pos - start); - ecs_os_memcpy(parser->token_cur, start, len); - out->value = parser->token_cur; - parser->token_cur += len; + /* Variables are always presented to a template in a well defined order, so + * we don't need dynamic variable binding. */ + bool old_dynamic_variable_binding = v->dynamic_variable_binding; + v->dynamic_variable_binding = false; - while (isspace(parser->token_cur[-1])) { - parser->token_cur --; + if (flecs_script_template_preprocess(v, template)) { + goto error; } - parser->token_cur[0] = '\0'; - parser->token_cur ++; - - return pos; -} + if (flecs_script_template_hoist_using(v, template)) { + goto error; + } -const char* flecs_script_token( - ecs_script_parser_t *parser, - const char *pos, - ecs_script_token_t *out, - bool is_lookahead) -{ - parser->pos = pos; + if (flecs_script_template_hoist_vars(v, template, v->vars)) { + goto error; + } - // Skip whitespace and comments - pos = flecs_scan_whitespace_and_comment(parser, pos); + v->dynamic_variable_binding = old_dynamic_variable_binding; - out->kind = EcsTokUnknown; - out->value = NULL; + /* If template has no props, give template dummy size so we can register + * hooks for it. */ + if (!ecs_has(v->world, template_entity, EcsComponent)) { + ecs_set(v->world, template_entity, EcsComponent, {1, 1}); + } - if (pos[0] == '\0') { - out->kind = EcsTokEnd; - return pos; - } else if (pos[0] == '\n') { - out->kind = EcsTokNewline; - - // Parse multiple newlines/whitespaces as a single token - pos = flecs_scan_whitespace_and_comment(parser, pos + 1); - if (pos[0] == '\n') { - pos ++; - } - return pos; + template->type_info = ecs_get_type_info(v->world, template_entity); - } else if (flecs_script_is_number(pos)) { - return flecs_script_number(parser, pos, out); + ecs_add_pair(v->world, template_entity, EcsOnInstantiate, EcsOverride); - OperatorMultiChar ("+=", EcsTokAddAssign) - OperatorMultiChar ("*=", EcsTokMulAssign) - Operator (":", EcsTokColon) - Operator ("{", EcsTokScopeOpen) - Operator ("}", EcsTokScopeClose) - Operator ("(", EcsTokParenOpen) - Operator (")", EcsTokParenClose) - Operator ("[", EcsTokBracketOpen) - Operator ("]", EcsTokBracketClose) - Operator ("@", EcsTokAnnotation) - Operator (",", EcsTokComma) - Operator (";", EcsTokSemiColon) - Operator ("+", EcsTokAdd) - Operator ("-", EcsTokSub) - Operator ("*", EcsTokMul) - Operator ("/", EcsTokDiv) - Operator ("%%", EcsTokMod) - Operator ("?", EcsTokOptional) - - OperatorMultiChar ("..", EcsTokRange) - Operator (".", EcsTokMember) + EcsScript *script = ecs_ensure(v->world, template_entity, EcsScript); + if (script->script) { + if (script->template_) { + flecs_script_template_fini( + flecs_script_impl(script->script), script->template_); + } + ecs_script_free(script->script); + } - OperatorMultiChar ("==", EcsTokEq) - OperatorMultiChar ("!=", EcsTokNeq) - OperatorMultiChar ("<<", EcsTokShiftLeft) - OperatorMultiChar (">>", EcsTokShiftRight) - OperatorMultiChar (">=", EcsTokGtEq) - OperatorMultiChar ("<=", EcsTokLtEq) - - OperatorMultiChar ("&&", EcsTokAnd) - OperatorMultiChar ("||", EcsTokOr) - OperatorMultiChar ("~=", EcsTokMatch) + script->script = &v->base.script->pub; + script->template_ = template; + ecs_modified(v->world, template_entity, EcsScript); - Operator ("!", EcsTokNot) - Operator ("=", EcsTokAssign) - Operator ("&", EcsTokBitwiseAnd) - Operator ("|", EcsTokBitwiseOr) - Operator (">", EcsTokGt) - Operator ("<", EcsTokLt) + ecs_set_hooks_id(v->world, template_entity, &(ecs_type_hooks_t) { + .ctor = flecs_script_template_ctor, + .on_set = flecs_script_template_on_set, + .ctx = v->world + }); - Keyword ("with", EcsTokKeywordWith) - Keyword ("using", EcsTokKeywordUsing) - Keyword ("template", EcsTokKeywordTemplate) - Keyword ("prop", EcsTokKeywordProp) - Keyword ("const", EcsTokKeywordConst) - Keyword ("if", EcsTokKeywordIf) - Keyword ("else", EcsTokKeywordElse) - Keyword ("for", EcsTokKeywordFor) - Keyword ("in", EcsTokKeywordIn) - Keyword ("match", EcsTokKeywordMatch) - Keyword ("module", EcsTokKeywordModule) + /* Keep script alive for as long as template is alive */ + v->base.script->refcount ++; - } else if (pos[0] == '"') { - return flecs_script_string(parser, pos, out); + return 0; +error: + flecs_script_template_fini(v->base.script, template); + return -1; +} - } else if (pos[0] == '`') { - return flecs_script_multiline_string(parser, pos, out); +void flecs_script_template_import( + ecs_world_t *world) +{ + ECS_COMPONENT_DEFINE(world, EcsScriptTemplateSetEvent); + ECS_TAG_DEFINE(world, EcsScriptTemplate); - } else if (flecs_script_is_identifier(pos[0])) { - return flecs_script_identifier(parser, pos, out); - } + ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); - if (!is_lookahead) { - ecs_parser_error(parser->script->pub.name, parser->script->pub.code, - pos - parser->script->pub.code, "unknown token '%c'", pos[0]); - } + ecs_set_hooks(world, EcsScriptTemplateSetEvent, { + .ctor = flecs_default_ctor, + .move = ecs_move(EcsScriptTemplateSetEvent), + .dtor = ecs_dtor(EcsScriptTemplateSetEvent), + .flags = ECS_TYPE_HOOK_COPY_ILLEGAL + }); - return NULL; + ecs_observer(world, { + .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), + .query.terms = {{ .id = EcsAny }}, + .events = { ecs_id(EcsScriptTemplateSetEvent) }, + .callback = flecs_on_template_set_event + }); } #endif @@ -61536,16 +62806,16 @@ const ecs_type_info_t* flecs_script_get_type_info( void *node, ecs_id_t id) { - ecs_id_record_t *idr = flecs_id_record_ensure(v->world, id); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_ensure(v->world, id); + if (!cdr) { goto error; } - if (!idr->type_info) { + if (!cdr->type_info) { goto error; } - return idr->type_info; + return cdr->type_info; error: { char *idstr = ecs_id_str(v->world, id); @@ -64677,7 +65947,7 @@ bool ecs_pipeline_stats_get( /* 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) { + if (flecs_component_get_table(pq->idr_inactive, it.table) != NULL) { continue; } active_sys_count += it.count; @@ -64711,7 +65981,7 @@ bool ecs_pipeline_stats_get( 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) { + if (flecs_component_get_table(pq->idr_inactive, it.table) != NULL) { continue; } @@ -66648,10 +67918,10 @@ int flecs_query_compile( } /* If variables have been written, but this term has no known variables, - * first try to resolve terms that have known variables. This can - * significantly reduce the search space. - * Only perform this optimization after at least one variable has been - * written to, as all terms are unknown otherwise. */ + * first try to resolve terms that have known variables. This can + * significantly reduce the search space. + * Only perform this optimization after at least one variable has been + * written to, as all terms are unknown otherwise. */ if (can_reorder && ctx.written && flecs_query_term_is_unknown(query, term, &ctx)) { @@ -66814,8 +68084,6 @@ ecs_var_id_t flecs_query_find_var_id( if (query->pub.flags & EcsQueryHasTableThisVar) { return 0; } else { - printf("VARNONE\n"); - flecs_dump_backtrace(stdout); return EcsVarNone; } } @@ -67633,7 +68901,7 @@ bool flecs_query_select_all( term->oper == EcsNotFrom || pred_match) { ecs_query_op_t match_any = {0}; - match_any.kind = EcsAnd; + match_any.kind = EcsQueryAll; match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); match_any.src = op->src; @@ -67970,8 +69238,8 @@ int flecs_query_compile_term( for (i = 0; i < count; i ++) { ecs_id_t ti_id = ti_ids[i]; - ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { + ecs_component_record_t *cdr = flecs_components_get(world, ti_id); + if (!(cdr->flags & EcsIdOnInstantiateDontInherit)) { break; } } @@ -68115,7 +69383,7 @@ int flecs_query_compile_term( } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { /* Lookup variables ($var.child_name) are always written */ if (!src_is_lookup) { - op.kind = EcsQueryOnlyAny; /* Uses Any (_) id record */ + op.kind = EcsQueryAll; /* Uses Any (_) component record */ } } @@ -68759,7 +70027,7 @@ ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( cache->query->world, qt); qm->table = table; - qm->trs = flecs_balloc(&cache->allocators.trs); + qm->trs = flecs_balloc(&cache->allocators.pointers); /* Insert match to iteration list if table is not empty */ flecs_query_cache_insert_table_node(cache, qm); @@ -68811,14 +70079,26 @@ void flecs_query_cache_set_table_match( if (i != field_count) { if (qm->sources == cache->sources || !qm->sources) { - qm->sources = flecs_balloc(&cache->allocators.sources); + qm->sources = flecs_balloc(&cache->allocators.ids); } ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); + if (!qm->tables) { + qm->tables = flecs_balloc(&cache->allocators.pointers); + } + for (i = 0; i < field_count; i ++) { + if (it->trs[i]) { + qm->tables[i] = it->trs[i]->hdr.table; + } + } } else { if (qm->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, qm->sources); + flecs_bfree(&cache->allocators.ids, qm->sources); qm->sources = cache->sources; } + if (qm->tables) { + flecs_bfree(&cache->allocators.pointers, qm->tables); + qm->tables = NULL; + } } qm->set_fields = it->set_fields; @@ -68838,7 +70118,8 @@ ecs_query_cache_table_t* flecs_query_cache_table_insert( qt->table_id = 0; } - ecs_table_cache_insert(&cache->cache, table, &qt->hdr); + ecs_table_cache_insert(&cache->cache, table, + ECS_CONST_CAST(ecs_table_cache_hdr_t*, &qt->hdr)); return qt; } @@ -69025,14 +70306,18 @@ void flecs_query_cache_table_match_free( ecs_world_t *world = cache->query->world; for (cur = first; cur != NULL; cur = next) { - flecs_bfree(&cache->allocators.trs, ECS_CONST_CAST(void*, cur->trs)); + flecs_bfree(&cache->allocators.pointers, ECS_CONST_CAST(void*, cur->trs)); if (cur->ids != cache->query->ids) { flecs_bfree(&cache->allocators.ids, cur->ids); } if (cur->sources != cache->sources) { - flecs_bfree(&cache->allocators.sources, cur->sources); + flecs_bfree(&cache->allocators.ids, cur->sources); + } + + if (cur->tables) { + flecs_bfree(&cache->allocators.pointers, cur->tables); } if (cur->monitor) { @@ -69066,7 +70351,8 @@ void flecs_query_cache_unmatch_table( elem = ecs_table_cache_get(&cache->cache, table); } if (elem) { - ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); + ecs_table_cache_remove(&cache->cache, elem->table_id, + ECS_CONST_CAST(ecs_table_cache_hdr_t*, &elem->hdr)); flecs_query_cache_table_free(cache, elem); } } @@ -69353,12 +70639,10 @@ void flecs_query_cache_allocators_init( { int32_t field_count = cache->query->field_count; if (field_count) { - flecs_ballocator_init(&cache->allocators.trs, + flecs_ballocator_init(&cache->allocators.pointers, field_count * ECS_SIZEOF(ecs_table_record_t*)); flecs_ballocator_init(&cache->allocators.ids, field_count * ECS_SIZEOF(ecs_id_t)); - flecs_ballocator_init(&cache->allocators.sources, - field_count * ECS_SIZEOF(ecs_entity_t)); flecs_ballocator_init(&cache->allocators.monitors, (1 + field_count) * ECS_SIZEOF(int32_t)); } @@ -69370,9 +70654,8 @@ void flecs_query_cache_allocators_fini( { int32_t field_count = cache->query->field_count; if (field_count) { - flecs_ballocator_fini(&cache->allocators.trs); + flecs_ballocator_fini(&cache->allocators.pointers); flecs_ballocator_fini(&cache->allocators.ids); - flecs_ballocator_fini(&cache->allocators.sources); flecs_ballocator_fini(&cache->allocators.monitors); } } @@ -69417,7 +70700,7 @@ void flecs_query_cache_fini( ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); if (cache->query->term_count) { - flecs_bfree(&cache->allocators.sources, cache->sources); + flecs_bfree(&cache->allocators.ids, cache->sources); } flecs_query_cache_allocators_fini(cache); @@ -69496,7 +70779,7 @@ ecs_query_cache_t* flecs_query_cache_init( * This reduces the amount of memory used by the cache, and improves CPU * cache locality during iteration when doing source checks. */ if (result->query->term_count) { - result->sources = flecs_bcalloc(&result->allocators.sources); + result->sources = flecs_bcalloc(&result->allocators.ids); } if (q->term_count) { @@ -69665,29 +70948,28 @@ void flecs_query_update_node_up_trs( ecs_termset_t fields = node->up_fields & node->set_fields; if (fields) { const ecs_query_impl_t *impl = ctx->query; - const ecs_query_t *q = &impl->pub; ecs_query_cache_t *cache = impl->cache; + const ecs_query_t *q = cache->query; + int32_t f, field_count = q->field_count; int8_t *field_map = cache->field_map; - int32_t i, field_count = q->field_count; - for (i = 0; i < field_count; i ++) { - if (!(fields & (1llu << i))) { + for (f = 0; f < field_count; f ++) { + if (!(fields & (1llu << f))) { continue; } - ecs_entity_t src = node->sources[i]; + ecs_entity_t src = node->sources[f]; if (src) { - const ecs_table_record_t *tr = node->trs[i]; ecs_record_t *r = flecs_entities_get(ctx->world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); - if (r->table != tr->hdr.table) { - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - ecs_assert(idr->id == q->ids[field_map ? field_map[i] : i], - ECS_INTERNAL_ERROR, NULL); - tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); - if (field_map) { - ctx->it->trs[field_map[i]] = tr; - } + if (r->table != node->tables[f]) { + ecs_component_record_t *cdr = flecs_components_get( + ctx->world, q->ids[f]); + const ecs_table_record_t *tr = node->trs[f] = + flecs_component_get_table(cdr, r->table); + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ctx->it->trs[field_map ? field_map[f] : f] = tr; + node->tables[f] = r->table; } } } @@ -70133,7 +71415,7 @@ void flecs_query_cache_sort_tables( bool tables_sorted = false; - ecs_id_record_t *idr = flecs_id_record_get(world, order_by); + ecs_component_record_t *cdr = flecs_components_get(world, order_by); ecs_table_cache_iter_t it; ecs_query_cache_table_t *qt; flecs_table_cache_all_iter(&cache->cache, &it); @@ -70166,8 +71448,8 @@ void flecs_query_cache_sort_tables( if (dirty) { column = -1; - const ecs_table_record_t *tr = flecs_id_record_get_table( - idr, table); + const ecs_table_record_t *tr = flecs_component_get_table( + cdr, table); if (tr) { column = tr->column; } @@ -70323,12 +71605,12 @@ bool flecs_query_get_fixed_monitor( continue; /* Entity is empty, nothing to track */ } - ecs_id_record_t *idr = flecs_id_record_get(world, term->id); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(world, term->id); + if (!cdr) { continue; /* If id doesn't exist, entity can't have it */ } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, r->table); if (!tr) { continue; /* Entity doesn't have the component */ } @@ -70793,24 +72075,24 @@ bool flecs_query_select_w_id( ecs_flags32_t filter_mask) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; + ecs_component_record_t *cdr = op_ctx->cdr; + const 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) { + if (!cdr || cdr->id != id) { + cdr = op_ctx->cdr = flecs_components_get(ctx->world, id); + if (!cdr) { return false; } } if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + if (!flecs_table_cache_all_iter(&cdr->cache, &op_ctx->it)) { return false; } } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + if (!flecs_table_cache_iter(&cdr->cache, &op_ctx->it)) { return false; } } @@ -70828,10 +72110,10 @@ bool flecs_query_select_w_id( table = tr->hdr.table; flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); } else { - tr = (ecs_table_record_t*)op_ctx->it.cur; + tr = (const ecs_table_record_t*)op_ctx->it.cur; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); table = tr->hdr.table; - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); + op_ctx->column = flecs_query_next_column(table, cdr->id, op_ctx->column); op_ctx->remaining --; } @@ -70862,8 +72144,8 @@ bool flecs_query_with( const ecs_query_run_ctx_t *ctx) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); - ecs_id_record_t *idr = op_ctx->idr; - ecs_table_record_t *tr; + ecs_component_record_t *cdr = op_ctx->cdr; + const ecs_table_record_t *tr; ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); if (!table) { @@ -70872,14 +72154,14 @@ bool flecs_query_with( if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); - if (!idr || idr->id != id) { - idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); - if (!idr) { + if (!cdr || cdr->id != id) { + cdr = op_ctx->cdr = flecs_components_get(ctx->world, id); + if (!cdr) { return false; } } - tr = flecs_id_record_get_table(idr, table); + tr = flecs_component_get_table(cdr, table); if (!tr) { return false; } @@ -70895,7 +72177,7 @@ bool flecs_query_with( return false; } - op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); + op_ctx->column = flecs_query_next_column(table, cdr->id, op_ctx->column); ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } @@ -70903,6 +72185,69 @@ bool flecs_query_with( return true; } +static +bool flecs_query_all( + const ecs_query_op_t *op, + bool redo, + const ecs_query_run_ctx_t *ctx) +{ + int8_t field_index = op->field_index; + ecs_iter_t *it = ctx->it; + + uint64_t written = ctx->written[ctx->op_index]; + if (written & (1ull << op->src.var)) { + if (field_index != -1) { + it->ids[field_index] = EcsWildcard; + } + return !redo; + } else { + ecs_query_all_ctx_t *op_ctx = flecs_op_ctx(ctx, all); + ecs_world_t *world = ctx->world; + ecs_sparse_t *tables = &world->store.tables; + bool match_empty = ctx->query->pub.flags & EcsQueryMatchEmptyTables; + ecs_table_t *table; + + if (!redo) { + op_ctx->cur = 0; + op_ctx->dummy_tr.column = -1; + op_ctx->dummy_tr.index = -1; + op_ctx->dummy_tr.count = 0; + op_ctx->dummy_tr.hdr.cache = NULL; + if (field_index != -1) { + it->ids[field_index] = EcsWildcard; + it->trs[field_index] = &op_ctx->dummy_tr; + } + table = &world->store.root; + } else if (op_ctx->cur < flecs_sparse_count(tables)) { + table = flecs_sparse_get_dense_t( + tables, ecs_table_t, op_ctx->cur); + } else { + return false; + } + +repeat: + op_ctx->cur ++; + + if (match_empty || ecs_table_count(table)) { + if (!flecs_query_table_filter(table, op->other, + (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) + { + op_ctx->dummy_tr.hdr.table = table; + flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); + return true; + } + } + + if (op_ctx->cur < flecs_sparse_count(tables)) { + table = flecs_sparse_get_dense_t( + tables, ecs_table_t, op_ctx->cur); + goto repeat; + } + + return false; + } +} + static bool flecs_query_and( const ecs_query_op_t *op, @@ -70930,20 +72275,20 @@ bool flecs_query_select_id( 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) { + ecs_component_record_t *cdr = op_ctx->cdr; + if (!cdr || cdr->id != id) { + cdr = op_ctx->cdr = flecs_components_get(ctx->world, id); + if (!cdr) { return false; } } if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { + if (!flecs_table_cache_all_iter(&cdr->cache, &op_ctx->it)) { return false; } } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + if (!flecs_table_cache_iter(&cdr->cache, &op_ctx->it)) { return false; } } @@ -70981,20 +72326,18 @@ bool flecs_query_with_id( ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); - if (!table) { - return false; - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); 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) { + ecs_component_record_t *cdr = op_ctx->cdr; + if (!cdr || cdr->id != id) { + cdr = op_ctx->cdr = flecs_components_get(ctx->world, id); + if (!cdr) { return false; } } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); if (!tr) { return false; } @@ -71067,26 +72410,11 @@ bool flecs_query_and_any( ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); } - ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; + ctx->it->trs[field] = (const ecs_table_record_t*)op_ctx->it.cur; return result; } -static -bool flecs_query_only_any( - const ecs_query_op_t *op, - bool redo, - const ecs_query_run_ctx_t *ctx) -{ - uint64_t written = ctx->written[ctx->op_index]; - if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { - return flecs_query_and_any(op, redo, ctx); - } else { - return flecs_query_select_w_id(op, redo, ctx, EcsAny, - (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); - } -} - static bool flecs_query_triv( const ecs_query_op_t *op, @@ -71148,8 +72476,8 @@ int32_t flecs_query_next_inheritable_id( { int32_t i; for (i = index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); - if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { + ecs_component_record_t *cdr = flecs_components_get(world, type->array[i]); + if (!(cdr->flags & EcsIdOnInstantiateDontInherit)) { return i; } } @@ -71175,11 +72503,9 @@ bool flecs_query_x_from( op_ctx->type_id = type_id; ecs_assert(ecs_is_alive(world, type_id), ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, type_id); - ecs_table_t *table; - if (!r || !(table = r->table)) { - /* Nothing to match */ - return false; - } + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_t *table = r->table; + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); /* Find first id to test against. Skip ids with DontInherit flag. */ type = op_ctx->type = &table->type; @@ -71245,16 +72571,16 @@ bool flecs_query_x_from( * components from the type list */ if (op_ctx->cur_id_index != op_ctx->first_id_index) { for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(world, ids[i]); + if (!cdr) { continue; } - if (idr->flags & EcsIdOnInstantiateDontInherit) { + if (cdr->flags & EcsIdOnInstantiateDontInherit) { continue; } - if (flecs_id_record_get_table(idr, src_table) != NULL) { + if (flecs_component_get_table(cdr, src_table) != NULL) { /* Already matched */ break; } @@ -71268,8 +72594,8 @@ bool flecs_query_x_from( if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { for (i = id_index; i < type->count; i ++) { - ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(world, ids[i]); + if (!cdr) { if (oper == EcsAndFrom) { return false; } else { @@ -71277,11 +72603,11 @@ bool flecs_query_x_from( } } - if (idr->flags & EcsIdOnInstantiateDontInherit) { + if (cdr->flags & EcsIdOnInstantiateDontInherit) { continue; } - if (flecs_id_record_get_table(idr, src_table) == NULL) { + if (flecs_component_get_table(cdr, src_table) == NULL) { if (oper == EcsAndFrom) { break; /* Must have all ids */ } @@ -71344,11 +72670,11 @@ bool flecs_query_ids( return false; } - ecs_id_record_t *cur; + ecs_component_record_t *cur; ecs_id_t id = flecs_query_op_get_id(op, ctx); { - cur = flecs_id_record_get(ctx->world, id); + cur = flecs_components_get(ctx->world, id); if (!cur || !cur->cache.tables.count) { return false; } @@ -71373,7 +72699,7 @@ bool flecs_query_idsright( const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; + ecs_component_record_t *cur; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); @@ -71385,7 +72711,7 @@ bool flecs_query_idsright( return true; } - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + cur = op_ctx->cur = flecs_components_get(ctx->world, id); if (!cur) { return false; } @@ -71395,14 +72721,21 @@ bool flecs_query_idsright( } } +next: do { - cur = op_ctx->cur = op_ctx->cur->first.next; + cur = op_ctx->cur = flecs_component_first_next(op_ctx->cur); } while (cur && !cur->cache.tables.count); /* Skip empty ids */ if (!cur) { return false; } + if (cur->id == ecs_pair(EcsChildOf, 0)) { + /* Skip the special (ChildOf, 0) entry for root entities, as 0 is + * not a valid target and could be matched by (ChildOf, *) */ + goto next; + } + flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { @@ -71423,7 +72756,7 @@ bool flecs_query_idsleft( const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); - ecs_id_record_t *cur; + ecs_component_record_t *cur; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); @@ -71435,7 +72768,7 @@ bool flecs_query_idsleft( return true; } - cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); + cur = op_ctx->cur = flecs_components_get(ctx->world, id); if (!cur) { return false; } @@ -71446,7 +72779,7 @@ bool flecs_query_idsleft( } do { - cur = op_ctx->cur = op_ctx->cur->second.next; + cur = op_ctx->cur = flecs_component_second_next(op_ctx->cur); } while (cur && !cur->cache.tables.count); /* Skip empty ids */ if (!cur) { @@ -71477,9 +72810,7 @@ bool flecs_query_each( ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); ecs_table_t *table = range.table; - if (!table) { - return false; - } + ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!redo) { if (!ecs_table_count(table)) { @@ -72108,12 +73439,12 @@ bool flecs_query_dispatch( ecs_query_run_ctx_t *ctx) { switch(op->kind) { + case EcsQueryAll: return flecs_query_all(op, redo, ctx); case EcsQueryAnd: return flecs_query_and(op, redo, ctx); case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); case EcsQueryCache: return flecs_query_cache(op, redo, ctx); case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); - case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); case EcsQueryUp: return flecs_query_up(op, redo, ctx); case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); case EcsQueryWith: return flecs_query_with(op, redo, ctx); @@ -72488,6 +73819,56 @@ void flecs_query_iter_fini( qit->query = NULL; } +static +void flecs_query_validate_final_fields( + const ecs_query_t *q) +{ + (void)q; +#ifdef FLECS_DEBUG + ecs_query_impl_t *impl = flecs_query_impl(q); + ecs_world_t *world = q->real_world; + + if (!impl->final_terms) { + return; + } + + if (!world->idr_isa_wildcard) { + return; + } + + int32_t i, count = impl->pub.term_count; + ecs_term_t *terms = impl->pub.terms; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &terms[i]; + ecs_id_t id = term->id; + + if (!(impl->final_terms & (1ull << i))) { + continue; + } + + if (ECS_IS_PAIR(id)) { + id = ECS_PAIR_FIRST(id); + } + + if (ecs_id_is_wildcard(id)) { + continue; + } + + if (flecs_components_get(world, ecs_pair(EcsIsA, id))) { + char *query_str = ecs_query_str(q); + char *id_str = ecs_id_str(world, id); + ecs_abort(ECS_INVALID_OPERATION, + "query '%s' was created before '(IsA, %s)' relationship, " + "create query after adding inheritance relationship " + "or add 'Inheritable' trait to '%s'", + query_str, id_str, id_str); + ecs_os_free(id_str); + ecs_os_free(query_str); + } + } +#endif +} + ecs_iter_t flecs_query_iter( const ecs_world_t *world, const ecs_query_t *q) @@ -72495,6 +73876,8 @@ ecs_iter_t flecs_query_iter( ecs_iter_t it = {0}; ecs_query_iter_t *qit = &it.priv_.iter.query; ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); + + flecs_query_validate_final_fields(q); flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); @@ -73558,7 +74941,7 @@ bool flecs_query_trav_unknown_src_up_fixed_second( 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 */ + trav_ctx->and.cdr = el->cdr; /* prevents lookup by select */ if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { @@ -73707,13 +75090,13 @@ bool flecs_query_union_with_wildcard( } op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { + op_ctx->cdr = flecs_components_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->cdr) { return neq; } if (neq) { - if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { + if (flecs_component_get_table(op_ctx->cdr, table) != NULL) { /* If table has (R, Union) none match !(R, _) */ return false; } else { @@ -73746,7 +75129,7 @@ bool flecs_query_union_with_wildcard( ecs_entity_t e = ecs_table_entities(range.table) [range.offset + op_ctx->row]; - ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); + ecs_entity_t tgt = flecs_switch_get(op_ctx->cdr->sparse, (uint32_t)e); if (!tgt) { op_ctx->row ++; goto next_row; @@ -73786,8 +75169,8 @@ bool flecs_query_union_with_tgt( } op_ctx->range = range; - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { + op_ctx->cdr = flecs_components_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->cdr) { return false; } @@ -73810,7 +75193,7 @@ bool flecs_query_union_with_tgt( ecs_entity_t e = ecs_table_entities(range.table) [range.offset + op_ctx->row]; - ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); + ecs_entity_t e_tgt = flecs_switch_get(op_ctx->cdr->sparse, (uint32_t)e); bool match = e_tgt == tgt; if (neq) { match = !match; @@ -73863,14 +75246,14 @@ bool flecs_query_union_select_tgt( int8_t field_index = op->field_index; if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { + op_ctx->cdr = flecs_components_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->cdr) { return false; } - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); + op_ctx->cur = flecs_switch_first(op_ctx->cdr->sparse, tgt); } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + op_ctx->cur = flecs_switch_next(op_ctx->cdr->sparse, (uint32_t)op_ctx->cur); } if (!op_ctx->cur) { @@ -73899,12 +75282,12 @@ bool flecs_query_union_select_wildcard( int8_t field_index = op->field_index; if (!redo) { - op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - if (!op_ctx->idr) { + op_ctx->cdr = flecs_components_get(ctx->world, ecs_pair(rel, EcsUnion)); + if (!op_ctx->cdr) { return false; } - op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); + op_ctx->tgt_iter = flecs_switch_targets(op_ctx->cdr->sparse); op_ctx->tgt = 0; } @@ -73920,9 +75303,9 @@ bool flecs_query_union_select_wildcard( } if (!op_ctx->cur) { - op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); + op_ctx->cur = flecs_switch_first(op_ctx->cdr->sparse, op_ctx->tgt); } else { - op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); + op_ctx->cur = flecs_switch_next(op_ctx->cdr->sparse, (uint32_t)op_ctx->cur); } if (!op_ctx->cur) { @@ -73986,18 +75369,18 @@ void flecs_query_union_set_shared( const ecs_query_run_ctx_t *ctx) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); - ecs_id_record_t *idr = op_ctx->idr_with; - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_component_record_t *cdr = op_ctx->idr_with; + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); - idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); - ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_entity_t rel = ECS_PAIR_FIRST(cdr->id); + cdr = flecs_components_get(ctx->world, ecs_pair(rel, EcsUnion)); + ecs_assert(cdr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(cdr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); int8_t field_index = op->field_index; ecs_iter_t *it = ctx->it; ecs_entity_t src = it->sources[field_index]; - ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); + ecs_entity_t tgt = flecs_switch_get(cdr->sparse, (uint32_t)src); it->ids[field_index] = ecs_pair(rel, tgt); } @@ -74187,13 +75570,13 @@ bool flecs_query_up_select( op_ctx->trav = q->terms[op->term_index].trav; - /* Reuse id record from previous iteration if possible*/ + /* Reuse component record from previous iteration if possible*/ if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, + op_ctx->idr_trav = flecs_components_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } - /* If id record is not found, or if it doesn't have any tables, revert to + /* If component record is not found, or if it doesn't have any tables, revert to * iterating owned components (no traversal) */ if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)) @@ -74218,10 +75601,10 @@ bool flecs_query_up_select( /* Get component id to match */ op_ctx->with = flecs_query_op_get_id(op, ctx); - /* Get id record for component to match */ - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + /* Get component record for component to match */ + op_ctx->idr_with = flecs_components_get(ctx->world, op_ctx->with); if (!op_ctx->idr_with) { - /* If id record does not exist, there can't be any results */ + /* If component record does not exist, there can't be any results */ return false; } @@ -74322,7 +75705,7 @@ bool flecs_query_up_with( op_ctx->trav = q->terms[op->term_index].trav; if (!op_ctx->idr_trav) { - op_ctx->idr_trav = flecs_id_record_get(ctx->world, + op_ctx->idr_trav = flecs_components_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } @@ -74337,9 +75720,9 @@ bool flecs_query_up_with( if (!redo) { op_ctx->trav = q->terms[op->term_index].trav; op_ctx->with = flecs_query_op_get_id(op, ctx); - op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); + op_ctx->idr_with = flecs_components_get(ctx->world, op_ctx->with); - /* If id record for component doesn't exist, there are no matches */ + /* If component record for component doesn't exist, there are no matches */ if (!op_ctx->idr_with) { return false; } @@ -74785,6 +76168,7 @@ ecs_id_t flecs_query_it_set_id( { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); return it->ids[field_index] = table->type.array[column]; } @@ -74794,9 +76178,8 @@ void flecs_query_set_match( int32_t column, const ecs_query_run_ctx_t *ctx) { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); int32_t field_index = op->field_index; - if (field_index == -1) { + if (field_index == -1 || column == -1) { return; } @@ -74852,19 +76235,19 @@ void flecs_query_build_down_cache( ecs_entity_t trav, ecs_entity_t entity) { - ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(world, ecs_pair(trav, entity)); + if (!cdr) { return; } ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); elem->entity = entity; - elem->idr = idr; + elem->cdr = cdr; ecs_table_cache_iter_t it; - if (flecs_table_cache_iter(&idr->cache, &it)) { - ecs_table_record_t *tr; + if (flecs_table_cache_iter(&cdr->cache, &it)) { + const 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; @@ -74911,12 +76294,12 @@ void flecs_query_build_up_cache( el->entity = second; el->tr = &table->_->records[i]; - el->idr = NULL; + el->cdr = 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); + const ecs_table_record_t *r_tr = flecs_component_get_table( + cache->cdr, r->table); if (!r_tr) { return; } @@ -74959,17 +76342,17 @@ void flecs_query_get_trav_up_cache( ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); - ecs_id_record_t *idr = cache->idr; - if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { - idr = cache->idr = flecs_id_record_get(world, + ecs_component_record_t *cdr = cache->cdr; + if (!cdr || cdr->id != ecs_pair(trav, EcsWildcard)) { + cdr = cache->cdr = flecs_components_get(world, ecs_pair(trav, EcsWildcard)); - if (!idr) { + if (!cdr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } } - ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); if (!tr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; @@ -74999,7 +76382,7 @@ void flecs_trav_entity_down_isa( ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_with, bool self, bool empty); @@ -75010,8 +76393,8 @@ ecs_trav_down_t* flecs_trav_entity_down( ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, - ecs_id_record_t *idr_trav, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_trav, + ecs_component_record_t *idr_with, bool self, bool empty); @@ -75039,7 +76422,7 @@ ecs_trav_down_t* flecs_trav_table_down( ecs_trav_down_t *dst, ecs_entity_t trav, const ecs_table_t *table, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_with, bool self, bool empty) { @@ -75062,7 +76445,7 @@ ecs_trav_down_t* flecs_trav_table_down( uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_component_record_t *idr_trav = flecs_components_get(world, ecs_pair(trav, entity)); if (!idr_trav) { continue; @@ -75084,7 +76467,7 @@ void flecs_trav_entity_down_isa( ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_with, bool self, bool empty) { @@ -75092,7 +76475,7 @@ void flecs_trav_entity_down_isa( return; } - ecs_id_record_t *idr_isa = flecs_id_record_get( + ecs_component_record_t *idr_isa = flecs_components_get( world, ecs_pair(EcsIsA, entity)); if (!idr_isa) { return; @@ -75100,7 +76483,7 @@ void flecs_trav_entity_down_isa( ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr_isa->cache, &it)) { - ecs_table_record_t *tr; + 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) { @@ -75123,7 +76506,7 @@ void flecs_trav_entity_down_isa( uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { - ecs_id_record_t *idr_trav = flecs_id_record_get(world, + ecs_component_record_t *idr_trav = flecs_components_get(world, ecs_pair(trav, e)); if (idr_trav) { flecs_trav_entity_down(world, a, cache, dst, trav, @@ -75145,8 +76528,8 @@ ecs_trav_down_t* flecs_trav_entity_down( ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, - ecs_id_record_t *idr_trav, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_trav, + ecs_component_record_t *idr_with, bool self, bool empty) { @@ -75171,7 +76554,7 @@ ecs_trav_down_t* flecs_trav_entity_down( ecs_table_t *table = tr->hdr.table; bool leaf = false; - if (flecs_id_record_get_table(idr_with, table) != NULL) { + if (flecs_component_get_table(idr_with, table) != NULL) { if (self) { continue; } @@ -75222,7 +76605,7 @@ ecs_trav_down_t* flecs_query_get_down_cache( ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t e, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_with, bool self, bool empty) { @@ -75238,7 +76621,7 @@ ecs_trav_down_t* flecs_query_get_down_cache( return result; } - ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); + ecs_component_record_t *idr_trav = flecs_components_get(world, ecs_pair(trav, e)); if (!idr_trav) { if (trav != EcsIsA) { flecs_trav_entity_down_isa( @@ -75301,7 +76684,7 @@ static int32_t flecs_trav_type_search( ecs_trav_up_t *up, const ecs_table_t *table, - ecs_id_record_t *idr_with, + ecs_component_record_t *idr_with, ecs_type_t *type) { ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); @@ -75346,8 +76729,8 @@ ecs_trav_up_t* flecs_trav_table_up( ecs_entity_t src, ecs_id_t with, ecs_id_t rel, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) + ecs_component_record_t *idr_with, + ecs_component_record_t *idr_trav) { ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); if (up->ready) { @@ -75437,8 +76820,8 @@ ecs_trav_up_t* flecs_query_get_up_cache( ecs_table_t *table, ecs_id_t with, ecs_entity_t trav, - ecs_id_record_t *idr_with, - ecs_id_record_t *idr_trav) + ecs_component_record_t *idr_with, + ecs_component_record_t *idr_trav) { if (cache->with && cache->with != with) { flecs_query_up_cache_fini(cache); @@ -75508,17 +76891,17 @@ bool flecs_query_trivial_search_init( ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); op_ctx->start_from = t; - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(ctx->world, query->ids[t]); + if (!cdr) { return false; } if (query->flags & EcsQueryMatchEmptyTables) { - if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ + if (!flecs_table_cache_all_iter(&cdr->cache, &op_ctx->it)){ return false; } } else { - if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { + if (!flecs_table_cache_iter(&cdr->cache, &op_ctx->it)) { return false; } } @@ -75571,13 +76954,13 @@ bool flecs_query_trivial_search( } const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(ctx->world, term->id); + if (!cdr) { break; } - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); + const ecs_table_record_t *tr_with = flecs_component_get_table( + cdr, table); if (!tr_with) { break; } @@ -75626,13 +77009,13 @@ bool flecs_query_is_trivial_search( } for (t = 1; t < term_count; t ++) { - ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(ctx->world, ids[t]); + if (!cdr) { return false; } - const ecs_table_record_t *tr_with = flecs_id_record_get_table( - idr, table); + const ecs_table_record_t *tr_with = flecs_component_get_table( + cdr, table); if (!tr_with) { goto next; } @@ -75673,12 +77056,12 @@ bool flecs_query_trivial_test( } const ecs_term_t *term = &terms[t]; - ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); - if (!idr) { + ecs_component_record_t *cdr = flecs_components_get(q->world, term->id); + if (!cdr) { return false; } - const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + const ecs_table_record_t *tr = flecs_component_get_table(cdr, table); if (!tr) { return false; } @@ -75708,7 +77091,7 @@ bool flecs_query_trivial_test( static void* flecs_expr_ast_new_( - ecs_script_parser_t *parser, + ecs_parser_t *parser, ecs_size_t size, ecs_expr_node_kind_t kind) { @@ -75751,7 +77134,7 @@ ecs_expr_variable_t* flecs_expr_variable_from( } ecs_expr_value_node_t* flecs_expr_bool( - ecs_script_parser_t *parser, + ecs_parser_t *parser, bool value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( @@ -75763,7 +77146,7 @@ ecs_expr_value_node_t* flecs_expr_bool( } ecs_expr_value_node_t* flecs_expr_int( - ecs_script_parser_t *parser, + ecs_parser_t *parser, int64_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( @@ -75775,7 +77158,7 @@ ecs_expr_value_node_t* flecs_expr_int( } ecs_expr_value_node_t* flecs_expr_uint( - ecs_script_parser_t *parser, + ecs_parser_t *parser, uint64_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( @@ -75787,7 +77170,7 @@ ecs_expr_value_node_t* flecs_expr_uint( } ecs_expr_value_node_t* flecs_expr_float( - ecs_script_parser_t *parser, + ecs_parser_t *parser, double value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( @@ -75799,7 +77182,7 @@ ecs_expr_value_node_t* flecs_expr_float( } ecs_expr_value_node_t* flecs_expr_string( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *value) { char *str = ECS_CONST_CAST(char*, value); @@ -75817,7 +77200,7 @@ ecs_expr_value_node_t* flecs_expr_string( } ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *value) { ecs_expr_interpolated_string_t *result = flecs_expr_ast_new( @@ -75834,7 +77217,7 @@ ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( } ecs_expr_value_node_t* flecs_expr_entity( - ecs_script_parser_t *parser, + ecs_parser_t *parser, ecs_entity_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( @@ -75846,7 +77229,7 @@ ecs_expr_value_node_t* flecs_expr_entity( } ecs_expr_initializer_t* flecs_expr_initializer( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_initializer_t *result = flecs_expr_ast_new( parser, ecs_expr_initializer_t, EcsExprInitializer); @@ -75856,7 +77239,7 @@ ecs_expr_initializer_t* flecs_expr_initializer( } ecs_expr_identifier_t* flecs_expr_identifier( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *value) { ecs_expr_identifier_t *result = flecs_expr_ast_new( @@ -75866,7 +77249,7 @@ ecs_expr_identifier_t* flecs_expr_identifier( } ecs_expr_variable_t* flecs_expr_variable( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *value) { ecs_expr_variable_t *result = flecs_expr_ast_new( @@ -75877,7 +77260,7 @@ ecs_expr_variable_t* flecs_expr_variable( } ecs_expr_unary_t* flecs_expr_unary( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_unary_t *result = flecs_expr_ast_new( parser, ecs_expr_unary_t, EcsExprUnary); @@ -75885,7 +77268,7 @@ ecs_expr_unary_t* flecs_expr_unary( } ecs_expr_binary_t* flecs_expr_binary( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_binary_t *result = flecs_expr_ast_new( parser, ecs_expr_binary_t, EcsExprBinary); @@ -75893,7 +77276,7 @@ ecs_expr_binary_t* flecs_expr_binary( } ecs_expr_member_t* flecs_expr_member( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_member_t *result = flecs_expr_ast_new( parser, ecs_expr_member_t, EcsExprMember); @@ -75901,7 +77284,7 @@ ecs_expr_member_t* flecs_expr_member( } ecs_expr_function_t* flecs_expr_function( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_function_t *result = flecs_expr_ast_new( parser, ecs_expr_function_t, EcsExprFunction); @@ -75909,7 +77292,7 @@ ecs_expr_function_t* flecs_expr_function( } ecs_expr_element_t* flecs_expr_element( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_element_t *result = flecs_expr_ast_new( parser, ecs_expr_element_t, EcsExprElement); @@ -75917,7 +77300,7 @@ ecs_expr_element_t* flecs_expr_element( } ecs_expr_match_t* flecs_expr_match( - ecs_script_parser_t *parser) + ecs_parser_t *parser) { ecs_expr_match_t *result = flecs_expr_ast_new( parser, ecs_expr_match_t, EcsExprMatch); @@ -76061,15 +77444,15 @@ static int flecs_expr_precedence[] = { static const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, + ecs_tokenizer_t *tokenizer, + ecs_token_kind_t left_oper, ecs_expr_node_t **out); static void flecs_script_parser_expr_free( - ecs_script_parser_t *parser, + ecs_parser_t *parser, ecs_expr_node_t *node) { flecs_expr_visit_free(&parser->script->pub, node); @@ -76077,8 +77460,8 @@ void flecs_script_parser_expr_free( static bool flecs_has_precedence( - ecs_script_token_kind_t first, - ecs_script_token_kind_t second) + ecs_token_kind_t first, + ecs_token_kind_t second) { if (!flecs_expr_precedence[first]) { return false; @@ -76098,7 +77481,7 @@ ecs_entity_t flecs_script_default_lookup( static const char* flecs_script_parse_match_elems( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, ecs_expr_match_t *node) { @@ -76156,7 +77539,7 @@ const char* flecs_script_parse_match_elems( } const char* flecs_script_parse_initializer( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, char until, ecs_expr_initializer_t **node_out) @@ -76250,7 +77633,7 @@ const char* flecs_script_parse_initializer( static const char* flecs_script_parse_collection_initializer( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, ecs_expr_initializer_t **node_out) { @@ -76301,10 +77684,10 @@ const char* flecs_script_parse_collection_initializer( static const char* flecs_script_parse_rhs( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, + ecs_tokenizer_t *tokenizer, + ecs_token_kind_t left_oper, ecs_expr_node_t **out) { const char *last_pos = pos; @@ -76343,7 +77726,7 @@ const char* flecs_script_parse_rhs( case EcsTokMember: case EcsTokParenOpen: { - ecs_script_token_kind_t oper = lookahead_token.kind; + ecs_token_kind_t oper = lookahead_token.kind; /* Only consume more tokens if operator has precedence */ if (flecs_has_precedence(left_oper, oper)) { @@ -76438,10 +77821,10 @@ const char* flecs_script_parse_rhs( static const char* flecs_script_parse_lhs( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, - ecs_script_tokenizer_t *tokenizer, - ecs_script_token_kind_t left_oper, + ecs_tokenizer_t *tokenizer, + ecs_token_kind_t left_oper, ecs_expr_node_t **out) { TokenFramePush(); @@ -76453,14 +77836,24 @@ const char* flecs_script_parse_lhs( const char *expr = Token(0); if (strchr(expr, '.') || strchr(expr, 'e')) { *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); - } else if (expr[0] == '-') { + break; + } + int base = 10; + if (expr[0] == '0' && (expr[1] == 'x' || expr[1] == 'X')) { + base = 16; + expr += 2; + } else if (expr[0] == '0' && (expr[1] == 'b' || expr[1] == 'B')) { + base = 2; + expr += 2; + } + if (expr[0] == '-') { char *end; *out = (ecs_expr_node_t*)flecs_expr_int(parser, - strtoll(expr, &end, 10)); + strtoll(expr, &end, base)); } else { char *end; *out = (ecs_expr_node_t*)flecs_expr_uint(parser, - strtoull(expr, &end, 10)); + strtoull(expr, &end, base)); } break; } @@ -76625,9 +78018,9 @@ const char* flecs_script_parse_lhs( } const char* flecs_script_parse_expr( - ecs_script_parser_t *parser, + ecs_parser_t *parser, const char *pos, - ecs_script_token_kind_t left_oper, + ecs_token_kind_t left_oper, ecs_expr_node_t **out) { ParserBegin; @@ -76656,7 +78049,9 @@ ecs_script_t* ecs_expr_parse( ecs_script_t *script = flecs_script_new(world); ecs_script_impl_t *impl = flecs_script_impl(script); - ecs_script_parser_t parser = { + ecs_parser_t parser = { + .name = script->name, + .code = script->code, .script = impl, .scope = impl->root, .significant_newline = false @@ -76724,7 +78119,6 @@ int ecs_expr_eval( return -1; } -FLECS_API const char* ecs_expr_run( ecs_world_t *world, const char *expr, @@ -76762,7 +78156,6 @@ const char* ecs_expr_run( return NULL; } -FLECS_API char* ecs_script_string_interpolate( ecs_world_t *world, const char *str, @@ -76982,7 +78375,7 @@ int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, - ecs_script_token_kind_t operator) + ecs_token_kind_t operator) { (void)script; switch(operator) { @@ -77161,7 +78554,7 @@ int flecs_value_binary( const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, - ecs_script_token_kind_t operator) + ecs_token_kind_t operator) { (void)script; @@ -79335,7 +80728,7 @@ int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_unary_t *node) { - ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); + ecs_strbuf_appendstr(v->buf, flecs_token_str(node->operator)); if (flecs_expr_node_to_str(v, node->expr)) { goto error; @@ -79391,7 +80784,7 @@ int flecs_expr_binary_to_str( ecs_strbuf_appendlit(v->buf, " "); - ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); + ecs_strbuf_appendstr(v->buf, flecs_token_str(node->operator)); ecs_strbuf_appendlit(v->buf, " "); @@ -79901,7 +81294,7 @@ static bool flecs_expr_oper_valid_for_type( ecs_world_t *world, ecs_entity_t type, - ecs_script_token_kind_t op) + ecs_token_kind_t op) { switch(op) { case EcsTokAdd: @@ -79979,7 +81372,7 @@ int flecs_expr_type_for_operator( ecs_entity_t node_type, ecs_expr_node_t *left, ecs_expr_node_t *right, - ecs_script_token_kind_t operator, + ecs_token_kind_t operator, ecs_entity_t *operand_type, ecs_entity_t *result_type) { @@ -80287,7 +81680,7 @@ int flecs_expr_interpolated_string_visit_type( if (ch == '$') { char *var_name = ++ ptr; - ptr = ECS_CONST_CAST(char*, flecs_script_identifier( + ptr = ECS_CONST_CAST(char*, flecs_tokenizer_identifier( NULL, ptr, NULL)); if (!ptr) { goto error; @@ -80309,7 +81702,7 @@ int flecs_expr_interpolated_string_visit_type( } else { ecs_script_impl_t *impl = flecs_script_impl(script); - ecs_script_parser_t parser = { + ecs_parser_t parser = { .script = impl, .scope = impl->root, .significant_newline = false, @@ -80659,7 +82052,7 @@ int flecs_expr_binary_visit_type( { char *type_str = ecs_get_path(script->world, result_type); flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", - flecs_script_token_str(node->operator), type_str); + flecs_token_str(node->operator), type_str); ecs_os_free(type_str); goto error; } diff --git a/libs/flecs/flecs.h b/libs/flecs/flecs.h index 867de1c..1343677 100644 --- a/libs/flecs/flecs.h +++ b/libs/flecs/flecs.h @@ -35,7 +35,7 @@ /* Flecs version macros */ #define FLECS_VERSION_MAJOR 4 /**< Flecs major version. */ #define FLECS_VERSION_MINOR 0 /**< Flecs minor version. */ -#define FLECS_VERSION_PATCH 4 /**< Flecs patch version. */ +#define FLECS_VERSION_PATCH 5 /**< Flecs patch version. */ /** Flecs version. */ #define FLECS_VERSION FLECS_VERSION_IMPL(\ @@ -110,6 +110,16 @@ #endif #endif +/** @def FLECS_DEBUG_INFO + * Adds additional debug information to internal data structures. Necessary when + * using natvis. + */ +#ifdef FLECS_DEBUG +#ifndef FLECS_DEBUG_INFO +#define FLECS_DEBUG_INFO +#endif +#endif + /* Tip: if you see weird behavior that you think might be a bug, make sure to * test with the FLECS_DEBUG or FLECS_SANITIZE flags enabled. There's a good * chance that this gives you more information about the issue! */ @@ -136,6 +146,14 @@ */ // #define FLECS_KEEP_ASSERT +/** @def FLECS_DEFAULT_TO_UNCACHED_QUERIES + * When set, this will cause queries with the EcsQueryCacheDefault policy + * to default to EcsQueryCacheNone. This can reduce the memory footprint of + * applications at the cost of performance. Queries that use features which + * require caching such as group_by and order_by will still use caching. + */ +// #define FLECS_DEFAULT_TO_UNCACHED_QUERIES + /** @def FLECS_CPP_NO_AUTO_REGISTRATION * When set, the C++ API will require that components are registered before they * are used. This is useful in multithreaded applications, where components need @@ -146,6 +164,21 @@ */ // #define FLECS_CPP_NO_AUTO_REGISTRATION +/** @def FLECS_CPP_NO_ENUM_REFLECTION + * When set, the C++ API will not attempt to discover and register enum + * constants for registered enum components. This will cause C++ APIs that + * accept enum constants to not work. + * Disabling this feature can significantly improve compile times and reduces + * the RAM footprint of an application. + */ +// #define FLECS_CPP_NO_ENUM_REFLECTION + +/** @def FLECS_NO_ALWAYS_INLINE + * When set, this will prevent functions from being annotated with always_inline + * which can improve performance at the cost of increased binary footprint. + */ +// #define FLECS_NO_ALWAYS_INLINE + /** @def FLECS_CUSTOM_BUILD * This macro lets you customize which addons to build flecs with. * Without any addons Flecs is just a minimal ECS storage, but addons add @@ -181,7 +214,7 @@ // #define FLECS_C /**< C API convenience macros, always enabled */ #define FLECS_CPP /**< C++ API */ #define FLECS_DOC /**< Document entities & components */ -// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ +// #define FLECS_JOURNAL /**< Journaling addon */ #define FLECS_JSON /**< Parsing JSON to/from component values */ #define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ #define FLECS_LOG /**< When enabled ECS provides more detailed logs */ @@ -189,9 +222,11 @@ #define FLECS_METRICS /**< Expose component data as statistics */ #define FLECS_MODULE /**< Module support */ #define FLECS_OS_API_IMPL /**< Default implementation for OS API */ -// #define FLECS_PERF_TRACE /**< Enable performance tracing (disabled by default) */ +// #define FLECS_PERF_TRACE /**< Enable performance tracing */ #define FLECS_PIPELINE /**< Pipeline support */ #define FLECS_REST /**< REST API for querying application data */ +#define FLECS_PARSER /**< Utilities for script and query DSL parsers */ +#define FLECS_QUERY_DSL /**< Flecs query DSL parser */ #define FLECS_SCRIPT /**< Flecs entity notation language */ // #define FLECS_SCRIPT_MATH /**< Math functions for flecs script (may require linking with libm) */ #define FLECS_SYSTEM /**< System support */ @@ -228,7 +263,7 @@ /** @def FLECS_HI_ID_RECORD_ID * This constant can be used to balance between performance and memory - * utilization. The constant is used to determine the size of the id record + * utilization. The constant is used to determine the size of the component record * lookup array. Id values that fall outside of this range use a regular map * lookup, which is slower but more memory efficient. */ @@ -366,7 +401,7 @@ extern "C" { //////////////////////////////////////////////////////////////////////////////// -//// Id flags (used by ecs_id_record_t::flags) +//// Id flags (used by ecs_component_record_t::flags) //////////////////////////////////////////////////////////////////////////////// #define EcsIdOnDeleteRemove (1u << 0) @@ -395,20 +430,18 @@ extern "C" { #define EcsIdWith (1u << 12) #define EcsIdCanToggle (1u << 13) #define EcsIdIsTransitive (1u << 14) +#define EcsIdIsInheritable (1u << 15) #define EcsIdHasOnAdd (1u << 16) /* Same values as table flags */ #define EcsIdHasOnRemove (1u << 17) #define EcsIdHasOnSet (1u << 18) -#define EcsIdHasOnTableFill (1u << 19) -#define EcsIdHasOnTableEmpty (1u << 20) #define EcsIdHasOnTableCreate (1u << 21) #define EcsIdHasOnTableDelete (1u << 22) #define EcsIdIsSparse (1u << 23) #define EcsIdIsUnion (1u << 24) #define EcsIdEventMask\ (EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet|\ - EcsIdHasOnTableFill|EcsIdHasOnTableEmpty|EcsIdHasOnTableCreate|\ - EcsIdHasOnTableDelete|EcsIdIsSparse|EcsIdIsUnion) + EcsIdHasOnTableCreate|EcsIdHasOnTableDelete|EcsIdIsSparse|EcsIdIsUnion) #define EcsIdMarkedForDelete (1u << 30) @@ -573,6 +606,8 @@ extern "C" { #if defined(_WIN32) || defined(_MSC_VER) #define ECS_TARGET_WINDOWS +#elif defined(__COSMOCC__) +#define ECS_TARGET_POSIX #elif defined(__ANDROID__) #define ECS_TARGET_ANDROID #define ECS_TARGET_POSIX @@ -854,6 +889,18 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define ECS_ALIGNOF(T) ((int64_t)&((struct { char c; T d; } *)0)->d) #endif +#ifndef FLECS_NO_ALWAYS_INLINE + #if defined(ECS_TARGET_CLANG) || defined(ECS_TARGET_GCC) + #define FLECS_ALWAYS_INLINE __attribute__((always_inline)) + #elif defined(ECS_TARGET_MSVC) + #define FLECS_ALWAYS_INLINE + #else + #define FLECS_ALWAYS_INLINE + #endif +#else + #define FLECS_ALWAYS_INLINE +#endif + #ifndef FLECS_NO_DEPRECATED_WARNINGS #if defined(ECS_TARGET_GNU) #define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) @@ -982,14 +1029,6 @@ typedef struct ecs_allocator_t ecs_allocator_t; #endif -//////////////////////////////////////////////////////////////////////////////// -//// Actions that drive iteration -//////////////////////////////////////////////////////////////////////////////// - -#define EcsIterNextYield (0) /* Move to next table, yield current */ -#define EcsIterYield (-1) /* Stay on current table, yield */ -#define EcsIterNext (1) /* Move to next table, don't yield */ - //////////////////////////////////////////////////////////////////////////////// //// Convenience macros for ctor, dtor, move and copy //////////////////////////////////////////////////////////////////////////////// @@ -1464,6 +1503,10 @@ FLECS_DBG_API const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse); +FLECS_DBG_API +void flecs_sparse_shrink( + ecs_sparse_t *sparse); + /* Publicly exposed APIs * These APIs are not part of the public API and as a result may change without * notice (though they haven't changed in a long time). */ @@ -1539,7 +1582,6 @@ typedef struct ecs_block_allocator_chunk_header_t { typedef struct ecs_block_allocator_t { ecs_block_allocator_chunk_header_t *head; ecs_block_allocator_block_t *block_head; - ecs_block_allocator_block_t *block_tail; int32_t chunk_size; int32_t data_size; int32_t chunks_per_block; @@ -1749,12 +1791,10 @@ typedef struct ecs_bucket_t { } ecs_bucket_t; struct ecs_map_t { - uint8_t bucket_shift; - bool shared_allocator; ecs_bucket_t *buckets; int32_t bucket_count; - int32_t count; - struct ecs_block_allocator_t *entry_allocator; + unsigned count : 26; + unsigned bucket_shift : 6; struct ecs_allocator_t *allocator; }; @@ -3160,7 +3200,7 @@ typedef struct ecs_type_info_t ecs_type_info_t; 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; +typedef struct ecs_component_record_t ecs_component_record_t; /** A poly object. * A poly (short for polymorph) object is an object that has a variable list of @@ -3194,31 +3234,7 @@ typedef struct ecs_header_t { ecs_mixins_t *mixins; /**< Table with offsets to (optional) mixins */ } ecs_header_t; -/** Record for entity index */ -struct ecs_record_t { - ecs_id_record_t *idr; /**< Id record to (*, entity) for target entities */ - ecs_table_t *table; /**< Identifies a type (and table) in world */ - uint32_t row; /**< Table row of the entity */ - int32_t dense; /**< Index in dense array of entity index */ -}; - -/** Header for table cache elements. */ -typedef struct ecs_table_cache_hdr_t { - struct ecs_table_cache_t *cache; /**< Table cache of element. Of type ecs_id_record_t* for component index elements. */ - ecs_table_t *table; /**< Table associated with element. */ - struct ecs_table_cache_hdr_t *prev, *next; /**< Next/previous elements for id in table cache. */ -} ecs_table_cache_hdr_t; - -/** Metadata describing where a component id is stored in a table. - * This type is used as element type for the component index table cache. One - * record exists per table/component in the table. Only records for wildcard ids - * can have a count > 1. */ -typedef struct ecs_table_record_t { - ecs_table_cache_hdr_t hdr; /**< Table cache header */ - int16_t index; /**< First type index where id occurs in table */ - int16_t count; /**< Number of times id occurs in table */ - int16_t column; /**< First column index where id occurs */ -} ecs_table_record_t; +typedef struct ecs_table_record_t ecs_table_record_t; /** @} */ @@ -3348,6 +3364,18 @@ typedef void (*ecs_move_t)( int32_t count, const ecs_type_info_t *type_info); +/** Compare hook to compare component instances */ +typedef int (*ecs_cmp_t)( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info); + +/** Equals operator hook */ +typedef bool (*ecs_equals_t)( + const void *a_ptr, + const void *b_ptr, + const ecs_type_info_t *type_info); + /** Destructor function for poly objects. */ typedef void (*flecs_poly_dtor_t)( ecs_poly_t *poly); @@ -3566,38 +3594,44 @@ struct ecs_observer_t { */ /* Flags that can be used to check which hooks a type has set */ -#define ECS_TYPE_HOOK_CTOR (1 << 0) -#define ECS_TYPE_HOOK_DTOR (1 << 1) -#define ECS_TYPE_HOOK_COPY (1 << 2) -#define ECS_TYPE_HOOK_MOVE (1 << 3) -#define ECS_TYPE_HOOK_COPY_CTOR (1 << 4) -#define ECS_TYPE_HOOK_MOVE_CTOR (1 << 5) -#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR (1 << 6) -#define ECS_TYPE_HOOK_MOVE_DTOR (1 << 7) +#define ECS_TYPE_HOOK_CTOR ECS_CAST(ecs_flags32_t, 1 << 0) +#define ECS_TYPE_HOOK_DTOR ECS_CAST(ecs_flags32_t, 1 << 1) +#define ECS_TYPE_HOOK_COPY ECS_CAST(ecs_flags32_t, 1 << 2) +#define ECS_TYPE_HOOK_MOVE ECS_CAST(ecs_flags32_t, 1 << 3) +#define ECS_TYPE_HOOK_COPY_CTOR ECS_CAST(ecs_flags32_t, 1 << 4) +#define ECS_TYPE_HOOK_MOVE_CTOR ECS_CAST(ecs_flags32_t, 1 << 5) +#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR ECS_CAST(ecs_flags32_t, 1 << 6) +#define ECS_TYPE_HOOK_MOVE_DTOR ECS_CAST(ecs_flags32_t, 1 << 7) +#define ECS_TYPE_HOOK_CMP ECS_CAST(ecs_flags32_t, 1 << 8) +#define ECS_TYPE_HOOK_EQUALS ECS_CAST(ecs_flags32_t, 1 << 9) + /* Flags that can be used to set/check which hooks of a type are invalid */ -#define ECS_TYPE_HOOK_CTOR_ILLEGAL (1 << 8) -#define ECS_TYPE_HOOK_DTOR_ILLEGAL (1 << 9) -#define ECS_TYPE_HOOK_COPY_ILLEGAL (1 << 10) -#define ECS_TYPE_HOOK_MOVE_ILLEGAL (1 << 11) -#define ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL (1 << 12) -#define ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL (1 << 13) -#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL (1 << 14) -#define ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL (1 << 15) +#define ECS_TYPE_HOOK_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 10) +#define ECS_TYPE_HOOK_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 12) +#define ECS_TYPE_HOOK_COPY_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 13) +#define ECS_TYPE_HOOK_MOVE_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 14) +#define ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 15) +#define ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 16) +#define ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 17) +#define ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 18) +#define ECS_TYPE_HOOK_CMP_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 19) +#define ECS_TYPE_HOOK_EQUALS_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 20) + /* All valid hook flags */ #define ECS_TYPE_HOOKS (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_DTOR|\ ECS_TYPE_HOOK_COPY|ECS_TYPE_HOOK_MOVE|ECS_TYPE_HOOK_COPY_CTOR|\ ECS_TYPE_HOOK_MOVE_CTOR|ECS_TYPE_HOOK_CTOR_MOVE_DTOR|\ - ECS_TYPE_HOOK_MOVE_DTOR) + ECS_TYPE_HOOK_MOVE_DTOR|ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS) /* All invalid hook flags */ #define ECS_TYPE_HOOKS_ILLEGAL (ECS_TYPE_HOOK_CTOR_ILLEGAL|\ ECS_TYPE_HOOK_DTOR_ILLEGAL|ECS_TYPE_HOOK_COPY_ILLEGAL|\ ECS_TYPE_HOOK_MOVE_ILLEGAL|ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL|\ ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL|ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL|\ - ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) - + ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL|ECS_TYPE_HOOK_CMP_ILLEGAL|\ + ECS_TYPE_HOOK_EQUALS_ILLEGAL) struct ecs_type_hooks_t { ecs_xtor_t ctor; /**< ctor */ ecs_xtor_t dtor; /**< dtor */ @@ -3622,11 +3656,18 @@ struct ecs_type_hooks_t { * not set explicitly it will be derived from other callbacks. */ ecs_move_t move_dtor; + /** Compare hook */ + ecs_cmp_t cmp; + + /** Equals hook */ + ecs_equals_t equals; + /** Hook flags. * Indicates which hooks are set for the type, and which hooks are illegal. * When an ILLEGAL flag is set when calling ecs_set_hooks() a hook callback * will be set that panics when called. */ ecs_flags32_t flags; + /** Callback that is invoked when an instance of a component is added. This * callback is invoked before triggers are invoked. */ @@ -3735,8 +3776,9 @@ struct ecs_ref_t { ecs_entity_t entity; /* Entity */ ecs_entity_t id; /* Component id */ uint64_t table_id; /* Table id for detecting ABA issues */ - struct ecs_table_record_t *tr; /* Table record for component */ + uint32_t table_version; /* Table version for detecting changes */ ecs_record_t *record; /* Entity index record */ + void *ptr; /* Cached component pointer */ }; @@ -3755,7 +3797,7 @@ typedef struct ecs_worker_iter_t { /* Convenience struct to iterate table array for id */ typedef struct ecs_table_cache_iter_t { - struct ecs_table_cache_hdr_t *cur, *next; + const struct ecs_table_cache_hdr_t *cur, *next; bool iter_fill; bool iter_empty; } ecs_table_cache_iter_t; @@ -3824,6 +3866,13 @@ typedef struct ecs_iter_private_t { ecs_iter_cache_t cache; /* Inline arrays to reduce allocations */ } ecs_iter_private_t; +/* Data structures that store the command queue */ +typedef struct ecs_commands_t { + ecs_vec_t queue; + ecs_stack_t stack; /* Temp memory used by deferred commands */ + ecs_sparse_t entries; /* - command batching */ +} ecs_commands_t; + #ifdef __cplusplus } #endif @@ -3836,8 +3885,9 @@ typedef struct ecs_iter_private_t { * @brief Support functions and constants. * * Supporting types and functions that need to be exposed either in support of - * the public API or for unit tests, but that may change between minor / patch - * releases. + * the public API or for unit tests. + * + * Operations may change without warning. */ #ifndef FLECS_API_SUPPORT_H @@ -3860,27 +3910,45 @@ extern "C" { /** Maximum length of a parser token (used by parser-related addons) */ #define ECS_MAX_TOKEN_SIZE (256) +/** Convert a C module name into a path. + * This operation converts a PascalCase name to a path, for example MyFooModule + * into my.foo.module. + * + * @param c_name The C module name + * @return The path. +*/ FLECS_API char* flecs_module_path_from_c( const char *c_name); -bool flecs_identifier_is_0( - const char *id); - -/* Constructor that zeromem's a component value */ +/** Constructor that zeromem's a component value. + * + * @param ptr Pointer to the value. + * @param count Number of elements to construct. + * @param type_info Type info for the component. + */ FLECS_API void flecs_default_ctor( void *ptr, int32_t count, - const ecs_type_info_t *ctx); + const ecs_type_info_t *type_info); -/* Create allocated string from format */ +/** Create allocated string from format. + * + * @param fmt The format string. + * @param args Format arguments. + * @return The formatted string. + */ FLECS_DBG_API char* flecs_vasprintf( const char *fmt, va_list args); -/* Create allocated string from format */ +/** Create allocated string from format. + * + * @param fmt The format string. + * @return The formatted string. + */ FLECS_API char* flecs_asprintf( const char *fmt, @@ -3971,51 +4039,137 @@ FLECS_API char* flecs_to_snake_case( const char *str); +/* Suspend/resume readonly state. To fully support implicit registration of + * components, it should be possible to register components while the world is + * in readonly mode. It is not uncommon that a component is used first from + * within a system, which are often ran while in readonly mode. + * + * Suspending readonly mode is only allowed when the world is not multithreaded. + * When a world is multithreaded, it is not safe to (even temporarily) leave + * readonly mode, so a multithreaded application should always explicitly + * register components in advance. + * + * These operations also suspend deferred mode. + * + * Functions are public to support language bindings. + */ +typedef struct ecs_suspend_readonly_state_t { + bool is_readonly; + bool is_deferred; + bool cmd_flushing; + int32_t defer_count; + ecs_entity_t scope; + ecs_entity_t with; + ecs_commands_t cmd_stack[2]; + ecs_commands_t *cmd; + ecs_stage_t *stage; +} ecs_suspend_readonly_state_t; + +FLECS_API +ecs_world_t* flecs_suspend_readonly( + const ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +FLECS_API +void flecs_resume_readonly( + ecs_world_t *world, + ecs_suspend_readonly_state_t *state); + +/** Number of observed entities in table. + * Operation is public to support test cases. + * + * @param table The table. + */ FLECS_DBG_API int32_t flecs_table_observed_count( const ecs_table_t *table); +/** Print backtrace to specified stream. + * + * @param stream The stream to use for printing the backtrace. + */ FLECS_DBG_API void flecs_dump_backtrace( void *stream); +/** Increase refcount of poly object. + * + * @param poly The poly object. + * @return The refcount after incrementing. + */ FLECS_API int32_t flecs_poly_claim_( ecs_poly_t *poly); +/** Decrease refcount of poly object. + * + * @param poly The poly object. + * @return The refcount after decrementing. + */ FLECS_API int32_t flecs_poly_release_( ecs_poly_t *poly); +#define flecs_poly_claim(poly) \ + flecs_poly_claim_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) + +#define flecs_poly_release(poly) \ + flecs_poly_release_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) + +/** Return refcount of poly object. + * + * @param poly The poly object. + * @return Refcount of the poly object. + */ FLECS_API int32_t flecs_poly_refcount( ecs_poly_t *poly); +/** Get unused index for static world local component id array. + * This operation returns an unused index for the world-local component id + * array. This index can be used by language bindings to obtain a component id. + * + * @return Unused index for component id array. + */ FLECS_API int32_t flecs_component_ids_index_get(void); +/** Get world local component id. + * + * @param world The world. + * @param index Component id array index. + * @return The component id. + */ FLECS_API ecs_entity_t flecs_component_ids_get( const ecs_world_t *world, int32_t index); +/** Get alive world local component id. + * Same as flecs_component_ids_get, but return 0 if component is no longer + * alive. + * + * @param world The world. + * @param index Component id array index. + * @return The component id. + */ FLECS_API ecs_entity_t flecs_component_ids_get_alive( - const ecs_world_t *stage_world, + const ecs_world_t *world, int32_t index); +/** Set world local component id. + * + * @param world The world. + * @param index Component id array index. + * @param id The component id. + */ FLECS_API void flecs_component_ids_set( ecs_world_t *world, int32_t index, ecs_entity_t id); -#define flecs_poly_claim(poly) \ - flecs_poly_claim_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) - -#define flecs_poly_release(poly) \ - flecs_poly_release_(ECS_CONST_CAST(void*, reinterpret_cast(poly))) - /** Calculate offset from address */ #ifdef __cplusplus @@ -4200,6 +4354,291 @@ void* flecs_hashmap_next_( #endif +/** + * @file api_internals.h + * @brief Access to internal data structures. + * + * Operations may change without warning. + */ + +#ifndef FLECS_API_INTERNALS_H +#define FLECS_API_INTERNALS_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** Record for entity index. */ +struct ecs_record_t { + ecs_component_record_t *cdr; /**< component record to (*, entity) for target entities */ + ecs_table_t *table; /**< Identifies a type (and table) in world */ + uint32_t row; /**< Table row of the entity */ + int32_t dense; /**< Index in dense array of entity index */ +}; + +/** Header for table cache elements. */ +typedef struct ecs_table_cache_hdr_t { + struct ecs_table_cache_t *cache; /**< Table cache of element. Of type ecs_component_record_t* for component index elements. */ + ecs_table_t *table; /**< Table associated with element. */ + struct ecs_table_cache_hdr_t *prev, *next; /**< Next/previous elements for id in table cache. */ +} ecs_table_cache_hdr_t; + +/** Record that stores location of a component in a table. + * Table records are registered with component records, which allows for quickly + * finding all tables for a specific component. */ +struct ecs_table_record_t { + ecs_table_cache_hdr_t hdr; /**< Table cache header */ + int16_t index; /**< First type index where id occurs in table */ + int16_t count; /**< Number of times id occurs in table */ + int16_t column; /**< First column index where id occurs */ +}; + +/** Find record for entity. + * An entity record contains the table and row for the entity. + * + * To use ecs_record_t::row as the record in the table, use: + * ECS_RECORD_TO_ROW(r->row) + * + * This removes potential entity bitflags from the row field. + * + * @param world The world. + * @param entity The entity. + * @return The record, NULL if the entity does not exist. + */ +FLECS_API +ecs_record_t* ecs_record_find( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get entity corresponding with record. + * This operation only works for entities that are not empty. + * + * @param record The record for which to obtain the entity id. + * @return The entity id for the record. + */ +FLECS_API +ecs_entity_t ecs_record_get_entity( + const ecs_record_t *record); + +/** Begin exclusive write access to entity. + * This operation provides safe exclusive access to the components of an entity + * without the overhead of deferring operations. + * + * When this operation is called simultaneously for the same entity more than + * once it will throw an assert. Note that for this to happen, asserts must be + * enabled. It is up to the application to ensure that access is exclusive, for + * example by using a read-write mutex. + * + * Exclusive access is enforced at the table level, so only one entity can be + * exclusively accessed per table. The exclusive access check is thread safe. + * + * This operation must be followed up with ecs_write_end(). + * + * @param world The world. + * @param entity The entity. + * @return A record to the entity. + */ +FLECS_API +ecs_record_t* ecs_write_begin( + ecs_world_t *world, + ecs_entity_t entity); + +/** End exclusive write access to entity. + * This operation ends exclusive access, and must be called after + * ecs_write_begin(). + * + * @param record Record to the entity. + */ +FLECS_API +void ecs_write_end( + ecs_record_t *record); + +/** Begin read access to entity. + * This operation provides safe read access to the components of an entity. + * Multiple simultaneous reads are allowed per entity. + * + * This operation ensures that code attempting to mutate the entity's table will + * throw an assert. Note that for this to happen, asserts must be enabled. It is + * up to the application to ensure that this does not happen, for example by + * using a read-write mutex. + * + * This operation does *not* provide the same guarantees as a read-write mutex, + * as it is possible to call ecs_read_begin() after calling ecs_write_begin(). It is + * up to application has to ensure that this does not happen. + * + * This operation must be followed up with ecs_read_end(). + * + * @param world The world. + * @param entity The entity. + * @return A record to the entity. + */ +FLECS_API +const ecs_record_t* ecs_read_begin( + ecs_world_t *world, + ecs_entity_t entity); + +/** End read access to entity. + * This operation ends read access, and must be called after ecs_read_begin(). + * + * @param record Record to the entity. + */ +FLECS_API +void ecs_read_end( + const ecs_record_t *record); + +/** Get component from entity record. + * This operation returns a pointer to a component for the entity + * associated with the provided record. For safe access to the component, obtain + * the record with ecs_read_begin() or ecs_write_begin(). + * + * Obtaining a component from a record is faster than obtaining it from the + * entity handle, as it reduces the number of lookups required. + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Pointer to component, or NULL if entity does not have the component. + * + * @see ecs_record_ensure_id() + */ +FLECS_API +const void* ecs_record_get_id( + const ecs_world_t *world, + const ecs_record_t *record, + ecs_id_t id); + +/** Same as ecs_record_get_id(), but returns a mutable pointer. + * For safe access to the component, obtain the record with ecs_write_begin(). + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Pointer to component, or NULL if entity does not have the component. + */ +FLECS_API +void* ecs_record_ensure_id( + ecs_world_t *world, + ecs_record_t *record, + ecs_id_t id); + +/** Test if entity for record has a (component) id. + * + * @param world The world. + * @param record Record to the entity. + * @param id The (component) id. + * @return Whether the entity has the component. + */ +FLECS_API +bool ecs_record_has_id( + ecs_world_t *world, + const ecs_record_t *record, + ecs_id_t id); + +/** Get component pointer from column/record. + * This returns a pointer to the component using a table column index. The + * table's column index can be found with ecs_table_get_column_index(). + * + * Usage: + * @code + * ecs_record_t *r = ecs_record_find(world, entity); + * int32_t column = ecs_table_get_column_index(world, table, ecs_id(Position)); + * Position *ptr = ecs_record_get_by_column(r, column, sizeof(Position)); + * @endcode + * + * @param record The record. + * @param column The column index in the entity's table. + * @param size The component size. + * @return The component pointer. + */ +FLECS_API +void* ecs_record_get_by_column( + const ecs_record_t *record, + int32_t column, + size_t size); + +/** Get component record for component id. + * + * @param world The world. + * @param id The component id. + * @return The component record, or NULL if it doesn't exist. + */ +FLECS_API +FLECS_ALWAYS_INLINE ecs_component_record_t* flecs_components_get( + const ecs_world_t *world, + ecs_id_t id); + +/** Find table record for component record. + * This operation returns the table record for the table/component record if it + * exists. If the record exists, it means the table has the component. + * + * @param cdr The component record. + * @param table The table. + * @return The table record if the table has the component, or NULL if not. + */ +FLECS_API +FLECS_ALWAYS_INLINE const ecs_table_record_t* flecs_component_get_table( + const ecs_component_record_t *cdr, + const ecs_table_t *table); + +/** Create component record iterator. + * A component record iterator iterates all tables for the specified component + * record. + * + * The iterator should be used like this: + * + * @code + * ecs_table_cache_iter_t it; + * if (flecs_component_iter(cdr, &it)) { + * const ecs_table_record_t *tr; + * while ((tr = flecs_component_next(&it))) { + * ecs_table_t *table = tr->hdr.table; + * // ... + * } + * } + * @endcode + * + * @param cdr The component record. + * @param iter_out Out parameter for the iterator. + * @return True if there are results, false if there are no results. + */ +FLECS_API +bool flecs_component_iter( + const ecs_component_record_t *cdr, + ecs_table_cache_iter_t *iter_out); + +/** Get next table record for iterator. + * Returns next table record for iterator. + * + * @param iter The iterator. + * @return The next table record, or NULL if there are no more results. + */ +FLECS_API +const ecs_table_record_t* flecs_component_next( + ecs_table_cache_iter_t *iter); + +/** Struct returned by flecs_table_records(). */ +typedef struct ecs_table_records_t { + const ecs_table_record_t *array; + int32_t count; +} ecs_table_records_t; + +/** Get table records. + * This operation returns an array with all records for the specified table. + * + * @param table The table. + * @return The table records for the table. + */ +FLECS_API +ecs_table_records_t flecs_table_records( + ecs_table_t* table); + +#ifdef __cplusplus +} +#endif + +#endif + /** Utility to hold a value of a dynamic type. */ typedef struct ecs_value_t { @@ -4889,6 +5328,13 @@ FLECS_API extern const ecs_entity_t EcsReflexive; */ FLECS_API extern const ecs_entity_t EcsFinal; +/** Mark component as inheritable. + * This is the opposite of Final. This trait can be used to enforce that queries + * take into account component inheritance before inheritance (IsA) + * relationships are added with the component as target. + */ +FLECS_API extern const ecs_entity_t EcsInheritable; + /** Relationship that specifies component inheritance behavior. */ FLECS_API extern const ecs_entity_t EcsOnInstantiate; @@ -5109,6 +5555,8 @@ FLECS_API extern const ecs_entity_t EcsOnStore; /**< OnStore pipeline phase. FLECS_API extern const ecs_entity_t EcsPostFrame; /**< PostFrame pipeline phase. */ FLECS_API extern const ecs_entity_t EcsPhase; /**< Phase pipeline phase. */ +FLECS_API extern const ecs_entity_t EcsConstant; /**< Tag added to enum/bitmask constants. */ + /** Value used to quickly check if component is builtin. This is used to quickly * filter out tables with builtin components (for example for ecs_delete()) */ #define EcsLastInternalComponentId (ecs_id(EcsPoly)) @@ -5758,6 +6206,24 @@ void ecs_dim( ecs_world_t *world, int32_t entity_count); +/** Free unused memory. + * This operation frees allocated memory that is no longer in use by the world. + * Examples of allocations that get cleaned up are: + * - Unused pages in the entity index + * - Component columns + * - Empty tables + * + * Flecs uses allocators internally for speeding up allocations. Allocators are + * not evaluated by this function, which means that the memory reported by the + * OS may not go down. For this reason, this function is most effective when + * combined with FLECS_USE_OS_ALLOC, which disables internal allocators. + * + * @param world The world. + */ +FLECS_API +void ecs_shrink( + ecs_world_t *world); + /** Set a range for issuing new entity ids. * This function constrains the entity identifiers returned by ecs_new_w() to the * specified range. This operation can be used to ensure that multiple processes @@ -5823,18 +6289,12 @@ void ecs_run_aperiodic( /** Used with ecs_delete_empty_tables(). */ typedef struct ecs_delete_empty_tables_desc_t { - /** Optional component filter for the tables to evaluate. */ - ecs_id_t id; - /** Free table data when generation > clear_generation. */ uint16_t clear_generation; /** Delete table when generation > delete_generation. */ uint16_t delete_generation; - /** Minimum number of component ids the table should have. */ - int32_t min_id_count; - /** Amount of time operation is allowed to spend. */ double time_budget_seconds; } ecs_delete_empty_tables_desc_t; @@ -6343,7 +6803,7 @@ bool ecs_is_enabled_id( * @see ecs_get_mut_id() */ FLECS_API -const void* ecs_get_id( +FLECS_ALWAYS_INLINE const void* ecs_get_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id); @@ -6360,7 +6820,7 @@ const void* ecs_get_id( * @return The component pointer, NULL if the entity does not have the component. */ FLECS_API -void* ecs_get_mut_id( +FLECS_ALWAYS_INLINE void* ecs_get_mut_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id); @@ -6447,164 +6907,6 @@ void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref); -/** Find record for entity. - * An entity record contains the table and row for the entity. - * - * @param world The world. - * @param entity The entity. - * @return The record, NULL if the entity does not exist. - */ -FLECS_API -ecs_record_t* ecs_record_find( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Begin exclusive write access to entity. - * This operation provides safe exclusive access to the components of an entity - * without the overhead of deferring operations. - * - * When this operation is called simultaneously for the same entity more than - * once it will throw an assert. Note that for this to happen, asserts must be - * enabled. It is up to the application to ensure that access is exclusive, for - * example by using a read-write mutex. - * - * Exclusive access is enforced at the table level, so only one entity can be - * exclusively accessed per table. The exclusive access check is thread safe. - * - * This operation must be followed up with ecs_write_end(). - * - * @param world The world. - * @param entity The entity. - * @return A record to the entity. - */ -FLECS_API -ecs_record_t* ecs_write_begin( - ecs_world_t *world, - ecs_entity_t entity); - -/** End exclusive write access to entity. - * This operation ends exclusive access, and must be called after - * ecs_write_begin(). - * - * @param record Record to the entity. - */ -FLECS_API -void ecs_write_end( - ecs_record_t *record); - -/** Begin read access to entity. - * This operation provides safe read access to the components of an entity. - * Multiple simultaneous reads are allowed per entity. - * - * This operation ensures that code attempting to mutate the entity's table will - * throw an assert. Note that for this to happen, asserts must be enabled. It is - * up to the application to ensure that this does not happen, for example by - * using a read-write mutex. - * - * This operation does *not* provide the same guarantees as a read-write mutex, - * as it is possible to call ecs_read_begin() after calling ecs_write_begin(). It is - * up to application has to ensure that this does not happen. - * - * This operation must be followed up with ecs_read_end(). - * - * @param world The world. - * @param entity The entity. - * @return A record to the entity. - */ -FLECS_API -const ecs_record_t* ecs_read_begin( - ecs_world_t *world, - ecs_entity_t entity); - -/** End read access to entity. - * This operation ends read access, and must be called after ecs_read_begin(). - * - * @param record Record to the entity. - */ -FLECS_API -void ecs_read_end( - const ecs_record_t *record); - -/** Get entity corresponding with record. - * This operation only works for entities that are not empty. - * - * @param record The record for which to obtain the entity id. - * @return The entity id for the record. - */ -FLECS_API -ecs_entity_t ecs_record_get_entity( - const ecs_record_t *record); - -/** Get component from entity record. - * This operation returns a pointer to a component for the entity - * associated with the provided record. For safe access to the component, obtain - * the record with ecs_read_begin() or ecs_write_begin(). - * - * Obtaining a component from a record is faster than obtaining it from the - * entity handle, as it reduces the number of lookups required. - * - * @param world The world. - * @param record Record to the entity. - * @param id The (component) id. - * @return Pointer to component, or NULL if entity does not have the component. - * - * @see ecs_record_ensure_id() - */ -FLECS_API -const void* ecs_record_get_id( - const ecs_world_t *world, - const ecs_record_t *record, - ecs_id_t id); - -/** Same as ecs_record_get_id(), but returns a mutable pointer. - * For safe access to the component, obtain the record with ecs_write_begin(). - * - * @param world The world. - * @param record Record to the entity. - * @param id The (component) id. - * @return Pointer to component, or NULL if entity does not have the component. - */ -FLECS_API -void* ecs_record_ensure_id( - ecs_world_t *world, - ecs_record_t *record, - ecs_id_t id); - -/** Test if entity for record has a (component) id. - * - * @param world The world. - * @param record Record to the entity. - * @param id The (component) id. - * @return Whether the entity has the component. - */ -FLECS_API -bool ecs_record_has_id( - ecs_world_t *world, - const ecs_record_t *record, - ecs_id_t id); - -/** Get component pointer from column/record. - * This returns a pointer to the component using a table column index. The - * table's column index can be found with ecs_table_get_column_index(). - * - * Usage: - * @code - * ecs_record_t *r = ecs_record_find(world, entity); - * int32_t column = ecs_table_get_column_index(world, table, ecs_id(Position)); - * Position *ptr = ecs_record_get_by_column(r, column, sizeof(Position)); - * @endcode - * - * @param record The record. - * @param column The column index in the entity's table. - * @param size The component size. - * @return The component pointer. - */ -FLECS_API -void* ecs_record_get_by_column( - const ecs_record_t *record, - int32_t column, - size_t size); - /** Emplace a component. * Emplace is similar to ecs_ensure_id() except that the component constructor * is not invoked for the returned pointer, allowing the component to be @@ -6931,7 +7233,7 @@ char* ecs_entity_str( * @see ecs_owns_id() */ FLECS_API -bool ecs_has_id( +FLECS_ALWAYS_INLINE bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id); @@ -6947,7 +7249,7 @@ bool ecs_has_id( * @return True if the entity has the id, false if not. */ FLECS_API -bool ecs_owns_id( +FLECS_ALWAYS_INLINE bool ecs_owns_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id); @@ -10429,6 +10731,12 @@ int ecs_value_move_ctor( #ifdef FLECS_NO_SCRIPT #undef FLECS_SCRIPT #endif +#ifdef FLECS_NO_PARSER +#undef FLECS_PARSER +#endif +#ifdef FLECS_NO_QUERY_DSL +#undef FLECS_QUERY_DSL +#endif #ifdef FLECS_NO_SCRIPT_MATH #undef FLECS_SCRIPT_MATH #endif @@ -10479,82 +10787,6 @@ int ecs_value_move_ctor( #endif /* Always included, if disabled functions are replaced with dummy macros */ -/** - * @file addons/journal.h - * @brief Journaling addon that logs API functions. - * - * The journaling addon traces API calls. The trace is formatted as runnable - * C code, which allows for (partially) reproducing the behavior of an app - * with the journaling trace. - * - * The journaling addon is disabled by default. Enabling it can have a - * significant impact on performance. - */ - -#ifdef FLECS_JOURNAL - -#ifndef FLECS_LOG -#define FLECS_LOG -#endif - -#ifndef FLECS_JOURNAL_H -#define FLECS_JOURNAL_H - -/** - * @defgroup c_addons_journal Journal - * @ingroup c_addons - * Journaling addon (disabled by default). - * - * - * @{ - */ - -/* Trace when log level is at or higher than level */ -#define FLECS_JOURNAL_LOG_LEVEL (0) - -#ifdef __cplusplus -extern "C" { -#endif - -/* Journaling API, meant to be used by internals. */ - -typedef enum ecs_journal_kind_t { - EcsJournalNew, - EcsJournalMove, - EcsJournalClear, - EcsJournalDelete, - EcsJournalDeleteWith, - EcsJournalRemoveAll, - EcsJournalTableEvents -} ecs_journal_kind_t; - -FLECS_DBG_API -void flecs_journal_begin( - ecs_world_t *world, - ecs_journal_kind_t kind, - ecs_entity_t entity, - ecs_type_t *add, - ecs_type_t *remove); - -FLECS_DBG_API -void flecs_journal_end(void); - -#define flecs_journal(...)\ - flecs_journal_begin(__VA_ARGS__);\ - flecs_journal_end(); - -#ifdef __cplusplus -} -#endif // __cplusplus -/** @} */ -#endif // FLECS_JOURNAL_H -#else -#define flecs_journal_begin(...) -#define flecs_journal_end(...) -#define flecs_journal(...) - -#endif // FLECS_JOURNAL - /** * @file addons/log.h * @brief Logging addon. @@ -13491,8 +13723,12 @@ void FlecsAlertsImport( #define FLECS_META #endif -#ifndef FLECS_SCRIPT -#define FLECS_SCRIPT +#ifndef FLECS_DOC +#define FLECS_DOC +#endif + +#ifndef FLECS_QUERY_DSL +#define FLECS_QUERY_DSL /* For parsing component id expressions */ #endif #ifndef FLECS_JSON_H @@ -14333,6 +14569,21 @@ void FlecsScriptMathImport( #endif +#ifdef FLECS_PARSER +#ifdef FLECS_NO_PARSER +#error "FLECS_NO_PARSER failed: PARSER is required by other addons" +#endif +#endif + +#ifdef FLECS_QUERY_DSL +#ifdef FLECS_NO_QUERY_DSL +#error "FLECS_NO_QUERY_DSL failed: QUERY_DSL is required by other addons" +#endif +#ifndef FLECS_PARSER +#define FLECS_PARSER +#endif +#endif + #ifdef FLECS_SCRIPT #ifdef FLECS_NO_SCRIPT #error "FLECS_NO_SCRIPT failed: SCRIPT is required by other addons" @@ -14362,6 +14613,9 @@ void FlecsScriptMathImport( #define FLECS_DOC #endif +#ifndef FLECS_PARSER +#define FLECS_PARSER +#endif #ifndef FLECS_SCRIPT_H #define FLECS_SCRIPT_H @@ -15592,7 +15846,6 @@ FLECS_API extern const ecs_entity_t ecs_id(EcsVector); /**< Id for comp FLECS_API extern const ecs_entity_t ecs_id(EcsOpaque); /**< Id for component that stores reflection data for an opaque type. */ FLECS_API extern const ecs_entity_t ecs_id(EcsUnit); /**< Id for component that stores unit data. */ FLECS_API extern const ecs_entity_t ecs_id(EcsUnitPrefix); /**< Id for component that stores unit prefix data. */ -FLECS_API extern const ecs_entity_t EcsConstant; /**< Tag added to enum/bitmask constants. */ FLECS_API extern const ecs_entity_t EcsQuantity; /**< Tag added to unit quantities. */ /* Primitive type component ids */ @@ -15850,6 +16103,19 @@ typedef int (*ecs_meta_serialize_t)( const ecs_serializer_t *ser, const void *src); /**< Pointer to value to serialize */ + +/** Callback invoked to serialize an opaque struct member */ +typedef int (*ecs_meta_serialize_member_t)( + const ecs_serializer_t *ser, + const void *src, /**< Pointer to value to serialize */ + const char* name); /**< Name of member to serialize */ + +/** Callback invoked to serialize an opaque vector/array element */ +typedef int (*ecs_meta_serialize_element_t)( + const ecs_serializer_t *ser, + const void *src, /**< Pointer to value to serialize */ + size_t elem); /**< Element index to serialize */ + /** Opaque type reflection data. * An opaque type is a type with an unknown layout that can be mapped to a type * known to the reflection framework. See the opaque type reflection examples. @@ -15857,6 +16123,8 @@ typedef int (*ecs_meta_serialize_t)( typedef struct EcsOpaque { ecs_entity_t as_type; /**< Type that describes the serialized output */ ecs_meta_serialize_t serialize; /**< Serialize action */ + ecs_meta_serialize_member_t serialize_member; /**< Serialize member action */ + ecs_meta_serialize_element_t serialize_element; /**< Serialize element action */ /* Deserializer interface * Only override the callbacks that are valid for the opaque type. If a @@ -17101,28 +17369,19 @@ const char* ecs_cpp_trim_module( ecs_world_t *world, const char *type_name); -FLECS_API -ecs_entity_t ecs_cpp_component_find( - ecs_world_t *world, - ecs_entity_t id, - const char *name, - const char *symbol, - size_t size, - size_t alignment, - bool implicit_name, - bool *existing_out); - FLECS_API ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, - ecs_entity_t s_id, ecs_entity_t id, + int32_t ids_index, const char *name, - const char *type_name, - const char *symbol, + const char *cpp_name, + const char *cpp_symbol, size_t size, size_t alignment, bool is_component, + bool explicit_registration, + bool *registered_out, bool *existing_out); FLECS_API @@ -17189,6 +17448,7 @@ struct untyped_component; template struct component; +struct untyped_ref; template struct ref; @@ -17290,6 +17550,7 @@ static const flecs::entity_t Monitor = EcsMonitor; static const flecs::entity_t System = EcsSystem; static const flecs::entity_t Pipeline = ecs_id(EcsPipeline); static const flecs::entity_t Phase = EcsPhase; +static const flecs::entity_t Constant = EcsConstant; /* Builtin event tags */ static const flecs::entity_t OnAdd = EcsOnAdd; @@ -17322,6 +17583,7 @@ static const flecs::entity_t This = EcsThis; static const flecs::entity_t Transitive = EcsTransitive; static const flecs::entity_t Reflexive = EcsReflexive; static const flecs::entity_t Final = EcsFinal; +static const flecs::entity_t Inheritable = EcsInheritable; static const flecs::entity_t PairIsTag = EcsPairIsTag; static const flecs::entity_t Exclusive = EcsExclusive; static const flecs::entity_t Acyclic = EcsAcyclic; @@ -17806,6 +18068,12 @@ struct string_view : string { #define FLECS_ENUM_MAX(T) _::to_constant::value #define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1) +// Flag to turn off enum reflection +#ifdef FLECS_CPP_NO_ENUM_REFLECTION +#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0 +#endif + +// Test if we're using a compiler that supports the required features #ifndef FLECS_CPP_ENUM_REFLECTION_SUPPORT #if !defined(__clang__) && defined(__GNUC__) #if __GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 5) @@ -17957,26 +18225,20 @@ struct enum_is_valid { /** Extract name of constant from string */ template static const char* enum_constant_to_name() { - static const size_t len = ECS_FUNC_TYPE_LEN(const char*, enum_constant_to_name, ECS_FUNC_NAME); + static const size_t len = ECS_FUNC_TYPE_LEN( + const char*, enum_constant_to_name, ECS_FUNC_NAME); static char result[len + 1] = {}; return ecs_cpp_get_constant_name( result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME), ECS_FUNC_NAME_BACK); } -/** Enumeration constant data */ -template -struct enum_constant_data { - int32_t index; // Global index used to obtain world local entity id - T offset; -}; - /** * @brief Provides utilities for enum reflection. * - * This struct provides static functions for enum reflection, including conversion - * between enum values and their underlying integral types, and iteration over enum - * values. + * This struct provides static functions for enum reflection, including + * conversion between enum values and their underlying integral types, and + * iteration over enum values. * * @tparam E The enum type. * @tparam Handler The handler for enum reflection operations. @@ -17986,11 +18248,13 @@ struct enum_reflection { using U = underlying_type_t; /** - * @brief Iterates over the range [Low, High] of enum values between Low and High. + * @brief Iterates over the range [Low, High] of enum values between Low and + * High. * - * Recursively divide and conquers the search space to reduce the template-depth. Once - * recursive division is complete, calls Handle::handle_constant in ascending order, - * passing the values computed up the chain. + * Recursively divide and conquers the search space to reduce the + * template-depth. Once recursive division is complete, calls + * Handle::handle_constant in ascending order, passing the values + * computed up the chain. * * @tparam Low The lower bound of the search range, inclusive. * @tparam High The upper bound of the search range, inclusive. @@ -18000,11 +18264,13 @@ struct enum_reflection { * @return constexpr U The result of the iteration. */ template - static constexpr U each_enum_range(U last_value, Args... args) { + static constexpr U each_enum_range(U last_value, Args&... args) { return High - Low <= 1 ? High == Low ? Handler::template handle_constant(last_value, args...) - : Handler::template handle_constant(Handler::template handle_constant(last_value, args...), args...) + : Handler::template handle_constant( + Handler::template handle_constant(last_value, args...), + args...) : each_enum_range<(Low + High) / 2 + 1, High>( each_enum_range(last_value, args...), args... @@ -18012,11 +18278,13 @@ struct enum_reflection { } /** - * @brief Iterates over the mask range (Low, High] of enum values between Low and High. + * @brief Iterates over the mask range (Low, High] of enum values between + * Low and High. * - * Recursively iterates the search space, looking for enums defined as multiple-of-2 - * bitmasks. Each iteration, shifts bit to the right until it hits Low, then calls - * Handler::handle_constant for each bitmask in ascending order. + * Recursively iterates the search space, looking for enums defined as + * multiple-of-2 bitmasks. Each iteration, shifts bit to the right until it + * hits Low, then calls Handler::handle_constant for each bitmask in + * ascending order. * * @tparam Low The lower bound of the search range, not inclusive * @tparam High The upper bound of the search range, inclusive. @@ -18026,8 +18294,9 @@ struct enum_reflection { * @return constexpr U The result of the iteration. */ template - static constexpr U each_mask_range(U last_value, Args... args) { - // If Low shares any bits with Current Flag, or if High is less than/equal to Low (and High isn't negative because max-flag signed) + static constexpr U each_mask_range(U last_value, Args&... args) { + // If Low shares any bits with Current Flag, or if High is less + // than/equal to Low (and High isn't negative because max-flag signed) return (Low & High) || (High <= Low && High != high_bit) ? last_value : Handler::template handle_constant( @@ -18049,103 +18318,116 @@ struct enum_reflection { * @return constexpr U The result of the iteration. */ template (FLECS_ENUM_MAX(E)), typename... Args> - static constexpr U each_enum(Args... args) { - return each_mask_range(each_enum_range<0, Value>(0, args...), args...); - } + static constexpr U each_enum(Args&... args) { + return each_mask_range( + each_enum_range<0, Value>(0, args...), args...); + } + /* to avoid warnings with bit manipulation, calculate the high bit with an + unsigned type of the same size: */ + using UU = typename std::make_unsigned::type; + static const U high_bit = + static_cast(static_cast(1) << (sizeof(UU) * 8 - 1)); +}; - static const U high_bit = static_cast(1) << (sizeof(U) * 8 - 1); +/** Enumeration constant data */ +template +struct enum_constant { + int32_t index; // Global index used to obtain world local entity id + T value; + T offset; + const char *name; }; -/** Enumeration type data */ -template -struct enum_data_impl { +/** Class that scans an enum for constants, extracts names & creates entities */ +template +struct enum_type { private: + using This = enum_type; using U = underlying_type_t; /** * @brief Handler struct for generating compile-time count of enum constants. */ struct reflection_count { - template () > = 0> + template () > = 0> static constexpr U handle_constant(U last_value) { return last_value; } - template () > = 0> + template () > = 0> static constexpr U handle_constant(U last_value) { return 1 + last_value; } }; -public: - int min; - int max; - bool has_contiguous; - // If enum constants start not-sparse, contiguous_until will be the index of the first sparse value, or end of the constants array - U contiguous_until; - // Compile-time generated count of enum constants. - static constexpr unsigned int constants_size = enum_reflection::template each_enum< static_cast(enum_last::value) >(); - // Constants array is sized to the number of found-constants, or 1 (to avoid 0-sized array) - enum_constant_data constants[constants_size? constants_size: 1]; -}; - -/** Class that scans an enum for constants, extracts names & creates entities */ -template -struct enum_type { -private: - using U = underlying_type_t; - /** - * @brief Helper struct for filling enum_type's static `enum_data_impl` member with reflection data. + * @brief Helper struct for filling enum_type's static `enum_data_impl` + * member with reflection data. * - * Because reflection occurs in-order, we can use current value/last value to determine continuity, and - * use that as a lookup heuristic later on. + * Because reflection occurs in-order, we can use current value/last value + * to determine continuity, and use that as a lookup heuristic later on. */ struct reflection_init { - template () > = 0> - static U handle_constant(U last_value, flecs::world_t*) { + template () > = 0> + static U handle_constant(U last_value, This&) { // Search for constant failed. Pass last valid value through. return last_value; } - template () > = 0> - static U handle_constant(U last_value, flecs::world_t *world) { + template () > = 0> + static U handle_constant(U last_value, This& me) { // Constant is valid, so fill reflection data. auto v = Value; const char *name = enum_constant_to_name(); - ++enum_type::data.max; // Increment cursor as we build constants array. + ++me.max; // Increment cursor as we build constants array. - // If the enum was previously contiguous, and continues to be through the current value... - if (enum_type::data.has_contiguous && static_cast(enum_type::data.max) == v && enum_type::data.contiguous_until == v) { - ++enum_type::data.contiguous_until; + // If the enum was previously contiguous, and continues to be + // through the current value... + if (me.has_contiguous && static_cast(me.max) == v && me.contiguous_until == v) { + ++me.contiguous_until; } - // else, if the enum was never contiguous and hasn't been set as not contiguous... - else if (!enum_type::data.contiguous_until && enum_type::data.has_contiguous) { - enum_type::data.has_contiguous = false; + + // else, if the enum was never contiguous and hasn't been set as not + // contiguous... + else if (!me.contiguous_until && me.has_contiguous) { + me.has_contiguous = false; } - ecs_assert(!(last_value > 0 && v < std::numeric_limits::min() + last_value), ECS_UNSUPPORTED, - "Signed integer enums causes integer overflow when recording offset from high positive to" - " low negative. Consider using unsigned integers as underlying type."); - enum_type::data.constants[enum_type::data.max].offset = v - last_value; - if (!enum_type::data.constants[enum_type::data.max].index) { - enum_type::data.constants[enum_type::data.max].index = + ecs_assert(!(last_value > 0 && + v < std::numeric_limits::min() + last_value), + ECS_UNSUPPORTED, + "Signed integer enums causes integer overflow when recording " + "offset from high positive to low negative. Consider using " + "unsigned integers as underlying type."); + + me.constants[me.max].value = v; + me.constants[me.max].offset = v - last_value; + me.constants[me.max].name = name; + if (!me.constants[me.max].index) { + me.constants[me.max].index = flecs_component_ids_index_get(); } - - flecs::entity_t constant = ecs_cpp_enum_constant_register( - world, type::id(world), 0, name, &v, type::id(world), sizeof(U)); - flecs_component_ids_set(world, - enum_type::data.constants[enum_type::data.max].index, - constant); return v; } }; public: - static enum_data_impl data; + enum_type() { + // Initialize/reset reflection data values to default state. + min = 0; + max = -1; + has_contiguous = true; + contiguous_until = 0; + + enum_reflection:: + template each_enum< static_cast(enum_last::value) >(*this); + } static enum_type& get() { static _::enum_type instance; @@ -18155,37 +18437,53 @@ struct enum_type { flecs::entity_t entity(E value) const { int index = index_by_value(value); if (index >= 0) { - return data.constants[index].id; + return constants[index].id; } return 0; } - void init(flecs::world_t *world, flecs::entity_t id) { + void register_for_world(flecs::world_t *world, flecs::entity_t id) { #if !FLECS_CPP_ENUM_REFLECTION_SUPPORT ecs_abort(ECS_UNSUPPORTED, "enum reflection requires gcc 7.5 or higher") #endif - // Initialize/reset reflection data values to default state. - data.min = 0; - data.max = -1; - data.has_contiguous = true; - data.contiguous_until = 0; ecs_log_push(); ecs_cpp_enum_init(world, id, type::id(world)); - // data.id = id; - // Generate reflection data - enum_reflection::template each_enum< static_cast(enum_last::value) >(world); + for (U v = 0; v < static_cast(max + 1); v ++) { + if (constants[v].index) { + flecs::entity_t constant = ecs_cpp_enum_constant_register(world, + type::id(world), 0, constants[v].name, &constants[v].value, + type::id(world), sizeof(U)); + + flecs_component_ids_set(world, constants[v].index, constant); + } + } + ecs_log_pop(); } -}; -template -enum_data_impl enum_type::data; + int min; + int max; + bool has_contiguous; + + // If enum constants start not-sparse, contiguous_until will be the index of + // the first sparse value, or end of the constants array + U contiguous_until; + + // Compile-time generated count of enum constants. + static constexpr unsigned int constants_size = + enum_reflection:: + template each_enum< static_cast(enum_last::value) >(); + + // Constants array is sized to the number of found-constants, or 1 + // to avoid 0-sized array + enum_constant constants[constants_size? constants_size: 1] = {}; +}; template ::value > = 0> inline static void init_enum(flecs::world_t *world, flecs::entity_t id) { - _::enum_type::get().init(world, id); + _::enum_type::get().register_for_world(world, id); } template ::value > = 0> @@ -18198,7 +18496,7 @@ template struct enum_data { using U = underlying_type_t; - enum_data(flecs::world_t *world, _::enum_data_impl& impl) + enum_data(flecs::world_t *world, _::enum_type& impl) : world_(world) , impl_(impl) { } @@ -18279,7 +18577,7 @@ struct enum_data { flecs::entity entity(E value) const; flecs::world_t *world_; - _::enum_data_impl& impl_; + _::enum_type& impl_; }; /** Convenience function for getting enum reflection data */ @@ -18287,7 +18585,7 @@ template enum_data enum_type(flecs::world_t *world) { _::type::id(world); // Ensure enum is registered auto& ref = _::enum_type::get(); - return enum_data(world, ref.data); + return enum_data(world, ref); } } // namespace flecs @@ -19186,7 +19484,6 @@ static const flecs::entity_t F32 = ecs_id(ecs_f32_t); static const flecs::entity_t F64 = ecs_id(ecs_f64_t); static const flecs::entity_t String = ecs_id(ecs_string_t); static const flecs::entity_t Entity = ecs_id(ecs_entity_t); -static const flecs::entity_t Constant = EcsConstant; static const flecs::entity_t Quantity = EcsQuantity; namespace meta { @@ -19425,6 +19722,15 @@ using serialize_t = ecs_meta_serialize_t; template using serialize = int(*)(const serializer *, const T*); +/** Type safe variant of serialize_member function */ +template +using serialize_member = int(*)(const serializer *, const T*, const char* name); + +/** Type safe variant of serialize_element function */ +template +using serialize_element = int(*)(const serializer *, const T*, size_t element); + + /** Type safe interface for opaque types */ template struct opaque { @@ -19448,6 +19754,22 @@ struct opaque { return *this; } + /** Serialize member function */ + opaque& serialize_member(flecs::serialize_member func) { + this->desc.type.serialize_member = + reinterpret_castdesc.type.serialize_member)>(func); + return *this; + } + + /** Serialize element function */ + opaque& serialize_element(flecs::serialize_element func) { + this->desc.type.serialize_element = + reinterpret_castdesc.type.serialize_element)>(func); + return *this; + } + /** Assign bool value */ opaque& assign_bool(void (*func)(T *dst, bool value)) { this->desc.type.assign_bool = @@ -20965,6 +21287,157 @@ ecs_move_t move_dtor(ecs_flags32_t &) { return move_dtor_impl; } +// Traits to check for operator<, operator>, and operator== +template +using void_t = void; + +// These traits causes a "float comparison warning" in some compilers +// when `T` is float or double. +// Disable this warning with the following pragmas: +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wfloat-equal" +#elif defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// Trait to check for operator< +template +struct has_operator_less : std::false_type {}; + +// Only enable if T has an operator< that takes T as the right-hand side (no implicit conversion) +template +struct has_operator_less() < std::declval())>> : + std::is_same() < std::declval()), bool> {}; + +// Trait to check for operator> +template +struct has_operator_greater : std::false_type {}; + +// Only enable if T has an operator> that takes T as the right-hand side (no implicit conversion) +template +struct has_operator_greater() > std::declval())>> : + std::is_same() > std::declval()), bool> {}; + +// Trait to check for operator== +template +struct has_operator_equal : std::false_type {}; + +// Only enable if T has an operator== that takes T as the right-hand side (no implicit conversion) +template +struct has_operator_equal() == std::declval())>> : + std::is_same() == std::declval()), bool> {}; + +// 1. Compare function if `<`, `>`, are defined +template ::value && + has_operator_greater::value && + !has_operator_equal::value > = 0> +int compare_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + if (lhs < rhs) return -1; + if (lhs > rhs) return 1; + return 0; +} + +// 2. Compare function if `<` and `==` are defined, ignoring `>` +// if defined. +template ::value && + has_operator_equal::value > = 0> +int compare_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + if (lhs == rhs) return 0; + if (lhs < rhs) return -1; + return 1; // If not less and not equal, must be greater +} + +// 3. Compare function if `>` and `==` are defined, deducing `<` +template ::value && + has_operator_equal::value && + !has_operator_less::value > = 0> +int compare_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + if (lhs == rhs) return 0; + if (lhs > rhs) return 1; + return -1; // If not greater and not equal, must be less +} + +// 4. Compare function if only `<` is defined, deducing the rest +template ::value && + !has_operator_greater::value && + !has_operator_equal::value > = 0> +int compare_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + if (lhs < rhs) return -1; + if (rhs < lhs) return 1; + return 0; // If neither is less, they must be equal +} + +// 5. Compare function if only `>` is defined, deducing the rest +template ::value && + !has_operator_less::value && + !has_operator_equal::value > = 0> +int compare_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + if (lhs > rhs) return 1; + if (rhs > lhs) return -1; + return 0; // If neither is greater, they must be equal +} + +// In order to have a generated compare hook, at least +// operator> or operator< must be defined: +template ::value || + has_operator_greater::value > = 0> +ecs_cmp_t compare() { + return compare_impl; +} + +template ::value && + !has_operator_greater::value > = 0> +ecs_cmp_t compare() { + return NULL; +} + +// Equals function enabled only if `==` is defined +template ::value > = 0> +bool equals_impl(const void *a, const void *b, const ecs_type_info_t *) { + const T& lhs = *static_cast(a); + const T& rhs = *static_cast(b); + return lhs == rhs; +} + +template ::value > = 0> +ecs_equals_t equals() { + return equals_impl; +} + +template ::value > = 0> +ecs_equals_t equals() { + return NULL; +} + +// re-enable the float comparison warning: +#if defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic pop +#endif + } // _ } // flecs @@ -22152,6 +22625,14 @@ struct world { return get_info()->delta_time; } + /** Free unused memory. + * + * @see ecs_shrink() + */ + void shrink() const { + ecs_shrink(world_); + } + /** * @file addons/cpp/mixins/id/mixin.inl * @brief Id world mixin. @@ -23509,6 +23990,121 @@ struct iter { /** @} */ +/** + * @file addons/cpp/ref.hpp + * @brief Class that caches data to speedup get operations. + */ + +#pragma once + +namespace flecs +{ + +/** + * @defgroup cpp_ref Refs + * @ingroup cpp_core + * Refs are a fast mechanism for referring to a specific entity/component. + * + * @{ + */ + +/** Untyped component reference. + * Reference to a component from a specific entity. + */ +struct untyped_ref { + + untyped_ref () : world_(nullptr), ref_{} {} + + untyped_ref(world_t *world, entity_t entity, flecs::id_t id) + : ref_() { + ecs_assert(id != 0, ECS_INVALID_PARAMETER, + "invalid id"); + // the world we were called with may be a stage; convert it to a world + // here if that is the case + world_ = world ? const_cast(ecs_get_world(world)) + : nullptr; + +#ifdef FLECS_DEBUG + flecs::entity_t type = ecs_get_typeid(world, id); + const flecs::type_info_t *ti = ecs_get_type_info(world, type); + ecs_assert(ti && ti->size != 0, ECS_INVALID_PARAMETER, + "cannot create ref to empty type"); +#endif + ref_ = ecs_ref_init_id(world_, entity, id); + } + + untyped_ref(flecs::entity entity, flecs::id_t id); + + /** Return entity associated with reference. */ + flecs::entity entity() const; + + /** Return component associated with reference. */ + flecs::id component() const { + return flecs::id(world_, ref_.id); + } + + void* get() { + return ecs_ref_get_id(world_, &ref_, this->ref_.id); + } + + bool has() { + return !!try_get(); + } + + /** implicit conversion to bool. return true if there is a valid + * component instance being referred to **/ + operator bool() { + return has(); + } + + void* try_get() { + if (!world_ || !ref_.entity) { + return nullptr; + } + + return get(); + } + +private: + world_t *world_; + flecs::ref_t ref_; +}; + +/** Component reference. + * Reference to a component from a specific entity. + */ +template +struct ref : public untyped_ref { + ref() : untyped_ref() { } + + ref(world_t *world, entity_t entity, flecs::id_t id = 0) + : untyped_ref(world, entity, id ? id : _::type::id(world)) + { } + + ref(flecs::entity entity, flecs::id_t id = 0); + + T* operator->() { + T* result = static_cast(get()); + + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, + "nullptr dereference by flecs::ref"); + + return result; + } + + T* get() { + return static_cast(untyped_ref::get()); + } + + T* try_get() { + return static_cast(untyped_ref::try_get()); + } +}; + +/** @} */ + +} + /** * @file addons/cpp/entity.hpp * @brief Entity class. @@ -23833,7 +24429,7 @@ struct entity_view : public id { * @tparam First The first element of the pair. * @param constant the enum constant. */ - template::value> = 0> + template::value && !std::is_same::value > = 0> const First* get(Second constant) const { const auto& et = enum_type(this->world_); flecs::entity_t target = et.entity(constant); @@ -24003,7 +24599,7 @@ struct entity_view : public id { * @tparam First The first element of the pair. * @param constant the enum constant. */ - template::value> = 0> + template::value && !std::is_same::value > = 0> First* get_mut(Second constant) const { const auto& et = enum_type(this->world_); flecs::entity_t target = et.entity(constant); @@ -24232,7 +24828,7 @@ struct entity_view : public id { * @param value The enum constant. * @return True if the entity has the provided component, false otherwise. */ - template::value > = 0> + template::value && !std::is_same::value > = 0> bool has(E value) const { const auto& et = enum_type(this->world_); flecs::entity_t second = et.entity(value); @@ -24743,7 +25339,7 @@ struct entity_builder : entity_view { * @tparam First The first element of the pair * @param constant the enum constant. */ - template::value > = 0> + template::value && !std::is_same::value > = 0> const Self& add(Second constant) const { flecs_static_assert(is_flecs_constructible::value, "cannot default construct type: add T::T() or use emplace()"); @@ -24942,23 +25538,12 @@ struct entity_builder : entity_view { * * @tparam T the type of the component to remove. */ - template ::value > = 0> + template const Self& remove() const { ecs_remove_id(this->world_, this->id_, _::type::id(this->world_)); return to_base(); } - /** Remove pair for enum. - * This operation will remove any `(Enum, *)` pair from the entity. - * - * @tparam E The enumeration type. - */ - template ::value > = 0> - const Self& remove() const { - flecs::entity_t first = _::type::id(this->world_); - return this->remove(first, flecs::Wildcard); - } - /** Remove an entity from an entity. * * @param entity The entity to remove. @@ -25508,20 +26093,6 @@ struct entity_builder : entity_view { * Emplace constructs a component in the storage, which prevents calling the * destructor on the value passed into the function. * - * Emplace attempts the following signatures to construct the component: - * - * @code - * T{Args...} - * T{flecs::entity, Args...} - * @endcode - * - * If the second signature matches, emplace will pass in the current entity - * as argument to the constructor, which is useful if the component needs - * to be aware of the entity to which it has been added. - * - * Emplace may only be called for components that have not yet been added - * to the entity. - * * @tparam T the component to emplace * @param args The arguments to pass to the constructor of T */ @@ -26046,6 +26617,29 @@ struct entity : entity_builder id_ = ecs_entity_init(world, &desc); } + /** Create a named entity. + * Named entities can be looked up with the lookup functions. Entity names + * may be scoped, where each element in the name is separated by sep. + * For example: "Foo.Bar". If parts of the hierarchy in the scoped name do + * not yet exist, they will be automatically created. + * + * @param world The world in which to create the entity. + * @param name The entity name. + * @param sep The separator to use for the scoped name. + * @param root_sep The separator to use for the root of the scoped name. + */ + explicit entity(world_t *world, const char *name, const char *sep, const char *root_sep) + : entity_builder() + { + world_ = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = sep; + desc.root_sep = root_sep; + id_ = ecs_entity_init(world, &desc); + } + /** Conversion from flecs::entity_t to flecs::entity. * * @param id The entity_t value to convert. @@ -26205,6 +26799,30 @@ struct entity : entity_builder ecs_modified_id(world_, id_, comp); } + /** Get reference to component specified by id. + * A reference allows for quick and safe access to a component value, and is + * a faster alternative to repeatedly calling 'get' for the same component. + * + * The method accepts a component id argument, which can be used to create a + * ref to a component that is different from the provided type. This allows + * for creating a base type ref that points to a derived type: + * + * @code + * flecs::ref r = e.get_ref(world.id()); + * @endcode + * + * If the provided component id is not binary compatible with the specified + * type, the behavior is undefined. + * + * @tparam T component for which to get a reference. + * @return The reference. + */ + template ::value > = 0> + ref get_ref_w_id(flecs::id_t component) const { + _::type::id(world_); // ensure type is registered + return ref(world_, id_, component); + } + /** Get reference to component. * A reference allows for quick and safe access to a component value, and is * a faster alternative to repeatedly calling 'get' for the same component. @@ -26247,6 +26865,14 @@ struct entity : entity_builder return ref(world_, id_, ecs_pair(first, second)); } + untyped_ref get_ref(flecs::id_t component) const { + return untyped_ref(world_, id_, component); + } + + untyped_ref get_ref(flecs::id_t first, flecs::id_t second) const { + return untyped_ref(world_, id_, ecs_pair(first, second)); + } + template ref get_ref_second(flecs::entity_t first) const { auto second = _::type::id(world_); @@ -26629,6 +27255,8 @@ struct each_delegate : public delegate { static void invoke_callback( ecs_iter_t *iter, const Func& func, size_t i, Args... comps) { + ecs_assert(iter->entities != nullptr, ECS_INVALID_PARAMETER, + "query does not return entities ($this variable is not populated)"); func(flecs::entity(iter->world, iter->entities[i]), (ColumnType< remove_reference_t >(iter, comps, i) .get_row())...); @@ -27335,6 +27963,7 @@ void register_lifecycle_actions( cl.ctor_move_dtor = ctor_move_dtor(cl.flags); cl.move_dtor = move_dtor(cl.flags); + cl.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, component, &cl); if (cl.flags & (ECS_TYPE_HOOK_MOVE_ILLEGAL|ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL)) @@ -27372,10 +28001,13 @@ struct type_impl { } // Register component id. - static entity_t register_id(world_t *world, - const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, - bool is_component = false, bool implicit_name = true, const char *n = nullptr, - flecs::entity_t module = 0) + static entity_t register_id( + world_t *world, // The world + const char *name = nullptr, // User provided name (overrides typename) + bool allow_tag = true, // Register empty types as zero-sized components + bool is_component = true, // Add flecs::Component to result + bool explicit_registration = false, // Entered from world.component()? + flecs::id_t id = 0) // User provided component id { if (!s_index) { // This is the first time (in this binary image) that this type is @@ -27385,39 +28017,16 @@ struct type_impl { ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL); } - flecs::entity_t c = flecs_component_ids_get(world, s_index); - - if (!c || !ecs_is_alive(world, c)) { - // When a component is implicitly registered, ensure that it's not - // registered in the current scope of the application/that "with" - // components get added to the component entity. - ecs_entity_t prev_scope = ecs_set_scope(world, module); - ecs_entity_t prev_with = ecs_set_with(world, 0); - - // At this point it is possible that the type was already registered - // with the world, just not for this binary. The registration code - // uses the type symbol to check if it was already registered. Note - // that the symbol is separate from the typename, as an application - // can override a component name when registering a type. - bool existing = false; - c = ecs_cpp_component_find( - world, id, n, symbol_name(), size(), alignment(), - implicit_name, &existing); - - const char *symbol = nullptr; - if (c) { - symbol = ecs_get_symbol(world, c); - } - if (!symbol) { - symbol = symbol_name(); - } + bool registered = false, existing = false; - c = ecs_cpp_component_register(world, c, c, name, type_name(), - symbol, size(), alignment(), is_component, &existing); + flecs::entity_t c = ecs_cpp_component_register( + world, id, s_index, name, type_name(), + symbol_name(), size(), alignment(), + is_component, explicit_registration, ®istered, &existing); - ecs_set_with(world, prev_with); - ecs_set_scope(world, prev_scope); + ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); + if (registered) { // Register lifecycle callbacks, but only if the component has a // size. Components that don't have a size are tags, and tags don't // require construction/destruction/copy/move's. @@ -27425,9 +28034,6 @@ struct type_impl { register_lifecycle_actions(world, c); } - // Set world local component id - flecs_component_ids_set(world, s_index, c); - // If component is enum type, register constants. Make sure to do // this after setting the component id, because the enum code will // be calling type::id(). @@ -27436,8 +28042,6 @@ struct type_impl { #endif } - ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); - return c; } @@ -27542,6 +28146,78 @@ struct type::value >> */ struct untyped_component : entity { using entity::entity; + + untyped_component() : entity() { } + explicit untyped_component(flecs::world_t *world, flecs::entity_t id) : entity(world, id) { } + explicit untyped_component(flecs::entity_t id) : entity(id) { } + + explicit untyped_component(flecs::world_t *world, const char *name) + { + world_ = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = "::"; + desc.root_sep = "::"; + desc.use_low_id = true; + id_ = ecs_entity_init(world, &desc); + } + + explicit untyped_component(world_t *world, const char *name, const char *sep, const char *root_sep) + { + world_ = world; + + ecs_entity_desc_t desc = {}; + desc.name = name; + desc.sep = sep; + desc.root_sep = root_sep; + desc.use_low_id = true; + id_ = ecs_entity_init(world, &desc); + } + +protected: + +flecs::type_hooks_t get_hooks() const { + const flecs::type_hooks_t* h = ecs_get_hooks_id(world_, id_); + if (h) { + return *h; + } else { + return {}; + } +} + +void set_hooks(flecs::type_hooks_t &h) { + h.flags &= ECS_TYPE_HOOKS_ILLEGAL; + ecs_set_hooks_id(world_, id_, &h); +} + +public: + +untyped_component& on_compare( + ecs_cmp_t compare_callback) +{ + ecs_assert(compare_callback, ECS_INVALID_PARAMETER, NULL); + flecs::type_hooks_t h = get_hooks(); + h.cmp = compare_callback; + h.flags &= ~ECS_TYPE_HOOK_CMP_ILLEGAL; + if(h.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) { + h.flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; + h.equals = NULL; + } + set_hooks(h); + return *this; +} + +untyped_component& on_equals( + ecs_equals_t equals_callback) +{ + ecs_assert(equals_callback, ECS_INVALID_PARAMETER, NULL); + flecs::type_hooks_t h = get_hooks(); + h.equals = equals_callback; + h.flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; + set_hooks(h); + return *this; +} # ifdef FLECS_META /** @@ -27734,9 +28410,10 @@ untyped_component& member( } /** Add constant. */ +template untyped_component& constant( const char *name, - int32_t value) + T value) { ecs_add_id(world_, id_, _::type::id(world_)); @@ -27747,16 +28424,17 @@ untyped_component& constant( ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); ecs_set_id(world_, eid, - ecs_pair(flecs::Constant, flecs::I32), sizeof(int32_t), + ecs_pair(flecs::Constant, _::type::id(world_)), sizeof(T), &value); return *this; } /** Add bitmask constant. */ +template untyped_component& bit( const char *name, - uint32_t value) + T value) { ecs_add_id(world_, id_, _::type::id(world_)); @@ -27767,7 +28445,7 @@ untyped_component& bit( ecs_assert(eid != 0, ECS_INTERNAL_ERROR, NULL); ecs_set_id(world_, eid, - ecs_pair(flecs::Constant, flecs::U32), sizeof(uint32_t), + ecs_pair(flecs::Constant, _::type::id(world_)), sizeof(T), &value); return *this; @@ -27915,47 +28593,8 @@ struct component : untyped_component { bool allow_tag = true, flecs::id_t id = 0) { - const char *n = name; - bool implicit_name = false; - if (!n) { - n = _::type_name(); - - // Keep track of whether name was explicitly set. If not, and the - // component was already registered, just use the registered name. - // The registered name may differ from the typename as the registered - // name includes the flecs scope. This can in theory be different from - // the C++ namespace though it is good practice to keep them the same */ - implicit_name = true; - } - - // If component is registered by module, ensure it's registered in the - // scope of the module. - flecs::entity_t module = ecs_get_scope(world); - - // Strip off the namespace part of the component name, unless a name was - // explicitly provided by the application. - if (module && implicit_name) { - // If the type is a template type, make sure to ignore - // inside the template parameter list. - const char *start = strchr(n, '<'), *last_elem = NULL; - if (start) { - const char *ptr = start; - while (ptr[0] && (ptr[0] != ':') && (ptr > n)) { - ptr --; - } - if (ptr[0] == ':') { - last_elem = ptr; - } - } - - if (last_elem) { - name = last_elem + 1; - } - } - world_ = world; - id_ = _::type::register_id( - world, name, allow_tag, id, true, implicit_name, n, module); + id_ = _::type::register_id(world, name, allow_tag, true, true, id); } /** Register on_add hook. */ @@ -27969,7 +28608,7 @@ struct component : untyped_component { h.on_add = Delegate::run_add; ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func)); ctx->free_on_add = _::free_obj; - ecs_set_hooks_id(world_, id_, &h); + set_hooks(h); return *this; } @@ -27985,7 +28624,7 @@ struct component : untyped_component { h.on_remove = Delegate::run_remove; ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func)); ctx->free_on_remove = _::free_obj; - ecs_set_hooks_id(world_, id_, &h); + set_hooks(h); return *this; } @@ -28001,7 +28640,41 @@ struct component : untyped_component { h.on_set = Delegate::run_set; ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func)); ctx->free_on_set = _::free_obj; - ecs_set_hooks_id(world_, id_, &h); + set_hooks(h); + return *this; + } + + /** Register operator compare hook. */ + using untyped_component::on_compare; + component& on_compare() { + ecs_cmp_t handler = _::compare(); + ecs_assert(handler != NULL, ECS_INVALID_OPERATION, + "Type does not have operator> or operator< const or is inaccessible"); + on_compare(handler); + return *this; + } + + /** Type safe variant of compare op function */ + using cmp_hook = int(*)(const T* a, const T* b, const ecs_type_info_t *ti); + component& on_compare(cmp_hook callback) { + on_compare(reinterpret_cast(callback)); + return *this; + } + + /** Register operator equals hook. */ + using untyped_component::on_equals; + component& on_equals() { + ecs_equals_t handler = _::equals(); + ecs_assert(handler != NULL, ECS_INVALID_OPERATION, + "Type does not have operator== const or is inaccessible"); + on_equals(handler); + return *this; + } + + /** Type safe variant of equals op function */ + using equals_hook = bool(*)(const T* a, const T* b, const ecs_type_info_t *ti); + component& on_equals(equals_hook callback) { + on_equals(reinterpret_cast(callback)); return *this; } @@ -28069,109 +28742,12 @@ component& constant(const char *name, T value) { } return result; } - - flecs::type_hooks_t get_hooks() { - const flecs::type_hooks_t* h = ecs_get_hooks_id(world_, id_); - if (h) { - return *h; - } else { - return {}; - } - } }; } /** @} */ -/** - * @file addons/cpp/ref.hpp - * @brief Class that caches data to speedup get operations. - */ - -#pragma once - -namespace flecs -{ - -/** - * @defgroup cpp_ref Refs - * @ingroup cpp_core - * Refs are a fast mechanism for referring to a specific entity/component. - * - * @{ - */ - -/** Component reference. - * Reference to a component from a specific entity. - */ -template -struct ref { - ref() : world_(nullptr), ref_{} { } - - ref(world_t *world, entity_t entity, flecs::id_t id = 0) - : ref_() - { - // the world we were called with may be a stage; convert it to a world - // here if that is the case - world_ = world ? const_cast(ecs_get_world(world)) - : nullptr; - if (!id) { - id = _::type::id(world); - } - - ecs_assert(_::type::size() != 0, ECS_INVALID_PARAMETER, - "operation invalid for empty type"); - - ref_ = ecs_ref_init_id(world_, entity, id); - } - - ref(flecs::entity entity, flecs::id_t id = 0) - : ref(entity.world(), entity.id(), id) { } - - T* operator->() { - T* result = static_cast(ecs_ref_get_id( - world_, &ref_, this->ref_.id)); - - ecs_assert(result != NULL, ECS_INVALID_PARAMETER, - "nullptr dereference by flecs::ref"); - - return result; - } - - T* get() { - return static_cast(ecs_ref_get_id( - world_, &ref_, this->ref_.id)); - } - - T* try_get() { - if (!world_ || !ref_.entity) { - return nullptr; - } - - return get(); - } - - bool has() { - return !!try_get(); - } - - /** implicit conversion to bool. return true if there is a valid T* being referred to **/ - operator bool() { - return has(); - } - - flecs::entity entity() const; - -private: - world_t *world_; - flecs::ref_t ref_; -}; - -/** @} */ - -} - /** * @file addons/cpp/type.hpp * @brief Utility functions for id vector. @@ -29101,11 +29677,17 @@ inline flecs::id world::pair(entity_t r, entity_t o) const { namespace flecs { -template -flecs::entity ref::entity() const { - return flecs::entity(world_, ref_.entity); +inline untyped_ref::untyped_ref(flecs::entity entity, flecs::id_t id) + : untyped_ref(entity.world(), entity.id(), id) { } + +inline flecs::entity untyped_ref::entity() const { + return flecs::entity(world_, ref_.entity); } +template +flecs::ref::ref(flecs::entity entity, flecs::id_t id) + : ref(entity.world(), entity.id(), id) { } + template template inline const Self& entity_builder::insert(const Func& func) const { @@ -29309,7 +29891,7 @@ inline flecs::entity world::entity(E value) const { template inline flecs::entity world::entity(const char *name) const { - return flecs::entity(world_, _::type::register_id(world_, name, true) ); + return flecs::entity(world_, _::type::register_id(world_, name, true, false) ); } template @@ -29321,7 +29903,7 @@ inline flecs::entity world::prefab(Args &&... args) const { template inline flecs::entity world::prefab(const char *name) const { - flecs::entity result = flecs::component(world_, name, true); + flecs::entity result = this->entity(name); result.add(flecs::Prefab); return result; } @@ -29841,7 +30423,7 @@ struct term_builder_i : term_ref_builder_i { if (!ECS_IS_PAIR(sid)) { term_->src.id = sid; } else { - term_->src.id = ecs_pair_first(world(), sid); + term_->src.id = ecs_pair_first(this->world_v(), sid); } return *this; } @@ -30134,63 +30716,42 @@ struct query_builder_i : term_builder_i { *this->term_ = flecs::term(_::type::id(this->world_v())); this->term_->inout = static_cast( _::type_to_inout()); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } Base& with(id_t id) { this->term(); *this->term_ = flecs::term(id); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } Base& with(const char *name) { this->term(); *this->term_ = flecs::term().first(name); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } Base& with(const char *first, const char *second) { this->term(); *this->term_ = flecs::term().first(first).second(second); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } Base& with(entity_t r, entity_t o) { this->term(); *this->term_ = flecs::term(r, o); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } Base& with(entity_t r, const char *o) { this->term(); *this->term_ = flecs::term(r).second(o); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } Base& with(const char *r, entity_t o) { this->term(); *this->term_ = flecs::term().first(r).second(o); - if (this->term_->inout == EcsInOutDefault) { - this->inout_none(); - } return *this; } @@ -30298,6 +30859,8 @@ struct query_builder_i : term_builder_i { /* Term notation for more complex query features */ + /** Sets the current term to next one in term list. + */ Base& term() { if (this->term_) { ecs_check(ecs_term_is_initialized(this->term_), @@ -30316,6 +30879,30 @@ struct query_builder_i : term_builder_i { return *this; } + /** Sets the current term to the one with the provided type. + * This loops over all terms to find the one with the provided type. + * For performance-critical paths, use term_at(int32_t) instead. + */ + template + Base& term_at() { + flecs::id_t term_id = _::type::id(this->world_v()); + for (int i = 0; i < term_index_; i ++) { + ecs_term_t cur_term = desc_->terms[i]; + ecs_id_t cur_term_id = cur_term.id; + ecs_id_t cur_term_pair = ecs_pair(cur_term.first.id, cur_term.second.id); + + if ((term_id == cur_term_id || (cur_term_id != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_id))) || + (term_id == cur_term_pair || (cur_term_pair != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_pair)))) { + return term_at(i); + } + } + + ecs_err("term not found"); + return *this; + } + + /** Sets the current term to the one at the provided index. + */ Base& term_at(int32_t term_index) { ecs_assert(term_index >= 0, ECS_INVALID_PARAMETER, NULL); int32_t prev_index = term_index_; @@ -30327,6 +30914,24 @@ struct query_builder_i : term_builder_i { return *this; } + /** Sets the current term to the one at the provided index and asserts that the type matches. + */ + template + Base& term_at(int32_t term_index) { + this->term_at(term_index); +#if !defined(FLECS_NDEBUG) || defined(FLECS_KEEP_ASSERT) + flecs::id_t term_id = _::type::id(this->world_v()); + ecs_term_t cur_term = *this->term_; + ecs_id_t cur_term_id = cur_term.id; + ecs_id_t cur_term_pair = ecs_pair(cur_term.first.id, cur_term.second.id); + + ecs_assert((term_id == cur_term_id || (cur_term_id != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_id))) || + (term_id == cur_term_pair || (cur_term_pair != 0 && term_id == ecs_get_typeid(this->world_v(), cur_term_pair))), + ECS_INVALID_PARAMETER, "term type mismatch"); +#endif + return *this; + } + /** Sort the output of a query. * This enables sorting of entities across matched tables. As a result of this * operation, the order of entities in the matched tables may be changed. @@ -30533,12 +31138,19 @@ struct query_base { query_base(const query_base& obj) { this->query_ = obj.query_; - flecs_poly_claim(this->query_); + if (this->query_) + { + flecs_poly_claim(this->query_); + } } query_base& operator=(const query_base& obj) { + this->~query_base(); this->query_ = obj.query_; - flecs_poly_claim(this->query_); + if (this->query_) + { + flecs_poly_claim(this->query_); + } return *this; } @@ -31281,8 +31893,8 @@ flecs::entity import(world& world) { template inline flecs::entity world::module(const char *name) const { - flecs::entity result = this->entity(_::type::register_id( - world_, nullptr, false)); + flecs::entity result = this->entity( + _::type::register_id(world_, nullptr, false)); if (name) { flecs::entity prev_parent = result.parent(); @@ -33126,8 +33738,8 @@ 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_query_iter_t *qit = &iter_->priv_.iter.query; - const flecs::query_t *q = qit->query; + const flecs::query_t *q = iter_->query; + int var_id = ecs_query_find_var(q, name); ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, name); return flecs::entity(iter_->world, ecs_iter_get_var(iter_, var_id)); @@ -33168,6 +33780,20 @@ inline void world::init_builtin_components() { this->component(); this->component(); + /* If meta is not defined and we're using enum reflection, make sure that + * primitive types are registered. This makes sure we can set components of + * underlying_type_t when registering constants. */ +# if !defined(FLECS_META) && !defined(FLECS_CPP_NO_ENUM_REFLECTION) + this->component("flecs::meta::u8"); + this->component("flecs::meta::u16"); + this->component("flecs::meta::u32"); + this->component("flecs::meta::u64"); + this->component("flecs::meta::i8"); + this->component("flecs::meta::i16"); + this->component("flecs::meta::i32"); + this->component("flecs::meta::i64"); +# endif + # ifdef FLECS_SYSTEM _::system_init(*this); # endif diff --git a/src/zflecs.zig b/src/zflecs.zig index 5f19c02..c2cd10b 100644 --- a/src/zflecs.zig +++ b/src/zflecs.zig @@ -5,7 +5,7 @@ const builtin = @import("builtin"); pub const flecs_version = std.SemanticVersion{ .major = 4, .minor = 0, - .patch = 4, + .patch = 5, }; // TODO: flecs_is_sanitize should come from flecs build flags. @@ -48,6 +48,46 @@ pub const WorldMeasureSystemTime = 1 << 6; pub const WorldMultiThreaded = 1 << 7; pub const WorldFrameInProgress = 1 << 8; +// Entity flags (set in upper bits of ecs_record_t::row) +pub const EcsEntityIsId = 1 << 31; +pub const EcsEntityIsTarget = 1 << 30; +pub const EcsEntityIsTraversable = 1 << 29; + +// Id flags (used by ecs_component_record_t::flags) +pub const EcsIdOnDeleteRemove = 1 << 0; +pub const EcsIdOnDeleteDelete = 1 << 1; +pub const EcsIdOnDeletePanic = 1 << 2; +pub const EcsIdOnDeleteMask = (EcsIdOnDeletePanic | EcsIdOnDeleteRemove | EcsIdOnDeleteDelete); + +pub const EcsIdOnDeleteObjectRemove = 1 << 3; +pub const EcsIdOnDeleteObjectDelete = 1 << 4; +pub const EcsIdOnDeleteObjectPanic = 1 << 5; +pub const EcsIdOnDeleteObjectMask = (EcsIdOnDeleteObjectPanic | EcsIdOnDeleteObjectRemove | EcsIdOnDeleteObjectDelete); + +pub const EcsIdOnInstantiateOverride = 1 << 6; +pub const EcsIdOnInstantiateInherit = 1 << 7; +pub const EcsIdOnInstantiateDontInherit = 1 << 8; +pub const EcsIdOnInstantiateMask = (EcsIdOnInstantiateOverride | EcsIdOnInstantiateInherit | EcsIdOnInstantiateDontInherit); + +pub const EcsIdExclusive = 1 << 9; +pub const EcsIdTraversable = 1 << 10; +pub const EcsIdTag = 1 << 11; +pub const EcsIdWith = 1 << 12; +pub const EcsIdCanToggle = 1 << 13; +pub const EcsIdIsTransitive = 1 << 14; +pub const EcsIdIsInheritable = 1 << 15; + +pub const EcsIdHasOnAdd = 1 << 16; +pub const EcsIdHasOnRemove = 1 << 17; +pub const EcsIdHasOnSet = 1 << 18; +pub const EcsIdHasOnTableCreate = 1 << 21; +pub const EcsIdHasOnTableDelete = 1 << 22; +pub const EcsIdIsSparse = 1 << 23; +pub const EcsIdIsUnion = 1 << 24; +pub const EcsIdEventMask = (EcsIdHasOnAdd | EcsIdHasOnRemove | EcsIdHasOnSet | EcsIdHasOnTableCreate | EcsIdHasOnTableDelete | EcsIdIsSparse | EcsIdIsUnion); + +pub const EcsIdMarkedForDelete = 1 << 30; + // Iterator flags pub const EcsIterIsValid = 1 << 0; pub const EcsIterNoData = 1 << 1; @@ -179,6 +219,7 @@ extern const EcsVariable: entity_t; extern const EcsTransitive: entity_t; extern const EcsReflexive: entity_t; extern const EcsFinal: entity_t; +extern const EcsInheritable: entity_t; extern const EcsOnInstantiate: entity_t; extern const EcsOverride: entity_t; extern const EcsInherit: entity_t; @@ -239,6 +280,8 @@ extern const EcsOnStore: entity_t; extern const EcsPostFrame: entity_t; extern const EcsPhase: entity_t; +extern const EcsConstant: entity_t; + pub const EcsDefaultChildComponent = extern struct { component: id_t, }; @@ -256,6 +299,7 @@ pub var Variable: entity_t = undefined; pub var Transitive: entity_t = undefined; pub var Reflexive: entity_t = undefined; pub var Final: entity_t = undefined; +pub var Inheritable: entity_t = undefined; pub var OnInstantiate: entity_t = undefined; pub var Override: entity_t = undefined; pub var Inherit: entity_t = undefined; @@ -316,6 +360,8 @@ pub var OnStore: entity_t = undefined; pub var PostFrame: entity_t = undefined; pub var Phase: entity_t = undefined; +pub var Constant: entity_t = undefined; + // pub var DefaultChildComponent: EcsDefaultChildComponent = undefined; //-------------------------------------------------------------------------------------------------- @@ -339,7 +385,7 @@ pub const query_cache_table_match_t = opaque {}; pub const data_t = opaque {}; pub const table_cache_t = opaque {}; -pub const id_record_t = opaque {}; +pub const component_record_t = opaque {}; pub const poly_t = anyopaque; @@ -352,25 +398,9 @@ pub const header_t = extern struct { mixins: ?*mixins_t = null, }; -pub const record_t = extern struct { - idr: *id_record_t, - table: *table_t, - row: u32, - dense: i32, -}; - -pub const table_cache_hdr_t = extern struct { - cache: *table_cache_t, - table: *table_t, - prev: *table_cache_hdr_t, -}; - -pub const table_record_t = extern struct { - hdr: table_cache_hdr_t, - index: i16, - count: i16, - column: i16, -}; +pub const record_t = opaque {}; +pub const table_cache_hdr_t = opaque {}; +pub const table_record_t = anyopaque; //-------------------------------------------------------------------------------------------------- // @@ -456,6 +486,18 @@ pub const move_t = *const fn ( type_info: *const type_info_t, ) callconv(.C) void; +pub const cmp_t = *const fn ( + a_ptr: *const anyopaque, + b_ptr: *const anyopaque, + type_info: *const type_info_t, +) callconv(.C) c_int; + +pub const equals_t = *const fn ( + a_ptr: *const anyopaque, + b_ptr: *const anyopaque, + type_info: *const type_info_t, +) callconv(.C) bool; + pub const poly_dtor_t = *const fn (poly: *poly_t) callconv(.C) void; pub const system_desc_t = extern struct { @@ -637,26 +679,30 @@ pub const observer_t = extern struct { }; //-------------------------------------------------------------------------------------------------- -pub const ECS_TYPE_HOOK_CTOR = (1 << 0); -pub const ECS_TYPE_HOOK_DTOR = (1 << 1); -pub const ECS_TYPE_HOOK_COPY = (1 << 2); -pub const ECS_TYPE_HOOK_MOVE = (1 << 3); -pub const ECS_TYPE_HOOK_COPY_CTOR = (1 << 4); -pub const ECS_TYPE_HOOK_MOVE_CTOR = (1 << 5); -pub const ECS_TYPE_HOOK_CTOR_MOVE_DTOR = (1 << 6); -pub const ECS_TYPE_HOOK_MOVE_DTOR = (1 << 7); - -pub const ECS_TYPE_HOOK_CTOR_ILLEGAL = (1 << 8); -pub const ECS_TYPE_HOOK_DTOR_ILLEGAL = (1 << 9); -pub const ECS_TYPE_HOOK_COPY_ILLEGAL = (1 << 10); -pub const ECS_TYPE_HOOK_MOVE_ILLEGAL = (1 << 11); -pub const ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL = (1 << 12); -pub const ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL = (1 << 13); -pub const ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL = (1 << 14); -pub const ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL = (1 << 15); - -pub const ECS_TYPE_HOOKS = (ECS_TYPE_HOOK_CTOR | ECS_TYPE_HOOK_DTOR | ECS_TYPE_HOOK_COPY | ECS_TYPE_HOOK_MOVE | ECS_TYPE_HOOK_COPY_CTOR | ECS_TYPE_HOOK_MOVE_CTOR | ECS_TYPE_HOOK_CTOR_MOVE_DTOR | ECS_TYPE_HOOK_MOVE_DTOR); -pub const ECS_TYPE_HOOKS_ILLEGAL = (ECS_TYPE_HOOK_CTOR_ILLEGAL | ECS_TYPE_HOOK_DTOR_ILLEGAL | ECS_TYPE_HOOK_COPY_ILLEGAL | ECS_TYPE_HOOK_MOVE_ILLEGAL | ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL | ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL | ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL | ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL); +pub const ECS_TYPE_HOOK_CTOR: flags32_t = 1 << 0; +pub const ECS_TYPE_HOOK_DTOR: flags32_t = 1 << 1; +pub const ECS_TYPE_HOOK_COPY: flags32_t = 1 << 2; +pub const ECS_TYPE_HOOK_MOVE: flags32_t = 1 << 3; +pub const ECS_TYPE_HOOK_COPY_CTOR: flags32_t = 1 << 4; +pub const ECS_TYPE_HOOK_MOVE_CTOR: flags32_t = 1 << 5; +pub const ECS_TYPE_HOOK_CTOR_MOVE_DTOR: flags32_t = 1 << 6; +pub const ECS_TYPE_HOOK_MOVE_DTOR: flags32_t = 1 << 7; +pub const ECS_TYPE_HOOK_CMP: flags32_t = 1 << 8; +pub const ECS_TYPE_HOOK_EQUALS: flags32_t = 1 << 9; + +pub const ECS_TYPE_HOOK_CTOR_ILLEGAL: flags32_t = 1 << 10; +pub const ECS_TYPE_HOOK_DTOR_ILLEGAL: flags32_t = 1 << 12; +pub const ECS_TYPE_HOOK_COPY_ILLEGAL: flags32_t = 1 << 13; +pub const ECS_TYPE_HOOK_MOVE_ILLEGAL: flags32_t = 1 << 14; +pub const ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL: flags32_t = 1 << 15; +pub const ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL: flags32_t = 1 << 16; +pub const ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL: flags32_t = 1 << 17; +pub const ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL: flags32_t = 1 << 18; +pub const ECS_TYPE_HOOK_CMP_ILLEGAL: flags32_t = 1 << 19; +pub const ECS_TYPE_HOOK_EQUALS_ILLEGAL: flags32_t = 1 << 20; + +pub const ECS_TYPE_HOOKS: flags32_t = (ECS_TYPE_HOOK_CTOR | ECS_TYPE_HOOK_DTOR | ECS_TYPE_HOOK_COPY | ECS_TYPE_HOOK_MOVE | ECS_TYPE_HOOK_COPY_CTOR | ECS_TYPE_HOOK_MOVE_CTOR | ECS_TYPE_HOOK_CTOR_MOVE_DTOR | ECS_TYPE_HOOK_MOVE_DTOR | ECS_TYPE_HOOK_CMP | ECS_TYPE_HOOK_EQUALS); +pub const ECS_TYPE_HOOKS_ILLEGAL: flags32_t = (ECS_TYPE_HOOK_CTOR_ILLEGAL | ECS_TYPE_HOOK_DTOR_ILLEGAL | ECS_TYPE_HOOK_COPY_ILLEGAL | ECS_TYPE_HOOK_MOVE_ILLEGAL | ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL | ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL | ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL | ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL | ECS_TYPE_HOOK_CMP_ILLEGAL | ECS_TYPE_HOOK_EQUALS_ILLEGAL); pub const type_hooks_t = extern struct { ctor: ?xtor_t = null, @@ -668,6 +714,8 @@ pub const type_hooks_t = extern struct { move_ctor: ?move_t = null, ctor_move_dtor: ?move_t = null, move_dtor: ?move_t = null, + cmp: ?cmp_t = null, + equals: ?equals_t = null, flags: flags32_t = 0, on_add: ?iter_action_t = null, @@ -725,8 +773,9 @@ pub const ref_t = extern struct { entity: entity_t, id: entity_t, table_id: u64, - tr: *table_record_t, + table_version: u32, record: *record_t, + ptr: ?*anyopaque, }; pub const stack_page_t = opaque {}; // TODO: Complete binding @@ -752,8 +801,8 @@ pub const worker_iter_t = extern struct { }; pub const table_cache_iter_t = extern struct { - cur: ?*table_cache_hdr_t, - next: ?*table_cache_hdr_t, + cur: ?*const table_cache_hdr_t, + next: ?*const table_cache_hdr_t, iter_fill: bool, iter_empty: bool, }; @@ -765,7 +814,7 @@ pub const each_iter_t = extern struct { sources: entity_t, sizes: size_t, columns: i32, - trs: ?[*]table_record_t, + trs: ?*table_record_t, }; pub const query_var_t = opaque {}; @@ -810,6 +859,12 @@ pub const iter_private_t = extern struct { cache: iter_cache_t, }; +pub const commands_t = extern struct { + queue: vec_t, + stack: stack_t, + entries: sparse_t, +}; + //-------------------------------------------------------------------------------------------------- // // allocator_t, vec_t, map_t, switch_node @@ -845,7 +900,6 @@ pub const block_allocator_block_t = extern struct { pub const block_allocator_t = extern struct { head: ?*block_allocator_chunk_header_t, block_head: ?*block_allocator_block_t, - block_tail: ?*block_allocator_block_t, chunk_size: i32, data_size: i32, chunks_per_block: i32, @@ -874,13 +928,11 @@ pub const bucket_t = extern struct { }; pub const map_t = extern struct { - bucket_shift: u8, - shared_allocator: bool, - buckets: [*c]bucket_t, + buckets: [*]bucket_t, bucket_count: i32, - count: i32, - entry_allocator: [*c]block_allocator_t, - allocator: [*c]allocator_t, + count: u26, + bucket_shift: u6, + allocator: *allocator_t, }; pub const map_iter_t = extern struct { @@ -1284,6 +1336,7 @@ pub fn init() *world_t { Transitive = EcsTransitive; Reflexive = EcsReflexive; Final = EcsFinal; + Inheritable = EcsInheritable; OnInstantiate = EcsOnInstantiate; Override = EcsOverride; Inherit = EcsInherit; @@ -1342,6 +1395,7 @@ pub fn init() *world_t { OnStore = EcsOnStore; PostFrame = EcsPostFrame; Phase = EcsPhase; + Constant = EcsConstant; // TODO DefaultChildComponent = EcsDefaultChildComponent; @@ -1512,6 +1566,10 @@ extern fn ecs_get_world_info(world: *const world_t) *const world_info_t; pub const dim = ecs_dim; extern fn ecs_dim(world: *world_t, entity_count: i32) void; +/// `pub fn shrink(world: *world_t, entity_count: i32) void` +pub const shrink = ecs_shrink; +extern fn ecs_shrink(world: *world_t) void; + /// `pub fn set_entity_range(world: *world_t, id_start: entity_t, id_end: entity_t) void` pub const set_entity_range = ecs_set_entity_range; extern fn ecs_set_entity_range(world: *world_t, id_start: entity_t, id_end: entity_t) void; @@ -1529,10 +1587,8 @@ pub const run_aperiodic = ecs_run_aperiodic; extern fn ecs_run_aperiodic(world: *world_t, flags: flags32_t) void; pub const delete_empty_tables_desc_t = struct { - id: id_t, clear_generation: u16, delete_generation: u16, - min_id_count: i32, time_budget_seconds: f64, }; @@ -3127,7 +3183,6 @@ extern fn ecs_import_c(world: *world_t, module: module_action_t, module_name_c: pub const module_init = ecs_module_init; extern fn ecs_module_init(world: *world_t, c_name: [*:0]const u8, desc: *component_desc_t) entity_t; - //-------------------------------------------------------------------------------------------------- // // FLECS_STATS