#include "flecs_no_addons.h" #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H #ifndef __MACH__ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif #include #include #include #include #ifndef FLECS_BITSET_H #define FLECS_BITSET_H #ifdef __cplusplus extern "C" { #endif /** A bitset data structure for compact boolean storage. */ typedef struct ecs_bitset_t { uint64_t *data; /**< Array of 64-bit words storing the bits. */ int32_t count; /**< Number of bits in the bitset. */ ecs_size_t size; /**< Allocated capacity in 64-bit words. */ } ecs_bitset_t; /** Initialize a bitset. * * @param bs The bitset to initialize. */ FLECS_DBG_API void flecs_bitset_init( ecs_bitset_t *bs); /** Deinitialize a bitset. * * @param bs The bitset to deinitialize. */ FLECS_DBG_API void flecs_bitset_fini( ecs_bitset_t *bs); /** Add n elements to a bitset. * * @param bs The bitset to add to. * @param count Number of bits to add. */ FLECS_DBG_API void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count); /** Ensure an element exists. * * @param bs The bitset to ensure capacity for. * @param count Minimum number of bits the bitset must hold. */ FLECS_DBG_API void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count); /** Set an element. * * @param bs The bitset to modify. * @param elem Index of the bit to set. * @param value The boolean value to set. */ FLECS_DBG_API void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value); /** Get an element. * * @param bs The bitset to read from. * @param elem Index of the bit to get. * @return The boolean value of the bit. */ FLECS_DBG_API bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem); /** Return the number of elements. * * @param bs The bitset. * @return The number of bits in the bitset. */ FLECS_DBG_API int32_t flecs_bitset_count( const ecs_bitset_t *bs); /** Remove from a bitset. * * @param bs The bitset to remove from. * @param elem Index of the bit to remove. */ FLECS_DBG_API void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem); /** Swap values in a bitset. * * @param bs The bitset. * @param elem_a Index of the first bit to swap. * @param elem_b Index of the second bit to swap. */ FLECS_DBG_API void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b); #ifdef __cplusplus } #endif #endif #ifndef FLECS_NAME_INDEX_H #define FLECS_NAME_INDEX_H /** Type used for internal string hashmap */ typedef struct ecs_hashed_string_t { char *value; ecs_size_t length; uint64_t hash; } ecs_hashed_string_t; void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator); void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator); bool flecs_name_index_is_init( const ecs_hashmap_t *hm); ecs_hashmap_t* flecs_name_index_new( ecs_allocator_t *allocator); void flecs_name_index_fini( ecs_hashmap_t *map); void flecs_name_index_free( ecs_hashmap_t *map); ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *dst); const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t id, uint64_t hash); bool flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name); #endif #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS) #define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1) typedef struct ecs_entity_index_page_t { ecs_record_t records[FLECS_ENTITY_PAGE_SIZE]; } ecs_entity_index_page_t; typedef struct ecs_entity_index_t { ecs_vec_t dense; ecs_vec_t pages; ecs_vec_t ranges; /* vec - sorted by min */ ecs_entity_range_t *active_range; /* Currently active range (NULL = off) */ int32_t alive_count; uint32_t max_id; ecs_allocator_t *allocator; } ecs_entity_index_t; /** Initialize entity index. */ void flecs_entity_index_init( ecs_allocator_t *allocator, ecs_entity_index_t *index); /** Deinitialize entity index. */ void flecs_entity_index_fini( ecs_entity_index_t *index); /* Get entity (must exist/must be alive) */ ecs_record_t* flecs_entity_index_get( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (must exist/may not be alive) */ ecs_record_t* flecs_entity_index_get_any( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (may not exist/must be alive) */ ecs_record_t* flecs_entity_index_try_get( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (may not exist/may not be alive) */ ecs_record_t* flecs_entity_index_try_get_any( const ecs_entity_index_t *index, uint64_t entity); /** Ensure entity exists. */ ecs_record_t* flecs_entity_index_ensure( ecs_entity_index_t *index, uint64_t entity); /* Remove entity */ void flecs_entity_index_remove( ecs_entity_index_t *index, uint64_t entity); /* Make entity alive */ void flecs_entity_index_make_alive( ecs_entity_index_t *index, uint64_t entity); /* Get current generation of entity */ uint64_t flecs_entity_index_get_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is alive */ bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is valid */ bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity exists */ bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity); /* Create or recycle entity id */ uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index); /* Bulk create or recycle new entity ids */ uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count); /* Set size of index */ void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size); /* Return number of entities in index */ int32_t flecs_entity_index_count( const ecs_entity_index_t *index); /* Return number of allocated entities in index */ int32_t flecs_entity_index_size( const ecs_entity_index_t *index); /* Return number of not alive entities in index */ int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index); /* Clear entity index */ void flecs_entity_index_clear( ecs_entity_index_t *index); /* Shrink entity index */ void flecs_entity_index_shrink( ecs_entity_index_t *index); /* Ensure page for entity id exists */ ecs_entity_index_page_t* flecs_entity_index_ensure_page( ecs_entity_index_t *index, uint32_t id); /* Set active entity range. Swaps not-alive entries between the entity index * and the previous/new range's recycled lists. */ void flecs_entity_index_set_range( ecs_entity_index_t *index, ecs_entity_range_t *range); /* Return ids of alive entities in index */ const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index); #define ecs_eis(world) (&((world)->store.entity_index)) #define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world)) #define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world)) #define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity) #define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity) #define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity) #define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity) #define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity) #define flecs_entities_make_alive(world, entity) flecs_entity_index_make_alive(ecs_eis(world), entity) #define flecs_entities_get_alive(world, entity) flecs_entity_index_get_alive(ecs_eis(world), entity) #define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) #define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) #define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) #define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) #define flecs_entities_max_id(world) (ecs_eis(world)->max_id) #define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) #define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) #define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) #define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) #define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) #define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) #endif #ifndef FLECS_TABLE_CACHE_H_ #define FLECS_TABLE_CACHE_H_ /** Linked list of tables in table cache */ typedef struct ecs_table_cache_list_t { ecs_table_cache_hdr_t *first; ecs_table_cache_hdr_t *last; int32_t count; } ecs_table_cache_list_t; /** Table cache */ typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_table_cache_list_t tables; } ecs_table_cache_t; void ecs_table_cache_init( ecs_world_t *world, ecs_table_cache_t *cache); void ecs_table_cache_fini( ecs_table_cache_t *cache); void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result); void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_remove( ecs_table_cache_t *cache, uint64_t table_id, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table); #define flecs_table_cache_count(cache) (cache)->tables.count bool flecs_table_cache_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_empty_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_all_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); const ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it); #define flecs_table_cache_next(it, T)\ (ECS_CONST_CAST(T*, flecs_table_cache_next_(it))) #endif #ifndef FLECS_COMPONENT_INDEX_H #define FLECS_COMPONENT_INDEX_H /* Linked list of id records */ typedef struct ecs_id_record_elem_t { struct ecs_component_record_t *prev, *next; } ecs_id_record_elem_t; typedef struct ecs_reachable_elem_t { const ecs_table_record_t *tr; ecs_record_t *record; ecs_entity_t src; ecs_id_t id; #ifndef FLECS_NDEBUG ecs_table_t *table; #endif } ecs_reachable_elem_t; typedef struct ecs_reachable_cache_t { int32_t generation; int32_t current; ecs_vec_t ids; /* vec */ } ecs_reachable_cache_t; /* Component index data that just applies to pairs */ typedef struct ecs_pair_record_t { /* Name lookup index (currently only used for ChildOf pairs) */ ecs_hashmap_t *name_index; /* Vector with ordered children */ ecs_vec_t ordered_children; /* Tables with non-fragmenting children */ ecs_map_t children_tables; /* map */ /* Track how many of the tables in children_tables are disabled. Used by * queries to determine whether logic is needed to skip Disabled entities * when iterating the ordered_children vector. */ int32_t disabled_tables; /* Same for prefab tables */ int32_t prefab_tables; /* Hierarchy depth (set for ChildOf pair) */ int32_t depth; /* 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_record_t; /* Payload for id index which contains all data structures for an id. */ struct ecs_component_record_t { /* Cache with all tables that contain the id. Must be first member. */ ecs_table_cache_t cache; /* table_cache */ /* 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; /* Storage for sparse components */ void *sparse; /* Backref to tables with edges to non-fragmenting component ids */ ecs_vec_t dont_fragment_tables; /* Pair data */ ecs_pair_record_t *pair; /* All non-fragmenting ids */ ecs_id_record_elem_t non_fragmenting; /* Refcount */ int32_t refcount; }; /* Iterator over all component records in the world */ typedef struct ecs_components_iter_t { int32_t lo; bool hi; ecs_map_iter_t map_it; } ecs_components_iter_t; /* Bootstrap cached id records */ void flecs_components_init( ecs_world_t *world); /* Cleanup all id records in world */ void flecs_components_fini( ecs_world_t *world); /* Increase refcount of component record */ void flecs_component_claim( ecs_world_t *world, ecs_component_record_t *cr); /* Decrease refcount of component record, delete if 0 */ int32_t flecs_component_release( ecs_world_t *world, ecs_component_record_t *cr); /* Release all empty tables in component record */ bool flecs_component_release_tables( ecs_world_t *world, ecs_component_record_t *cr); /* Set (component) type info for component record */ bool flecs_component_set_type_info( ecs_world_t *world, ecs_component_record_t *cr, const ecs_type_info_t *ti); /* Return next (R, *) record */ ecs_component_record_t* flecs_component_first_next( ecs_component_record_t *cr); /* Return next (*, T) record */ ecs_component_record_t* flecs_component_second_next( ecs_component_record_t *cr); /* Return next traversable (*, T) record */ ecs_component_record_t* flecs_component_trav_next( ecs_component_record_t *cr); /* Return next component record, or NULL when iteration is done. */ ecs_component_record_t* flecs_components_next( const ecs_world_t *world, ecs_components_iter_t *it); /* Ensure name index for component record */ ecs_hashmap_t* flecs_component_name_index_ensure( ecs_world_t *world, ecs_component_record_t *cr); /* Get name index for component record */ ecs_hashmap_t* flecs_component_name_index_get( const ecs_world_t *world, ecs_component_record_t *cr); /* Init sparse storage */ void flecs_component_init_sparse( ecs_world_t *world, ecs_component_record_t *cr); /* Return flags for matching component records */ ecs_flags32_t flecs_id_flags_get( ecs_world_t *world, ecs_id_t id); /* Delete entities in sparse storage */ void flecs_component_delete_sparse( ecs_world_t *world, ecs_component_record_t *cr); void flecs_component_record_init_dont_fragment( ecs_world_t *world, ecs_component_record_t *cr); void flecs_component_record_init_exclusive( ecs_world_t *world, ecs_component_record_t *cr); void flecs_component_shrink( ecs_component_record_t *cr); void flecs_component_update_childof_depth( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t tgt, const ecs_record_t *tgt_record); void flecs_component_update_childof_w_depth( ecs_world_t *world, ecs_component_record_t *cr, int32_t depth); void flecs_component_ordered_children_init( ecs_world_t *world, ecs_component_record_t *cr); #endif #ifndef FLECS_TABLE_H #define FLECS_TABLE_H #ifndef FLECS_TABLE_GRAPH_H #define FLECS_TABLE_GRAPH_H /** Cache of added/removed components for non-trivial edges between tables */ #define ECS_TABLE_DIFF_INIT { .added = {0}} /** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to * conserve memory on table edges (a type doesn't have the size field), whereas * a vec for the builder is more convenient to use & has allocator support. */ typedef struct ecs_table_diff_builder_t { ecs_vec_t added; ecs_vec_t removed; ecs_flags32_t added_flags; ecs_flags32_t removed_flags; } ecs_table_diff_builder_t; /** Edge linked list (used to keep track of incoming edges) */ typedef struct ecs_graph_edge_hdr_t { struct ecs_graph_edge_hdr_t *prev; struct ecs_graph_edge_hdr_t *next; } ecs_graph_edge_hdr_t; /** Single edge. */ typedef struct ecs_graph_edge_t { ecs_graph_edge_hdr_t hdr; ecs_table_t *from; /* Edge source table */ ecs_table_t *to; /* Edge destination table */ ecs_table_diff_t *diff; /* Added/removed components for edge */ ecs_id_t id; /* Id associated with edge */ } ecs_graph_edge_t; /* Edges to other tables. */ typedef struct ecs_graph_edges_t { ecs_graph_edge_t *lo; /* Small array optimized for low edges */ ecs_map_t *hi; /* Map for hi edges (map) */ } ecs_graph_edges_t; /* Table graph node */ typedef struct ecs_graph_node_t { /* Outgoing edges */ ecs_graph_edges_t add; ecs_graph_edges_t remove; /* Incoming edges (next = add edges, prev = remove edges) */ ecs_graph_edge_hdr_t refs; } ecs_graph_node_t; /** Add to existing type */ void flecs_type_add( ecs_world_t *world, ecs_type_t *type, ecs_id_t add); /* Remove from existing type, matching entity ids by raw id only. */ void flecs_type_remove_ignoring_generation( ecs_world_t *world, ecs_type_t *type, ecs_id_t remove); /** Free type. */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type); /* Find table by removing id from current table */ ecs_table_t *flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); /* Cleanup incoming and outgoing edges for table */ void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table); /* Table diff builder, used to build id lists that indicate the difference in * ids between two tables. */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder); void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src); void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff); void flecs_table_edges_add_flags( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_flags32_t flags); 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); void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm); void flecs_table_clear_edges_for_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t component); #endif #ifdef FLECS_SANITIZE #define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ .array = (arg_column)->data,\ .count = table->data.count,\ .size = table->data.size,\ .elem_size = arg_elem_size\ } #define ecs_vec_from_column_ext(arg_column, arg_count, arg_size, arg_elem_size) {\ .array = (arg_column)->data,\ .count = arg_count,\ .size = arg_size,\ .elem_size = arg_elem_size\ } #define ecs_vec_from_entities(table) {\ .array = table->data.entities,\ .count = table->data.count,\ .size = table->data.size,\ .elem_size = ECS_SIZEOF(ecs_entity_t)\ } #else #define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ .array = (arg_column)->data,\ .count = table->data.count,\ .size = table->data.size,\ } #define ecs_vec_from_column_ext(arg_column, arg_count, arg_size, arg_elem_size) {\ .array = (arg_column)->data,\ .count = arg_count,\ .size = arg_size,\ } #define ecs_vec_from_entities(table) {\ .array = table->data.entities,\ .count = table->data.count,\ .size = table->data.size,\ } #endif #define ecs_vec_from_column_t(arg_column, table, T)\ ecs_vec_from_column(arg_column, table, ECS_SIZEOF(T)) /* Table event type for notifying tables of world events */ typedef enum ecs_table_eventkind_t { EcsTableTriggersForId, EcsTableNoTriggersForId, } ecs_table_eventkind_t; typedef struct ecs_table_event_t { ecs_table_eventkind_t kind; /* Component info event */ ecs_entity_t component; /* Event match */ ecs_entity_t event; /* If the number of fields gets out of hand, this can be turned into a union * but since events are very temporary objects, this works for now and makes * initializing an event a bit simpler. */ } ecs_table_event_t; /** Overrides (set if table overrides components) */ /* Override type used for tables with a single IsA pair */ typedef struct ecs_table_1_override_t { const ecs_pair_record_t *pair; /* Pair data for (IsA, base) */ int32_t generation; /* Reachable cache generation for IsA pair */ } ecs_table_1_override_t; /* Override type used for tables with n IsA pairs (less common) */ typedef struct ecs_table_n_overrides_t { const ecs_table_record_t *tr; /* Table record for (IsA, *) */ int32_t *generations; /* Reachable cache generations (one per IsA pair) */ } ecs_table_n_overrides_t; typedef struct ecs_table_overrides_t { union { ecs_table_1_override_t _1; ecs_table_n_overrides_t _n; } is; ecs_ref_t *refs; /* Refs to base components (one for each column) */ } ecs_table_overrides_t; /** Infrequently accessed data not stored inline in ecs_table_t */ typedef struct ecs_table__t { uint64_t hash; /* Type hash */ int32_t lock; /* Prevents modifications */ int32_t traversable_count; /* Traversable relationship targets in table */ uint16_t generation; /* Used for table cleanup */ int16_t record_count; /* Table record count including wildcards */ int16_t bs_count; int16_t bs_offset; ecs_bitset_t *bs_columns; /* Bitset columns */ struct ecs_table_record_t *records; /* Array with table records */ #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 */ typedef struct ecs_column_t { void *data; /* Array with component data */ ecs_type_info_t *ti; /* Component type info */ } ecs_column_t; /** Table data */ struct ecs_data_t { ecs_entity_t *entities; /* Entity ids */ ecs_column_t *columns; /* Component data */ ecs_table_overrides_t *overrides; /* Component overrides (for tables with IsA pairs) */ int32_t count; int32_t size; }; /** A table is the Flecs equivalent of an archetype. Tables store all entities * with a specific set of components. Tables are automatically created when an * entity has a set of components not previously observed before. When a new * table is created, it is automatically matched with existing queries */ struct ecs_table_t { uint64_t id; /* Table id in sparse set */ ecs_flags32_t flags; /* Flags for testing table properties */ int16_t column_count; /* Number of components (excluding tags) */ uint16_t version; /* Version of table */ uint64_t bloom_filter; /* For quick matching with queries */ ecs_flags32_t trait_flags; /* Cached trait flags for entities in table */ int16_t keep; /* Refcount for keeping table alive. */ int16_t childof_index; /* Quick access to index of ChildOf pair in table. */ ecs_type_t type; /* Vector with component ids */ ecs_data_t data; /* Component storage */ ecs_graph_node_t node; /* Graph node */ int16_t *component_map; /* Get column for component id */ int32_t *dirty_state; /* Keep track of changes in columns */ int16_t *column_map; /* Map type index <-> column * - 0..count(T): type index -> column * - count(T)..count(C): column -> type index */ ecs_table__t *_; /* Infrequently accessed table metadata */ }; /* Init table */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from); /** Find or create table for a set of components */ ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type); /* Reset a table to its initial state */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table); /* Add a new entry to the table for the specified entity */ void flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, bool construct, bool on_add); /* Delete an entity from the table. */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct); /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, int32_t new_index, ecs_table_t *old_table, int32_t old_index, ecs_id_t emplace_id); /* Grow table with specified number of records. Populate table with the * specified entity ids. */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, int32_t count, const ecs_entity_t *ids); /* Shrink table to contents */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table); /* Get dirty state for table columns */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table); /* Initialize root table */ void flecs_init_root_table( ecs_world_t *world); /* Remove components in table */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table); /* Free table type */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table); /* Merge data of one table into another table */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *new_table, ecs_table_t *old_table); void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component); void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_event_t *event); /* Increase traversable count of table */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value); ecs_bitset_t* flecs_table_get_toggle( ecs_table_t *table, ecs_id_t id); ecs_id_t flecs_column_id( ecs_table_t *table, int32_t column_index); uint64_t flecs_table_bloom_filter_add( uint64_t filter, uint64_t value); bool flecs_table_bloom_filter_test( const ecs_table_t *table, uint64_t filter); const ecs_ref_t* flecs_table_get_override( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, const ecs_component_record_t *cr, ecs_ref_t *storage); void flecs_table_keep( ecs_table_t *table); void flecs_table_release( ecs_table_t *table); ecs_component_record_t* flecs_table_get_childof_cr( const ecs_world_t *world, const ecs_table_t *table); ecs_pair_record_t* flecs_table_get_childof_pr( const ecs_world_t *world, const ecs_table_t *table); ecs_hashmap_t* flecs_table_get_name_index( const ecs_world_t *world, const ecs_table_t *table); #endif #ifndef FLECS_SPARSE_STORAGE_H #define FLECS_SPARSE_STORAGE_H bool flecs_component_sparse_has( ecs_component_record_t *cr, ecs_entity_t entity); void* flecs_component_sparse_get( const ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, ecs_entity_t entity); void* flecs_component_sparse_insert( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row); void* flecs_component_sparse_emplace( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row); void flecs_component_sparse_remove( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row); void flecs_component_sparse_remove_all( ecs_world_t *world, ecs_component_record_t *cr); #endif #ifndef FLECS_ORDERED_CHILDREN_H #define FLECS_ORDERED_CHILDREN_H /* Initialize ordered children storage. */ void flecs_ordered_children_init( ecs_world_t *world, ecs_component_record_t *cr); /* Free ordered children storage. */ void flecs_ordered_children_fini( ecs_world_t *world, ecs_component_record_t *cr); /* Populate ordered children storage with existing children. */ void flecs_ordered_children_populate( ecs_world_t *world, ecs_component_record_t *cr); /* Clear ordered children storage. */ void flecs_ordered_children_clear( ecs_component_record_t *cr); /* Reparent entities in ordered children storage. */ void flecs_ordered_children_reparent( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count); /* Unparent entities in ordered children storage. */ void flecs_ordered_children_unparent( ecs_world_t *world, const ecs_table_t *src, int32_t row, int32_t count); /* Reorder entities in ordered children storage. */ void flecs_ordered_children_reorder( ecs_world_t *world, ecs_entity_t parent, const ecs_entity_t *children, int32_t child_count); /* Directly add child to ordered children array. */ void flecs_ordered_entities_append( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t e); /* Promote ordered children storage to track prefab children. */ void flecs_ordered_children_set_prefab( ecs_world_t *world, ecs_component_record_t *cr); /* Directly remove child from ordered children array. */ void flecs_ordered_entities_remove( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t e); #endif #ifndef FLECS_NON_FRAGMENTING_CHILDOF_H #define FLECS_NON_FRAGMENTING_CHILDOF_H void flecs_bootstrap_parent_component( ecs_world_t *world); void flecs_on_non_fragmenting_child_move_add( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count); void flecs_on_non_fragmenting_child_move_remove( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count, bool update_parent_records); void flecs_non_fragmenting_childof_reparent( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count); void flecs_non_fragmenting_childof_unparent( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count); bool flecs_component_has_non_fragmenting_childof( ecs_component_record_t *cr); int flecs_add_non_fragmenting_child_w_records( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t entity, ecs_component_record_t *cr, const ecs_record_t *r); #endif #ifndef FLECS_QUERY_H #define FLECS_QUERY_H #ifndef FLECS_QUERY_COMPILER_H #define FLECS_QUERY_COMPILER_H #ifndef FLECS_QUERY_TYPES_H #define FLECS_QUERY_TYPES_H typedef struct ecs_query_impl_t ecs_query_impl_t; typedef uint8_t ecs_var_id_t; typedef int16_t ecs_query_lbl_t; typedef ecs_flags64_t ecs_write_flags_t; #define flecs_query_impl(query) (ECS_CONST_CAST(ecs_query_impl_t*, query)) #define EcsQueryMaxVarCount (64) #define EcsVarNone ((ecs_var_id_t)-1) #define EcsThisName "this" /* -- Variable types -- */ typedef enum { EcsVarEntity, /* Variable that stores an entity id */ EcsVarTable, /* Variable that stores a table */ EcsVarAny /* Used when requesting either entity or table var */ } ecs_var_kind_t; typedef struct ecs_query_var_t { int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */ bool anonymous; /* variable is anonymous */ ecs_var_id_t id; /* variable id */ ecs_var_id_t table_id; /* id to table variable, if any */ ecs_var_id_t base_id; /* id to base entity variable, for lookups */ const char *name; /* variable name */ const char *lookup; /* Lookup string for variable */ #ifdef FLECS_DEBUG const char *label; /* for debugging */ #endif } ecs_query_var_t; /* Placeholder values for queries with only the $this variable */ extern ecs_query_var_t flecs_this_array; 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 */ EcsQueryAndWcTgt, /* And operator for (*, T) queries */ EcsQueryTriv, /* Trivial search (batches multiple terms) */ EcsQueryCache, /* Cached search */ EcsQueryIsCache, /* Cached search for queries that are entirely cached */ EcsQueryUp, /* Up traversal */ EcsQuerySelfUp, /* Self|up traversal */ EcsQueryWith, /* Match id against fixed or variable source */ EcsQueryWithWcTgt, /* Match (*, T) id against fixed or variable source */ EcsQueryTrav, /* Support for transitive/reflexive queries */ EcsQueryAndFrom, /* AndFrom operator */ EcsQueryOrFrom, /* OrFrom operator */ EcsQueryNotFrom, /* NotFrom operator */ EcsQueryIds, /* Test for existence of ids matching wildcard */ EcsQueryIdsRight, /* Find ids in use that match (R, *) wildcard */ EcsQueryIdsLeft, /* Find ids in use that match (*, T) wildcard */ EcsQueryIdsAll, /* Find all non-pair ids in use that match (*) */ EcsQueryEach, /* Iterate entities in table, populate entity variable */ EcsQueryStore, /* Store table or entity in variable */ EcsQueryReset, /* Reset value of variable to wildcard (*) */ EcsQueryOr, /* Or operator */ EcsQueryOptional, /* Optional operator */ EcsQueryIfVar, /* Conditional execution on whether variable is set */ EcsQueryIfSet, /* Conditional execution on whether term is set */ EcsQueryNot, /* Sets iterator state after term was not matched */ EcsQueryEnd, /* End of control flow block */ EcsQueryPredEq, /* Test if variable is equal to, or assign if not set */ EcsQueryPredNeq, /* Test if variable is not equal to */ EcsQueryPredEqName, /* Same as EcsQueryPredEq but with matching by name */ EcsQueryPredNeqName, /* Same as EcsQueryPredNeq but with matching by name */ EcsQueryPredEqMatch, /* Same as EcsQueryPredEq but with fuzzy matching by name */ EcsQueryPredNeqMatch, /* Same as EcsQueryPredNeq but with fuzzy matching by name */ EcsQueryMemberEq, /* Compare member value */ EcsQueryMemberNeq, /* Compare member value */ EcsQueryToggle, /* Evaluate toggle bitset, if present */ EcsQueryToggleOption, /* Toggle for optional terms */ EcsQuerySparse, /* Evaluate sparse component */ EcsQuerySparseNot, /* Evaluate sparse component with not operator */ EcsQuerySparseSelfUp, EcsQuerySparseUp, EcsQuerySparseWith, /* Evaluate sparse component against fixed or variable source */ EcsQueryTree, EcsQueryTreeWildcard, EcsQueryTreeWith, /* Evaluate (ChildOf, tgt) against fixed or variable source */ EcsQueryTreeUp, /* Return union of up(ChildOf) and tables with Parent */ EcsQueryTreeSelfUp, EcsQueryTreePre, /* Tree instruction that doesn't filter Parent component / returns entire tables. */ EcsQueryTreePost, /* Tree instruction that applies filter to Parent component. */ EcsQueryTreeUpPre, /* Up traversal for ChildOf that doesn't filter Parent component / returns entire tables */ EcsQueryTreeSelfUpPre, /* Up traversal for ChildOf that doesn't filter Parent component / returns entire tables */ EcsQueryTreeUpPost, /* Up traversal for ChildOf that filters cached tables w/Parent component. */ EcsQueryTreeSelfUpPost, /* Up traversal for ChildOf that filters cached tables w/Parent component. */ EcsQueryChildren, /* Return children for parent, if possible in order */ EcsQueryChildrenWc, /* Return children for parents, if possible in order */ EcsQueryLookup, /* Lookup relative to variable */ EcsQuerySetVars, /* Populate it.sources from variables */ EcsQuerySetThis, /* Populate This entity variable */ EcsQuerySetFixed, /* Set fixed source entity ids */ EcsQuerySetIds, /* Set fixed (component) ids */ EcsQuerySetId, /* Set id if not set */ EcsQueryContain, /* Test if table contains entity */ EcsQueryPairEq, /* Test if both elements of pair are the same */ EcsQueryYield, /* Yield result back to application */ EcsQueryNothing /* Must be last */ } ecs_query_op_kind_t; /* Op flags to indicate if ecs_query_ref_t is an entity or variable */ #define EcsQueryIsEntity (1 << 0) #define EcsQueryIsVar (1 << 1) #define EcsQueryIsSelf (1 << 6) /* Op flags used to shift EcsQueryIsEntity and EcsQueryIsVar */ #define EcsQuerySrc 0 #define EcsQueryFirst 2 #define EcsQuerySecond 4 /* References to variable or entity */ typedef union { ecs_var_id_t var; ecs_entity_t entity; } ecs_query_ref_t; /* Query instruction */ typedef struct ecs_query_op_t { uint8_t kind; /* Instruction kind */ ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ int8_t field_index; /* Query field corresponding with operation */ int8_t term_index; /* Query term corresponding with operation */ ecs_query_lbl_t prev; /* Backtracking label (no data) */ ecs_query_lbl_t next; /* Forwarding label. Must come after prev */ ecs_query_lbl_t other; /* Misc register used for control flow */ ecs_flags16_t match_flags; /* Flags that modify matching behavior */ ecs_query_ref_t src; ecs_query_ref_t first; ecs_query_ref_t second; ecs_flags64_t written; /* Bitset with variables written by op */ } ecs_query_op_t; /* All context */ typedef struct { int32_t cur; ecs_table_record_t dummy_tr; } ecs_query_all_ctx_t; /* And context */ typedef struct { ecs_component_record_t *cr; ecs_table_cache_iter_t it; int16_t column; int16_t remaining; bool non_fragmenting; } ecs_query_and_ctx_t; /* Sparse context */ typedef struct { ecs_query_and_ctx_t and_; /* For mixed results */ ecs_sparse_t *sparse; ecs_table_range_t range; int32_t cur; bool self; bool exclusive; ecs_component_record_t *cr; ecs_table_range_t prev_range; int32_t prev_cur; } ecs_query_sparse_ctx_t; typedef enum ecs_query_tree_iter_state_t { EcsQueryTreeIterNext = 1, EcsQueryTreeIterTables = 2, EcsQueryTreeIterEntities = 3 } ecs_query_tree_iter_state_t; typedef struct { ecs_query_and_ctx_t and_; /* For mixed results */ ecs_component_record_t *cr; ecs_entity_t tgt; ecs_entity_t *entities; const EcsParent *parents; ecs_table_range_t range; int32_t cur; ecs_query_tree_iter_state_t state; } ecs_query_tree_ctx_t; typedef struct { ecs_component_record_t *cr; ecs_table_cache_iter_t it; ecs_entity_t *entities; int32_t count; int32_t cur; ecs_query_tree_iter_state_t state; } ecs_query_tree_wildcard_ctx_t; /* Down traversal cache (for resolving up queries w/unknown source) */ typedef struct { ecs_table_range_t range; bool leaf; /* Table owns and inherits id (for Up queries without Self) */ } ecs_trav_down_elem_t; typedef struct { ecs_vec_t elems; /* vector */ bool ready; } ecs_trav_down_t; typedef struct { ecs_entity_t src; ecs_id_t id; ecs_table_record_t *tr; bool ready; } ecs_trav_up_t; typedef enum { EcsTravUp = 1, EcsTravDown = 2 } ecs_trav_direction_t; typedef struct { ecs_map_t src; ecs_trav_down_t down; ecs_trav_up_t up; ecs_id_t with; ecs_trav_direction_t dir; } ecs_trav_up_cache_t; /* And up context */ typedef struct { ecs_table_t *table; int32_t row; int32_t end; ecs_entity_t trav; ecs_id_t with; ecs_id_t matched; ecs_component_record_t *cr_with; ecs_component_record_t *cr_trav; /* If the queried-for component is a ChildOf pair that uses the non-fragmenting * ChildOf storage, iterate the ordered children vector instead of tables with * ChildOf pairs as roots for the down cache. */ ecs_entity_t *entities; int32_t entities_cur; int32_t entities_count; ecs_trav_down_t *down; int32_t cache_elem; ecs_trav_up_cache_t cache; } ecs_query_up_impl_t; typedef struct { union { ecs_query_and_ctx_t and; ecs_query_sparse_ctx_t sparse_; } is; /* Indirection because otherwise the ctx struct gets too large */ ecs_query_up_impl_t *impl; /* Data for returning tables with non-fragmenting ChildOf */ const EcsParent *parents; ecs_table_range_t range; int32_t cur; } ecs_query_up_ctx_t; typedef struct { union { ecs_query_and_ctx_t and; ecs_query_up_ctx_t up_; } is; ecs_query_tree_iter_state_t state; } ecs_query_tree_pre_ctx_t; /* Cache for storing results of upward/downward "all" traversal. This type of * traversal iterates and caches the entire tree. */ typedef struct { ecs_entity_t entity; ecs_component_record_t *cr; const ecs_table_record_t *tr; } ecs_trav_elem_t; typedef struct { ecs_id_t id; ecs_component_record_t *cr; ecs_vec_t entities; bool up; } ecs_trav_cache_t; /* Trav context */ typedef struct { ecs_query_and_ctx_t and; int32_t index; int32_t offset; int32_t count; ecs_trav_cache_t cache; bool yield_reflexive; } ecs_query_trav_ctx_t; /* Eq context */ typedef struct { ecs_table_range_t range; int32_t index; int16_t name_col; bool redo; } ecs_query_eq_ctx_t; /* Each context */ typedef struct { int32_t row; } ecs_query_each_ctx_t; /* Setthis context */ typedef struct { ecs_table_range_t range; } ecs_query_setthis_ctx_t; /* Ids context */ typedef struct { ecs_component_record_t *cur; ecs_components_iter_t components_it; } ecs_query_ids_ctx_t; /* Control flow context */ typedef struct { ecs_query_lbl_t op_index; ecs_id_t field_id; bool is_set; } ecs_query_ctrl_ctx_t; /* Trivial iterator context */ typedef struct { ecs_table_cache_iter_t it; const ecs_table_record_t *tr; int32_t start_from; int32_t first_to_eval; } ecs_query_trivial_ctx_t; /* *From operator iterator context */ typedef struct { ecs_query_and_ctx_t and; ecs_entity_t type_id; ecs_type_t *type; int32_t first_id_index; int32_t cur_id_index; } ecs_query_xfrom_ctx_t; /* Member equality context */ typedef struct { ecs_query_each_ctx_t each; void *data; } ecs_query_membereq_ctx_t; /* Toggle context */ typedef struct { ecs_table_range_t range; int32_t cur; int32_t block_index; ecs_flags64_t block; ecs_termset_t prev_set_fields; bool optional_not; bool has_bitset; } ecs_query_toggle_ctx_t; /* Optional context */ typedef struct { ecs_table_range_t range; } ecs_query_optional_ctx_t; 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; ecs_query_trav_ctx_t trav; ecs_query_ids_ctx_t ids; ecs_query_eq_ctx_t eq; ecs_query_each_ctx_t each; ecs_query_setthis_ctx_t setthis; ecs_query_ctrl_ctx_t ctrl; ecs_query_trivial_ctx_t trivial; ecs_query_membereq_ctx_t membereq; ecs_query_toggle_ctx_t toggle; ecs_query_sparse_ctx_t sparse; ecs_query_tree_ctx_t tree; ecs_query_tree_pre_ctx_t tree_pre; ecs_query_tree_wildcard_ctx_t tree_wildcard; ecs_query_optional_ctx_t optional; } is; } ecs_query_op_ctx_t; typedef struct { /* Labels used for control flow */ ecs_query_lbl_t lbl_query; /* Used to find the op that does the actual searching */ ecs_query_lbl_t lbl_begin; ecs_query_lbl_t lbl_cond_eval; ecs_write_flags_t written_or; /* Written flags at start of or chain */ ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ ecs_query_ref_t src_or; /* Source for terms in current or chain */ bool src_written_or; /* Was src populated before OR chain */ bool in_or; /* Whether we're in an or chain */ } ecs_query_compile_ctrlflow_t; /* Query compiler state */ typedef struct { ecs_vec_t *ops; ecs_write_flags_t written; /* Bitmask to check which variables have been written */ ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ /* Maintain control flow per scope */ ecs_query_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; ecs_query_compile_ctrlflow_t *cur; /* Current scope */ int32_t scope; /* Nesting level of query scopes */ ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ ecs_oper_kind_t oper; /* Temp storage to track current operator for term */ int32_t skipped; /* Term skipped during compilation */ } ecs_query_compile_ctx_t; /* Query run state */ typedef struct { uint64_t *written; /* Bitset to check which variables have been written */ ecs_query_lbl_t op_index; /* Currently evaluated operation */ ecs_var_t *vars; /* Variable storage */ ecs_iter_t *it; /* Iterator */ ecs_query_op_ctx_t *op_ctx; /* Operation context (stack) */ ecs_world_t *world; /* Reference to world */ const ecs_query_impl_t *query; /* Reference to query */ const ecs_query_var_t *query_vars; /* Reference to query variable array */ ecs_query_iter_t *qit; } ecs_query_run_ctx_t; struct ecs_query_impl_t { ecs_query_t pub; /* Public query data */ ecs_stage_t *stage; /* Stage used for allocations */ /* Variables */ ecs_query_var_t *vars; /* Variables */ int32_t var_count; /* Number of variables */ int32_t var_size; /* Size of variable array */ ecs_hashmap_t tvar_index; /* Name index for table variables */ ecs_hashmap_t evar_index; /* Name index for entity variables */ ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */ /* Query plan */ ecs_query_op_t *ops; /* Operations */ int32_t op_count; /* Number of operations */ /* Misc */ int32_t tokens_len; /* Length of tokens buffer */ 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 */ /* User context */ ecs_ctx_free_t ctx_free; /* Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ /* Mixins */ flecs_poly_dtor_t dtor; }; #endif /* Compile query to list of operations */ int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query); /* Compile single term */ int flecs_query_compile_term( ecs_world_t *world, ecs_query_impl_t *query, ecs_term_t *term, ecs_query_compile_ctx_t *ctx); /* Compile term ref (first, second or src) */ void flecs_query_compile_term_ref( ecs_world_t *world, ecs_query_impl_t *query, ecs_query_op_t *op, ecs_term_ref_t *term_ref, ecs_query_ref_t *ref, ecs_flags8_t ref_kind, ecs_var_kind_t kind, ecs_query_compile_ctx_t *ctx, bool create_wildcard_vars); /* Mark variable as written */ void flecs_query_write( ecs_var_id_t var_id, uint64_t *written); /* Mark variable as written in compiler context */ void flecs_query_write_ctx( ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx, bool cond_write); /* Add operation to query plan */ ecs_query_lbl_t flecs_query_op_insert( ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx); /* Insert each instruction */ void flecs_query_insert_each( ecs_var_id_t tvar, ecs_var_id_t evar, ecs_query_compile_ctx_t *ctx, bool cond_write); /* Add discovered variable */ ecs_var_id_t flecs_query_add_var( ecs_query_impl_t *query, const char *name, ecs_vec_t *vars, ecs_var_kind_t kind); /* Find variable by name/kind */ ecs_var_id_t flecs_query_find_var_id( const ecs_query_impl_t *query, const char *name, ecs_var_kind_t kind); #endif #ifndef FLECS_QUERY_CACHE_H #define FLECS_QUERY_CACHE_H /** Table match data. * Each table matched by the query is represented by an ecs_query_cache_match_t * instance. A table may match a query multiple times (due to wildcard queries) * with different columns being matched by the query. */ typedef struct ecs_query_triv_cache_match_t { ecs_table_t *table; /* The current table. */ int16_t *columns; ecs_termset_t set_fields; /* Fields that are set (used by fields with Optional/Not). */ } ecs_query_triv_cache_match_t; struct ecs_query_cache_match_t { ecs_query_triv_cache_match_t base; const ecs_table_record_t **_trs; int32_t _offset; /* Starting point in table. */ int32_t _count; /* Number of entities to iterate in table. */ ecs_id_t *_ids; /* Resolved (component) ids for current table. */ ecs_entity_t *_sources; /* Sources of ids. */ ecs_termset_t _up_fields; /* Fields that are matched through traversal. */ int32_t *_monitor; /* Used to monitor table for changes. */ int32_t rematch_count; /* Track whether table was rematched. */ ecs_vec_t *wildcard_matches; /* Additional matches for table for wildcard queries. */ }; /** Query group */ struct ecs_query_cache_group_t { ecs_vec_t tables; /* vec */ ecs_query_group_info_t info; /* Group info available to application. */ ecs_query_cache_group_t *next; /* Next group to iterate (only set for queries with group_by). */ }; /** Table record type for query table cache. A query only has one per table. */ typedef struct ecs_query_cache_table_t { ecs_query_cache_group_t *group; /* Group the table is added to. */ int32_t index; /* Index into group->tables. */ } ecs_query_cache_table_t; /* Query level block allocators have sizes that depend on query field count */ typedef struct ecs_query_cache_allocators_t { ecs_block_allocator_t pointers; ecs_block_allocator_t ids; ecs_block_allocator_t monitors; ecs_block_allocator_t columns; } ecs_query_cache_allocators_t; /** Query that is automatically matched against tables */ typedef struct ecs_query_cache_t { /* Uncached query used to populate the cache */ ecs_query_t *query; /* Observer to keep the cache in sync */ ecs_observer_t *observer; /* Tables matched with query */ ecs_map_t tables; /* Query groups, if group_by is used */ ecs_map_t groups; /* Default query group */ ecs_query_cache_group_t default_group; /* Groups in iteration order */ ecs_query_cache_group_t *first_group; /* Table sorting */ ecs_entity_t order_by; ecs_order_by_action_t order_by_callback; ecs_sort_table_action_t order_by_table_callback; ecs_vec_t table_slices; int32_t order_by_term; /* Table grouping */ ecs_entity_t group_by; ecs_group_by_action_t group_by_callback; ecs_group_create_action_t on_group_create; ecs_group_delete_action_t on_group_delete; void *group_by_ctx; ecs_ctx_free_t group_by_ctx_free; /* Monitor generation */ int32_t monitor_generation; int32_t cascade_by; /* Identify cascade term */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ ecs_entity_t entity; /* Entity associated with query */ /* Zeroed-out sources array, used for results that only match on $this */ ecs_entity_t *sources; /* Map field indices from cache query to actual query */ int8_t *field_map; /* Query-level allocators */ ecs_query_cache_allocators_t allocators; } ecs_query_cache_t; ecs_query_cache_t* flecs_query_cache_init( ecs_query_impl_t *impl, const ecs_query_desc_t *desc); void flecs_query_cache_fini( ecs_query_impl_t *impl); void flecs_query_cache_sort_tables( ecs_world_t *world, ecs_query_impl_t *impl); void flecs_query_cache_build_sorted_tables( ecs_query_cache_t *cache); bool flecs_query_cache_is_trivial( const ecs_query_cache_t *cache); ecs_size_t flecs_query_cache_elem_size( const ecs_query_cache_t *cache); #ifndef FLECS_QUERY_CACHE_ITER_H #define FLECS_QUERY_CACHE_ITER_H void flecs_query_cache_iter_init( ecs_iter_t *it, ecs_query_iter_t *qit, ecs_query_impl_t *impl); /* Cache search */ bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache search where entire query is cached */ bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test */ bool flecs_query_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test where entire query is cached */ bool flecs_query_is_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); bool flecs_query_is_trivial_cache_search( const ecs_query_run_ctx_t *ctx); bool flecs_query_is_trivial_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); #endif #ifndef FLECS_QUERY_GROUP_H #define FLECS_QUERY_GROUP_H ecs_query_cache_group_t* flecs_query_cache_get_group( const ecs_query_cache_t *cache, uint64_t group_id); ecs_query_cache_match_t* flecs_query_cache_add_table( ecs_query_cache_t *cache, ecs_table_t *table); ecs_query_cache_match_t* flecs_query_cache_ensure_table( ecs_query_cache_t *cache, ecs_table_t *table); void flecs_query_cache_remove_table( ecs_query_cache_t *cache, ecs_table_t *table); void flecs_query_cache_remove_all_tables( ecs_query_cache_t *cache); ecs_query_cache_table_t* flecs_query_cache_get_table( const ecs_query_cache_t *cache, ecs_table_t *table); ecs_query_cache_match_t* flecs_query_cache_match_from_table( const ecs_query_cache_t *cache, const ecs_query_cache_table_t *qt); #endif #ifndef FLECS_QUERY_MATCH_H #define FLECS_QUERY_MATCH_H void flecs_query_cache_match_fini( ecs_query_cache_t *cache, ecs_query_cache_match_t *qm); bool flecs_query_cache_match_next( ecs_query_cache_t *cache, ecs_iter_t *it); #endif #ifndef FLECS_QUERY_CHANGE_DETECTION_H #define FLECS_QUERY_CHANGE_DETECTION_H void flecs_query_sync_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *match); void flecs_query_mark_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it); bool flecs_query_check_table_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *qm, int32_t term); void flecs_query_mark_fixed_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it); bool flecs_query_update_fixed_monitor( ecs_query_impl_t *impl); #endif #endif #ifndef FLECS_QUERY_ENGINE_H #define FLECS_QUERY_ENGINE_H #ifndef FLECS_QUERY_TRAV_CACHE_H #define FLECS_QUERY_TRAV_CACHE_H /* Traversal cache for transitive queries. Finds all reachable entities by * following a relationship. */ /* Find all entities when traversing downwards */ void flecs_query_get_trav_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity); /* Find all entities when traversing upwards */ void flecs_query_get_trav_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table); /* Free traversal cache */ void flecs_query_trav_cache_fini( ecs_allocator_t *a, ecs_trav_cache_t *cache); /* Traversal caches for up traversal. Enables searching upwards until an entity * with the queried-for id has been found. */ /* Traverse downwards from starting entity to find all tables for which the * specified entity is the source of the queried-for id ('with'). */ ecs_trav_down_t* flecs_query_get_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity, ecs_component_record_t *cr_with, bool self, bool empty); /* Free down traversal cache */ void flecs_query_down_cache_fini( ecs_allocator_t *a, ecs_trav_up_cache_t *cache); ecs_trav_up_t* flecs_query_get_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_table_t *table, int32_t row, ecs_id_t with, ecs_entity_t trav, ecs_component_record_t *cr_with, ecs_component_record_t *cr_trav); /* Free up traversal cache */ void flecs_query_up_cache_fini( ecs_trav_up_cache_t *cache); #endif #ifndef FLECS_QUERY_TRIVIAL_ITER_H #define FLECS_QUERY_TRIVIAL_ITER_H /* Iterator for queries with trivial terms. */ bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t field_set); /* Iterator for queries with only trivial terms. */ bool flecs_query_is_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo); /* Trivial test for constrained $this. */ bool flecs_query_trivial_test( const ecs_query_run_ctx_t *ctx, bool first, ecs_flags64_t field_set); #endif /* Query evaluation utilities */ void flecs_query_set_iter_this( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx); ecs_query_op_ctx_t* flecs_op_ctx_( const ecs_query_run_ctx_t *ctx); #define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) void flecs_query_op_ctx_fini( ecs_iter_t *it, const ecs_query_op_t *op, ecs_query_op_ctx_t *ctx); void flecs_reset_source_set_flag( ecs_iter_t *it, int32_t field_index); void flecs_set_source_set_flag( ecs_iter_t *it, int32_t field_index); ecs_table_range_t flecs_query_var_get_range( int32_t var_id, const ecs_query_run_ctx_t *ctx); ecs_table_t* flecs_query_var_get_table( int32_t var_id, const ecs_query_run_ctx_t *ctx); ecs_table_t* flecs_query_get_table( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx); ecs_table_range_t flecs_query_get_range( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx); ecs_entity_t flecs_query_var_get_entity( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx); void flecs_query_var_reset( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx); void flecs_query_var_set_range( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx); void flecs_query_src_set_single( const ecs_query_op_t *op, int32_t row, const ecs_query_run_ctx_t *ctx); void flecs_query_src_set_range( const ecs_query_op_t *op, const ecs_table_range_t *range, const ecs_query_run_ctx_t *ctx); void flecs_query_var_set_entity( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_entity_t entity, const ecs_query_run_ctx_t *ctx); void flecs_query_set_vars( const ecs_query_op_t *op, ecs_id_t id, const ecs_query_run_ctx_t *ctx); ecs_table_range_t flecs_get_ref_range( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx); ecs_entity_t flecs_get_ref_entity( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx); ecs_id_t flecs_query_op_get_id_w_written( const ecs_query_op_t *op, uint64_t written, const ecs_query_run_ctx_t *ctx); ecs_id_t flecs_query_op_get_id( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx); int16_t flecs_query_next_column( ecs_table_t *table, ecs_id_t id, int32_t column); void flecs_query_it_set_tr( ecs_iter_t *it, int32_t field_index, const ecs_table_record_t *tr); void flecs_query_set_match( const ecs_query_op_t *op, ecs_table_t *table, int32_t column, const ecs_query_run_ctx_t *ctx); void flecs_query_set_trav_match( const ecs_query_op_t *op, const ecs_table_record_t *tr, ecs_entity_t trav, ecs_entity_t second, const ecs_query_run_ctx_t *ctx); bool flecs_query_table_filter( ecs_table_t *table, ecs_query_lbl_t other, ecs_flags32_t filter_mask); bool flecs_query_setids( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_run_until( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last); /* And evaluation */ bool flecs_query_and( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_and_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); /* Select evaluation */ bool flecs_query_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_select_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_filter); bool flecs_query_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_select_w_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_id_t id, ecs_flags32_t filter_mask); /* Sparse evaluation */ bool flecs_query_sparse( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_sparse_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_mask); bool flecs_query_sparse_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool not); bool flecs_query_sparse_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_sparse_self_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); /* Hierarchy evaluation */ const EcsParent* flecs_query_tree_get_parents( ecs_table_range_t range); bool flecs_query_tree_and( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_tree_and_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool bulk_return); bool flecs_query_tree_pre( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_tree_post( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_tree_up_pre( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool self); bool flecs_query_tree_up_post( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool self); bool flecs_query_tree_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_children( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); /* Toggle evaluation */ bool flecs_query_toggle( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_toggle_option( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Equality predicate evaluation */ bool flecs_query_pred_eq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_neq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_eq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_neq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Component member evaluation */ bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Up traversal */ typedef enum ecs_query_up_select_trav_kind_t { FlecsQueryUpSelectUp, FlecsQueryUpSelectSelfUp } ecs_query_up_select_trav_kind_t; typedef enum ecs_query_up_select_kind_t { FlecsQueryUpSelectDefault, FlecsQueryUpSelectId, FlecsQueryUpSelectSparse } ecs_query_up_select_kind_t; bool flecs_query_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_self_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind); bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_self_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); /* Transitive relationship traversal */ bool flecs_query_trav( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); #endif #ifndef FLECS_QUERY_UTIL_H #define FLECS_QUERY_UTIL_H /* Helper type for passing around context required for error messages */ typedef struct { const ecs_world_t *world; const ecs_query_desc_t *desc; ecs_query_t *query; ecs_term_t *term; int32_t term_index; } ecs_query_validator_ctx_t; /* Fill out all term fields, check for consistency. */ int flecs_term_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx); /* Convert integer to label */ ecs_query_lbl_t flecs_itolbl( int64_t val); /* Convert integer to variable id */ ecs_var_id_t flecs_itovar( int64_t val); /* Convert unsigned integer to variable id */ ecs_var_id_t flecs_utovar( uint64_t val); /* Get name for term ref */ const char* flecs_term_ref_var_name( ecs_term_ref_t *ref); /* Is term ref wildcard */ bool flecs_term_ref_is_wildcard( ecs_term_ref_t *ref); /* Does term use builtin predicates (eq, neq, ...) */ bool flecs_term_is_builtin_pred( ecs_term_t *term); /* Does term have fixed id */ bool flecs_term_is_fixed_id( ecs_query_t *q, ecs_term_t *term); /* Is term part of OR chain */ bool flecs_term_is_or( const ecs_query_t *q, const ecs_term_t *term); /* Get ref flags (IsEntity or IsVar) for ref (Src, First, Second) */ ecs_flags16_t flecs_query_ref_flags( ecs_flags16_t flags, ecs_flags16_t kind); /* Check if variable is written */ bool flecs_query_is_written( ecs_var_id_t var_id, uint64_t written); /* Check if ref is written (calls flecs_query_is_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); /* Get allocator from iterator */ ecs_allocator_t* flecs_query_get_allocator( const ecs_iter_t *it); /* Convert instruction kind to string */ const char* flecs_query_op_str( uint16_t kind); /* Convert term to string */ void flecs_term_to_buf( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf, int32_t t); /* Apply iterator flags from query */ void flecs_query_apply_iter_flags( ecs_iter_t *it, const ecs_query_t *query); ecs_id_t flecs_query_iter_set_id( ecs_iter_t *it, int8_t field, ecs_id_t id); #endif #ifdef FLECS_DEBUG #define flecs_set_var_label(var, lbl) (var)->label = lbl #else #define flecs_set_var_label(var, lbl) #endif /* Fast function for finalizing simple queries */ bool flecs_query_finalize_simple( ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc); /* Finalize query data & validate */ int flecs_query_finalize_query( ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc); /* Copy terms, sizes and ids arrays from stack to heap */ void flecs_query_copy_arrays( ecs_query_t *q); /* Internal function for creating an iterator, doesn't run aperiodic tasks */ ecs_iter_t flecs_query_iter( const ecs_world_t *world, const ecs_query_t *q); int flecs_query_trivial_has_range( const ecs_query_t *q, ecs_iter_t *it, const ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count); /* Internal function for initializing an iterator after vars are constrained */ void flecs_query_iter_constrain( ecs_iter_t *it); /* Rematch query after cache could have been invalidated */ void flecs_query_rematch( ecs_world_t *world, ecs_query_t *q); /* Reclaim memory from queries */ void flecs_query_reclaim( ecs_query_t *query); #endif #ifndef FLECS_COMPONENT_ACTIONS_H #define FLECS_COMPONENT_ACTIONS_H /* Invoke component hook. */ void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, const ecs_component_record_t *cr, 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); /* Invoke replace hook. */ void flecs_invoke_replace_hook( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_id_t id, const void *old_ptr, const void *new_ptr, const ecs_type_info_t *ti); /* Add action for sparse components. */ bool flecs_sparse_on_add( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *added, ecs_id_t emplace_id); /* Add action for single sparse component. */ bool flecs_sparse_on_add_cr( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr, bool construct, void **ptr_out); /* Run actions for creating new entity in table. */ void flecs_actions_new( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_table_diff_t *diff, ecs_flags32_t flags, bool sparse, ecs_id_t emplace_id); /* Run actions for deleting an entity and its children. */ void flecs_actions_delete_tree( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_table_diff_t *diff); /* Run actions for added components in table move. */ void flecs_actions_move_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, bool sparse, ecs_id_t emplace_id, bool update_parent_records); /* Run actions for removed components in table move. */ void flecs_actions_move_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); /* Run on_set actions. */ void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id, bool invoke_hook); /* Same as flecs_notify_on_set but for multiple component ids. */ void flecs_notify_on_set_ids( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *ids); #endif #ifndef FLECS_ENTITY_NAME_H #define FLECS_ENTITY_NAME_H /* Called during bootstrap to register entity name observers with world. */ void flecs_bootstrap_entity_name( ecs_world_t *world); /* Update lookup index for entity names. */ void flecs_reparent_name_index( ecs_world_t *world, ecs_table_t *dst, ecs_table_t *src, int32_t offset, int32_t count); void flecs_unparent_name_index( ecs_world_t *world, ecs_table_t *src, ecs_table_t *dst, int32_t offset, int32_t count); /* Hook (on_set/on_remove) for updating lookup index for entity names. */ void ecs_on_set(EcsIdentifier)( ecs_iter_t *it); #endif #ifndef FLECS_COMMANDS_H #define FLECS_COMMANDS_H /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { EcsCmdClone, EcsCmdBulkNew, EcsCmdAdd, EcsCmdRemove, EcsCmdSet, EcsCmdSetDontFragment, EcsCmdEmplace, EcsCmdEnsure, EcsCmdEnsureDontFragment, EcsCmdModified, EcsCmdModifiedNoHook, EcsCmdAddModified, EcsCmdPath, EcsCmdDelete, EcsCmdClear, EcsCmdOnDeleteAction, EcsCmdEnable, EcsCmdDisable, EcsCmdEvent, EcsCmdSkip } ecs_cmd_kind_t; /* Entity specific metadata for command in queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / ensure) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_cmd_entry_t *entry; ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; ecs_entity_t system; /* System that enqueued the command */ } ecs_cmd_t; /** Callback used to capture commands of a frame */ typedef void (*ecs_on_commands_action_t)( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx); /* Initialize command queue data structure for stage. */ void flecs_commands_init( ecs_stage_t *stage, ecs_commands_t *cmd); /* Free command queue data structure for stage. */ void flecs_commands_fini( ecs_stage_t *stage, ecs_commands_t *cmd); /* Begin deferring, or return whether already deferred. */ bool flecs_defer_cmd( ecs_stage_t *stage); /* Begin deferred mode. */ bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage); /* Purge command queue without executing commands. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage); /* Insert modified command. */ bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component); /* Insert clone command. */ bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value); /* Insert bulk_new command. */ 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); /* Insert path command (sets entity path name). */ bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name); /* Insert delete command. */ bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity); /* Insert clear command. */ bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity); /* Insert delete_with/remove_all command. */ bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action); /* Insert enable command (component toggling). */ bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component, bool enable); /* Insert add component command. */ bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); /* Insert remove component command. */ bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); void* flecs_defer_emplace( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, bool *is_new); void* flecs_defer_ensure( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size); /* Insert set component command. */ void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value); void* flecs_defer_cpp_set( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, const void *value); void* flecs_defer_cpp_assign( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, const void *value); /* Insert event command. */ void flecs_enqueue( ecs_world_t *world, ecs_stage_t *stage, ecs_event_desc_t *desc); #endif #ifndef FLECS_ENTITY_H #define FLECS_ENTITY_H #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));\ } typedef struct { const ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; flecs_component_ptr_t flecs_ensure( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, ecs_record_t *r, ecs_size_t size); flecs_component_ptr_t flecs_get_mut( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r, ecs_size_t size); /* Get component pointer. */ void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr); /* Create new entity. */ ecs_entity_t flecs_new_id( const ecs_world_t *world); /* Create new entities in bulk. */ 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); /* Add new entity id to root table. */ void flecs_add_to_root_table( ecs_world_t *world, ecs_entity_t e); /* Add a flag to an entity record (e.g. EcsEntityIsTraversable). */ void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag); /* Same as flecs_add_flag but for ecs_record_t. */ void flecs_record_add_flag( ecs_record_t *record, uint32_t flag); /* Get entity that should be used for OneOf constraint from relationship. */ ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e); /* Compute relationship depth for table. */ int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table); /* Get component from base entity (follows IsA relationship). */ void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_component_record_t *table_index, int32_t recur_depth); /* Commit entity to (new) table. */ 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, ecs_id_t emplace_id, ecs_flags32_t evt_flags); /* Add multiple component ids to entity. */ void flecs_add_ids( ecs_world_t *world, ecs_entity_t entity, ecs_id_t *ids, int32_t count); /* Like regular modified, but doesn't assert if entity doesn't have component. */ void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, bool invoke_hook); /* Like regular set, but uses move instead of copy. */ 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); /* Add single component id. */ void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id); /* Remove single component id. */ void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id); /* Run on delete action. */ void flecs_on_delete( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id, bool force_delete); /* Remove non-fragmenting components from entity. */ void flecs_entity_remove_non_fragmenting( ecs_world_t *world, ecs_entity_t e, ecs_record_t *r); ecs_table_range_t flecs_range_from_entity( const ecs_world_t *world, ecs_entity_t e); 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); const char* flecs_entity_invalid_reason( const ecs_world_t *world, ecs_entity_t entity); #endif #ifndef FLECS_INSTANTIATE_H #define FLECS_INSTANTIATE_H typedef struct ecs_instantiate_ctx_t { ecs_entity_t root_prefab; ecs_entity_t root_instance; } ecs_instantiate_ctx_t; void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance, const ecs_instantiate_ctx_t *ctx, int32_t depth); void flecs_instantiate_dont_fragment( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance); void flecs_instantiate_sparse( ecs_world_t *world, const ecs_table_range_t *base_child_range, const ecs_entity_t *base_children, ecs_table_t *instance_table, const ecs_entity_t *instance_children, int32_t row_offset, bool emit_non_sparse); ecs_entity_t flecs_instantiate_alloc_child_id( ecs_world_t *world, ecs_entity_t prefab_child, ecs_entity_t root_prefab, ecs_entity_t root_instance); #endif #ifndef FLECS_OBSERVABLE_H #define FLECS_OBSERVABLE_H /** All observers for a specific (component) id */ typedef struct ecs_event_id_record_t { /* Observers for Self */ ecs_map_t self; /* map */ ecs_map_t self_up; /* map */ ecs_map_t up; /* map */ /* Number of active observers for (component) id */ int32_t observer_count; } ecs_event_id_record_t; typedef struct ecs_observer_impl_t { ecs_observer_t pub; int32_t *last_event_id; /**< Last handled event id */ int32_t last_event_id_storage; ecs_flags32_t flags; /**< Observer flags */ int8_t term_index; /**< Index of the term in parent observer (single term observers only) */ ecs_id_t register_id; /**< Id observer is registered with (single term observers only) */ uint64_t id; /**< Internal id (not entity id) */ ecs_vec_t children; /**< If multi observer, vector stores child observers */ ecs_query_t *not_query; /**< Query used to populate observer data when a term with a not operator triggers. */ /* Mixins */ flecs_poly_dtor_t dtor; } ecs_observer_impl_t; #define flecs_observer_impl(observer) (ECS_CONST_CAST(ecs_observer_impl_t*, observer)) /* Get event record (all observers for an event). */ ecs_event_record_t* flecs_event_record_get( const ecs_observable_t *o, ecs_entity_t event); /* Get or create event record. */ ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event); /* Get event id record (all observers for an event/component). */ ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id); /* Get or create event id record. */ ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id); /* Remove event id record. */ void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id); /* Initialize observable (typically the world). */ void flecs_observable_init( ecs_observable_t *observable); /* Free observable. */ void flecs_observable_fini( ecs_observable_t *observable); /* Check if any observers exist for event/component. */ bool flecs_observers_exist( const ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event); /* Initialize observer. */ ecs_observer_t* flecs_observer_init( ecs_world_t *world, ecs_entity_t entity, const ecs_observer_desc_t *desc); /* Free observer. */ void flecs_observer_fini( ecs_observer_t *observer); /* Emit event. */ void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_event_desc_t *desc); /* Default function to set in iter::next */ bool flecs_default_next_callback( ecs_iter_t *it); /* Invoke observers. */ void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav); /* Invalidate reachable cache. */ void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count); /* Invalidate reachable cache for traversable relationships targeting cr. */ void flecs_emit_propagate_invalidate_tables( ecs_world_t *world, ecs_component_record_t *tgt_cr); /* Set bit indicating that observer is disabled. */ void flecs_observer_set_disable_bit( ecs_world_t *world, ecs_entity_t e, ecs_flags32_t bit, bool cond); #endif #ifndef FLECS_ITER_H #define FLECS_ITER_H /* Initialize iterator. */ void flecs_iter_init( const ecs_world_t *world, ecs_iter_t *it, bool alloc_resources); /* Slow path for ecs_field when field is not a $this column. */ void* flecs_field_shared( const ecs_iter_t *it, size_t size, int8_t index); /* Free iterator memory block. */ void flecs_iter_free( void *ptr, ecs_size_t size); /* Allocate zero initialized memory from iterator allocator. */ void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align); #define flecs_iter_calloc_t(it, T)\ flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_iter_calloc_n(it, T, count)\ flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) #define flecs_iter_free_t(ptr, T)\ flecs_iter_free(ptr, ECS_SIZEOF(T)) #define flecs_iter_free_n(ptr, T, count)\ flecs_iter_free(ptr, ECS_SIZEOF(T) * count) #endif #ifndef FLECS_POLY_H #define FLECS_POLY_H /* Tags associated with poly for (Poly, tag) components */ #define ecs_world_t_tag invalid #define ecs_stage_t_tag invalid #define ecs_query_t_tag EcsQuery #define ecs_observer_t_tag EcsObserver /* Mixin kinds */ typedef enum ecs_mixin_kind_t { EcsMixinWorld, EcsMixinEntity, EcsMixinObservable, EcsMixinDtor, EcsMixinMax } ecs_mixin_kind_t; /* The mixin array contains offsets to mixin members for different kinds of * flecs objects. This allows the API to retrieve data from an object regardless * of its type. Each mixin array is only stored once per type */ struct ecs_mixins_t { const char *type_name; /* Include name of mixin so debug code doesn't * need to know about every object */ ecs_size_t elems[EcsMixinMax]; }; /* Mixin tables */ extern ecs_mixins_t ecs_world_t_mixins; extern ecs_mixins_t ecs_stage_t_mixins; extern ecs_mixins_t ecs_query_t_mixins; extern ecs_mixins_t ecs_observer_t_mixins; /* Types that have no mixins */ #define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) /* Initialize poly */ void* flecs_poly_init_( ecs_poly_t *object, int32_t kind, ecs_size_t size, ecs_mixins_t *mixins); #define flecs_poly_init(object, type)\ flecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) /* Deinitialize object for specified type */ void flecs_poly_fini_( ecs_poly_t *object, int32_t kind); #define flecs_poly_fini(object, type)\ flecs_poly_fini_(object, type##_magic) /* Utility functions for creating an object on the heap */ #define flecs_poly_new(type)\ (type*)flecs_poly_init(ecs_os_calloc_t(type), type) #define flecs_poly_free(obj, type)\ flecs_poly_fini(obj, type);\ ecs_os_free(obj) /* Get or create poly component for an entity */ EcsPoly* flecs_poly_bind_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_bind(world, entity, T) \ flecs_poly_bind_(world, entity, T##_tag) /* Send modified event for (Poly, Tag) pair. */ void flecs_poly_modified_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_modified(world, entity, T) \ flecs_poly_modified_(world, entity, T##_tag) /* Get (Poly, Tag) poly object from entity. */ ecs_poly_t* flecs_poly_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_get(world, entity, T) \ ((T*)flecs_poly_get_(world, entity, T##_tag)) /* Utilities for testing/asserting an object type */ #ifndef FLECS_NDEBUG #define flecs_poly_assert(object, ty)\ do {\ ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\ const ecs_header_t *hdr = (const ecs_header_t *)object;\ const char *type_name = hdr->mixins->type_name;\ ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, "%s", type_name);\ } while (0) #else #define flecs_poly_assert(object, ty) #endif /* Get observable mixin from poly object. */ ecs_observable_t* flecs_get_observable( const ecs_poly_t *object); /* Get dtor mixin from poly object. */ flecs_poly_dtor_t* flecs_get_dtor( const ecs_poly_t *poly); #endif #ifndef FLECS_TREE_SPAWNER_H #define FLECS_TREE_SPAWNER_H /* Called during bootstrap to register spawner entities with the world. */ void flecs_bootstrap_spawner( ecs_world_t *world); EcsTreeSpawner* flecs_prefab_spawner_build( ecs_world_t *world, ecs_entity_t base); #ifdef FLECS_DEBUG void flecs_tree_spawner_assert_not_instantiated( ecs_world_t *world, ecs_entity_t parent); #else #define flecs_tree_spawner_assert_not_instantiated(world, parent) #endif void flecs_spawner_instantiate( ecs_world_t *world, EcsTreeSpawner *spawner, ecs_entity_t base, ecs_entity_t instance, const ecs_instantiate_ctx_t *ctx); void flecs_fini_tree_spawners( ecs_world_t *world); #endif #ifndef FLECS_STAGE_H #define FLECS_STAGE_H /* Stage-level allocators are for operations that can be multithreaded */ typedef struct ecs_stage_allocators_t { ecs_stack_t iter_stack; ecs_block_allocator_t cmd_entry_chunk; ecs_block_allocator_t query_impl; ecs_block_allocator_t query_cache; } ecs_stage_allocators_t; /** A stage is a context that allows for safely using the API from multiple * threads. Stage pointers can be passed to the world argument of API * operations, which causes the operation to be run on the stage instead of the * world. The features provided by a stage are: * * - A command queue for deferred ECS operations and events * - Thread-specific allocators * - Thread-specific world state (like current scope, with, current system) * - Thread-specific buffers for preventing allocations */ struct ecs_stage_t { ecs_header_t hdr; /* Unique id that identifies the stage */ int32_t id; /* Zero if not deferred, positive if deferred, negative if suspended */ int32_t defer; /* Command queue */ ecs_commands_t *cmd; ecs_commands_t cmd_stack[2]; /* Two so we can flush one & populate the other */ bool cmd_flushing; /* Ensures only one defer_end call flushes */ /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when used as a thread stage */ ecs_world_t *world; /* Reference to world */ ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_vec_t post_frame_actions; /* Namespacing */ ecs_entity_t scope; /* Entity of current scope */ ecs_entity_t with; /* Id to add by default to new entities */ ecs_entity_t base; /* Currently instantiated top-level base */ const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Running system */ ecs_entity_t system; /* Thread-specific allocators */ ecs_stage_allocators_t allocators; ecs_allocator_t allocator; /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; }; /* Post-frame merge actions. */ void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage); /* Set system id for debugging which system inserted which commands. */ ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system); /* Get allocator from stage/world. */ ecs_allocator_t* flecs_stage_get_allocator( ecs_world_t *world); /* Get stack allocator from stage/world. */ ecs_stack_t* flecs_stage_get_stack_allocator( ecs_world_t *world); /* Shrink memory for stage data structures. */ void ecs_stage_shrink( ecs_stage_t *stage); #endif #ifndef FLECS_WORLD_H #define FLECS_WORLD_H /* 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) /* World-level allocators are for operations that are not multithreaded */ typedef struct ecs_world_allocators_t { ecs_block_allocator_t graph_edge_lo; ecs_block_allocator_t graph_edge; ecs_block_allocator_t component_record; ecs_block_allocator_t pair_record; ecs_block_allocator_t table_diff; ecs_block_allocator_t sparse_chunk; /* Temporary vectors used for creating table diff id sequences */ ecs_table_diff_builder_t diff_builder; /* Temporary vector for tree spawner */ ecs_vec_t tree_spawner; } ecs_world_allocators_t; /* Component monitor */ typedef struct ecs_monitor_t { ecs_vec_t queries; /* vector */ bool is_dirty; /* Should queries be rematched? */ } ecs_monitor_t; /* Component monitors */ typedef struct ecs_monitor_set_t { ecs_map_t monitors; /* map */ bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { ecs_component_record_t *cr; ecs_id_t id; ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; } ecs_marked_id_t; typedef struct ecs_store_t { /* Entity lookup */ ecs_entity_index_t entity_index; /* Tables */ ecs_sparse_t tables; /* sparse */ /* Table lookup by hash */ ecs_hashmap_t table_map; /* hashmap */ /* Root table */ ecs_table_t root; /* Records cache */ ecs_vec_t records; /* Stack of ids being deleted during cleanup action. */ ecs_vec_t marked_ids; /* vector */ /* Components deleted during cleanup action. Used to delay cleaning up of * type info so it's guaranteed that this data is available while the * storage is cleaning up tables. */ ecs_vec_t deleted_components; /* vector */ } ecs_store_t; /* fini actions */ typedef struct ecs_action_elem_t { ecs_fini_action_t action; void *ctx; } ecs_action_elem_t; typedef struct ecs_pipeline_state_t ecs_pipeline_state_t; /** The world stores and manages all ECS data. An application can have more than * one world, but data is not shared between worlds. */ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ ecs_component_record_t **id_index_lo; ecs_map_t id_index_hi; /* map */ ecs_map_t type_info; /* map */ #ifdef FLECS_DEBUG /* Locked components. When a component is queried for, it is no longer * possible to change traits and/or to delete the component. */ ecs_map_t locked_components; /* map */ /* Locked entities. This is used for pair targets used in queries. It is * possible to add traits, but entities cannot be deleted. */ ecs_map_t locked_entities; /* map */ #endif /* -- Cached handle to id records -- */ ecs_component_record_t *cr_wildcard; ecs_component_record_t *cr_wildcard_wildcard; ecs_component_record_t *cr_any; ecs_component_record_t *cr_isa_wildcard; ecs_component_record_t *cr_childof_0; ecs_component_record_t *cr_childof_wildcard; ecs_component_record_t *cr_identifier_name; /* Head of list that points to all non-fragmenting component ids */ ecs_component_record_t *cr_non_fragmenting_head; /* -- Mixins -- */ ecs_world_t *self; ecs_observable_t observable; /* 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]; /* Array for checking if components can be looked up trivially */ ecs_flags8_t non_trivial_lookup[FLECS_HI_COMPONENT_ID]; /* Array for checking if components can be set trivially */ ecs_flags8_t non_trivial_set[FLECS_HI_COMPONENT_ID]; /* -- Data storage -- */ ecs_store_t store; /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ ecs_stage_t **stages; /* Stages */ int32_t stage_count; /* Number of stages */ /* -- Component ids -- */ ecs_vec_t component_ids; /* World local component ids */ /* Index of prefab children in ordered children vector. Used by ecs_get_target. */ ecs_map_t prefab_child_indices; /* Internal callback for command inspection. Only one callback can be set at * a time. After assignment, the action will become active at the start of * the next frame, set by ecs_frame_begin, and will be reset by * ecs_frame_end. */ ecs_on_commands_action_t on_commands; ecs_on_commands_action_t on_commands_active; void *on_commands_ctx; void *on_commands_ctx_active; /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ /* -- Exclusive access -- */ ecs_os_thread_id_t exclusive_access; /* If set, world can only be mutated by thread */ const char *exclusive_thread_name; /* Name of thread with exclusive access (used for debugging) */ /* -- Time management -- */ ecs_time_t world_start_time; /* Timestamp of simulation start */ ecs_time_t frame_start_time; /* Timestamp of frame start */ ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ /* -- Metrics -- */ ecs_world_info_t info; /* -- World flags -- */ ecs_flags32_t flags; /* -- Default query flags -- */ ecs_flags32_t default_query_flags; /* Count that increases when component monitors change */ int32_t monitor_generation; /* -- Allocators -- */ ecs_world_allocators_t allocators; /* Static allocation sizes */ ecs_allocator_t allocator; /* Dynamic allocation sizes */ void *ctx; /* Application context */ void *binding_ctx; /* Binding-specific context */ ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ }; /* Get current stage. */ ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr); /* Get current thread-specific stage from readonly world. */ ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world); /* Get or create component callbacks. */ ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component); /* Initialize type info for component. */ bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li); #define flecs_type_info_init(world, T, ...)\ flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ &(ecs_type_hooks_t)__VA_ARGS__) /* Free type info for component id. */ void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component); /* Check component monitors (triggers query cache revalidation, not related to * EcsMonitor). */ void flecs_eval_component_monitors( ecs_world_t *world); /* Register component monitor. */ void flecs_monitor_register( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query); /* Unregister component monitor. */ void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query); /* Update component monitors for added/removed components. */ void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed); /* Notify tables with component of event (or all tables if id is 0). */ void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event); /* Increase table version (used for invalidating ecs_ref_t's). */ void flecs_increment_table_version( ecs_world_t *world, ecs_table_t *table); /* Get table version. */ uint32_t flecs_get_table_version_fast( const ecs_world_t *world, const uint64_t table_id); /* Throws error when (OnDelete*, Panic) constraint is violated. */ void flecs_throw_invalid_delete( ecs_world_t *world, ecs_id_t id); #ifdef FLECS_DEBUG void flecs_component_lock( ecs_world_t *world, ecs_id_t component); void flecs_component_unlock( ecs_world_t *world, ecs_id_t component); bool flecs_component_is_trait_locked( ecs_world_t *world, ecs_id_t component); bool flecs_component_is_delete_locked( ecs_world_t *world, ecs_id_t component); #else #define flecs_component_lock(world, component) (void)world; (void)component #define flecs_component_unlock(world, component) (void)world; (void)component #define flecs_component_is_trait_locked(world, component) (false) #define flecs_component_is_delete_locked(world, component) (false) #endif /* Convenience macros for world allocator */ #define flecs_walloc(world, size)\ flecs_alloc(&world->allocator, size) #define flecs_walloc_t(world, T)\ flecs_alloc_t(&world->allocator, T) #define flecs_walloc_n(world, T, count)\ flecs_alloc_n(&world->allocator, T, count) #define flecs_wcalloc(world, size)\ flecs_calloc(&world->allocator, size) #define flecs_wfree_t(world, T, ptr)\ flecs_free_t(&world->allocator, T, ptr) #define flecs_wcalloc_n(world, T, count)\ flecs_calloc_n(&world->allocator, T, count) #define flecs_wfree(world, size, ptr)\ flecs_free(&world->allocator, size, ptr) #define flecs_wfree_n(world, T, count, ptr)\ flecs_free_n(&world->allocator, T, count, ptr) #define flecs_wrealloc(world, size_dst, size_src, ptr)\ flecs_realloc(&world->allocator, size_dst, size_src, ptr) #define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) #define flecs_wdup(world, size, ptr)\ flecs_dup(&world->allocator, size, ptr) #define flecs_wdup_n(world, T, count, ptr)\ flecs_dup_n(&world->allocator, T, count, ptr) /* Convenience macro that iterates over all component records in the world */ #define FLECS_EACH_COMPONENT_RECORD(cr, ...)\ for (int32_t _i = 0; _i < FLECS_HI_ID_RECORD_ID; _i++) {\ ecs_component_record_t *cr = world->id_index_lo[_i];\ if (cr) {\ __VA_ARGS__\ }\ }\ {\ ecs_map_iter_t _it = ecs_map_iter(&world->id_index_hi);\ while (ecs_map_next(&_it)) {\ ecs_component_record_t *cr = ecs_map_ptr(&_it);\ __VA_ARGS__\ }\ } /* Convenience macro that iterates over all queries in the world */ #define FLECS_EACH_QUERY(query, ...)\ {\ ecs_iter_t _it = ecs_each_pair(world, ecs_id(EcsPoly), EcsQuery);\ while (ecs_each_next(&_it)) {\ EcsPoly *queries = ecs_field(&_it, EcsPoly, 0);\ for (int32_t _i = 0; _i < _it.count; _i++) {\ ecs_query_t *query = queries[_i].poly;\ if (!query) {\ continue;\ }\ flecs_poly_assert(query, ecs_query_t);\ ecs_assert(query->entity != 0, ECS_INTERNAL_ERROR, NULL);\ __VA_ARGS__\ }\ }\ } #endif #define flecs_journal_begin(...) #define flecs_journal_end(...) #define flecs_journal(...) /* Used in id records to keep track of entities used with id flags */ extern const ecs_entity_t EcsFlag; //////////////////////////////////////////////////////////////////////////////// //// Bootstrap API //////////////////////////////////////////////////////////////////////////////// /* Bootstrap world */ void flecs_bootstrap( ecs_world_t *world); #define flecs_bootstrap_component(world, id_)\ ecs_component_init(world, &(ecs_component_desc_t){\ .entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ .type.size = sizeof(id_),\ .type.alignment = ECS_ALIGNOF(id_)\ }); #define flecs_bootstrap_tag(world, name)\ ecs_make_alive(world, name);\ ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]); #define flecs_bootstrap_trait(world, name)\ flecs_bootstrap_tag(world, name)\ ecs_add_id(world, name, EcsTrait) //////////////////////////////////////////////////////////////////////////////// //// Safe(r) integer casting //////////////////////////////////////////////////////////////////////////////// #define FLECS_CONVERSION_ERR(T, value)\ "illegal conversion from value " #value " to type " #T #define flecs_signed_char__ (CHAR_MIN < 0) #define flecs_signed_short__ true #define flecs_signed_int__ true #define flecs_signed_long__ true #define flecs_signed_size_t__ false #define flecs_signed_int8_t__ true #define flecs_signed_int16_t__ true #define flecs_signed_int32_t__ true #define flecs_signed_int64_t__ true #define flecs_signed_intptr_t__ true #define flecs_signed_uint8_t__ false #define flecs_signed_uint16_t__ false #define flecs_signed_uint32_t__ false #define flecs_signed_uint64_t__ false #define flecs_signed_uintptr_t__ false #define flecs_signed_ecs_size_t__ true #define flecs_signed_ecs_entity_t__ false uint64_t flecs_ito_( size_t dst_size, bool dst_signed, bool lt_zero, uint64_t value, const char *err); #ifndef FLECS_NDEBUG #define flecs_ito(T, value)\ (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ (value) < 0,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #define flecs_uto(T, value)\ (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ false,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #else #define flecs_ito(T, value) (T)(value) #define flecs_uto(T, value) (T)(value) #endif #define flecs_itosize(value) flecs_ito(size_t, (value)) #define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) #define flecs_itoi16(value) flecs_ito(int16_t, (value)) #define flecs_itoi32(value) flecs_ito(int32_t, (value)) //////////////////////////////////////////////////////////////////////////////// //// Utilities //////////////////////////////////////////////////////////////////////////////// /* Check if component is valid, return reason if it's not */ const char* flecs_id_invalid_reason( const ecs_world_t *world, ecs_id_t id); /* Generate 64bit hash from buffer. */ uint64_t flecs_hash( const void *data, ecs_size_t length); /* Get next power of 2 */ int32_t flecs_next_pow_of_2( int32_t n); /* Compare function for entity ids used for order_by */ int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); /* Compare function for component ids used for qsort */ int flecs_id_qsort_cmp( const void *a, const void *b); /* Load file contents into string */ char* flecs_load_from_file( const char *filename); /* Test whether entity name is an entity id (starts with a #). */ bool flecs_name_is_id( const char *name); /* Convert entity name to entity id. */ ecs_entity_t flecs_name_to_id( const char *name); /* Replace #[color] tokens with terminal color symbols. */ void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf); /* Get line/column of first error logged during last log capture. */ void flecs_log_get_captured_error_pos( int32_t *line, int32_t *column); /* Check whether id can be inherited. */ bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_component_record_t *cr, ecs_id_t id); /* Cleanup type info data. */ void flecs_fini_type_info( ecs_world_t *world); const ecs_type_info_t* flecs_determine_type_info_for_component( const ecs_world_t *world, ecs_id_t component); ecs_size_t flecs_type_size( ecs_world_t *world, ecs_entity_t type); /* Utility for using allocated strings in assert/error messages */ const char* flecs_errstr( char *str); const char* flecs_errstr_1( char *str); const char* flecs_errstr_2( char *str); const char* flecs_errstr_3( char *str); const char* flecs_errstr_4( char *str); const char* flecs_errstr_5( char *str); #endif /* -- Identifier Component -- */ static ECS_DTOR(EcsIdentifier, ptr, { ecs_os_strset(&ptr->value, NULL); }) static ECS_COPY(EcsIdentifier, dst, src, { ecs_os_strset(&dst->value, src->value); dst->hash = src->hash; dst->length = src->length; dst->index_hash = src->index_hash; dst->index = src->index; }) static ECS_MOVE(EcsIdentifier, dst, src, { ecs_os_strset(&dst->value, NULL); dst->value = src->value; dst->hash = src->hash; dst->length = src->length; dst->index_hash = src->index_hash; dst->index = src->index; src->value = NULL; src->hash = 0; src->index_hash = 0; src->index = 0; src->length = 0; }) /* -- Poly component -- */ static ECS_COPY(EcsPoly, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); }) static ECS_MOVE(EcsPoly, dst, src, { if (dst->poly && (dst->poly != src->poly)) { flecs_poly_dtor_t *dtor = flecs_get_dtor(dst->poly); ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); dtor[0](dst->poly); } dst->poly = src->poly; src->poly = NULL; }) static ECS_DTOR(EcsPoly, ptr, { if (ptr->poly) { flecs_poly_dtor_t *dtor = flecs_get_dtor(ptr->poly); ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); dtor[0](ptr->poly); } }) /* -- Builtin observers -- */ static void flecs_assert_relation_unused( ecs_world_t *world, ecs_entity_t rel, ecs_entity_t trait) { if (world->flags & (EcsWorldInit|EcsWorldFini)) { return; } ecs_vec_t *marked_ids = &world->store.marked_ids; int32_t i, count = ecs_vec_count(marked_ids); for (i = 0; i < count; i ++) { ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i); if (mid->id == ecs_pair(rel, EcsWildcard)) { /* If id is being cleaned up, no need to throw error as tables will * be cleaned up */ return; } } bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); in_use |= ecs_id_in_use(world, rel); if (in_use) { char *r_str = ecs_get_path(world, rel); char *p_str = ecs_get_path(world, trait); ecs_throw(ECS_INVALID_OPERATION, "cannot change trait '%s' for '%s': component is already in use", p_str, r_str); ecs_os_free(r_str); ecs_os_free(p_str); } error: return; } static bool flecs_set_id_flag( ecs_world_t *world, ecs_component_record_t *cr, ecs_flags32_t flag, ecs_entity_t trait) { (void)trait; if (!(cr->flags & flag)) { cr->flags |= flag; if (flag == EcsIdSparse) { flecs_component_init_sparse(world, cr); } if (flag == EcsIdDontFragment) { flecs_component_record_init_dont_fragment(world, cr); } if (flag == EcsIdExclusive) { flecs_component_record_init_exclusive(world, cr); } return true; } return false; } static bool flecs_unset_id_flag( ecs_component_record_t *cr, ecs_flags32_t flag) { if (cr->flags & EcsIdMarkedForDelete) { /* Don't change flags for a record that's about to be deleted */ return false; } if ((cr->flags & flag)) { cr->flags &= ~flag; return true; } return false; } typedef struct ecs_on_trait_ctx_t { ecs_flags32_t flag, not_flag; } ecs_on_trait_ctx_t; static bool flecs_trait_can_add_after_query( ecs_entity_t trait) { if (trait == EcsWith) { return true; } return false; } static void flecs_register_flag_for_trait( ecs_iter_t *it, ecs_entity_t trait, ecs_flags32_t flag, ecs_flags32_t not_flag, ecs_flags32_t entity_flag) { ecs_world_t *world = it->world; ecs_entity_t event = it->event; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; bool changed = false; if (event == EcsOnAdd) { if (flag == EcsIdOnInstantiateInherit) { if (e < FLECS_HI_COMPONENT_ID) { world->non_trivial_lookup[e] |= EcsNonTrivialIdInherit; } } if (!(world->flags & EcsWorldInit) && !flecs_trait_can_add_after_query(trait)) { ecs_check(!flecs_component_is_trait_locked(world, e), ECS_INVALID_OPERATION, "cannot set '%s' trait for component '%s' because it is already" " queried for (apply traits before creating queries)", flecs_errstr(ecs_get_path(world, trait)), flecs_errstr_1(ecs_id_str(world, e))); } ecs_component_record_t *cr = flecs_components_get(world, e); if (cr) { changed |= flecs_set_id_flag(world, cr, flag, trait); } cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); if (cr) { do { changed |= flecs_set_id_flag(world, cr, flag, trait); } while ((cr = flecs_component_first_next(cr))); } if (entity_flag) flecs_add_flag(world, e, entity_flag); } else if (event == EcsOnRemove) { ecs_component_record_t *cr = flecs_components_get(world, e); if (cr) changed |= flecs_unset_id_flag(cr, not_flag); cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); if (cr) { do { changed |= flecs_unset_id_flag(cr, not_flag); } while ((cr = flecs_component_first_next(cr))); } } if (changed) { flecs_assert_relation_unused(world, e, trait); } } error: return; } static void flecs_register_final(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (flecs_components_get(world, ecs_pair(EcsIsA, e)) != NULL) { ecs_throw(ECS_INVALID_OPERATION, "cannot change trait 'Final' for '%s': already inherited from", flecs_errstr(ecs_get_path(world, e))); } ecs_check(!flecs_component_is_trait_locked(world, e), ECS_INVALID_OPERATION, "cannot change " "trait 'Final' for '%s': already queried for (apply traits " "before creating queries)", flecs_errstr(ecs_get_path(world, e))); error: continue; } } static void flecs_register_tag(ecs_iter_t *it) { flecs_register_flag_for_trait(it, EcsPairIsTag, EcsIdPairIsTag, EcsIdPairIsTag, 0); /* Ensure that all id records for tag have type info set to NULL */ ecs_world_t *world = it->real_world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (it->event == EcsOnAdd) { ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)); if (cr) { do { if (cr->type_info != NULL) { flecs_assert_relation_unused(world, e, EcsPairIsTag); } cr->type_info = NULL; } while ((cr = flecs_component_first_next(cr))); } } } } static void flecs_register_on_delete(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 0); flecs_register_flag_for_trait(it, EcsOnDelete, ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteMask, EcsEntityIsId); } static void flecs_register_on_delete_object(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 0); flecs_register_flag_for_trait(it, EcsOnDeleteTarget, ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteTargetMask, EcsEntityIsId); } static void flecs_register_on_instantiate(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 0); flecs_register_flag_for_trait(it, EcsOnInstantiate, ECS_ID_ON_INSTANTIATE_FLAG(ECS_PAIR_SECOND(id)), 0, 0); } static void flecs_register_trait(ecs_iter_t *it) { ecs_on_trait_ctx_t *ctx = it->ctx; flecs_register_flag_for_trait( it, it->ids[0], ctx->flag, ctx->not_flag, 0); } static void flecs_register_trait_pair(ecs_iter_t *it) { ecs_on_trait_ctx_t *ctx = it->ctx; flecs_register_flag_for_trait( it, ecs_pair_first(it->world, it->ids[0]), ctx->flag, ctx->not_flag, 0); } static void flecs_register_slot_of(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_add_id(it->world, it->entities[i], EcsDontFragment); ecs_add_id(it->world, it->entities[i], EcsExclusive); } } static void flecs_on_symmetric_add_remove(ecs_iter_t *it) { ecs_entity_t pair = ecs_field_id(it, 0); if (!ECS_HAS_ID_FLAG(pair, PAIR)) { /* If relationship was not added as a pair, there's nothing to do */ return; } ecs_world_t *world = it->world; ecs_entity_t rel = ECS_PAIR_FIRST(pair); ecs_entity_t tgt = ecs_pair_second(world, pair); ecs_entity_t event = it->event; if (tgt) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t subj = it->entities[i]; if (event == EcsOnAdd) { if (!ecs_has_id(it->real_world, tgt, ecs_pair(rel, subj))) { ecs_add_pair(it->world, tgt, rel, subj); } } else { if (ecs_has_id(it->real_world, tgt, ecs_pair(rel, subj))) { ecs_remove_pair(it->world, tgt, rel, subj); } } } } } static void flecs_register_symmetric(ecs_iter_t *it) { ecs_world_t *world = it->real_world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t r = it->entities[i]; flecs_assert_relation_unused(world, r, EcsSymmetric); /* Create observer that adds the reverse relationship when R(X, Y) is * added, or removes the reverse relationship when R(X, Y) is removed. */ ecs_observer(world, { .entity = ecs_entity(world, { .parent = r }), .query.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, .callback = flecs_on_symmetric_add_remove, .events = {EcsOnAdd, EcsOnRemove} }); } } #ifdef FLECS_DEBUG static void flecs_on_singleton_add_remove(ecs_iter_t *it) { ecs_entity_t component = ecs_field_id(it, 0); ecs_world_t *world = it->real_world; (void)world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (ECS_IS_PAIR(component)) { ecs_entity_t relationship = ECS_PAIR_FIRST(component); e = (uint32_t)e; ecs_check(relationship == e, ECS_CONSTRAINT_VIOLATED, "cannot add singleton pair '%s' to entity '%s': singleton " "component '%s' must be added to itself", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_1(ecs_get_path(world, it->entities[i])), flecs_errstr_2(ecs_get_path(it->world, relationship))); (void)relationship; } else { ecs_check(component == e, ECS_CONSTRAINT_VIOLATED, "cannot add singleton component '%s' to entity '%s': singleton" " component must be added to itself", flecs_errstr(ecs_get_path(it->world, component)), flecs_errstr_1(ecs_get_path(it->world, it->entities[i]))); } error: continue; } } #endif static void flecs_register_singleton(ecs_iter_t *it) { ecs_world_t *world = it->real_world; (void)world; flecs_register_flag_for_trait(it, EcsSingleton, EcsIdSingleton, 0, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t component = it->entities[i]; (void)component; /* Create observer that enforces that singleton is only added to self */ #ifdef FLECS_DEBUG ecs_observer(world, { .entity = ecs_entity(world, { .name = "debug_only_SingletonInvariantCheck", .parent = component }), .query.terms[0] = { .id = component, .src.id = EcsThis|EcsSelf }, .callback = flecs_on_singleton_add_remove, .events = {EcsOnAdd} }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "debug_only_SingletonPairInvariantCheck", .parent = component }), .query.terms[0] = { .id = ecs_pair(component, EcsWildcard), .src.id = EcsThis|EcsSelf }, .callback = flecs_on_singleton_add_remove, .events = {EcsOnAdd} }); #endif } } static void flecs_on_component(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsComponent *c = ecs_field(it, EcsComponent, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; uint32_t component_id = (uint32_t)e; /* Strip generation */ ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, "component id must be smaller than %u", ECS_MAX_COMPONENT_ID); (void)component_id; if (it->event != EcsOnRemove) { ecs_entity_t parent = ecs_get_parent(world, e); if (parent) { ecs_record_t *parent_record = flecs_entities_get(world, parent); ecs_table_t *parent_table = parent_record->table; if (!ecs_table_has_id(world, parent_table, EcsModule)) { if (!ecs_table_has_id(world, parent_table, ecs_id(EcsComponent))) { ecs_add_id(world, parent, EcsModule); } } } } if (it->event == EcsOnSet) { if (flecs_type_info_init_id( world, e, c[i].size, c[i].alignment, NULL)) { flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); } } else if (it->event == EcsOnRemove) { #ifdef FLECS_DEBUG if (ecs_should_log(0)) { char *path = ecs_get_path(world, e); ecs_trace("unregistering component '%s'", path); ecs_os_free(path); } #endif if (!ecs_vec_count(&world->store.marked_ids)) { flecs_type_info_free(world, e); } else { ecs_vec_append_t(&world->allocator, &world->store.deleted_components, ecs_entity_t)[0] = e; } } } } static void flecs_ensure_module_tag(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } } static void flecs_disable_observer( ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_entity_t evt = it->event; int32_t i, count = it->count; for (i = 0; i < count; i ++) { flecs_observer_set_disable_bit(world, it->entities[i], EcsObserverIsDisabled, evt == EcsOnAdd); } } static void flecs_disable_module_observers( ecs_world_t *world, ecs_entity_t module, bool should_disable) { ecs_iter_t child_it = ecs_children(world, module); while (ecs_children_next(&child_it)) { ecs_table_t *table = child_it.table; bool table_disabled = table->flags & EcsTableIsDisabled; int32_t i; /* Recursively walk modules, don't propagate to disabled modules */ if (ecs_table_has_id(world, table, EcsModule) && !table_disabled) { for (i = 0; i < child_it.count; i ++) { flecs_disable_module_observers( world, child_it.entities[i], should_disable); } continue; } /* Only disable observers */ if (!ecs_table_has_id(world, table, EcsObserver)) { continue; } for (i = 0; i < child_it.count; i ++) { flecs_observer_set_disable_bit(world, child_it.entities[i], EcsObserverIsParentDisabled, should_disable); } } } static void flecs_disable_module(ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { flecs_disable_module_observers( it->real_world, it->entities[i], it->event == EcsOnAdd); } } static void flecs_on_add_prefab(ecs_iter_t *it) { ecs_world_t *world = it->world; for (int32_t i = 0; i < it->count; i ++) { ecs_entity_t p = it->entities[i]; ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(p)); if (cr && (cr->flags & EcsIdOrderedChildren)) { flecs_ordered_children_set_prefab(world, cr); } ecs_iter_t cit = ecs_children(world, p); while (ecs_children_next(&cit)) { for (int32_t j = 0; j < cit.count; j ++) { ecs_add_id(world, cit.entities[j], EcsPrefab); } } } } static void flecs_register_ordered_children(ecs_iter_t *it) { int32_t i; if (it->event == EcsOnAdd) { for (i = 0; i < it->count; i ++) { ecs_entity_t parent = it->entities[i]; ecs_component_record_t *cr = flecs_components_ensure( it->world, ecs_childof(parent)); if (!(cr->flags & EcsIdOrderedChildren)) { flecs_ordered_children_init(it->world, cr); flecs_ordered_children_populate(it->world, cr); cr->flags |= EcsIdOrderedChildren; } } } else if (!(it->real_world->flags & EcsWorldFini) && it->other_table) { ecs_assert(it->event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < it->count; i ++) { ecs_entity_t parent = it->entities[i]; ecs_component_record_t *cr = flecs_components_get( it->world, ecs_childof(parent)); if (cr && (cr->flags & EcsIdOrderedChildren)) { flecs_ordered_children_clear(cr); cr->flags &= ~EcsIdOrderedChildren; } } } } /* -- Bootstrapping -- */ #define flecs_bootstrap_builtin_t(world, table, name)\ flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ ECS_ALIGNOF(name)) static void flecs_bootstrap_builtin( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, const char *symbol, ecs_size_t size, ecs_size_t alignment) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *columns = table->data.columns; ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_ensure(world, entity); 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 row = ecs_table_count(table); flecs_table_append(world, table, entity, false, false); record->row = ECS_ROW_TO_RECORD(row, 0); EcsComponent *component = columns[0].data; component[row].size = size; component[row].alignment = alignment; const char *name = &symbol[3]; /* Strip 'Ecs' */ ecs_size_t symbol_length = ecs_os_strlen(symbol); ecs_size_t name_length = symbol_length - 3; EcsIdentifier *name_col = columns[1].data; uint64_t name_hash = flecs_hash(name, name_length); name_col[row].value = ecs_os_strdup(name); name_col[row].length = name_length; name_col[row].hash = name_hash; name_col[row].index_hash = 0; ecs_hashmap_t *name_index = flecs_table_get_name_index(world, table); name_col[row].index = name_index; flecs_name_index_ensure(name_index, entity, name, name_length, name_hash); EcsIdentifier *symbol_col = columns[2].data; symbol_col[row].value = ecs_os_strdup(symbol); symbol_col[row].length = symbol_length; symbol_col[row].hash = flecs_hash(symbol, symbol_length); symbol_col[row].index_hash = 0; symbol_col[row].index = NULL; } /** Initialize component table. This table is manually constructed to bootstrap * Flecs. After this function has been called, the builtin components can be * created. * The reason this table is constructed manually is because it requires the size * and alignment of the EcsComponent and EcsIdentifier components, which haven't * been created yet. */ static ecs_table_t* flecs_bootstrap_component_table( ecs_world_t *world) { /* Before creating table, manually set flags for ChildOf/Identifier, as this * can no longer be done after they are in use. */ /* Initialize id records cached on world */ world->cr_childof_wildcard = flecs_components_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); world->cr_childof_wildcard->flags |= EcsIdOnDeleteTargetDelete | EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdPairIsTag | EcsIdExclusive; ecs_component_record_t *cr = flecs_components_ensure( world, ecs_pair_t(EcsIdentifier, EcsWildcard)); cr->flags |= EcsIdOnInstantiateDontInherit; world->cr_identifier_name = flecs_components_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); world->cr_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), ecs_pair_t(EcsIdentifier, EcsName), ecs_pair_t(EcsIdentifier, EcsSymbol), ecs_pair(EcsChildOf, EcsFlecsCore), }; ecs_type_t array = { .array = ids, .count = 4 }; ecs_table_t *result = flecs_table_find_or_create(world, &array); /* Preallocate enough memory for initial components */ ecs_vec_t v_entities = ecs_vec_from_entities(result); ecs_vec_init_t(NULL, &v_entities, ecs_entity_t, EcsFirstUserComponentId); { ecs_column_t *column = &result->data.columns[0]; ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsComponent); ecs_vec_init_t(NULL, &v, EcsComponent, EcsFirstUserComponentId); ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); column->data = v.array; } { ecs_column_t *column = &result->data.columns[1]; ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsIdentifier); ecs_vec_init_t(NULL, &v, EcsIdentifier, EcsFirstUserComponentId); ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); column->data = v.array; } { ecs_column_t *column = &result->data.columns[2]; ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsIdentifier); ecs_vec_init_t(NULL, &v, EcsIdentifier, EcsFirstUserComponentId); ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); column->data = v.array; } result->data.entities = v_entities.array; result->data.count = 0; result->data.size = v_entities.size; 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(NULL, &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, ecs_entity_t id, const char *name, ecs_entity_t parent) { flecs_bootstrap_make_alive(world, id); ecs_add_pair(world, id, EcsChildOf, parent); ecs_set_name(world, id, name); ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); if (!parent || parent == EcsFlecsCore) { ecs_assert(ecs_lookup(world, name) == id, ECS_INTERNAL_ERROR, NULL); } } 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) { ecs_log_push(); ecs_set_name_prefix(world, "Ecs"); /* Ensure builtin ids are alive */ 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(EcsParent)); flecs_bootstrap_make_alive(world, ecs_id(EcsTreeSpawner)); 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, 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, EcsDontFragment); flecs_bootstrap_make_alive(world, EcsObserver); flecs_bootstrap_make_alive(world, EcsPairIsTag); /* Register type information for builtin components */ flecs_type_info_init(world, EcsComponent, { .ctor = flecs_default_ctor, .on_set = flecs_on_component, .on_remove = flecs_on_component }); flecs_type_info_init(world, EcsIdentifier, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsIdentifier), .copy = ecs_copy(EcsIdentifier), .move = ecs_move(EcsIdentifier), .on_set = ecs_on_set(EcsIdentifier), .on_remove = ecs_on_set(EcsIdentifier) }); flecs_type_info_init(world, EcsPoly, { .ctor = flecs_default_ctor, .copy = ecs_copy(EcsPoly), .move = ecs_move(EcsPoly), .dtor = ecs_dtor(EcsPoly) }); flecs_type_info_init(world, EcsParent, { .ctor = flecs_default_ctor }); flecs_type_info_init(world, EcsDefaultChildComponent, { .ctor = flecs_default_ctor, }); /* Create and cache often used id records on 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 * other tables once properties are added (see below) */ ecs_table_t *table = flecs_bootstrap_component_table(world); assert(table != NULL); /* Bootstrap builtin components */ flecs_bootstrap_builtin_t(world, table, EcsIdentifier); flecs_bootstrap_builtin_t(world, table, EcsComponent); flecs_bootstrap_builtin_t(world, table, EcsPoly); flecs_bootstrap_builtin_t(world, table, EcsParent); flecs_bootstrap_builtin_t(world, table, EcsTreeSpawner); flecs_bootstrap_builtin_t(world, table, EcsDefaultChildComponent); /* Initialize default entity id range */ world->info.last_component_id = EcsFirstUserComponentId; flecs_entities_max_id(world) = EcsFirstUserEntityId; /* Register observer for trait before adding EcsPairIsTag */ ecs_observer(world, { .query.terms[0] = { .id = EcsPairIsTag }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_tag, .yield_existing = true, .global_observer = true }); /* Populate core module */ ecs_set_scope(world, EcsFlecsCore); flecs_bootstrap_tag(world, EcsName); flecs_bootstrap_tag(world, EcsSymbol); flecs_bootstrap_tag(world, EcsAlias); flecs_bootstrap_tag(world, EcsParentDepth); flecs_bootstrap_tag(world, EcsQuery); flecs_bootstrap_tag(world, EcsObserver); flecs_bootstrap_tag(world, EcsModule); flecs_bootstrap_tag(world, EcsPrefab); flecs_bootstrap_tag(world, EcsSlotOf); flecs_bootstrap_tag(world, EcsDisabled); flecs_bootstrap_tag(world, EcsNotQueryable); flecs_bootstrap_tag(world, EcsEmpty); /* Initialize builtin modules */ ecs_set_name(world, EcsFlecs, "flecs"); ecs_add_id(world, EcsFlecs, EcsModule); ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); ecs_set_name(world, EcsFlecsCore, "core"); ecs_add_id(world, EcsFlecsCore, EcsModule); /* Self check */ ecs_record_t *r = flecs_entities_get(world, EcsFlecs); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL); (void)r; /* Initialize builtin entities */ flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore); flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); /* Component/relationship properties */ flecs_bootstrap_trait(world, EcsTransitive); flecs_bootstrap_trait(world, EcsReflexive); flecs_bootstrap_trait(world, EcsSymmetric); flecs_bootstrap_trait(world, EcsSingleton); 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); flecs_bootstrap_trait(world, EcsTraversable); flecs_bootstrap_trait(world, EcsWith); flecs_bootstrap_trait(world, EcsOneOf); flecs_bootstrap_trait(world, EcsCanToggle); flecs_bootstrap_trait(world, EcsTrait); flecs_bootstrap_trait(world, EcsRelationship); flecs_bootstrap_trait(world, EcsTarget); flecs_bootstrap_trait(world, EcsOnDelete); flecs_bootstrap_trait(world, EcsOnDeleteTarget); flecs_bootstrap_trait(world, EcsOnInstantiate); flecs_bootstrap_trait(world, EcsSparse); flecs_bootstrap_trait(world, EcsDontFragment); flecs_bootstrap_tag(world, EcsRemove); flecs_bootstrap_tag(world, EcsDelete); flecs_bootstrap_tag(world, EcsPanic); flecs_bootstrap_tag(world, EcsOverride); flecs_bootstrap_tag(world, EcsInherit); flecs_bootstrap_tag(world, EcsDontInherit); flecs_bootstrap_tag(world, EcsOrderedChildren); /* Builtin predicates */ flecs_bootstrap_tag(world, EcsPredEq); flecs_bootstrap_tag(world, EcsPredMatch); flecs_bootstrap_tag(world, EcsPredLookup); flecs_bootstrap_tag(world, EcsScopeOpen); flecs_bootstrap_tag(world, EcsScopeClose); /* Builtin relationships */ flecs_bootstrap_tag(world, EcsIsA); flecs_bootstrap_tag(world, EcsChildOf); flecs_bootstrap_tag(world, EcsDependsOn); /* Builtin events */ flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); /* Constant tag */ flecs_bootstrap_entity(world, EcsConstant, "constant", EcsFlecsCore); /* Sync properties of ChildOf and Identifier with bootstrapped flags */ ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); ecs_add_id(world, EcsChildOf, EcsTrait); ecs_add_id(world, EcsIsA, EcsTrait); ecs_add_id(world, EcsChildOf, EcsAcyclic); ecs_add_id(world, EcsChildOf, EcsTraversable); ecs_add_pair(world, EcsChildOf, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, ecs_id(EcsIdentifier), EcsOnInstantiate, EcsDontInherit); /* Register observers for components/relationship properties. Most observers * set flags on a component record when a trait is added to a component, which * allows for quick trait testing in various operations. */ ecs_observer(world, { .query.terms = {{ .id = EcsFinal }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_final, .global_observer = true }); static ecs_on_trait_ctx_t inheritable_trait = { EcsIdInheritable, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsInheritable }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &inheritable_trait, .global_observer = true }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete, .global_observer = true }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete_object, .global_observer = true }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnInstantiate, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_on_instantiate, .global_observer = true }); ecs_observer(world, { .query.terms = {{ .id = EcsSymmetric }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_symmetric, .global_observer = true }); ecs_observer(world, { .query.terms = {{ .id = EcsSingleton }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_singleton, .global_observer = true }); static ecs_on_trait_ctx_t traversable_trait = { EcsIdTraversable, EcsIdTraversable }; ecs_observer(world, { .query.terms = {{ .id = EcsTraversable }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_trait, .ctx = &traversable_trait, .global_observer = true }); static ecs_on_trait_ctx_t exclusive_trait = { EcsIdExclusive, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsExclusive }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_trait, .ctx = &exclusive_trait, .global_observer = true }); static ecs_on_trait_ctx_t toggle_trait = { EcsIdCanToggle, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsCanToggle }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &toggle_trait, .global_observer = true }); static ecs_on_trait_ctx_t with_trait = { EcsIdWith, 0 }; ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsWith, EcsWildcard) }, }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait_pair, .ctx = &with_trait, .global_observer = true }); static ecs_on_trait_ctx_t sparse_trait = { EcsIdSparse, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsSparse }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &sparse_trait, .global_observer = true }); static ecs_on_trait_ctx_t dont_fragment_trait = { EcsIdDontFragment, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsDontFragment }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &dont_fragment_trait, .global_observer = true }); ecs_observer(world, { .query.terms = {{ .id = EcsOrderedChildren }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_ordered_children, .global_observer = true }); /* Entities used as slots are marked as exclusive to ensure a slot can always * only point to a single entity. */ ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsSlotOf, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_slot_of, .global_observer = true }); /* Define observer to make sure that adding a module to a child entity also * adds it to the parent. */ ecs_observer(world, { .query.terms = {{ .id = EcsModule } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_ensure_module_tag, .global_observer = true }); /* Observer that tracks whether observers are disabled */ ecs_observer(world, { .query.terms = { { .id = EcsObserver }, { .id = EcsDisabled }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_observer, .global_observer = true }); /* Observer that tracks whether modules are disabled */ ecs_observer(world, { .query.terms = { { .id = EcsModule }, { .id = EcsDisabled }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_module, .global_observer = true }); /* Observer that ensures children of a prefab are also prefabs */ ecs_observer(world, { .query.terms = { { .id = EcsPrefab }, }, .events = {EcsOnAdd}, .callback = flecs_on_add_prefab, .global_observer = true }); /* Exclusive properties */ ecs_add_id(world, EcsChildOf, EcsExclusive); ecs_add_id(world, EcsOnDelete, EcsExclusive); ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); ecs_add_id(world, EcsOnInstantiate, EcsExclusive); ecs_add_id(world, EcsParentDepth, EcsExclusive); /* Unqueryable entities */ ecs_add_id(world, EcsThis, EcsNotQueryable); ecs_add_id(world, EcsWildcard, EcsNotQueryable); ecs_add_id(world, EcsAny, EcsNotQueryable); ecs_add_id(world, EcsVariable, EcsNotQueryable); /* Tag relationships (relationships that should never have data) */ ecs_add_id(world, EcsIsA, EcsPairIsTag); ecs_add_id(world, EcsChildOf, EcsPairIsTag); ecs_add_id(world, EcsSlotOf, EcsPairIsTag); ecs_add_id(world, EcsDependsOn, EcsPairIsTag); ecs_add_id(world, EcsFlag, EcsPairIsTag); ecs_add_id(world, EcsWith, EcsPairIsTag); /* Relationships */ ecs_add_id(world, EcsChildOf, EcsRelationship); ecs_add_id(world, EcsIsA, EcsRelationship); ecs_add_id(world, EcsSlotOf, EcsRelationship); ecs_add_id(world, EcsDependsOn, EcsRelationship); ecs_add_id(world, EcsWith, EcsRelationship); ecs_add_id(world, EcsOnDelete, EcsRelationship); ecs_add_id(world, EcsOnDeleteTarget, EcsRelationship); ecs_add_id(world, EcsOnInstantiate, EcsRelationship); ecs_add_id(world, ecs_id(EcsIdentifier), EcsRelationship); /* Targets */ ecs_add_id(world, EcsOverride, EcsTarget); ecs_add_id(world, EcsInherit, EcsTarget); ecs_add_id(world, EcsDontInherit, EcsTarget); /* Traversable relationships are always acyclic */ ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); /* Transitive relationships are always Traversable */ ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); /* DontFragment components are always sparse */ ecs_add_pair(world, EcsDontFragment, EcsWith, EcsSparse); /* Modules are singletons */ ecs_add_pair(world, EcsModule, EcsWith, EcsSingleton); /* DontInherit components */ ecs_add_pair(world, EcsPrefab, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, ecs_id(EcsComponent), EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, EcsOnDelete, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, EcsExclusive, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, EcsDontFragment, EcsOnInstantiate, EcsDontInherit); /* Acyclic/Traversable components */ ecs_add_id(world, EcsIsA, EcsTraversable); ecs_add_id(world, EcsDependsOn, EcsTraversable); ecs_add_id(world, EcsWith, EcsAcyclic); /* Transitive relationships */ ecs_add_id(world, EcsIsA, EcsTransitive); ecs_add_id(world, EcsIsA, EcsReflexive); /* Exclusive properties */ ecs_add_id(world, EcsSlotOf, EcsExclusive); ecs_add_id(world, EcsOneOf, EcsExclusive); /* Inherited components */ ecs_add_pair(world, EcsIsA, EcsOnInstantiate, EcsInherit); ecs_add_pair(world, EcsDependsOn, EcsOnInstantiate, EcsInherit); /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_entity_name(world); flecs_bootstrap_parent_component(world); flecs_bootstrap_spawner(world); ecs_set_scope(world, 0); ecs_set_name_prefix(world, NULL); ecs_assert(world->cr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(world->cr_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(); } static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_remove(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static 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; } 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_t( &stage->cmd->entries, ecs_cmd_entry_t, e); 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; } if (is_new) { cmd->entry = entry = flecs_sparse_ensure_fast_t( &stage->cmd->entries, ecs_cmd_entry_t, e); entry->first = cur; } entry->last = cur; return cmd; } bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage) { flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); flecs_check_exclusive_world_access_write(world); (void)world; if (stage->defer < 0) return false; return (++ stage->defer) == 1; } bool flecs_defer_cmd( ecs_stage_t *stage) { if (stage->defer) { return (stage->defer > 0); } stage->defer ++; return false; } bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); if (cmd) { cmd->kind = EcsCmdModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; return true; } return false; } bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name) { if (stage->defer > 0) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdPath; cmd->entity = entity; cmd->id = parent; cmd->is._1.value = ecs_os_strdup(name); return true; } return false; } bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdDelete; cmd->entity = entity; return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdClear; cmd->entity = entity; return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; cmd->entity = entity; cmd->id = id; return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new as this is thread safe */ int i; for (i = 0; i < count; i ++) { ids[i] = ecs_new(world); } *ids_out = ids; /* Store data in cmd */ ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; cmd->entity = 0; return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdAdd; cmd->id = id; cmd->entity = entity; return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdRemove; cmd->id = id; cmd->entity = entity; /* If an override is removed, restore the component to the value of * the overridden component. This serves two purposes: * * - the application immediately sees the correct component value * - if a remove command is followed up by an add command, the override * will still be applied vs. getting cancelled out because of * command batching. */ ecs_world_t *world = stage->world; ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; if (table->flags & EcsTableHasIsA) { ecs_component_record_t *cr = flecs_components_get(world, id); const ecs_type_info_t *ti; if (cr && (ti = cr->type_info)) { if (cr->flags & (EcsIdSparse | EcsIdDontFragment)) { void *dst = flecs_component_sparse_get( world, cr, table, entity); if (dst) { ecs_entity_t base = 0; if (ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, &base, NULL, NULL) != -1 && base) { ecs_record_t *base_r = flecs_entities_get( world, base); ecs_table_t *base_table = base_r ? base_r->table : NULL; void *src = flecs_component_sparse_get( world, cr, base_table, base); if (src) { flecs_type_info_copy(dst, src, 1, ti); } } } } else { ecs_table_overrides_t *o = table->data.overrides; if (o) { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (tr) { ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_ref_t *ref = &o->refs[tr->column]; if (ref->entity) { void *dst = ECS_OFFSET( table->data.columns[tr->column].data, ti->size * ECS_RECORD_TO_ROW(r->row)); const void *src = ecs_ref_get_id( world, &o->refs[tr->column], id); flecs_type_info_copy(dst, src, 1, ti); } } } } } } return true; } return false; } /* Return existing component pointer & type info */ static flecs_component_ptr_t flecs_defer_get_existing( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *r, ecs_id_t id, ecs_size_t size) { flecs_component_ptr_t ptr = flecs_get_mut(world, entity, id, r, size); /* Make sure we return type info, even if entity doesn't have component */ if (!ptr.ti) { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { /* If cr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (world->flags & EcsWorldMultiThreaded) { ptr.ti = ecs_get_type_info(world, id); } else { /* When not in multithreaded mode, it's safe to find or * create the component record. */ cr = flecs_components_ensure(world, id); ecs_assert(cr != 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. */ ptr.ti = cr->type_info; } } else { ptr.ti = cr->type_info; } } return ptr; } void* flecs_defer_emplace( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, bool *is_new) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->entity = entity; cmd->id = id; ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t ptr = flecs_defer_get_existing( world, entity, r, id, size); const ecs_type_info_t *ti = ptr.ti; ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "provided component is not a type"); ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, "mismatching size specified for component in ensure/emplace/set"); size = ti->size; void *cmd_value = ptr.ptr; if (!ptr.ptr) { ecs_stack_t *stack = &stage->cmd->stack; cmd_value = flecs_stack_alloc(stack, size, ti->alignment); cmd->kind = EcsCmdEmplace; cmd->is._1.size = size; cmd->is._1.value = cmd_value; if (is_new) *is_new = true; } else { cmd->kind = EcsCmdAdd; if (is_new) *is_new = false; } return cmd_value; error: return NULL; } void* flecs_defer_ensure( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size) { ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->entity = entity; cmd->id = id; ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t ptr = flecs_defer_get_existing( world, entity, r, id, size); const ecs_type_info_t *ti = ptr.ti; ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "provided component is not a type"); ecs_assert(size == ti->size, ECS_INVALID_PARAMETER, "bad size for component in ensure"); ecs_table_t *table = r->table; if (!ptr.ptr) { ecs_stack_t *stack = &stage->cmd->stack; cmd->kind = EcsCmdEnsure; cmd->is._1.size = size; cmd->is._1.value = ptr.ptr = flecs_stack_alloc(stack, size, ti->alignment); /* Check if entity inherits component */ void *base = NULL; if (table && (table->flags & EcsTableHasIsA)) { ecs_component_record_t *cr = flecs_components_get(world, id); base = flecs_get_base_component(world, table, id, cr, 0); } if (!base) { /* Normal ctor */ flecs_type_info_ctor(ptr.ptr, 1, ti); } else { /* Override */ flecs_type_info_copy_ctor(ptr.ptr, base, 1, ti); } } else { cmd->kind = EcsCmdAdd; } return ptr.ptr; error: return NULL; } void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value) { ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); ecs_assert(cmd != NULL, ECS_INTERNAL_ERROR, NULL); cmd->entity = entity; cmd->id = id; ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t ptr = flecs_defer_get_existing( world, entity, r, id, size); if (world->stage_count != 1) { /* If world has multiple stages we need to insert a set command * with temporary storage, as the value could be lost otherwise * by a command in another stage. */ ptr.ptr = NULL; } const ecs_type_info_t *ti = ptr.ti; ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "provided component is not a type"); ecs_assert(size == ti->size, ECS_INVALID_PARAMETER, "mismatching size specified for component in ensure/emplace/set (%u vs %u)", size, ti->size); /* Handle trivial set command (no hooks, OnSet observers) */ if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[id]) { if (!ptr.ptr) { ptr.ptr = flecs_stack_alloc( &stage->cmd->stack, size, ti->alignment); /* No OnSet observers, so ensure is enough */ cmd->kind = EcsCmdEnsure; cmd->is._1.size = size; cmd->is._1.value = ptr.ptr; } else { /* No OnSet observers, so the only thing we need to do is make sure * that a preceding remove command doesn't cause the entity to * end up without the component. */ cmd->kind = EcsCmdAdd; } ecs_os_memcpy(ptr.ptr, value, size); return ptr.ptr; } } if (!ptr.ptr) { bool is_dont_fragment = flecs_component_get_flags(world, id) & EcsIdDontFragment; cmd->kind = is_dont_fragment ? EcsCmdSetDontFragment : EcsCmdSet; cmd->is._1.size = size; ptr.ptr = cmd->is._1.value = flecs_stack_alloc(&stage->cmd->stack, size, ti->alignment); flecs_type_info_copy_ctor(ptr.ptr, value, 1, ti); } else { cmd->kind = EcsCmdAddModified; /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, id, ptr.ptr, value, ti); } flecs_type_info_copy(ptr.ptr, value, 1, ti); } return ptr.ptr; error: return NULL; } /* Same as flecs_defer_set, but doesn't copy value into storage. */ void* flecs_defer_cpp_set( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, const void *value) { ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); ecs_assert(cmd != NULL, ECS_INTERNAL_ERROR, NULL); cmd->entity = entity; cmd->id = id; ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t ptr = flecs_defer_get_existing( world, entity, r, id, size); const ecs_type_info_t *ti = ptr.ti; ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "provided component is not a type"); ecs_assert(size == ti->size, ECS_INVALID_PARAMETER, "mismatching size specified for component in ensure/emplace/set"); /* Handle trivial set command (no hooks, OnSet observers) */ if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[id]) { if (!ptr.ptr) { ptr.ptr = flecs_stack_alloc( &stage->cmd->stack, size, ti->alignment); /* No OnSet observers, so ensure is enough */ cmd->kind = EcsCmdEnsure; cmd->is._1.size = size; cmd->is._1.value = ptr.ptr; } else { /* No OnSet observers, so the only thing we need to do is make sure * that a preceding remove command doesn't cause the entity to * end up without the component. */ cmd->kind = EcsCmdAdd; } ecs_os_memcpy(ptr.ptr, value, size); return ptr.ptr; } } if (!ptr.ptr) { bool is_dont_fragment = flecs_component_get_flags(world, id) & EcsIdDontFragment; cmd->kind = is_dont_fragment ? EcsCmdSetDontFragment : EcsCmdSet; cmd->is._1.size = size; ptr.ptr = cmd->is._1.value = flecs_stack_alloc(&stage->cmd->stack, size, ti->alignment); flecs_type_info_ctor(ptr.ptr, 1, ti); } else { cmd->kind = EcsCmdAddModified; /* Call on_replace hook before copying the new value. */ if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, id, ptr.ptr, value, ti); } } return ptr.ptr; error: return NULL; } void* flecs_defer_cpp_assign( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, const void *value) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); flecs_component_ptr_t ptr = flecs_get_mut(world, entity, id, r, size); ecs_assert(ptr.ptr != NULL, ECS_INVALID_PARAMETER, "entity does not have component, use set() instead"); if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[id]) { return ptr.ptr; /* Nothing more to do */ } } /* Call on_replace hook before copying the new value. */ ecs_iter_action_t on_replace = ptr.ti->hooks.on_replace; if (on_replace) { flecs_invoke_replace_hook( world, r->table, entity, id, ptr.ptr, value, ptr.ti); } ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdModified; cmd->entity = entity; cmd->id = id; return ptr.ptr; } void flecs_enqueue( ecs_world_t *world, ecs_stage_t *stage, ecs_event_desc_t *desc) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdEvent; cmd->entity = desc->entity; ecs_stack_t *stack = &stage->cmd->stack; ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); if (desc->ids && desc->ids->count != 0) { ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); int32_t id_count = desc->ids->count; type_cmd->count = id_count; type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); desc_cmd->ids = type_cmd; } else { desc_cmd->ids = NULL; } cmd->is._1.value = desc_cmd; cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); if (desc->param || desc->const_param) { ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, "cannot set param and const_param at the same time"); const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "can only enqueue events with data for events that are components"); void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); if (desc->param) { flecs_type_info_move_ctor(param_cmd, desc->param, 1, ti); } else { flecs_type_info_copy_ctor(param_cmd, desc->const_param, 1, ti); } desc_cmd->param = param_cmd; desc_cmd->const_param = NULL; } } static void flecs_flush_bulk_new( ecs_world_t *world, ecs_cmd_t *cmd) { ecs_entity_t *entities = cmd->is._n.entities; if (cmd->id) { int i, count = cmd->is._n.count; for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_ensure(world, entities[i]); if (!r->table) { flecs_add_to_root_table(world, entities[i]); } flecs_add_id(world, entities[i], cmd->id); } } ecs_os_free(entities); } static void flecs_dtor_value( ecs_world_t *world, ecs_id_t id, void *value) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_info_dtor(value, 1, ti); } static void flecs_free_cmd_event( ecs_world_t *world, ecs_event_desc_t *desc) { if (desc->ids) { flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); } /* Safe const cast, command makes a copy of type object */ flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), ecs_type_t); if (desc->param) { flecs_dtor_value(world, desc->event, /* Safe const cast, command makes copy of value */ ECS_CONST_CAST(void*, desc->param)); } } 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); flecs_stack_free(value, cmd->is._1.size); } } } static bool flecs_remove_invalid( ecs_world_t *world, ecs_id_t id, ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); if (!flecs_entities_is_valid(world, rel)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } else { ecs_entity_t tgt = ECS_PAIR_SECOND(id); if (!flecs_entities_is_valid(world, tgt)) { /* Check the relationship's policy for deleted targets */ ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(rel, EcsWildcard)); if (cr) { ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(cr->flags); if (action == EcsDelete) { /* Entity should be deleted, don't bother checking * other ids */ return false; } else if (action == EcsPanic) { /* If policy is Panic, this target 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 component is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } } return true; } static void flecs_cmd_batch_for_entity( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_entity_t entity, ecs_cmd_t *cmds, int32_t start) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); world->info.cmd.batched_entity_count ++; bool has_set = false; ecs_table_t *start_table = table; ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ int32_t cur = start; int32_t next_for_entity; do { ecs_cmd_t *cmd = &cmds[cur]; ecs_id_t id = cmd->id; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { /* First command for an entity has a negative index, flip sign */ next_for_entity *= -1; } /* Check if added id is still valid (like is the parent of a ChildOf * pair still alive), if not run cleanup actions for entity */ if (id) { ecs_component_record_t *cr = NULL; if ((id < FLECS_HI_COMPONENT_ID)) { if (world->non_trivial_lookup[id]) { cr = flecs_components_get(world, id); } } else { cr = flecs_components_get(world, id); } if (cr && cr->flags & EcsIdDontFragment) { /* Nothing to batch for non-fragmenting components */ if (cmd->kind == EcsCmdSet) { cmd->kind = EcsCmdSetDontFragment; } else if (cmd->kind == EcsCmdEnsure) { cmd->kind = EcsCmdEnsureDontFragment; } continue; } if (flecs_remove_invalid(world, id, &id)) { if (!id) { /* Entity should remain alive but id should not be added */ cmd->kind = EcsCmdSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsCmdSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsCmdAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsCmdModified; table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsCmdAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; cmd->kind = EcsCmdSkip; break; case EcsCmdSet: case EcsCmdEnsure: { table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; has_set = true; break; } case EcsCmdEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsCmdRemove: { table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; cmd->kind = EcsCmdSkip; 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 ++; cmd->kind = EcsCmdSkip; break; case EcsCmdDelete: /* Entity is deleted, don't batch commands after the delete */ next_for_entity = 0; break; case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdPath: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: case EcsCmdModifiedNoHook: case EcsCmdModified: case EcsCmdSetDontFragment: case EcsCmdEnsureDontFragment: break; } } while ((cur = next_for_entity)); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); /* Save added ids and clear from diff so that they won't be emitted as * part of the commit below. OnAdd events will be emitted separately after * the commit, so 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, 0, 0); flecs_defer_end(world, world->stages[0]); /* If destination table has new sparse components, make sure they're created * for the entity. */ if ((table_diff.added_flags & (EcsTableHasSparse|EcsTableHasDontFragment)) && added.count) { if (flecs_sparse_on_add( world, table, ECS_RECORD_TO_ROW(r->row), 1, &added, 0)) { table_diff.added_flags |= EcsTableHasOnAdd; } } /* If the batch contains set commands, copy the component value from the * temporary command storage to the actual component storage before OnSet * observers are invoked. This ensures that for multi-component OnSet * observers all components are assigned a valid value before the observer * is invoked. * This only happens for entities that didn't have the assigned component * yet, as for entities that did have the component already the value will * have been assigned directly to the component storage. */ if (has_set) { cur = start; do { ecs_cmd_t *cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsCmdSet: case EcsCmdEnsure: { flecs_component_ptr_t dst = flecs_get_mut( world, entity, cmd->id, r, cmd->is._1.size); /* 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 (dst.ptr) { void *ptr = cmd->is._1.value; const ecs_type_info_t *ti = dst.ti; if (ti->hooks.on_replace) { ecs_table_t *prev_table = r->table; flecs_invoke_replace_hook(world, prev_table, entity, cmd->id, dst.ptr, ptr, ti); if (!r->table) { /* Entity was deleted */ goto done; } /* Refetch pointer as the hook could have grown the * table or moved the entity to a different table. */ dst = flecs_get_mut( world, entity, cmd->id, r, cmd->is._1.size); if (!dst.ptr) { cmd->kind = EcsCmdSkip; break; } } bool move_hook = ti->hooks.move != NULL; flecs_type_info_move(dst.ptr, ptr, 1, ti); if (move_hook) { flecs_type_info_dtor(ptr, 1, ti); } flecs_stack_free(ptr, cmd->is._1.size); cmd->is._1.value = NULL; 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 EcsCmdDelete: /* Entity is deleted, don't process commands after delete */ next_for_entity = 0; break; case EcsCmdRemove: case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdEmplace: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdSetDontFragment: case EcsCmdEnsureDontFragment: case EcsCmdPath: 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 = table_diff.added_flags; if (r->row & EcsEntityIsTraversable) { /* Update monitors since we didn't do this in flecs_commit. Do this * before calling flecs_actions_move_add() since this can trigger * prefab instantiation logic. When that happens, prefab children * can be created for this instance which would mean that the table * count of cr would always be >0. * Since those tables are new, we don't have to invoke component * monitors since queries will have correctly matched them. */ ecs_component_record_t *cr = flecs_components_get( world, ecs_pair(EcsWildcard, entity)); if (cr && ecs_map_count(&cr->cache.index)) { flecs_update_component_monitors(world, &added, NULL); } } bool update_parent_records = !table_diff.removed.count || !(start_table->flags & EcsTableHasParent); flecs_defer_begin(world, world->stages[0]); flecs_actions_move_add(world, r->table, start_table, ECS_RECORD_TO_ROW(r->row), 1, &add_diff, 0, false, 0, update_parent_records); flecs_defer_end(world, world->stages[0]); } done: diff->added.array = added.array; diff->added.count = added.count; flecs_table_diff_builder_clear(diff); } /* Leave safe section. Run all deferred commands. */ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); flecs_check_exclusive_world_access_write(world); if (stage->defer < 0) { /* Suspending defer makes it possible to do operations on the storage * without flushing the commands in the queue */ return false; } ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); if (!--stage->defer && !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; } do { ecs_stage_t *dst_stage = flecs_stage_from_world(&world); ecs_commands_t *commands = stage->cmd; ecs_vec_t *queue = &commands->queue; if (stage->cmd == &stage->cmd_stack[0]) { stage->cmd = &stage->cmd_stack[1]; } else { stage->cmd = &stage->cmd_stack[0]; } if (!ecs_vec_count(queue)) { break; } stage->cmd_flushing = true; /* Internal callback for capturing commands */ if (world->on_commands_active) { world->on_commands_active(stage, queue, world->on_commands_ctx_active); } ecs_cmd_t *cmds = ecs_vec_first(queue); int32_t i, count = ecs_vec_count(queue); ecs_table_diff_builder_t diff = {0}; bool diff_builder_used = false; 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)) { diff.added_flags = 0; diff.removed_flags = 0; /* Batch commands for entity to limit archetype moves */ if (is_alive) { if (!diff_builder_used) { flecs_table_diff_builder_init(world, &diff); diff_builder_used = true; } 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: if (flecs_entities_is_alive(world, id)) { ecs_clone(world, e, id, cmd->is._1.clone_value); world->info.cmd.other_count ++; } else { world->info.cmd.discard_count ++; } break; case EcsCmdSet: case EcsCmdSetDontFragment: 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, flecs_itosize(cmd->is._1.size), &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: case EcsCmdEnsureDontFragment: 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, 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; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } stage->cmd_flushing = false; flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); if (diff_builder_used) { 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); } } while (true); ecs_os_perf_trace_pop("flecs.commands.merge"); return true; } return false; } /* Discard commands from queue without executing them. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); if (!--stage->defer) { ecs_vec_t commands = stage->cmd->queue; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->cmd->stack); flecs_sparse_clear(&stage->cmd->entries); } return true; } error: return false; } void flecs_commands_init( ecs_stage_t *stage, ecs_commands_t *cmd) { flecs_stack_init(&cmd->stack); ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); flecs_sparse_init_t(&cmd->entries, &stage->allocator, &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } 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); flecs_stack_fini(&cmd->stack); ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); flecs_sparse_fini(&cmd->entries); } bool ecs_defer_begin( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); return flecs_defer_begin(world, stage); error: return false; } bool ecs_defer_end( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); return flecs_defer_end(world, stage); error: return false; } void ecs_defer_suspend( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, "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; } 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; } void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, const ecs_component_record_t *cr, 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_iter_t it = { .field_count = 1 }; it.entities = entities; ecs_table_record_t dummy_tr; if (!tr) { dummy_tr.hdr.cr = ECS_CONST_CAST(ecs_component_record_t*, cr); dummy_tr.hdr.table = table; dummy_tr.index = -1; dummy_tr.column = -1; dummy_tr.count = 0; tr = &dummy_tr; } ecs_entity_t dummy_src = 0; int16_t column = tr->column; it.world = world; it.real_world = world; it.table = table; it.trs = &tr; it.columns = &column; it.row_fields = !!(tr->hdr.cr->flags & EcsIdSparse); it.ref_fields = it.row_fields; it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); it.ids = &id; it.sources = &dummy_src; 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; hook(&it); world->stages[0]->defer = defer; } void flecs_invoke_replace_hook( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_id_t id, const void *old_ptr, const void *new_ptr, const ecs_type_info_t *ti) { int32_t defer = world->stages[0]->defer; if (defer < 0) { world->stages[0]->defer *= -1; } ecs_iter_t it = { .field_count = 2 }; it.entities = &entity; const ecs_table_record_t *trs[] = {NULL, NULL}; ecs_size_t sizes[] = {ti->size, ti->size}; ecs_id_t ids[] = {id, id}; ecs_entity_t srcs[] = {0, 0}; const void *ptrs[] = {old_ptr, new_ptr}; it.world = world; it.real_world = world; it.table = table; it.trs = trs; it.row_fields = 0; it.ref_fields = it.row_fields; it.sizes = sizes; it.ptrs = ECS_CONST_CAST(void**, ptrs); it.ids = ids; it.sources = srcs; it.event = 0; it.event_id = id; it.ctx = ti->hooks.ctx; it.callback_ctx = ti->hooks.binding_ctx; it.count = 1; it.offset = 0; /* Don't set row because we don't want to offset ptrs */ it.flags = EcsIterIsValid; ti->hooks.on_replace(&it); world->stages[0]->defer = defer; } static void flecs_on_reparent( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count) { flecs_reparent_name_index(world, table, other_table, row, count); flecs_ordered_children_reparent(world, table, other_table, row, count); flecs_non_fragmenting_childof_reparent(world, table, other_table, row, count); } static void flecs_on_unparent( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, ecs_flags32_t diff_flags) { if (diff_flags & EcsTableEdgeReparent) { if (other_table) { flecs_unparent_name_index(world, table, other_table, row, count); } flecs_non_fragmenting_childof_unparent( world, other_table, table, row, count); } if (diff_flags & EcsTableHasOrderedChildren) { flecs_ordered_children_unparent(world, table, row, count); } } bool flecs_sparse_on_add_cr( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr, bool construct, void **ptr_out) { bool is_new = false; if (cr && cr->flags & EcsIdSparse) { void *result = NULL; int32_t sparse_count = flecs_sparse_count(cr->sparse); if (construct) { result = flecs_component_sparse_insert( world, cr, table, row); } else { result = flecs_component_sparse_emplace( world, cr, table, row); } if (ptr_out) { *ptr_out = result; } if (cr->flags & EcsIdDontFragment) { is_new = sparse_count != flecs_sparse_count(cr->sparse); if (is_new) { const ecs_entity_t *entities = ecs_table_entities(table); ecs_record_t *r = flecs_entities_get(world, entities[row]); r->row |= EcsEntityHasDontFragment; } } } return is_new; } bool flecs_sparse_on_add( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *added, ecs_id_t emplace_id) { bool is_new = false; bool construct_any = (emplace_id != EcsWildcard); int32_t i, j; for (i = 0; i < added->count; i ++) { ecs_id_t id = added->array[i]; ecs_component_record_t *cr = flecs_components_get(world, id); bool id_construct = construct_any && (id != emplace_id); for (j = 0; j < count; j ++) { is_new |= flecs_sparse_on_add_cr( world, table, row + j, cr, id_construct, NULL); } } return is_new; } 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 *cr = flecs_components_get(world, id); if (cr && cr->flags & EcsIdSparse) { for (j = 0; j < count; j ++) { flecs_component_sparse_remove(world, cr, table, row + j); } } } } static bool flecs_dont_fragment_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 *cr = flecs_components_get(world, id); if (cr && cr->flags & EcsIdDontFragment) { const ecs_entity_t *entities = ecs_table_entities(table); for (j = 0; j < count; j ++) { ecs_entity_t e = entities[row + j]; if (flecs_component_sparse_has(cr, e)) { return true; } } } } return false; } void flecs_entity_remove_non_fragmenting( ecs_world_t *world, ecs_entity_t e, ecs_record_t *r) { if (!r) { r = flecs_entities_get(world, e); } if (!r || !(r->row & EcsEntityHasDontFragment)) { return; } ecs_component_record_t *cur = world->cr_non_fragmenting_head; while (cur) { ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); if (cur->sparse && !(ecs_id_is_wildcard(cur->id))) { if (flecs_sparse_has(cur->sparse, e)) { ecs_type_t type = { .count = 1, .array = &cur->id }; flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = &type, .table = r->table, .other_table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1, .observable = world }); flecs_component_sparse_remove( world, cur, r->table, ECS_RECORD_TO_ROW(r->row)); } } cur = cur->non_fragmenting.next; } r->row &= ~EcsEntityHasDontFragment; } static void flecs_actions_on_add_intern( 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, bool sparse, ecs_id_t emplace_id) { ecs_flags32_t diff_flags = diff->added_flags; if (!diff_flags) { return; } const ecs_type_t *added = &diff->added; if (diff_flags & EcsTableEdgeReparent) { flecs_on_reparent(world, table, other_table, row, count); } if (sparse && (diff_flags & EcsTableHasSparse)) { if (flecs_sparse_on_add( world, table, row, count, added, emplace_id)) { diff_flags |= EcsTableHasOnAdd; } } if (diff_flags & (EcsTableHasOnAdd|EcsTableHasTraversable)) { flecs_emit(world, world, &(ecs_event_desc_t){ .event = EcsOnAdd, .ids = added, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world, .flags = flags }); } } static void flecs_actions_on_remove_intern( 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 diff_flags) { const ecs_type_t *removed = &diff->removed; ecs_assert(diff_flags != 0, ECS_INTERNAL_ERROR, NULL); if (diff_flags & EcsTableHasDontFragment) { if (flecs_dont_fragment_on_remove( world, table, row, count, removed)) { diff_flags |= EcsTableHasOnRemove; } } if (diff_flags & EcsTableHasOnRemove) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = removed, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world }); } if (diff_flags & EcsTableHasSparse) { flecs_sparse_on_remove(world, table, row, count, removed); } } static void flecs_actions_on_remove_intern_w_reparent( 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) { if (!(world->flags & EcsWorldFini)) { ecs_check(!(table->flags & EcsTableHasBuiltins), ECS_INVALID_OPERATION, "removing components from builtin entities is not allowed"); } ecs_flags32_t diff_flags = diff->removed_flags; if (!diff_flags) { return; } if (diff_flags & (EcsTableEdgeReparent|EcsTableHasOrderedChildren)) { if (!other_table || !(other_table->flags & EcsTableHasChildOf)) { flecs_on_unparent( world, table, other_table, row, count, diff_flags); } } flecs_actions_on_remove_intern( world, table, other_table, row, count, diff, diff_flags); error: return; } void flecs_actions_new( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_table_diff_t *diff, ecs_flags32_t flags, bool sparse, ecs_id_t emplace_id) { flecs_actions_on_add_intern(world, table, NULL, row, count, diff, flags, sparse, emplace_id); } void flecs_actions_delete_tree( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_table_diff_t *diff) { if (diff->removed.count) { ecs_flags32_t diff_flags = diff->removed_flags; if (!diff_flags) { return; } if (table->flags & EcsTableHasTraversable) { flecs_emit_propagate_invalidate(world, table, row, count); } flecs_actions_on_remove_intern( world, table, NULL, row, count, diff, diff_flags); } } void flecs_actions_move_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, bool sparse, ecs_id_t emplace_id, bool update_parent_records) { ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_t *added = &diff->added; if (added->count) { ecs_flags32_t table_flags = table->flags; if (table_flags & EcsTableHasTraversable) { flecs_emit_propagate_invalidate(world, table, row, count); } if (update_parent_records && (table_flags & EcsTableHasParent)) { flecs_on_non_fragmenting_child_move_add( world, table, other_table, row, count); } flecs_actions_on_add_intern(world, table, other_table, row, count, diff, flags, sparse, emplace_id); } } void flecs_actions_move_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); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); if (diff->removed.count) { ecs_flags32_t table_flags = table->flags; if (table_flags & EcsTableHasTraversable) { flecs_emit_propagate_invalidate(world, table, row, count); } if (table_flags & EcsTableHasParent) { bool update_parent_records = true; if (diff->added.count && other_table && (other_table->flags & EcsTableHasParent)) { update_parent_records = false; } flecs_on_non_fragmenting_child_move_remove( world, other_table, table, row, count, update_parent_records); } flecs_actions_on_remove_intern_w_reparent( world, table, other_table, row, count, diff); } } void flecs_notify_on_set_ids( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *ids) { 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); bool dont_fragment = false; int i; for (i = 0; i < ids->count; i ++) { ecs_id_t id = ids->array[i]; ecs_component_record_t *cr = flecs_components_get(world, id); dont_fragment |= (cr->flags & EcsIdDontFragment) != 0; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = cr->type_info; ecs_iter_action_t on_set = ti->hooks.on_set; if (!on_set) { continue; } ecs_table_record_t dummy_tr; const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { dummy_tr.hdr.cr = cr; dummy_tr.hdr.table = table; dummy_tr.column = -1; dummy_tr.index = -1; dummy_tr.count = 0; tr = &dummy_tr; } if (cr->flags & EcsIdSparse) { int32_t j; for (j = 0; j < count; j ++) { flecs_invoke_hook(world, table, cr, 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, cr, tr, count, row, entities, id, ti, EcsOnSet, on_set); } } } /* Run OnSet notifications */ if ((dont_fragment || table->flags & EcsTableHasOnSet) && ids->count) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = ids, .table = table, .offset = row, .count = count, .observable = world }); } } void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id, bool invoke_hook) { ecs_assert(id != 0, 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 <= ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = flecs_components_get(world, id); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); bool dont_fragment = (cr->flags & EcsIdDontFragment) != 0; if (invoke_hook) { const ecs_type_info_t *ti = cr->type_info; ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_table_record_t dummy_tr; const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { dummy_tr.hdr.cr = cr; dummy_tr.hdr.table = table; dummy_tr.column = -1; dummy_tr.index = -1; dummy_tr.count = 0; tr = &dummy_tr; } if (cr->flags & EcsIdSparse) { flecs_invoke_hook(world, table, cr, tr, 1, row, entities, 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, cr, tr, 1, row, entities, id, ti, EcsOnSet, on_set); } } } } if ((dont_fragment || table->flags & EcsTableHasOnSet)) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = &ids, .table = table, .offset = row, .count = 1, .observable = world }); } } static bool flecs_each_component_record( ecs_iter_t *it, ecs_component_record_t *cr, ecs_id_t id) { ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(id == cr->id, ECS_INTERNAL_ERROR, NULL); ecs_each_iter_t *each_iter = &it->priv_.iter.each; each_iter->ids = id; each_iter->sizes = 0; if (cr->type_info) { each_iter->sizes = cr->type_info->size; } each_iter->sources = 0; each_iter->trs = NULL; flecs_table_cache_iter((ecs_table_cache_t*)cr, &each_iter->it); return true; error: return false; } ecs_iter_t ecs_each_id( const ecs_world_t *stage, ecs_id_t id) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); flecs_check_exclusive_world_access_write(world); ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), .field_count = 1, .next = ecs_each_next }; ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return it; } if (!flecs_each_component_record(&it, cr, id)) { return (ecs_iter_t){0}; } return it; error: return (ecs_iter_t){0}; } bool ecs_each_next( ecs_iter_t *it) { ecs_each_iter_t *each_iter = &it->priv_.iter.each; const ecs_table_record_t *next = flecs_table_cache_next( &each_iter->it, ecs_table_record_t); it->flags |= EcsIterIsValid; if (next) { each_iter->trs = next; each_iter->columns = next->column; ecs_table_t *table = next->hdr.table; it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); if (next->index != -1) { it->ids = &table->type.array[next->index]; } else { it->ids = NULL; } it->trs = &each_iter->trs; it->columns = &each_iter->columns; it->sources = &each_iter->sources; it->sizes = &each_iter->sizes; it->set_fields = 1; return true; } else { return false; } } static bool flecs_children_next_ordered( ecs_iter_t *it) { return ecs_children_next(it); } ecs_iter_t ecs_children_w_rel( const ecs_world_t *stage, ecs_entity_t relationship, ecs_entity_t parent) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); flecs_check_exclusive_world_access_read(world); ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), .field_count = 1, .next = ecs_children_next }; ecs_component_record_t *cr = flecs_components_get( world, ecs_pair(relationship, parent)); if (!cr) { return (ecs_iter_t){0}; } if (cr->flags & EcsIdOrderedChildren) { ecs_vec_t *v = &cr->pair->ordered_children; it.entities = ecs_vec_first_t(v, ecs_entity_t); it.count = ecs_vec_count(v); it.next = flecs_children_next_ordered; return it; } else if (cr->flags & EcsIdSparse) { it.entities = flecs_sparse_ids(cr->sparse); it.count = flecs_sparse_count(cr->sparse); it.next = flecs_children_next_ordered; return it; } return ecs_each_id(stage, ecs_pair(relationship, parent)); error: return (ecs_iter_t){0}; } ecs_iter_t ecs_children( const ecs_world_t *stage, ecs_entity_t parent) { return ecs_children_w_rel(stage, EcsChildOf, parent); } bool ecs_children_next( ecs_iter_t *it) { if (it->next == NULL) { return false; } if (it->next == flecs_children_next_ordered) { if (!it->count) { return false; } it->next = NULL; /* Only return once with ordered children vector */ return true; } return ecs_each_next(it); } int32_t ecs_count_id( const ecs_world_t *world, ecs_entity_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!id) { return 0; } int32_t count = 0; ecs_iter_t it = ecs_each_id(world, id); while (ecs_each_next(&it)) { count += it.count * it.trs[0]->count; } return count; error: return 0; } static flecs_component_ptr_t flecs_table_get_component( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, 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) }; } static flecs_component_ptr_t flecs_get_component_ptr( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!cr) { return (flecs_component_ptr_t){0}; } if (cr->flags & (EcsIdSparse|EcsIdDontFragment)) { ecs_entity_t entity = ecs_table_entities(table)[row]; return (flecs_component_ptr_t){ .ti = cr->type_info, .ptr = flecs_component_sparse_get(world, cr, table, entity) }; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr || (tr->column == -1)) { return (flecs_component_ptr_t){0}; } return flecs_table_get_component(table, tr->column, row); } void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_component_record_t *cr) { return flecs_get_component_ptr(world, table, row, cr).ptr; } void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t component, ecs_component_record_t *cr, int32_t recur_depth) { ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_OPERATION, "cycle detected in IsA relationship"); /* Table (and thus entity) does not have component, look for base */ if (!(table->flags & EcsTableHasIsA)) { return NULL; } if (!(cr->flags & EcsIdOnInstantiateInherit)) { return NULL; } /* Exclude Name */ if (component == 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->cr_isa_wildcard, table); ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; void *ptr = NULL; do { ecs_id_t pair = ids[i ++]; ecs_entity_t base = ecs_pair_second(world, pair); ecs_record_t *r = flecs_entities_get(world, base); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { if (cr->flags & EcsIdDontFragment) { ptr = flecs_component_sparse_get(world, cr, table, base); } if (!ptr) { ptr = flecs_get_base_component(world, table, component, cr, recur_depth + 1); } } else { if (cr->flags & EcsIdSparse) { return flecs_component_sparse_get(world, cr, table, base); } else if (tr->column != -1) { int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_table_get_component(table, tr->column, row).ptr; } } } while (!ptr && (i < end)); return ptr; error: return NULL; } ecs_entity_t flecs_new_id( const ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); flecs_check_exclusive_world_access_write(world); /* It is possible that the world passed to this function is a stage, so * make sure we have the actual world. Cast away const since this is one of * the few functions that may modify the world while it is in readonly mode, * since it is thread-safe (uses atomic inc when in threading mode). */ ecs_world_t *unsafe_world = ECS_CONST_CAST(ecs_world_t*, world); ecs_assert(!(unsafe_world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot create entities in multithreaded mode"); ecs_entity_t entity = flecs_entities_new_id(unsafe_world); ecs_assert(!ecs_eis(unsafe_world)->active_range || !ecs_eis(unsafe_world)->active_range->max || ecs_entity_t_lo(entity) <= ecs_eis(unsafe_world)->active_range->max, ECS_OUT_OF_RANGE, NULL); return entity; } static ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *r, ecs_table_t *table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); r->table = &world->store.root; flecs_table_append(world, table, entity, ctor, true); int32_t row = ecs_table_count(table) - 1; r->table = table; r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); ecs_assert(ecs_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); flecs_actions_new(world, table, row, 1, diff, evt_flags, true, ctor ? 0 : EcsWildcard); ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); return r; } 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, ecs_id_t emplace_id, ecs_flags32_t evt_flags) { ecs_table_t *src_table = record->table; int32_t src_row = ECS_RECORD_TO_ROW(record->row); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table->type.count >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_table_count(src_table) > src_row, ECS_INTERNAL_ERROR, 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 = ecs_table_count(dst_table); flecs_table_append(world, dst_table, entity, false, false); /* Invoke remove actions for removed components */ flecs_actions_move_remove(world, src_table, dst_table, src_row, 1, diff); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, emplace_id); 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_actions_move_add(world, dst_table, src_table, dst_row, 1, diff, evt_flags, true, emplace_id, true); ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); } 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, ecs_id_t emplace_id, 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_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); 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 non-fragmenting component could have * changed. */ ecs_flags32_t non_fragment_flags = src_table->flags & EcsTableHasDontFragment; if (non_fragment_flags) { diff->added_flags |= non_fragment_flags; diff->removed_flags |= non_fragment_flags; flecs_actions_move_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, true, emplace_id, true); flecs_actions_move_remove(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, diff); } flecs_journal_end(); return; } ecs_os_perf_trace_push("flecs.commit"); ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_traversable_add(dst_table, is_trav); flecs_move_entity(world, entity, record, dst_table, diff, emplace_id, evt_flags); flecs_table_traversable_add(src_table, -is_trav); /* If the entity is traversable, it is being monitored for changes and * requires rematching queries when components are added or removed. This * ensures that queries that rely on components from traversable entities * update the matched tables when the application adds or removes a * component from, for example, a parent. */ if (is_trav) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } ecs_os_perf_trace_pop("flecs.commit"); flecs_journal_end(); return; } const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **component_data, bool is_move, int32_t *row_out, ecs_table_diff_t *diff) { int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); } ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_defer_begin(world, world->stages[0]); int32_t row = flecs_table_appendn(world, table, count, entities); ecs_type_t type = table->type; if (!type.count && !component_data) { flecs_defer_end(world, world->stages[0]); return entities; } ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; component_array.array = type.array; component_array.count = type.count; } flecs_actions_move_add(world, table, NULL, row, count, diff, (component_data == NULL) ? 0 : EcsEventNoOnSet, true, 0, true); if (component_data) { int32_t c_i; for (c_i = 0; c_i < component_ids->count; c_i ++) { void *src_ptr = component_data[c_i]; if (!src_ptr) { continue; } /* Find component in storage type */ ecs_entity_t id = component_ids->array[c_i]; ecs_component_record_t *cr = flecs_components_get(world, id); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = cr->type_info; if (!ti) { ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "component '%s' passed to bulk_new() at index %d is a " "tag/zero sized", flecs_errstr(ecs_id_str(world, id)), c_i); } int32_t size = ti->size; void *ptr; if (cr->flags & EcsIdSparse) { int32_t e; for (e = 0; e < count; e ++) { ptr = flecs_component_sparse_get( world, cr, table, entities[e]); if (is_move) { flecs_type_info_move(ptr, src_ptr, 1, ti); } else { flecs_type_info_copy(ptr, src_ptr, 1, ti); } flecs_notify_on_set(world, table, row + e, id, true); src_ptr = ECS_OFFSET(src_ptr, size); } } else { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, "component '%s' passed to bulk_new() at index %d is a " "tag/zero sized", flecs_errstr(ecs_id_str(world, id))); ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, "component passed to bulk_new() at index %d is " "invalid/a wildcard", flecs_errstr(ecs_id_str(world, id))); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ptr = ECS_ELEM(column->data, size, row); if (is_move) { flecs_type_info_move(ptr, src_ptr, count, ti); } else { flecs_type_info_copy(ptr, src_ptr, count, ti); } } }; int32_t j, storage_count = table->column_count; for (j = 0; j < storage_count; j ++) { ecs_id_t component = flecs_column_id(table, j); ecs_type_t set_type = { .array = &component, .count = 1 }; flecs_notify_on_set_ids(world, table, row, count, &set_type); } } flecs_defer_end(world, world->stages[0]); if (row_out) { *row_out = row; } if (sparse_count) { entities = flecs_entities_ids(world); return &entities[sparse_count]; } else { return entities; } } static void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_id_t component, ecs_id_t emplace_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, &component, &diff); flecs_commit(world, entity, record, dst_table, &diff, emplace_id, EcsEventNoOnSet); /* No OnSet, this function is only called from * functions that are about to set the component. */ } void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, entity, component)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *src_table = r->table; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &component, &diff); flecs_commit(world, entity, r, dst_table, &diff, 0, 0); flecs_defer_end(world, stage); } void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, entity, component)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = r->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &component, &diff); flecs_commit(world, entity, r, dst_table, &diff, 0, 0); flecs_defer_end(world, stage); } 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_table_t *table = ecs_get_table(world, entity); int32_t i; for (i = 0; i < count; i ++) { ecs_id_t component = ids[i]; table = flecs_find_table_add(world, table, component, &diff); } ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, entity, r, table, &table_diff, 0, 0); flecs_table_diff_builder_fini(world, &diff); } flecs_component_ptr_t flecs_ensure( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, ecs_record_t *r, ecs_size_t size) { flecs_component_ptr_t dst = {0}; flecs_poly_assert(world, ecs_world_t); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = NULL; ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { int16_t column_index = table->component_map[component]; if (column_index > 0) { ecs_column_t *column = &table->data.columns[column_index - 1]; ecs_assert(column->ti->size == size, ECS_INTERNAL_ERROR, NULL); dst.ptr = ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); dst.ti = column->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]; cr = tr->hdr.cr; if (cr->flags & EcsIdSparse) { dst.ptr = flecs_component_sparse_get( world, cr, r->table, entity); dst.ti = cr->type_info; ecs_assert(dst.ti->size == size, ECS_INTERNAL_ERROR, NULL); return dst; } } } else { cr = flecs_components_get(world, component); dst = flecs_get_component_ptr( world, table, ECS_RECORD_TO_ROW(r->row), cr); if (dst.ptr) { ecs_assert(dst.ti->size == size, ECS_INTERNAL_ERROR, NULL); return dst; } } /* If entity didn't have component yet, add it */ flecs_add_id_w_record(world, entity, r, component, 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]); if (!cr) { cr = flecs_components_get(world, component); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } flecs_component_ptr_t flecs_get_mut( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r, ecs_size_t size) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); (void)entity; world = ecs_get_world(world); flecs_check_exclusive_world_access_write(world); flecs_component_ptr_t result; if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[id]) { ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); 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]; ecs_check(column->ti->size == size, ECS_INVALID_PARAMETER, "invalid component size"); result.ptr = ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); result.ti = column->ti; return result; } return (flecs_component_ptr_t){0}; } } ecs_component_record_t *cr = flecs_components_get(world, id); int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_ptr(world, r->table, row, cr); error: return (flecs_component_ptr_t){0}; } 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; } 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); } void flecs_add_to_root_table( ecs_world_t *world, ecs_entity_t e) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INTERNAL_ERROR, NULL); 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); int32_t row = ecs_table_count(&world->store.root); flecs_table_append(world, &world->store.root, e, false, false); r->table = &world->store.root; r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); flecs_journal(world, EcsJournalNew, e, 0, 0); } const char* flecs_entity_invalid_reason( const ecs_world_t *world, ecs_entity_t entity) { if (!entity) { return "entity id cannot be 0"; } if (entity & ECS_PAIR) { return "cannot use a pair as an entity"; } if (entity & ECS_ID_FLAGS_MASK) { return "entity id contains flag bits (TOGGLE or AUTO_OVERRIDE)"; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return "entity id is not a valid bit pattern for an entity"; } if (!ecs_is_alive(world, entity)) { return "entity is not alive"; } return NULL; } #define flecs_assert_entity_valid(world, entity, function) \ ecs_check(entity && ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, \ "invalid entity '%s' passed to %s(): %s", \ flecs_errstr(ecs_id_str(world, entity)),\ function,\ flecs_entity_invalid_reason(world, entity)); #define flecs_assert_component_valid(world, entity, component, function)\ ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, \ "invalid component '%s' passed to %s() for entity '%s': %s", \ flecs_errstr(ecs_id_str(world, component)), \ function,\ flecs_errstr_1(ecs_get_path(world, entity)), \ flecs_id_invalid_reason(world, component)) /* -- Public functions -- */ bool ecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, const ecs_type_t *added, const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "commit"); ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, "commit cannot be called on stage or while world is deferred"); ecs_table_t *src_table = NULL; if (!record) { record = flecs_entities_get(world, entity); src_table = record->table; } ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; if (added) { diff.added = *added; diff.added_flags = table->flags & EcsTableAddEdgeFlags; } if (removed) { diff.removed = *removed; if (src_table) { diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; } } ecs_defer_begin(world); flecs_commit(world, entity, record, table, &diff, 0, 0); ecs_defer_end(world); return src_table != table; error: return false; } ecs_entity_t ecs_new( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t e = flecs_new_id(world); flecs_add_to_root_table(world, e); return e; error: return 0; } ecs_entity_t ecs_new_low_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot create entities in multithreaded mode"); flecs_stage_from_world(&world); flecs_check_exclusive_world_access_write(world); 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); } 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, e); } return e; error: return 0; } ecs_entity_t ecs_new_w_table( ecs_world_t *world, ecs_table_t *table) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, 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; } ecs_table_diff_t table_diff = { .added = table->type, .added_flags = flags }; flecs_new_entity(world, entity, r, table, &table_diff, true, 0); return entity; error: return 0; } ecs_entity_t ecs_new_w_id( ecs_world_t *world, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, "invalid component '%s' passed to new_w(): %s", flecs_errstr(ecs_id_str(world, component)), flecs_id_invalid_reason(world, component)) ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { ecs_entity_t e = ecs_new(world); ecs_add_id(world, e, component); return e; } ecs_table_diff_t table_diff = ECS_TABLE_DIFF_INIT; ecs_table_t *table = flecs_table_traverse_add( world, &world->store.root, &component, &table_diff); ecs_entity_t entity = flecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); flecs_new_entity(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, stage); return entity; error: return 0; } static void flecs_copy_id( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *r, ecs_id_t component, size_t size, void *dst_ptr, const void *src_ptr, const ecs_type_info_t *ti) { ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); (void)size; if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, component, dst_ptr, src_ptr, ti); } flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); flecs_table_mark_dirty(world, r->table, component); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), component, true); } /* Traverse table graph by adding 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) { (void)world; (void)name; (void)expr; (void)ids; ecs_err("cannot parse component expression: script addon required"); goto error; error: return -1; } /* Add 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)world; (void)entity; (void)name; (void)expr; ecs_err("cannot parse component expression: script addon required"); } /* 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; /* 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; } } /* 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, "entity symbol inconsistent: %s (provided) vs. %s (existing)", desc->symbol, sym); } else { ecs_set_symbol(world, result, desc->symbol); } } /* 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); } /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* Add components from the 'add' array */ if (desc->add) { int32_t i = 0; ecs_id_t component; while ((component = desc->add[i ++])) { table = flecs_find_table_add(world, table, component, &diff); } } /* Add components from the 'set' array */ if (desc->set) { int32_t i = 0; ecs_id_t component; while ((component = desc->set[i ++].type)) { table = flecs_find_table_add(world, table, component, &diff); } } /* 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); } } /* 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); } } /* 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, 0, 0); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, world->stages[0]); } /* 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]); 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_component_record_t *cr = flecs_components_get(world, v->type); flecs_component_ptr_t ptr = flecs_get_component_ptr( world, table, row, cr); ecs_check(ptr.ptr != NULL, ECS_INVALID_OPERATION, "component '%s' added to entity '%s' was removed during the " "operation, make sure not to remove the component in hooks/observers", flecs_errstr(ecs_id_str(world, v->type)), flecs_errstr_2(ecs_get_path(world, result))); const ecs_type_info_t *ti = cr->type_info; flecs_copy_id(world, result, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } flecs_defer_end(world, world->stages[0]); } flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return 0; error: flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return -1; } /* When in deferred mode, we need to add/remove components one by one using * the regular operations. */ static void flecs_deferred_add_remove( ecs_world_t *world, ecs_entity_t entity, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (new_entity) { if (new_entity && scope && !name && !name_assigned) { ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); } if (with) { ecs_add_id(world, entity, with); } } /* Add components from the 'add' id array */ if (desc->add) { int32_t i = 0; ecs_id_t component; while ((component = desc->add[i ++])) { bool defer = true; if (ECS_HAS_ID_FLAG(component, PAIR) && ECS_PAIR_FIRST(component) == EcsChildOf) { scope = ECS_PAIR_SECOND(component); 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, component); } } } /* 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_check(v->type != 0, ECS_INVALID_PARAMETER, "0 passed for component to ecs_entity_desc_t::set[%d]", i); const ecs_type_info_t *ti = ecs_get_type_info(world, v->type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "component '%s' passed to ecs_entity_desc_t::set[%d] is a " "tag/zero sized", flecs_errstr(ecs_id_str(world, v->type)), i); ecs_set_id(world, entity, v->type, flecs_ito(size_t, ti->size), v->ptr); } else { ecs_add_id(world, entity, v->type); } } } /* Add components from the 'add_expr' expression */ if (desc->add_expr) { flecs_defer_from_expr(world, entity, name, desc->add_expr); } int32_t thread_count = ecs_get_stage_count(world); /* 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); } } } /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } error: return; } ecs_entity_t ecs_entity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_entity_desc_t is uninitialized, initialize to {0} before using"); 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 component; int32_t i = 0; while ((component = desc->add[i ++])) { if (ECS_HAS_ID_FLAG(component, PAIR) && (ECS_PAIR_FIRST(component) == 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 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( "the '#xxx' string provided to ecs_entity_desc_t::name " "does not match the id provided to ecs_entity_desc_t::id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool new_entity = false; bool name_assigned = false; /* Remove optional prefix from name. Entity names can be derived from * language identifiers, such as components (typenames) and system * 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; } } } /* Parent field takes precedence over scope */ if (desc->parent) { scope = desc->parent; ecs_check(ecs_is_valid(world, desc->parent), ECS_INVALID_PARAMETER, "the entity provided in ecs_entity_desc_t::parent is not valid"); } /* Find or create entity */ if (!result) { if (name) { /* Look up entity by name in scope */ result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new(world); } 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); 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, new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, new_entity, name_assigned)) { return 0; } } return result; error: return 0; } const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc) { 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 is uninitialized, set to {0} before using"); flecs_check_exclusive_world_access_write(world); 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_assert(!ecs_is_alive(world, entities[i]), ECS_INVALID_PARAMETER, "cannot pass alive entities to ecs_bulk_init()"); flecs_entities_ensure(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { table = &world->store.root; } 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 component; while ((component = desc->ids[i])) { table = flecs_find_table_add(world, table, component, &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 }; int32_t i = 0; while ((desc->ids[i])) { i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; 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; } const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, ecs_id_t component, int32_t count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, count, component, &ids)) { return ids; } ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); if (component) { table = flecs_find_table_add(world, table, component, &diff); } ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, stage); return ids; error: return NULL; } static void flecs_check_component( ecs_world_t *world, ecs_entity_t result, const EcsComponent *ptr, ecs_size_t size, ecs_size_t alignment) { if (ptr->size != size) { char *path = ecs_get_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, "%s", path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, "%s", path); ecs_os_free(path); } } ecs_entity_t ecs_component_init( ecs_world_t *world, const ecs_component_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_component_desc_t is uninitialized, set to {0} before using"); /* If existing entity is provided, check if it is already registered as a * component and matches the size/alignment. This can prevent having to * suspend readonly mode, and increases the number of scenarios in which * this function can be called in multithreaded mode. */ ecs_entity_t result = desc->entity; if (result && ecs_is_alive(world, result)) { const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); if (const_ptr) { flecs_check_component(world, result, const_ptr, desc->type.size, desc->type.alignment); return result; } } ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; if (!result) { result = ecs_new_low_id(world); } else { ecs_make_alive(world, result); new_component = ecs_has(world, result, EcsComponent); } 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)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } 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; } void ecs_clear( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, entity)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; 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_commit(world, entity, r, &world->store.root, &diff, 0, 0); } flecs_entity_remove_non_fragmenting(world, entity, NULL); flecs_defer_end(world, stage); error: return; } void ecs_delete( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_delete(stage, entity)) { return; } ecs_os_perf_trace_push("flecs.delete"); ecs_record_t *r = flecs_entities_try(world, entity); if (r) { ecs_check(!ecs_has_pair(world, entity, EcsOnDelete, EcsPanic), ECS_CONSTRAINT_VIOLATED, "cannot delete entity '%s' with (OnDelete, Panic) trait", flecs_errstr(ecs_get_path(world, entity))); 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, true); flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true, true); } if (row_flags & EcsEntityIsId) { flecs_on_delete(world, entity, 0, true, true); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true, true); } /* Merge operations before deleting entity */ flecs_defer_end(world, stage); flecs_defer_begin(world, stage); } /* Entity is still in use by a query */ ecs_assert((world->flags & EcsWorldQuit) || !flecs_component_is_delete_locked(world, entity), ECS_INVALID_OPERATION, "cannot delete '%s' as it is still in use by queries", flecs_errstr(ecs_id_str(world, entity))); table = r->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_actions_move_remove( world, table, &world->store.root, row, 1, &diff); flecs_entity_remove_non_fragmenting(world, entity, r); flecs_table_delete(world, table, row, true); if (row_flags & EcsEntityIsTraversable) { flecs_table_traversable_add(table, -1); } } flecs_entities_remove(world, entity); flecs_journal_end(); } flecs_defer_end(world, stage); error: ecs_os_perf_trace_pop("flecs.delete"); return; } void ecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "add"); flecs_assert_component_valid(world, entity, component, "add"); flecs_add_id(world, entity, component); error: return; } void ecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "remove"); /* Component validity check is slightly different for remove() because it is * allowed to remove wildcards, but not allowed to add wildcards. */ ecs_check(ecs_id_is_valid(world, component) || ecs_id_is_wildcard(component), ECS_INVALID_PARAMETER, "invalid component '%s' passed to remove() for entity '%s': %s", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_1(ecs_get_path(world, entity)), flecs_id_invalid_reason(world, component)); flecs_remove_id(world, entity, component); error: return; } void ecs_auto_override_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { flecs_assert_component_valid(world, entity, component, "auto_override"); ecs_add_id(world, entity, ECS_AUTO_OVERRIDE | component); 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(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); ecs_check(!dst || (ecs_get_table(world, dst)->type.count == 0), ECS_INVALID_PARAMETER, "target entity for clone() cannot have components"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!dst) { dst = ecs_new(world); } if (flecs_defer_clone(stage, dst, src, copy_value)) { return dst; } ecs_record_t *src_r = flecs_entities_get(world, src); ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = src_r->table; 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)); dst_table = ecs_table_remove_id(world, dst_table, ecs_pair_t(EcsIdentifier, EcsSymbol)); } 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); if (dst_table != dst_r->table) { flecs_move_entity(world, dst, dst_r, dst_table, &diff, 0, 0); } if (copy_value) { int32_t row = ECS_RECORD_TO_ROW(dst_r->row); int32_t i, count = dst_table->column_count; for (i = 0; i < count; i ++) { int32_t index = ecs_table_column_to_type_index(dst_table, i); ecs_id_t component = dst_table->type.array[index]; void *dst_ptr = ecs_get_mut_id(world, dst, component); if (!dst_ptr) { continue; } const void *src_ptr = ecs_get_id(world, src, component); const ecs_type_info_t *ti = dst_table->data.columns[i].ti; flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); flecs_notify_on_set(world, dst_table, row, component, true); } if (dst_table->flags & EcsTableHasSparse) { count = dst_table->type.count; for (i = 0; i < count; i ++) { const ecs_table_record_t *tr = &dst_table->_->records[i]; ecs_component_record_t *cr = tr->hdr.cr; if (cr->sparse) { void *src_ptr = flecs_component_sparse_get( world, cr, src_table, src); if (src_ptr) { ecs_set_id(world, dst, cr->id, flecs_ito(size_t, cr->type_info->size), src_ptr); } } } } } if (src_r->row & EcsEntityHasDontFragment) { ecs_component_record_t *cur = world->cr_non_fragmenting_head; while (cur) { if (!ecs_id_is_wildcard(cur->id)) { ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); if (cur->sparse) { if (cur->type_info) { void *src_ptr = flecs_sparse_get(cur->sparse, 0, src); if (src_ptr) { ecs_set_id(world, dst, cur->id, flecs_ito(size_t, cur->type_info->size), src_ptr); } } else { if (flecs_sparse_has(cur->sparse, src)) { ecs_add_id(world, dst, cur->id); } } } } cur = cur->non_fragmenting.next; } } flecs_defer_end(world, stage); return dst; error: return 0; } const void* ecs_get_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get"); ecs_check(ecs_id_is_valid(world, component) || ecs_id_is_wildcard(component), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { ecs_get_low_id(table, r, component); return NULL; } } ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return NULL; } if (cr->flags & EcsIdDontFragment) { void *ptr = flecs_component_sparse_get(world, cr, table, entity); if (ptr) { return ptr; } } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return flecs_get_base_component(world, table, component, cr, 0); } else { if (cr->flags & EcsIdSparse) { return flecs_component_sparse_get(world, cr, table, entity); } ecs_check(tr->column != -1, ECS_INVALID_PARAMETER, "component '%s' passed to get() is a tag/zero sized", flecs_errstr(ecs_id_str(world, component))); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_table_get_component(table, tr->column, row).ptr; error: return NULL; } #ifdef FLECS_DEBUG static bool flecs_component_has_on_replace( const ecs_world_t *world, ecs_id_t component, const char *funcname) { const ecs_type_info_t *ti = ecs_get_type_info(world, component); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "invalid component '%s' for %s(): component cannot be a tag/zero sized", flecs_errstr(ecs_id_str(world, component)), funcname); return ti->hooks.on_replace != NULL; error: return false; } #endif void* ecs_get_mut_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_mut"); flecs_assert_component_valid(world, entity, component, "get_mut"); ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "get_mut"), ECS_INVALID_PARAMETER, "cannot call get_mut() for component '%s' which has an on_replace hook " "(use set()/assign())", flecs_errstr(ecs_id_str(world, component))); world = ecs_get_world(world); flecs_check_exclusive_world_access_write(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { ecs_get_low_id(r->table, r, component); return NULL; } } ecs_component_record_t *cr = flecs_components_get(world, component); int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_ptr(world, r->table, row, cr).ptr; error: return NULL; } void* ecs_ensure_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "ensure"); flecs_assert_component_valid(world, entity, component, "ensure"); ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "ensure"), ECS_INVALID_PARAMETER, "cannot call ensure() for component '%s' which has an on_replace hook " "(use set()/assign())", flecs_errstr(ecs_id_str(world, component))); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_ensure( world, stage, entity, component, flecs_uto(int32_t, size)); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)).ptr; ecs_check(result != NULL, ECS_INVALID_OPERATION, "component '%s' ensured on entity '%s' was removed during the " "operation, make sure not to remove the component in hooks/observers", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_2(ecs_get_path(world, entity))); flecs_defer_end(world, stage); return result; error: return NULL; } void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size, bool *is_new) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "emplace"); flecs_assert_component_valid(world, entity, component, "emplace"); ecs_dbg_assert(!flecs_component_has_on_replace(world, component, "emplace"), ECS_INVALID_PARAMETER, "cannot call emplace() for component '%s' which has an on_replace hook " "(use set()/entity::replace())", flecs_errstr(ecs_id_str(world, component))); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_emplace( world, stage, entity, component, flecs_uto(int32_t, size), is_new); } ecs_check(is_new || !ecs_has_id(world, entity, component), ECS_INVALID_PARAMETER, "cannot emplace() existing component '%s' for entity '%s' unless " "'is_new' argument is provided", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_2(ecs_get_path(world, entity))); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_component_record_t *cr = flecs_components_ensure(world, component); if (cr->flags & EcsIdDontFragment) { void *ptr = flecs_component_sparse_get(world, cr, table, entity); if (ptr) { if (is_new) { *is_new = false; } flecs_defer_end(world, stage); return ptr; } if (is_new) { *is_new = true; } is_new = NULL; } flecs_add_id_w_record(world, entity, r, component, component); flecs_defer_end(world, stage); void *ptr = flecs_get_component( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); ecs_check(ptr != NULL, ECS_INVALID_OPERATION, "component '%s' emplaced on entity '%s' was removed during the " "operation, make sure not to remove the component in hooks/observers", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_2(ecs_get_path(world, entity))); if (is_new) { *is_new = table != r->table; } return ptr; error: return NULL; } static ecs_record_t* flecs_access_begin( ecs_world_t *stage, ecs_entity_t entity, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); const ecs_world_t *world = ecs_get_world(stage); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_os_ainc(&table->_->lock); (void)count; if (write) { ecs_check(count == 1, ECS_ACCESS_VIOLATION, "invalid concurrent access to table for entity '%s'", flecs_errstr(ecs_get_path(world, entity))); } return r; error: return NULL; } static void flecs_access_end( const ecs_record_t *r, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); int32_t count = ecs_os_adec(&r->table->_->lock); (void)count; if (write) { ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); } ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); error: return; } ecs_record_t* ecs_write_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, true); } void ecs_write_end( ecs_record_t *r) { flecs_access_end(r, true); } const ecs_record_t* ecs_read_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, false); } void ecs_read_end( const ecs_record_t *r) { flecs_access_end(r, false); } ecs_entity_t ecs_record_get_entity( const ecs_record_t *record) { ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = record->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.entities[ECS_RECORD_TO_ROW(record->row)]; error: return 0; } const void* ecs_record_get_id( const ecs_world_t *stage, const ecs_record_t *r, ecs_id_t component) { const ecs_world_t *world = ecs_get_world(stage); ecs_component_record_t *cr = flecs_components_get(world, component); return flecs_get_component( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } bool ecs_record_has_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t component) { const ecs_world_t *world = ecs_get_world(stage); if (r->table) { return ecs_table_has_id(world, r->table, component); } return false; } void* ecs_record_ensure_id( ecs_world_t *stage, ecs_record_t *r, ecs_id_t component) { const ecs_world_t *world = ecs_get_world(stage); ecs_component_record_t *cr = flecs_components_get(world, component); return flecs_get_component( world, r->table, ECS_RECORD_TO_ROW(r->row), cr); } void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, bool invoke_hook) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "modified"); flecs_assert_component_valid(world, entity, component, "modified"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, component)) { return; } 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 *cr = flecs_components_get(world, component); int32_t row = ECS_RECORD_TO_ROW(r->row); if (!cr || !flecs_get_component(world, table, row, cr)) { flecs_defer_end(world, stage); return; } flecs_notify_on_set(world, table, row, component, invoke_hook); flecs_table_mark_dirty(world, table, component); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "modified"); flecs_assert_component_valid(world, entity, component, "modified"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[component]) { return; } } if (flecs_defer_modified(stage, entity, component)) { return; } /* If the entity does not have the component, calling ecs_modified is * invalid. The assert needs to happen after the defer statement, as the * entity may not have the component when this function is called while * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, component), ECS_INVALID_PARAMETER, "invalid call to modified(), entity '%s' does not have component '%s'", flecs_errstr(ecs_get_path(world, entity)), flecs_errstr_2(ecs_id_str(world, component))); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), component, true); flecs_table_mark_dirty(world, table, component); flecs_defer_end(world, stage); error: return; } void flecs_set_id_move( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t component, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { ecs_throw(ECS_INVALID_OPERATION, "cannot flush a command queue to a deferred stage. This happens " "when a stage is explicitly merged into the world/another stage " "that is deferred"); } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure( world, entity, component, r, flecs_uto(int32_t, size)); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, component, dst.ptr, ptr, ti); } if (cmd_kind != EcsCmdEmplace) { /* ctor will have happened by ensure */ flecs_type_info_move_dtor(dst.ptr, ptr, 1, ti); } else { flecs_type_info_ctor_move_dtor(dst.ptr, ptr, 1, ti); } flecs_table_mark_dirty(world, r->table, component); if (cmd_kind == EcsCmdSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &component, .count = 1 }; flecs_notify_on_set_ids( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids); } } else if (cmd_kind == EcsCmdSetDontFragment) { flecs_notify_on_set( world, r->table, ECS_RECORD_TO_ROW(r->row), component, true); } flecs_defer_end(world, stage); error: return; } void ecs_set_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size, const void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "set"); flecs_assert_component_valid(world, entity, component, "set"); ecs_check(size != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, "invalid call to set() for component '%s' and entity '%s': no " "component value provided", flecs_errstr(ecs_id_str(world, component)), flecs_errstr_1(ecs_id_str(world, entity))); if (component == ecs_id(EcsParent)) { ecs_check(!((const EcsParent*)ptr)->value || ecs_is_alive(world, ((const EcsParent*)ptr)->value), ECS_INVALID_OPERATION, "cannot set Parent component to entity that is not alive"); } ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, entity, component, flecs_utosize(size), ECS_CONST_CAST(void*, ptr)); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, component, r, flecs_uto(int32_t, size)); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[component]) { ecs_os_memcpy(dst.ptr, ptr, size); goto done; } } flecs_copy_id(world, entity, r, component, size, dst.ptr, ptr, dst.ti); done: flecs_defer_end(world, stage); error: return; } #if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) static bool flecs_can_toggle( ecs_world_t *world, ecs_id_t component) { ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return ecs_has_id(world, component, EcsCanToggle); } return (cr->flags & EcsIdCanToggle) != 0; } #endif void ecs_enable_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, bool enable) { 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); flecs_assert_component_valid(world, entity, component, "enable_component"); ecs_check(flecs_can_toggle(world, component), ECS_INVALID_OPERATION, "cannot enable/disable component '%s' as it does not have the CanToggle trait", flecs_errstr(ecs_id_str(world, component))); ecs_entity_t bs_id = component | ECS_TOGGLE; ecs_add_id((ecs_world_t*)stage, entity, bs_id); if (flecs_defer_enable(stage, entity, component, enable)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULL, since entity is stored in the table */ ecs_bitset_t *bs = flecs_table_get_toggle(table, component); 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; } bool ecs_is_enabled_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "is_enabled"); flecs_assert_component_valid(world, entity, component, "is_enabled"); /* 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; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = flecs_table_get_toggle(table, component); if (!bs) { /* If table does not have TOGGLE column for component, component is * always enabled, if the entity has it */ return ecs_has_id(world, entity, component); } return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } void ecs_set_child_order( ecs_world_t *world, ecs_entity_t parent, const ecs_entity_t *children, int32_t child_count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, parent), ECS_INVALID_PARAMETER, NULL); ecs_check(children == NULL || child_count, ECS_INVALID_PARAMETER, "children array passed to set_child_order() cannot be NULL if " "child_count is not 0"); ecs_check(children != NULL || !child_count, ECS_INVALID_PARAMETER, "children array passed to set_child_order() cannot be not-NULL if " "child_count is 0"); ecs_check(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot call set_child_order() while in multithreaded mode"); flecs_stage_from_world(&world); flecs_check_exclusive_world_access_write(world); flecs_ordered_children_reorder(world, parent, children, child_count); error: return; } ecs_entities_t ecs_get_ordered_children( const ecs_world_t *world, ecs_entity_t parent) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, parent), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(parent)); ecs_check(cr != NULL && (cr->flags & EcsIdOrderedChildren), ECS_INVALID_PARAMETER, "invalid call to get_ordered_children(): parent '%s' does not have " "the OrderedChildren trait", flecs_errstr(ecs_get_path(world, parent))); ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); return (ecs_entities_t){ .count = ecs_vec_count(&cr->pair->ordered_children), .alive_count = ecs_vec_count(&cr->pair->ordered_children), .ids = ecs_vec_first(&cr->pair->ordered_children), }; error: return (ecs_entities_t){0}; } bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "has"); ecs_check(component != 0, ECS_INVALID_PARAMETER, "invalid component passed to has(): component cannot be 0"); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get_any(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { return table->component_map[component] != 0; } } ecs_component_record_t *cr = flecs_components_get(world, component); bool can_inherit = false; if (cr) { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (tr) { return true; } if (cr->flags & (EcsIdDontFragment|EcsIdMatchDontFragment)) { if (flecs_component_sparse_has(cr, entity)) { return true; } else { return flecs_get_base_component( world, table, component, cr, 0) != NULL; } } if (ECS_PAIR_FIRST(component) == EcsChildOf) { if (table->flags & EcsTableHasParent) { if (ECS_PAIR_SECOND(component) == EcsWildcard) { return true; } int32_t column = table->component_map[ecs_id(EcsParent)]; EcsParent *p = flecs_table_get_component( table, column - 1, ECS_RECORD_TO_ROW(r->row)).ptr; return (uint32_t)p->value == ECS_PAIR_SECOND(component); } } can_inherit = cr->flags & EcsIdOnInstantiateInherit; } if (!(table->flags & EcsTableHasIsA)) { return false; } if (!can_inherit) { return false; } ecs_table_record_t *tr; int32_t column = ecs_search_relation(world, table, 0, component, EcsIsA, 0, 0, 0, &tr); if (column == -1) { return false; } return true; error: return false; } bool ecs_owns_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "owns"); ecs_check(component != 0, ECS_INVALID_PARAMETER, "invalid component passed to owns(): component cannot be 0"); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get_any(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (component < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_lookup[component]) { return table->component_map[component] != 0; } } ecs_component_record_t *cr = flecs_components_get(world, component); if (cr) { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (tr) { return true; } if (cr->flags & (EcsIdDontFragment|EcsIdMatchDontFragment)) { return flecs_component_sparse_has(cr, entity); } if (ECS_PAIR_FIRST(component) == EcsChildOf) { if (table->flags & EcsTableHasParent) { if (ECS_PAIR_SECOND(component) == EcsWildcard) { return true; } int32_t column = table->component_map[ecs_id(EcsParent)]; EcsParent *p = flecs_table_get_component( table, column - 1, ECS_RECORD_TO_ROW(r->row)).ptr; return (uint32_t)p->value == ECS_PAIR_SECOND(component); } } } error: return false; } static ecs_entity_t flecs_get_prefab_instance_child( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t prefab_child) { ecs_map_val_t *index_ptr = ecs_map_get( &world->prefab_child_indices, prefab_child); if (!index_ptr) { return 0; } flecs_assert_entity_valid(world, prefab_child, "get_target"); ecs_check(ecs_owns_id(world, prefab_child, EcsPrefab), ECS_INVALID_OPERATION, "cannot get target for '%s': entity is not/no longer a prefab", flecs_errstr(ecs_id_str(world, prefab_child))); #ifdef FLECS_DEBUG ecs_entity_t prefab = ecs_get_parent(world, prefab_child); ecs_check(prefab != 0, ECS_INVALID_OPERATION, "cannot get target for '%s': entity has no parent", flecs_errstr(ecs_id_str(world, prefab_child))); ecs_check(ecs_owns_id(world, prefab, EcsPrefab), ECS_INVALID_OPERATION, "cannot get target for '%s': parent is not/no longer a prefab", flecs_errstr(ecs_id_str(world, prefab)), flecs_errstr_1(ecs_id_str(world, prefab_child))); ecs_check(ecs_has_pair(world, entity, EcsIsA, prefab), ECS_INVALID_OPERATION, "cannot get target for '%s': entity '%s' is not an instance of prefab '%s'", flecs_errstr(ecs_id_str(world, prefab_child)), flecs_errstr_1(ecs_id_str(world, entity)), flecs_errstr_2(ecs_id_str(world, prefab))); #endif ecs_component_record_t *childof_cr = flecs_components_get( world, ecs_childof(entity)); ecs_check(childof_cr != NULL, ECS_INVALID_OPERATION, "cannot get target for '%s': children of '%s' have changed since " "prefab instantiation", flecs_errstr(ecs_id_str(world, prefab_child)), flecs_errstr_1(ecs_id_str(world, entity))); ecs_vec_t *v = &childof_cr->pair->ordered_children; int32_t index = flecs_uto(int32_t, *index_ptr); ecs_check(ecs_vec_count(v) > index, ECS_INVALID_OPERATION, "cannot get target for '%s': children of '%s' have changed since " "prefab instantiation", flecs_errstr(ecs_id_str(world, prefab_child)), flecs_errstr_1(ecs_id_str(world, entity))); ecs_entity_t tgt = ecs_vec_get_t(v, ecs_entity_t, index)[0]; ecs_check(ecs_has_pair(world, tgt, EcsIsA, prefab_child), ECS_INVALID_OPERATION, "cannot get target for '%s': children of '%s' have changed since " "prefab instantiation", flecs_errstr(ecs_id_str(world, prefab_child)), flecs_errstr_1(ecs_id_str(world, entity))); return tgt; error: return 0; } 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); flecs_assert_entity_valid(world, entity, "get_target"); ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); if (index < 0) { return 0; } if (rel == EcsChildOf) { if (index > 0) { return 0; } return ecs_get_parent(world, entity); } 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 *cr = flecs_components_get(world, wc); if (!cr) { if (!index) { return flecs_get_prefab_instance_child(world, entity, rel); } return 0; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table);; if (!tr) { if (cr->flags & EcsIdDontFragment) { if (cr->flags & EcsIdExclusive) { if (index > 0) { return 0; } ecs_entity_t *tgt = flecs_sparse_get(cr->sparse, 0, entity); if (tgt) { return *tgt; } } else { ecs_type_t *type = flecs_sparse_get(cr->sparse, 0, entity); if (type && (index < type->count)) { return type->array[index]; } } } if (cr->flags & EcsIdOnInstantiateInherit) { goto look_in_base; } return 0; } if (index >= tr->count) { index -= tr->count; goto look_in_base; } ecs_id_t pair_id = table->type.array[tr->index + index]; if (!ECS_IS_VALUE_PAIR(pair_id)) { return flecs_entities_get_alive(world, pair_id); } else { return ECS_PAIR_SECOND(pair_id); } look_in_base: if (table->flags & EcsTableHasIsA) { const ecs_table_record_t *tr_isa = flecs_component_get_table( world->cr_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; } } } error: return 0; } ecs_entity_t ecs_get_parent( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_parent"); 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); if (table->flags & EcsTableHasParent) { int32_t column = table->component_map[ecs_id(EcsParent)]; ecs_assert(column > 0, ECS_INTERNAL_ERROR, NULL); EcsParent *p = ecs_table_get_column( table, column - 1, ECS_RECORD_TO_ROW(r->row)); ecs_assert(ecs_is_valid(world, p->value), ECS_INVALID_OPERATION, "Parent component points to invalid parent %u", p->value, entity); return p->value; } ecs_component_record_t *cr = world->cr_childof_wildcard; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return 0; } ecs_entity_t id = table->type.array[tr->index]; return flecs_entities_get_alive(world, ECS_PAIR_SECOND(id)); error: return 0; } ecs_entity_t ecs_new_w_parent( ecs_world_t *world, ecs_entity_t parent, const char *name) { ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot create new entity while world is multithreaded"); ecs_component_record_t *cr = flecs_components_ensure( world, ecs_childof(parent)); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *pr = cr->pair; ecs_assert(pr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = 0; if (name) { ecs_hashmap_t *name_index = pr->name_index; if (name_index) { entity = flecs_name_index_find(name_index, name, 0, 0); if (entity) { return entity; } } } ecs_id_t type_ids[] = { ecs_id(EcsParent), ecs_value_pair(EcsParentDepth, pr->depth)}; ecs_type_t type = { .count = 2, .array = type_ids }; ecs_table_t *table = flecs_table_find_or_create(world, &type); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); entity = flecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; ecs_table_diff_t table_diff = { .added = table->type, .added_flags = flags }; int32_t row = ecs_table_count(table); flecs_table_append(world, table, entity, false, false); r->table = table; r->row = (uint32_t)row; EcsParent *parent_ptr = table->data.columns[0].data; parent_ptr = &parent_ptr[row]; parent_ptr->value = parent; flecs_actions_new(world, table, row, 1, &table_diff, 0, true, EcsWildcard); if (name) { bool is_deferred = ecs_is_deferred(world); if (is_deferred) ecs_defer_suspend(world); flecs_set_identifier(world, stage, entity, EcsName, name); if (is_deferred) ecs_defer_resume(world); } flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r); return entity; } ecs_entity_t ecs_get_target_for_id( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, ecs_id_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_target_for_id"); if (!component) { return ecs_get_target(world, entity, rel, 0); } world = ecs_get_world(world); ecs_table_t *table = ecs_get_table(world, entity); ecs_entity_t src = 0; if (rel) { ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return 0; } int32_t column = ecs_search_relation_for_entity( world, entity, component, ecs_pair(rel, EcsWildcard), true, cr, &src, 0, 0); if (column == -1) { return 0; } } else { if (table) { ecs_id_t *ids = table->type.array; int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { ecs_id_t ent = ids[i]; if (ent & ECS_ID_FLAGS_MASK) { /* Skip ids with pairs or flags since 0 was provided for rel */ break; } if (ecs_has_id(world, ent, component)) { src = ent; break; } } } } return src; error: return 0; } int32_t ecs_get_depth( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel) { ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_depth"); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, "cannot determine depth for non-acyclic relationship '%s' " "(add Acyclic trait to relationship)", flecs_errstr(ecs_get_path(world, rel))); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return ecs_table_get_depth(world, table, rel); } return 0; error: return -1; } bool ecs_is_valid( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_DEBUG world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); #endif /* 0 is not a valid entity id */ if (!entity) { return false; } /* Entity identifiers should not contain flag bits */ if (entity & ECS_ID_FLAGS_MASK) { return false; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return false; } /* If id exists, it must be alive (the generation count must match) */ return ecs_is_alive(world, entity); error: return false; } ecs_id_t ecs_strip_generation( ecs_entity_t e) { /* If this is not a pair, erase the generation bits */ if (!(e & ECS_ID_FLAGS_MASK)) { e &= ~ECS_GENERATION_MASK; } return e; } bool ecs_is_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); return flecs_entities_is_alive(world, entity); error: return false; } ecs_entity_t ecs_get_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { return 0; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); if (flecs_entities_is_alive(world, entity)) { return entity; } /* Make sure id does not have generation. This guards against accidentally * "upcasting" a not alive identifier to an alive one. */ if ((uint32_t)entity != entity) { return 0; } ecs_entity_t current = flecs_entities_get_alive(world, entity); if (!current || !flecs_entities_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_make_alive( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); flecs_check_exclusive_world_access_write(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 call make_alive() while world is in multithreaded mode"); /* Check if a version of the provided id is alive */ ecs_entity_t current = ecs_get_alive(world, (uint32_t)entity); if (current == entity) { /* If alive and equal to the argument, there's nothing left to do */ return; } /* If the id is currently alive but did not match the argument, fail */ ecs_check(!current, ECS_INVALID_OPERATION, "invalid call to make_alive(): entity %u is alive with different " "generation (%u vs %u)", (uint32_t)entity, (uint32_t)(current >> 32), (uint32_t)(entity >> 32)); /* 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 flecs_entities_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_make_alive. 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. */ 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_table_count(&world->store.root); flecs_table_append(world, &world->store.root, entity, false, false); r->table = &world->store.root; r->row = ECS_ROW_TO_RECORD(row, r->row & ECS_ROW_FLAGS_MASK); error: return; } void ecs_make_alive_id( ecs_world_t *world, ecs_id_t component) { flecs_poly_assert(world, ecs_world_t); if (ECS_HAS_ID_FLAG(component, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(component); ecs_entity_t t = ECS_PAIR_SECOND(component); ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ECS_IS_VALUE_PAIR(component) || t != 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"); ecs_make_alive(world, r); } if (!ECS_IS_VALUE_PAIR(component)) { if (flecs_entities_get_alive(world, t) == 0) { ecs_assert(!ecs_exists(world, t), ECS_INVALID_PARAMETER, "second element of pair is not alive"); ecs_make_alive(world, t); } } } else { ecs_make_alive(world, component & ECS_COMPONENT_MASK); } error: return; } bool ecs_exists( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_check_exclusive_world_access_read(world); return flecs_entities_exists(world, entity); error: return false; } void ecs_set_version( ecs_world_t *world, ecs_entity_t entity_with_generation) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot change generation for entity %u when world is in readonly mode", (uint32_t)entity_with_generation); ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, "cannot change generation for entity %u while world is deferred", (uint32_t)entity_with_generation); flecs_check_exclusive_world_access_write(world); 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; } } } uint32_t ecs_get_version( ecs_entity_t entity) { return flecs_uto(uint32_t, ECS_GENERATION(entity)); } ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_table"); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_check_exclusive_world_access_read(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_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "get_type"); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return &table->type; } error: return NULL; } void ecs_enable( ecs_world_t *world, ecs_entity_t entity, bool enabled) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_assert_entity_valid(world, entity, "enable"); if (ecs_has_id(world, entity, EcsPrefab)) { /* If entity is a prefab, 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 component = ids[i]; if (component & ECS_ID_FLAGS_MASK) { continue; } ecs_flags32_t flags = ecs_id_get_flags(world, component); if (!(flags & EcsIdOnInstantiateDontInherit)){ ecs_enable(world, component, enabled); } } } else { if (enabled) { ecs_remove_id(world, entity, EcsDisabled); } else { ecs_add_id(world, entity, EcsDisabled); } } error: return; } static void ecs_type_str_buf( const ecs_world_t *world, const ecs_type_t *type, ecs_strbuf_t *buf) { ecs_entity_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; if (i) { ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, ' '); } if (id == 1) { ecs_strbuf_appendlit(buf, "Component"); } else { ecs_id_str_buf(world, id, buf); } } } char* ecs_type_str( const ecs_world_t *world, const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_type_str_buf(world, type, &buf); return ecs_strbuf_get(&buf); } char* ecs_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_assert_entity_valid(world, entity, "entity_str"); 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, ']'); return ecs_strbuf_get(&buf); error: return NULL; } ecs_table_range_t flecs_range_from_entity( const ecs_world_t *world, ecs_entity_t e) { ecs_record_t *r = flecs_entities_get(world, e); if (!r) { return (ecs_table_range_t){ 0 }; } return (ecs_table_range_t){ .table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1 }; } #define ECS_NAME_BUFFER_LENGTH (64) static bool flecs_path_append( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix, ecs_strbuf_t *buf, bool escape) { 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); ecs_assert(r != NULL, ECS_INVALID_OPERATION, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); bool 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 (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 (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 cur != 0; } bool flecs_name_is_id( const char *name) { 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; } return false; } ecs_entity_t flecs_name_to_id( const char *name) { 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 */ } return res; } static ecs_entity_t flecs_get_builtin( const char *name) { if (name[0] == '.' && name[1] == '\0') { return EcsThis; } else if (name[0] == '*' && name[1] == '\0') { return EcsWildcard; } else if (name[0] == '_' && name[1] == '\0') { return EcsAny; } else if (name[0] == '$' && name[1] == '\0') { return EcsVariable; } return 0; } static bool flecs_is_sep( const char **ptr, const char *sep) { ecs_size_t len = ecs_os_strlen(sep); if (!ecs_os_strncmp(*ptr, sep, len)) { *ptr += len; return true; } else { return false; } } static const char* flecs_path_elem( const char *path, const char *sep, char **buffer_out, ecs_size_t *size_out) { char *buffer = NULL; if (buffer_out) { buffer = *buffer_out; } const char *ptr; char ch; int32_t template_nesting = 0; int32_t pos = 0; ecs_size_t size = size_out ? *size_out : 0; for (ptr = path; (ch = *ptr); ptr ++) { bool escaped = false; if (ch == '<') { template_nesting ++; } else if (ch == '>') { template_nesting --; } else if (ch == '\\') { ptr ++; ch = ptr[0]; if (!ch) { break; } escaped = true; } ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, "%s", path); if (!escaped && !template_nesting && flecs_is_sep(&ptr, sep)) { break; } if (buffer) { ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); 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 (buffer) { buffer[pos] = '\0'; *buffer_out = buffer; *size_out = size; } if (pos || ptr[0]) { return ptr; } else { return NULL; } error: return NULL; } static bool flecs_is_root_path( const char *path, const char *prefix) { if (prefix) { return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); } else { return false; } } static ecs_entity_t flecs_get_parent_from_path( const ecs_world_t *world, ecs_entity_t parent, const char **path_ptr, const char *sep, const char *prefix, bool new_entity, bool *error) { ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); bool start_from_root = false; const char *path = *path_ptr; if (flecs_is_root_path(path, prefix)) { path += ecs_os_strlen(prefix); parent = 0; start_from_root = true; } if (path[0] == '#') { parent = flecs_name_to_id(path); if (!parent && ecs_os_strncmp(path, "#0", 2)) { *error = true; return 0; } path ++; while (path[0] && isdigit(path[0])) { path ++; /* Skip id part of path */ } /* 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); } *path_ptr = path; return parent; } static void flecs_on_set_symbol( ecs_iter_t *it) { EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); ecs_world_t *world = it->real_world; int i; for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; flecs_name_index_ensure( &world->symbols, e, n[i].value, n[i].length, n[i].hash); } } void flecs_bootstrap_entity_name( ecs_world_t *world) { ecs_observer(world, { .query.terms[0] = { .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol) }, .callback = flecs_on_set_symbol, .events = {EcsOnSet}, .yield_existing = true, .global_observer = true }); } void ecs_on_set(EcsIdentifier)( ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 0); ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t evt = it->event; ecs_id_t evt_id = it->event_id; ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ ecs_id_t pair = ecs_childof(0); ecs_hashmap_t *index = NULL; bool has_parent = false; EcsParent *parents = NULL; if (kind == EcsSymbol) { index = &world->symbols; } else if (kind == EcsAlias) { index = &world->aliases; } else if (kind == EcsName) { ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); if (it->table->flags & EcsTableHasParent) { has_parent = true; parents = ecs_table_get(world, it->table, EcsParent, it->offset); } else { ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = flecs_components_get(world, pair); if (evt == EcsOnSet) { index = flecs_component_name_index_ensure(world, cr); } else { index = flecs_component_name_index_get(world, cr); } } } int i, count = it->count; for (i = 0; i < count; i ++) { EcsIdentifier *cur = &ptr[i]; uint64_t hash; ecs_size_t len; const char *name = cur->value; if (kind == EcsName) { ecs_assert((world->flags & (EcsWorldInit|EcsWorldFini)) || !(cur->index) || !(it->table->flags & EcsTableHasBuiltins), ECS_INVALID_OPERATION, "cannot rename builtin entity to '%s'", name); ecs_assert((world->flags & (EcsWorldInit|EcsWorldFini)) || (it->entities[i] != EcsFlecs), ECS_INVALID_OPERATION, "cannot rename flecs root module"); ecs_assert((world->flags & (EcsWorldInit|EcsWorldFini)) || (it->entities[i] != EcsFlecsCore), ECS_INVALID_OPERATION, "cannot rename flecs.core module"); if (has_parent) { ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(parents[i].value)); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); index = flecs_component_name_index_ensure(world, cr); } } if (cur->index && cur->index != index) { /* If index doesn't match up, the value must have been copied from * another entity, so reset index & cached index hash */ cur->index = NULL; cur->index_hash = 0; } if (cur->value && (evt == EcsOnSet)) { len = cur->length = ecs_os_strlen(name); hash = cur->hash = flecs_hash(name, len); } else { len = cur->length = 0; hash = cur->hash = 0; cur->index = NULL; } if (index) { uint64_t index_hash = cur->index_hash; ecs_entity_t e = it->entities[i]; if (hash != index_hash) { if (index_hash) { flecs_name_index_remove(index, e, index_hash); } if (hash) { if (kind == EcsSymbol || kind == EcsAlias) { uint64_t existing = flecs_name_index_find( index, name, len, hash); if (existing && existing != e) { ecs_abort(ECS_ALREADY_DEFINED, "conflicting %s '%s' " "(existing = %u, new = %u)", kind == EcsSymbol ? "symbol" : "alias", name, (uint32_t)existing, (uint32_t)e); } } flecs_name_index_ensure(index, e, name, len, hash); cur->index_hash = hash; cur->index = index; } } else if (!flecs_name_index_update_name(index, e, hash, name) && kind == EcsName) { flecs_name_index_ensure(index, e, name, len, hash); cur->index_hash = hash; cur->index = index; } } } } static void flecs_reparent_name_index_intern( const ecs_entity_t *entities, ecs_hashmap_t *src_index, ecs_hashmap_t *dst_index, EcsIdentifier *names, int32_t count) { int32_t i; for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; EcsIdentifier *name = &names[i]; ecs_assert(e != EcsFlecs, ECS_INVALID_OPERATION, "cannot reparent flecs root module"); ecs_assert(e != EcsFlecsCore, ECS_INVALID_OPERATION, "cannot reparent flecs.core module"); uint64_t index_hash = name->index_hash; if (index_hash) { flecs_name_index_remove(src_index, e, index_hash); } if (dst_index) { const char *name_str = name->value; if (name_str) { if (name->hash == 0) { name->length = ecs_os_strlen(name_str); name->hash = flecs_hash(name_str, name->length); } ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); flecs_name_index_ensure( dst_index, e, name_str, name->length, name->hash); name->index = dst_index; } } } } void flecs_reparent_name_index( ecs_world_t *world, ecs_table_t *dst, ecs_table_t *src, int32_t offset, int32_t count) { 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 (!src) { src = &world->store.root; } ecs_hashmap_t *src_index = flecs_table_get_name_index(world, src); ecs_hashmap_t *dst_index = flecs_table_get_name_index(world, dst); ecs_assert(src_index != dst_index, ECS_INTERNAL_ERROR, NULL); if ((!src_index && !dst_index)) { return; } EcsIdentifier *names = ecs_table_get_pair(world, dst, EcsIdentifier, EcsName, offset); ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); flecs_reparent_name_index_intern(&ecs_table_entities(dst)[offset], src_index, dst_index, names, count); } void flecs_unparent_name_index( ecs_world_t *world, ecs_table_t *src, ecs_table_t *dst, int32_t offset, int32_t count) { if (!(src->flags & EcsTableHasName)) { return; } if (!dst || !(dst->flags & EcsTableHasName)) { /* If destination table doesn't have a name, we don't need to update the * name index. Even if the src table had a name, the on_remove hook for * EcsIdentifier will remove the entity from the index. */ return; } ecs_hashmap_t *src_index = flecs_table_get_name_index(world, src); ecs_hashmap_t *dst_index = dst ? flecs_table_get_name_index(world, dst) : NULL; ecs_assert(src_index != NULL, ECS_INTERNAL_ERROR, NULL); EcsIdentifier *names = ecs_table_get_pair(world, src, EcsIdentifier, EcsName, offset); ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); flecs_reparent_name_index_intern(&ecs_table_entities(src)[offset], src_index, dst_index, names, count); } /* Public functions */ void ecs_get_path_w_sep_buf( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix, ecs_strbuf_t *buf, bool escape) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (child == EcsWildcard) { ecs_strbuf_appendch(buf, '*'); return; } if (child == EcsAny) { ecs_strbuf_appendch(buf, '_'); return; } if (!sep) { sep = "."; } if (!child || parent != child) { flecs_path_append(world, parent, child, sep, prefix, buf, escape); } else { ecs_strbuf_appendstrn(buf, "", 0); } error: return; } char* ecs_get_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf, false); return ecs_strbuf_get(&buf); } ecs_entity_t ecs_lookup_child( const ecs_world_t *world, ecs_entity_t parent, const char *name) { ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); if (flecs_name_is_id(name)) { ecs_entity_t result = flecs_name_to_id(name); if (result && ecs_is_alive(world, result)) { if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { return 0; } return result; } } ecs_id_t pair = ecs_childof(parent); ecs_component_record_t *cr = flecs_components_get(world, pair); ecs_hashmap_t *index = NULL; if (cr) { index = flecs_component_name_index_get(world, cr); } if (index) { return flecs_name_index_find(index, name, 0, 0); } else { return 0; } error: return 0; } ecs_entity_t ecs_lookup( const ecs_world_t *world, const char *path) { return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); } ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, const char *name, bool lookup_as_path, bool recursive) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = 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) { if (!path) { return 0; } 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); ecs_entity_t e = flecs_get_builtin(path); if (e) { return e; } e = flecs_name_index_find(&world->aliases, path, 0, 0); if (e) { return e; } char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; const char *ptr; int32_t size = ECS_NAME_BUFFER_LENGTH; ecs_entity_t cur; bool lookup_path_search = false; const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); const ecs_entity_t *lookup_path_cur = lookup_path; while (lookup_path_cur && *lookup_path_cur) { lookup_path_cur ++; } if (!sep) { sep = "."; } 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; } } tail: if (!cur && recursive) { if (!lookup_path_search) { if (parent) { parent = ecs_get_target(world, parent, EcsChildOf, 0); goto retry; } else { lookup_path_search = true; } } if (lookup_path_search) { if (lookup_path_cur != lookup_path) { lookup_path_cur --; parent = lookup_path_cur[0]; goto retry; } } } if (elem != buff) { ecs_os_free(elem); } return cur; error: return 0; } ecs_entity_t ecs_set_scope( ecs_world_t *world, ecs_entity_t scope) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t cur = stage->scope; stage->scope = scope; return cur; error: return 0; } ecs_entity_t ecs_get_scope( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->scope; error: return 0; } ecs_entity_t* ecs_set_lookup_path( ecs_world_t *world, const ecs_entity_t *lookup_path) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Safe: application owns lookup path */ ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); stage->lookup_path = lookup_path; return cur; error: return NULL; } ecs_entity_t* ecs_get_lookup_path( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); /* Safe: application owns lookup path */ return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); error: return NULL; } const char* ecs_set_name_prefix( ecs_world_t *world, const char *prefix) { 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_add_path( ecs_world_t *world, bool defer_suspend, ecs_entity_t parent, ecs_entity_t entity, const char *name) { ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (defer_suspend) { real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, parent); } ecs_set_name(world, entity, name); if (defer_suspend) { ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_resume_readonly(real_world, &srs); flecs_defer_path(stage, parent, entity, name); } } ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *real_world = world; if (flecs_poly_is(world, ecs_stage_t)) { real_world = ecs_get_world(world); } if (!sep) { sep = "."; } if (!path) { if (!entity) { entity = ecs_new(world); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, parent); } 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; } 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; ecs_entity_t cur_parent = parent; char *name = NULL; if (sep[0]) { while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { ecs_entity_t e = ecs_lookup_child(world, cur, elem); if (name) { ecs_os_free(name); } name = ecs_os_strdup(elem); if (!e) { /* 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 (!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); } } if (!cur && last_elem && root_path) { ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); } flecs_add_path(world, suspend_defer, cur, e, name); } cur_parent = cur; cur = e; } if (entity && (cur != entity)) { flecs_add_path(world, suspend_defer, cur_parent, entity, name); cur = entity; } if (name) { ecs_os_free(name); } if (elem != buff) { ecs_os_free(elem); } } else { flecs_add_path(world, suspend_defer, parent, entity, path); } return cur; error: return 0; } ecs_entity_t ecs_new_from_path_w_sep( ecs_world_t *world, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } static const char* flecs_get_identifier( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); const EcsIdentifier *ptr = ecs_get_pair( world, entity, EcsIdentifier, tag); if (ptr) { return ptr->value; } else { return NULL; } error: return NULL; } const char* ecs_get_name( const ecs_world_t *world, ecs_entity_t entity) { return flecs_get_identifier(world, entity, EcsName); } const char* ecs_get_symbol( const ecs_world_t *world, ecs_entity_t entity) { world = ecs_get_world(world); if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { return flecs_get_identifier(world, entity, EcsSymbol); } else { return NULL; } } ecs_entity_t flecs_set_identifier( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t tag, const char *name) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { entity = ecs_new(world); } if (!name) { ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; } EcsIdentifier *ptr = ecs_ensure_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); 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); } char *old = ptr->value; ptr->value = ecs_os_strdup(name); ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); /* Free old name after updating name index in on_set handler. */ ecs_os_free(old); return entity; error: return 0; } ecs_entity_t ecs_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { if (!entity) { return ecs_entity(world, { .name = name }); } ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_set_identifier(world, stage, entity, EcsName, name); return entity; } ecs_entity_t ecs_set_symbol( ecs_world_t *world, ecs_entity_t entity, const char *name) { return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); } void ecs_set_alias( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_set_identifier(world, NULL, entity, EcsAlias, name); } bool ecs_id_match( ecs_id_t id, ecs_id_t pattern) { if (id == pattern) { return true; } if (ECS_HAS_ID_FLAG(pattern, PAIR)) { if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } ecs_entity_t id_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); ecs_check(id_first != 0, ECS_INVALID_PARAMETER, "first element of pair cannot be 0"); ecs_check(ECS_IS_VALUE_PAIR(id) || id_second != 0, ECS_INVALID_PARAMETER, "second element of pair cannot be 0"); ecs_check(pattern_first != 0, ECS_INVALID_PARAMETER, "first element of pair cannot be 0"); ecs_check(ECS_IS_VALUE_PAIR(pattern) || pattern_second != 0, ECS_INVALID_PARAMETER, "second element of pair cannot be 0"); bool pattern_first_wildcard = pattern_first == EcsWildcard; bool pattern_second_wc = pattern_second == EcsWildcard; if (ECS_IS_VALUE_PAIR(pattern)) { if (!ECS_IS_VALUE_PAIR(id)) { return false; } pattern_first_wildcard = false; pattern_second_wc = false; } if (pattern_first_wildcard) { if (pattern_second_wc || 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; } } } else { if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { return false; } if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { return true; } } error: return false; } bool ecs_id_is_pair( ecs_id_t id) { return ECS_HAS_ID_FLAG(id, PAIR); } bool ecs_id_is_wildcard( ecs_id_t id) { if ((id == EcsWildcard) || (id == EcsAny)) { return true; } bool is_pair = ECS_IS_PAIR(id); if (!is_pair) { return false; } ecs_entity_t first = ECS_PAIR_FIRST(id); if (ECS_IS_VALUE_PAIR(id)) { return (first == EcsWildcard) || (first == EcsAny); } ecs_entity_t second = ECS_PAIR_SECOND(id); return (first == EcsWildcard) || (second == EcsWildcard) || (first == EcsAny) || (second == EcsAny); } bool ecs_id_is_any( ecs_id_t id) { if (id == EcsAny) { return true; } bool is_pair = ECS_IS_PAIR(id); if (!is_pair) { return false; } ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_entity_t second = ECS_PAIR_SECOND(id); return (first == EcsAny) || (second == EcsAny); } const char* flecs_id_invalid_reason( const ecs_world_t *world, ecs_id_t id) { if (!id) { return "components cannot be 0 (is the component registered?)"; } if (ecs_id_is_wildcard(id)) { return "cannot add wildcards"; } if (ECS_IS_VALUE_PAIR(id)) { if (!ECS_PAIR_FIRST(id)) { return "invalid value pair: first element is 0 (is the relationship registered?)"; } } else if (ECS_HAS_ID_FLAG(id, PAIR)) { if (!ECS_PAIR_FIRST(id) && !ECS_PAIR_SECOND(id)) { return "invalid pair: both elements are 0"; } if (!ECS_PAIR_FIRST(id)) { return "invalid pair: first element is 0 (is the relationship registered?)"; } if (!ECS_PAIR_SECOND(id)) { return "invalid pair: second element is 0"; } } else if (id & ECS_ID_FLAGS_MASK) { if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { ecs_abort(ECS_INTERNAL_ERROR, NULL); } } return NULL; } bool ecs_id_is_valid( const ecs_world_t *world, ecs_id_t id) { return flecs_id_invalid_reason(world, id) == NULL; } ecs_flags32_t ecs_id_get_flags( const ecs_world_t *world, ecs_id_t id) { ecs_component_record_t *cr = flecs_components_get(world, id); if (cr) { return cr->flags; } else { return 0; } } ecs_id_t ecs_id_from_str( const ecs_world_t *world, const char *expr) { (void)world; (void)expr; ecs_abort(ECS_UNSUPPORTED, "ecs_id_from_str requires FLECS_QUERY_DSL addon"); } const char* ecs_id_flag_str( uint64_t entity) { if (ECS_IS_VALUE_PAIR(entity)) { return "VALUE_PAIR"; } else 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_id_str_buf( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, 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 tgt = ECS_PAIR_SECOND(id); ecs_entity_t e; if ((e = ecs_get_alive(world, rel))) { rel = e; } ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf, false); ecs_strbuf_appendch(buf, ','); if (ECS_IS_VALUE_PAIR(id)) { ecs_strbuf_appendlit(buf, "@"); ecs_strbuf_appendint(buf, (uint32_t)tgt); } else { if ((e = ecs_get_alive(world, tgt))) { tgt = e; } ecs_get_path_w_sep_buf(world, 0, tgt, 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; } char* ecs_id_str( const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_id_str_buf(world, id, &buf); return ecs_strbuf_get(&buf); } ecs_id_t ecs_make_pair( ecs_entity_t relationship, ecs_entity_t target) { ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), ECS_INVALID_PARAMETER, "cannot create nested pairs"); return ecs_pair(relationship, target); } bool ecs_id_is_tag( const ecs_world_t *world, ecs_id_t id) { if (ecs_id_is_wildcard(id)) { /* If id is a wildcard, we can't tell if it's a tag or not, except * when the relationship part of a pair has the PairIsTag property */ if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t first = ECS_PAIR_FIRST(id); if (first != EcsWildcard && first != EcsAny) { 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; } } return false; } ecs_entity_t ecs_get_typeid( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); return ti->component; } error: return 0; } bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return false; } return (flecs_table_cache_count(&cr->cache) != 0); } 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_component_record_t *cr = flecs_components_ensure( world, ecs_pair(slot, child)); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, instance); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); flecs_sparse_on_add_cr(world, r->table, ECS_RECORD_TO_ROW(r->row), cr, true, NULL); } 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); } 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_add_pair(world, parent, slot, child); break; } depth ++; } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); if (parent == 0) { char *slot_of_str = ecs_get_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); } } error: return; } 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; } if (cur > id) { /* A larger id was found so id can't be part of the type. */ break; } } /* 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; } ecs_entity_t flecs_instantiate_alloc_child_id( ecs_world_t *world, ecs_entity_t prefab_child, ecs_entity_t root_prefab, ecs_entity_t root_instance) { if ((uint32_t)prefab_child < (uint32_t)root_prefab) { return flecs_new_id(world); } ecs_entity_t prefab_offset = (uint32_t)prefab_child - (uint32_t)root_prefab; ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t instance_child = (uint32_t)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)) { return flecs_new_id(world); } instance_child = 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); return instance_child; } void flecs_instantiate_sparse( ecs_world_t *world, const ecs_table_range_t *base_child_range, const ecs_entity_t *base_children, ecs_table_t *instance_table, const ecs_entity_t *instance_children, int32_t row_offset, bool emit_non_sparse) { ecs_table_t *base_child_table = base_child_range->table; if (!emit_non_sparse && !(base_child_table->flags & EcsTableHasSparse)) { return; } ecs_table_record_t *trs = base_child_table->_->records; int32_t i, count = base_child_table->type.count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &trs[i]; ecs_component_record_t *cr = tr->hdr.cr; bool sparse = cr->flags & EcsIdSparse; if (!sparse && !emit_non_sparse) { continue; } const ecs_type_info_t *ti = cr->type_info; if (!ti) { continue; } if (cr->flags & EcsIdOnInstantiateDontInherit) { continue; } ecs_id_t id = base_child_table->type.array[i]; for (int32_t j = 0; j < base_child_range->count; j ++) { ecs_entity_t instance_child = instance_children[j]; /* Sparse component values live outside the instance table, so they * are copied here. Non-sparse override values are already in place * (copied when the instance entered its table). */ if (sparse) { ecs_entity_t child = base_children[j + base_child_range->offset]; void *src_ptr = flecs_sparse_get(cr->sparse, ti->size, child); ecs_assert(src_ptr != NULL, ECS_INTERNAL_ERROR, NULL); void *dst_ptr = flecs_sparse_get( cr->sparse, ti->size, instance_child); ecs_assert(dst_ptr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); } flecs_notify_on_set( world, instance_table, row_offset + j, id, true); } } } static void flecs_instantiate_children( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance, ecs_table_range_t child_range, const ecs_instantiate_ctx_t *ctx, int32_t depth) { if (!child_range.count) { return; } ecs_table_t *child_table = child_range.table; ecs_type_t type = child_table->type; ecs_entity_t slot_of = 0; ecs_entity_t *ids = type.array; int32_t type_count = type.count; ecs_record_t *r = flecs_entities_get(world, instance); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); /* 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_component_record_t *cr = tr->hdr.cr; if (cr->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; } } /* 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 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; int32_t insert_index = flecs_child_type_insert( &diff.added, component_data, concreteId); if (childof_base_index != -1 && insert_index != -1 && insert_index <= childof_base_index) { childof_base_index ++; } continue; } int32_t column = ecs_table_type_to_column_index(child_table, i); if (column != -1) { component_data[diff.added.count] = ecs_table_get_column( child_table, column, child_range.offset); } else { component_data[diff.added.count] = NULL; } 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 ++; } } /* Instantiate the prefab child table for each new instance */ ecs_entity_t *child_ids = flecs_walloc_n( world, ecs_entity_t, child_range.count); 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); /* Find or create table */ i_table = flecs_table_find_or_create(world, &diff.added); ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == diff.added.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself, * which would cause infinite recursion. */ const ecs_entity_t *children = ecs_table_entities(child_table); #ifdef FLECS_DEBUG for (j = 0; j < child_range.count; j ++) { ecs_entity_t child = children[j + child_range.offset]; 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 /* 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; } for (j = 0; j < child_range.count; j ++) { ecs_entity_t prefab_child = children[j + child_range.offset]; child_ids[j] = flecs_instantiate_alloc_child_id( world, prefab_child, ctx_cur.root_prefab, ctx_cur.root_instance); } /* Create children */ int32_t child_row; diff.added_flags |= EcsTableEdgeReparent; const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids, &diff.added, child_range.count, component_data, false, &child_row, &diff); flecs_instantiate_sparse( world, &child_range, children, i_table, i_children, child_row, false); /* If children are slots, add slot relationships to parent */ if (slot_of) { for (j = 0; j < child_range.count; j ++) { ecs_entity_t child = children[j + child_range.offset]; ecs_entity_t i_child = i_children[j]; flecs_instantiate_slot( world, base, instance, slot_of, child, i_child); } } /* If prefab child table has children itself, recursively instantiate */ for (j = 0; j < child_range.count; j ++) { ecs_entity_t child = children[j + child_range.offset]; flecs_instantiate(world, child, i_children[j], &ctx_cur, depth + 1); } flecs_wfree_n(world, ecs_entity_t, child_range.count, child_ids); error: return; } void flecs_instantiate_dont_fragment( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance) { ecs_component_record_t *cur = world->cr_non_fragmenting_head; while (cur) { ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); if (cur->sparse && !(cur->flags & EcsIdOnInstantiateInherit) && !ecs_id_is_wildcard(cur->id)) { if (flecs_component_sparse_has(cur, base)) { void *base_ptr = flecs_component_sparse_get( world, cur, NULL, base); const ecs_type_info_t *ti = cur->type_info; ecs_record_t *r = flecs_entities_get(world, instance); void *ptr = NULL; flecs_sparse_on_add_cr(world, r->table, ECS_RECORD_TO_ROW(r->row), cur, true, &ptr); if (ti) { ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_info_copy(ptr, base_ptr, 1, ti); } if (ti) { flecs_notify_on_set( world, r->table, ECS_RECORD_TO_ROW(r->row), cur->id, true); } } } cur = cur->non_fragmenting.next; } } static void flecs_instantiate_override_dont_fragment( ecs_world_t *world, ecs_table_t *base_table, ecs_entity_t instance) { int32_t i, type_count = base_table->type.count; for (i = 0; i < type_count; i ++) { ecs_id_t id = base_table->type.array[i]; if (!(id & ECS_AUTO_OVERRIDE)) { continue; } id &= ~ECS_AUTO_OVERRIDE; ecs_flags32_t flags = flecs_component_get_flags(world, id); if (!(flags & EcsIdDontFragment)) { continue; } ecs_add_id(world, instance, id); } } void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance, const ecs_instantiate_ctx_t *ctx, int32_t depth) { 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_check(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, "likely cycle detected during instantiation of base %s", flecs_errstr(ecs_get_path(world, base))); if (base_table->flags & EcsTableOverrideDontFragment) { flecs_instantiate_override_dont_fragment( world, base_table, instance); } /* If base has non-fragmenting components, add to instance */ if (record->row & EcsEntityHasDontFragment) { flecs_instantiate_dont_fragment(world, base, instance); } if (!(base_table->flags & EcsTableIsPrefab)) { /* Don't instantiate children from base entities that aren't prefabs */ return; } ecs_component_record_t *cr = flecs_components_get(world, ecs_childof(base)); if (cr) { ecs_os_perf_trace_push("flecs.instantiate"); if (cr->flags & EcsIdOrderedChildren) { if (flecs_component_has_non_fragmenting_childof(cr)) { EcsTreeSpawner *ts = flecs_get_mut( world, base, ecs_id(EcsTreeSpawner), record, sizeof(EcsTreeSpawner)).ptr; if (!ts) { ts = flecs_prefab_spawner_build(world, base); } if (ts) { flecs_spawner_instantiate(world, ts, base, instance, ctx); } ecs_os_perf_trace_pop("flecs.instantiate"); return; } ecs_vec_t *children_vec = &cr->pair->ordered_children; int32_t i, count = ecs_vec_count(children_vec); ecs_entity_t *children = ecs_vec_first(children_vec); for (i = 0; i < count; i ++) { ecs_entity_t child = children[i]; ecs_table_range_t range = flecs_range_from_entity(world, child); if (!(range.table->flags & EcsTableHasChildOf)) { continue; } flecs_instantiate_children( world, base, instance, range, ctx, depth); } } else { ecs_table_cache_iter_t it; if (flecs_table_cache_all_iter((ecs_table_cache_t*)cr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_range_t range = { tr->hdr.table, 0, ecs_table_count(tr->hdr.table) }; flecs_instantiate_children( world, base, instance, range, ctx, depth); } } } ecs_os_perf_trace_pop("flecs.instantiate"); } error: return; } /* Utility macros to enforce consistency when initializing iterator fields */ /* If term count is smaller than cache size, initialize with inline array, * otherwise allocate. */ void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align) { ecs_world_t *world = it->world; ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); ecs_stack_t *stack = &stage->allocators.iter_stack; return flecs_stack_calloc(stack, size, align); } void flecs_iter_free( void *ptr, ecs_size_t size) { flecs_stack_free(ptr, size); } void flecs_iter_init( const ecs_world_t *world, ecs_iter_t *it, bool alloc_resources) { ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INTERNAL_ERROR, NULL); ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); ecs_stack_t *stack = &stage->allocators.iter_stack; it->priv_.stack_cursor = flecs_stack_get_cursor(stack); if (alloc_resources && it->field_count) { ecs_assert(it->ids == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(it->sources == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(it->trs == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(it->columns == NULL, ECS_INTERNAL_ERROR, NULL); int32_t fc = it->field_count; ecs_size_t wide = (ecs_size_t)(sizeof(ecs_id_t) + sizeof(ecs_entity_t) + sizeof(ecs_table_record_t*)) * fc; ecs_size_t cols = (ecs_size_t)sizeof(int16_t) * fc; char *buf = flecs_stack_alloc(stack, wide + cols, ECS_ALIGNOF(ecs_id_t)); it->ids = (ecs_id_t*)(void*)buf; it->sources = (ecs_entity_t*)(void*)(buf + (ecs_size_t)sizeof(ecs_id_t) * fc); it->trs = (const ecs_table_record_t**)(void*)(buf + (ecs_size_t)(sizeof(ecs_id_t) + sizeof(ecs_entity_t)) * fc); int16_t *columns = (int16_t*)(void*)(buf + wide); ecs_os_memset(buf, 0, wide); ecs_os_memset(columns, 0xFF, cols); it->columns = columns; } } void ecs_iter_fini( ecs_iter_t *it) { if (it->fini) { it->fini(it); } ECS_BIT_CLEAR(it->flags, EcsIterIsValid); ecs_world_t *world = it->world; if (!world) { return; } /* Make sure arrays are below stack page size, which means they don't have * to get freed explicitly. */ ecs_assert(ECS_SIZEOF(ecs_id_t) * it->field_count < FLECS_STACK_PAGE_SIZE, ECS_UNSUPPORTED, NULL); ecs_assert(ECS_SIZEOF(ecs_entity_t) * it->field_count < FLECS_STACK_PAGE_SIZE, ECS_UNSUPPORTED, NULL); ecs_assert(ECS_SIZEOF(ecs_table_record_t*) * it->field_count < FLECS_STACK_PAGE_SIZE, ECS_UNSUPPORTED, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_stack_restore_cursor(&stage->allocators.iter_stack, it->priv_.stack_cursor); } /* --- Public API --- */ void* ecs_field_w_size( const ecs_iter_t *it, size_t size, 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); ecs_check(size != 0, ECS_INVALID_PARAMETER, "missing size for field %d", index); ecs_check(ecs_field_size(it, index) == size || !ecs_field_size(it, index), ECS_INVALID_PARAMETER, "mismatching size for field %d (expected '%s')", index, flecs_errstr(ecs_id_str(it->world, it->ids[index]))); (void)size; if (it->ptrs) { return it->ptrs[index]; } int16_t column = it->columns[index]; if (column >= 0) { return ECS_ELEM(it->table->data.columns[column].data, (ecs_size_t)size, it->offset); } return flecs_field_shared(it, size, index); error: return NULL; } void* flecs_field_shared( const ecs_iter_t *it, size_t size, int8_t index) { if (!ecs_field_is_set(it, index)) { return NULL; } ecs_entity_t src = it->sources[index]; ecs_record_t *r = flecs_entities_get(it->real_world, src); ecs_table_t *table = r->table; ecs_component_record_t *cr = flecs_components_get( it->real_world, it->ids[index]); const ecs_table_record_t *tr = flecs_component_get_table(cr, table); int16_t column = tr->column; return ECS_ELEM(table->data.columns[column].data, (ecs_size_t)size, ECS_RECORD_TO_ROW(r->row)); } 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); ecs_component_record_t *cr = NULL; const ecs_table_record_t *tr = it->trs[index]; if (!tr) { cr = flecs_components_get(it->real_world, it->ids[index]); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); } else { cr = tr->hdr.cr; } ecs_assert((cr->flags & EcsIdSparse), 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]; } return flecs_sparse_get(cr->sparse, flecs_uto(int32_t, size), src); error: return NULL; } bool ecs_field_is_readonly( const ecs_iter_t *it, int8_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); if (!it->query) { return false; } 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; } } 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); 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 false; } 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); return it->sources == NULL || it->sources[index] == 0; error: return false; } 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); return it->ids[index]; error: return 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); if (!ecs_field_is_set(it, index)) { return -1; } if (it->columns && it->columns[index] >= 0) { return ecs_table_column_to_type_index(it->table, it->columns[index]); } if (it->trs && it->trs[index]) { return it->trs[index]->index; } ecs_entity_t src = it->sources[index]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(it->real_world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cr = flecs_components_get( it->real_world, it->ids[index]); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table(cr, r->table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); return tr->index; 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; } error: return 0; } size_t ecs_field_size( 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); return (size_t)it->sizes[index]; error: return 0; } char* ecs_iter_str( const ecs_iter_t *it) { if (!(it->flags & EcsIterIsValid)) { return NULL; } ecs_world_t *world = it->world; ecs_strbuf_t buf = ECS_STRBUF_INIT; 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"); 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"); 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"); } int32_t var_count = ecs_iter_get_var_count(it); if (var_count) { int32_t actual_count = 0; for (i = 0; i < var_count; i ++) { const char *var_name = ecs_iter_get_var_name(it, i); if (!var_name || var_name[0] == '_' || !strcmp(var_name, "this")) { /* Skip anonymous variables */ continue; } ecs_var_t var = ecs_iter_get_vars(it)[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"); } } 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); } } return ecs_strbuf_get(&buf); } bool ecs_iter_next( ecs_iter_t *iter) { ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); return iter->next(iter); error: return false; } int32_t ecs_iter_count( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); int32_t count = 0; while (ecs_iter_next(it)) { count += it->count; } return count; error: return 0; } ecs_entity_t ecs_iter_first( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ecs_entity_t result = 0; if (ecs_iter_next(it)) { result = it->entities[0]; ecs_iter_fini(it); } return result; error: return 0; } bool ecs_iter_is_true( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); bool result = ecs_iter_next(it); if (result) { ecs_iter_fini(it); } return result; error: return false; } ecs_entity_t ecs_iter_get_var( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &ecs_iter_get_vars(it)[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 0; } ecs_table_t* ecs_iter_get_var_as_table( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; ecs_table_t *table = var->range.table; if (!table && !var_id) { table = it->table; } if (!table) { /* If table is not set, try to get table from entity */ ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { table = r->table; if (ecs_table_count(table) != 1) { /* If table contains more than the entity, make sure not to * return a partial table. */ return NULL; } } } } if (table) { if (var->range.offset) { /* Don't return whole table if only partial table is matched */ return NULL; } if (!var->range.count || ecs_table_count(table) == var->range.count) { /* Return table if count matches */ return table; } } error: return NULL; } ecs_table_range_t ecs_iter_get_var_as_range( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(ecs_iter_get_vars(it) != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_range_t result = { 0 }; ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; ecs_table_t *table = var->range.table; if (!table && !var_id) { table = it->table; } if (!table) { ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { result.table = r->table; result.offset = ECS_RECORD_TO_ROW(r->row); result.count = 1; } } } else { result.table = table; result.offset = var->range.offset; result.count = var->range.count; if (!result.count) { result.count = ecs_table_count(table); } } return result; error: return (ecs_table_range_t){0}; } const char* ecs_iter_get_var_name( const ecs_iter_t *it, int32_t var_id) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); if (var_id == 0) { return "this"; } const ecs_query_t *query = it->query; ecs_check(query != NULL, ECS_INVALID_PARAMETER, "can only obtain variable name for iterators that iterate query"); ecs_check(var_id < query->var_count, ECS_INVALID_PARAMETER, "variable index out of range for query"); return query->vars[var_id]; error: return NULL; } int32_t ecs_iter_get_var_count( const ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); if (it->query) { return it->query->var_count; } return 1; error: return 0; } ecs_var_t* ecs_iter_get_vars( const ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); if (!it->query) { return NULL; } if (it->chain_it) { return ecs_iter_get_vars(it->chain_it); } return it->priv_.iter.query.vars; error: return 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); if (it->chain_it) { ecs_iter_set_var(it->chain_it, var_id, entity); return; } 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 < ecs_iter_get_var_count(it), 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(ecs_iter_get_vars(it) != NULL, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; var->entity = entity; ecs_record_t *r = flecs_entities_get(it->real_world, entity); if (r) { var->range.table = r->table; var->range.offset = ECS_RECORD_TO_ROW(r->row); var->range.count = 1; } else { var->range.table = NULL; var->range.offset = 0; var->range.count = 0; } it->constrained_vars |= 1llu << var_id; /* Update iterator for constrained iterator */ flecs_query_iter_constrain(it); error: return; } void ecs_iter_set_var_as_table( ecs_iter_t *it, int32_t var_id, const ecs_table_t *table) { ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; ecs_iter_set_var_as_range(it, var_id, &range); } void ecs_iter_set_var_as_range( ecs_iter_t *it, int32_t var_id, const ecs_table_range_t *range) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); if (it->chain_it) { ecs_iter_set_var_as_range(it->chain_it, var_id, range); return; } ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < ecs_iter_get_var_count(it), 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_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, "cannot set query variables while iterating"); ecs_var_t *var = &ecs_iter_get_vars(it)[var_id]; var->range = *range; if (range->count == 1) { ecs_table_t *table = range->table; var->entity = ecs_table_entities(table)[range->offset]; } else { var->entity = 0; } it->constrained_vars |= 1llu << var_id; /* Update iterator for constrained iterator */ flecs_query_iter_constrain(it); error: return; } bool ecs_iter_var_is_constrained( ecs_iter_t *it, int32_t var_id) { if (it->chain_it) { return ecs_iter_var_is_constrained(it->chain_it, var_id); } return (it->constrained_vars & (1llu << var_id)) != 0; } uint64_t ecs_iter_get_group( const ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); if (it->chain_it) { return ecs_iter_get_group(it->chain_it); } ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, "ecs_iter_get_group must be called on iterator that iterates a query"); const ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_check(qit->group != NULL, ECS_INVALID_PARAMETER, "ecs_iter_get_group must be called on iterator that iterates a cached " "query (query is uncached)"); return qit->group->info.id; error: return 0; } static void ecs_chained_iter_fini( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_fini(it->chain_it); it->chain_it = NULL; } ecs_iter_t ecs_page_iter( const ecs_iter_t *it, int32_t offset, int32_t limit) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = *it; result.priv_.stack_cursor = NULL; /* Don't copy allocator cursor */ result.priv_.iter.page = (ecs_page_iter_t){ .offset = offset, .limit = limit, .remaining = limit }; result.next = ecs_page_next; result.fini = ecs_chained_iter_fini; result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); return result; error: return (ecs_iter_t){ 0 }; } 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; } } 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 (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; } ecs_iter_t ecs_worker_iter( const ecs_iter_t *it, int32_t index, int32_t count) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid worker index %d", index); ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = *it; result.priv_.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 }; } bool ecs_worker_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_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_)); 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; } } 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; } #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, "%s", err); ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, "%s", err); } else { ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, "%s", err); ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, "%s", err); } return u; } #endif int32_t flecs_next_pow_of_2( int32_t n) { n --; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n ++; return n; } /** Convert time to double */ double ecs_time_to_double( ecs_time_t t) { double result; result = t.sec; return result + (double)t.nanosec / (double)1000000000; } ecs_time_t ecs_time_sub( ecs_time_t t1, ecs_time_t t2) { ecs_time_t result; if (t1.nanosec >= t2.nanosec) { result.nanosec = t1.nanosec - t2.nanosec; result.sec = t1.sec - t2.sec; } else { result.nanosec = t1.nanosec - t2.nanosec + 1000000000; result.sec = t1.sec - t2.sec - 1; } return result; } void ecs_sleepf( double t) { if (t > 0) { int sec = (int)t; int nsec = (int)((t - sec) * 1000000000); ecs_os_sleep(sec, nsec); } } double ecs_time_measure( ecs_time_t *start) { ecs_time_t stop, temp; ecs_os_get_time(&stop); temp = stop; stop = ecs_time_sub(stop, *start); *start = temp; return ecs_time_to_double(stop); } void* ecs_os_memdup( const void *src, ecs_size_t size) { if (!src) { return NULL; } void *dst = ecs_os_malloc(size); ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_memcpy(dst, src, size); return dst; } int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2) { (void)ptr1; (void)ptr2; return (e1 > e2) - (e1 < e2); } 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); } char* flecs_vasprintf( const char *fmt, va_list args) { ecs_size_t size = 0; char *result = NULL; va_list tmpa; va_copy(tmpa, args); size = vsnprintf(result, 0, fmt, tmpa); va_end(tmpa); if ((int32_t)size < 0) { return NULL; } result = (char *) ecs_os_malloc(size + 1); if (!result) { return NULL; } ecs_os_vsnprintf(result, size + 1, fmt, args); return result; } char* flecs_asprintf( const char *fmt, ...) { va_list args; va_start(args, fmt); char *result = flecs_vasprintf(fmt, args); va_end(args); return result; } char* flecs_to_snake_case(const char *str) { int32_t upper_count = 0, len = 1; const char *ptr = str; char ch, *out, *out_ptr; for (ptr = &str[1]; (ch = *ptr); ptr ++) { if (isupper(ch)) { upper_count ++; } len ++; } out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1); for (ptr = str; (ch = *ptr); ptr ++) { if (isupper(ch)) { if ((ptr != str) && (out_ptr[-1] != '_')) { out_ptr[0] = '_'; out_ptr ++; } out_ptr[0] = (char)tolower(ch); out_ptr ++; } else { out_ptr[0] = ch; out_ptr ++; } } out_ptr[0] = '\0'; return out; } char* flecs_load_from_file( const char *filename) { FILE* file; char* content = NULL; int32_t bytes; size_t size; /* Open file for reading */ file = ecs_os_fopen(filename, "r"); if (!file) { ecs_err("%s (%s)", ecs_os_strerror(errno), filename); goto error; } /* Determine file size */ fseek(file, 0, SEEK_END); bytes = (int32_t)ftell(file); if (bytes == -1) { goto error; } fseek(file, 0, SEEK_SET); /* 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'; } ecs_os_fclose(file); return content; error: if (file) { ecs_os_fclose(file); } ecs_os_free(content); return NULL; } char* flecs_chresc( char *out, char in, char delimiter) { char *bptr = out; switch(in) { case '\a': *bptr++ = '\\'; *bptr = 'a'; break; case '\b': *bptr++ = '\\'; *bptr = 'b'; break; case '\f': *bptr++ = '\\'; *bptr = 'f'; break; case '\n': *bptr++ = '\\'; *bptr = 'n'; break; case '\r': *bptr++ = '\\'; *bptr = 'r'; break; case '\t': *bptr++ = '\\'; *bptr = 't'; break; case '\v': *bptr++ = '\\'; *bptr = 'v'; break; case '\\': *bptr++ = '\\'; *bptr = '\\'; break; case '\033': *bptr = '['; /* Used for terminal colors */ break; default: if (in == delimiter) { *bptr++ = '\\'; *bptr = delimiter; } else { *bptr = in; } break; } *(++bptr) = '\0'; return bptr; } const char* flecs_chrparse( const char *in, char *out) { const char *result = in + 1; char ch; if (in[0] == '\\') { result ++; switch(in[1]) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '\\': ch = '\\'; break; case '"': ch = '"'; break; case '0': ch = '\0'; break; case ' ': ch = ' '; break; case '$': ch = '$'; break; default: goto error; } } else { ch = in[0]; } if (out) { *out = ch; } return result; error: return NULL; } ecs_size_t flecs_stresc( char *out, ecs_size_t n, char delimiter, const char *in) { const char *ptr = in; char ch, *bptr = out, buff[3]; ecs_size_t written = 0; while ((ch = *ptr++)) { if ((written += (ecs_size_t)(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++; } } } if (bptr) { while (written < n) { *bptr = '\0'; bptr++; written++; } } return written; error: return 0; } char* flecs_astresc( char delimiter, const char *in) { 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; } static bool flecs_parse_is_e( char e) { return e == 'e' || e == 'E'; } const char* flecs_parse_digit( const char *ptr, char *token, int32_t token_size) { char *tptr = token; char ch = ptr[0]; if (!isdigit(ch) && ch != '-') { ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); return NULL; } tptr[0] = ch; tptr ++; ptr ++; for (; (ch = *ptr); ptr ++) { if (!isdigit(ch) && (ch != '.') && !flecs_parse_is_e(ch)) { if (ch != '-' || !flecs_parse_is_e(ptr[-1])) { break; } } if ((tptr - token) >= (token_size - 1)) { ecs_parser_error(NULL, NULL, 0, "number too long"); return NULL; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; return ptr; } const char* flecs_parse_ws_eol( const char *ptr) { while (isspace(*ptr)) { ptr ++; } return ptr; } #define FLECS_ERRSTR_MAX (256) static char flecs_errstr_buf[FLECS_ERRSTR_MAX]; static char flecs_errstr_buf_1[FLECS_ERRSTR_MAX]; static char flecs_errstr_buf_2[FLECS_ERRSTR_MAX]; static char flecs_errstr_buf_3[FLECS_ERRSTR_MAX]; static char flecs_errstr_buf_4[FLECS_ERRSTR_MAX]; static char flecs_errstr_buf_5[FLECS_ERRSTR_MAX]; const char* flecs_errstr( char *str) { ecs_os_strncpy(flecs_errstr_buf, str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_buf; } const char* flecs_errstr_1( char *str) { ecs_os_strncpy(flecs_errstr_buf_1, str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_buf_1; } const char* flecs_errstr_2( char *str) { ecs_os_strncpy(flecs_errstr_buf_2, str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_buf_2; } const char* flecs_errstr_3( char *str) { ecs_os_strncpy(flecs_errstr_buf_3, str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_buf_3; } const char* flecs_errstr_4( char *str) { ecs_os_strncpy(flecs_errstr_buf_4, str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_buf_4; } const char* flecs_errstr_5( char *str) { ecs_os_strncpy(flecs_errstr_buf_5, str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_buf_5; } void flecs_observable_init( ecs_observable_t *observable) { flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); ecs_vec_init_t(NULL, &observable->global_observers, ecs_observer_t*, 0); observable->on_add.event = EcsOnAdd; observable->on_remove.event = EcsOnRemove; observable->on_set.event = EcsOnSet; } void flecs_observable_fini( ecs_observable_t *observable) { int32_t i, count = ecs_vec_count(&observable->global_observers); ecs_observer_t **observers = ecs_vec_first(&observable->global_observers); for (i = 0; i < count; i ++) { flecs_observer_fini(observers[i]); } 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_sparse_t *events = &observable->events; count = flecs_sparse_count(events); for (i = 0; i < count; i ++) { ecs_event_record_t *er = flecs_sparse_get_dense_t(events, ecs_event_record_t, i); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); (void)er; /* All observers should've unregistered by now */ ecs_assert(!ecs_map_is_init(&er->event_ids), ECS_INTERNAL_ERROR, NULL); } ecs_vec_fini_t(NULL, &observable->global_observers, ecs_observer_t*); 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_get_t(&o->events, ecs_event_record_t, event); } ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { return er; } er = flecs_sparse_get_t(&o->events, ecs_event_record_t, event); if (!er) { er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event, NULL); } er->event = event; return er; } static const ecs_event_record_t* flecs_event_record_get_if( const ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { if (ecs_map_is_init(&er->event_ids)) { return er; } if (er->any) { return er; } if (er->wildcard) { return er; } if (er->wildcard_pair) { return er; } } return NULL; } ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id) { if (!er) { return NULL; } if (id == EcsAny) return er->any; else if (id == EcsWildcard) return er->wildcard; else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; else { if (ecs_map_is_init(&er->event_ids)) { return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); } return NULL; } } static ecs_event_id_record_t* flecs_event_id_record_get_if( const ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (!ider) { return NULL; } if (ider->observer_count) { return ider; } return NULL; } ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (ider) { return ider; } ider = ecs_os_calloc_t(ecs_event_id_record_t); if (id == EcsAny) { return er->any = ider; } else if (id == EcsWildcard) { return er->wildcard = ider; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { return er->wildcard_pair = ider; } ecs_map_init_if(&er->event_ids, &world->allocator); ecs_map_insert_ptr(&er->event_ids, id, ider); return ider; } void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id) { if (id == EcsAny) { er->any = NULL; } else if (id == EcsWildcard) { er->wildcard = NULL; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { er->wildcard_pair = NULL; } else { ecs_map_remove(&er->event_ids, id); if (!ecs_map_count(&er->event_ids)) { ecs_map_fini(&er->event_ids); } } } static int32_t flecs_event_observers_get( const ecs_event_record_t *er, ecs_id_t id, ecs_event_id_record_t **iders) { if (!er) { return 0; } /* Populate array with observer sets matching the id */ int32_t count = 0; if (id != EcsAny) { iders[0] = flecs_event_id_record_get_if(er, EcsAny); count += iders[count] != 0; } iders[count] = flecs_event_id_record_get_if(er, id); count += iders[count] != 0; if (id != EcsAny) { if (ECS_IS_PAIR(id)) { ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); 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; } } return count; } bool flecs_observers_exist( const ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event) { const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); if (!er) { return false; } return flecs_event_id_record_get_if(er, id) != NULL; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_component_record_t *cr, ecs_component_record_t *tgt_cr, ecs_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count); static void flecs_emit_propagate_id_for_range( ecs_world_t *world, ecs_iter_t *it, ecs_component_record_t *cr, ecs_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count, ecs_table_range_t *range) { ecs_table_t *table = range->table; int32_t count = range->count; int32_t offset = range->offset; bool owned = flecs_component_get_table(cr, table) != NULL; int32_t e; it->table = table; it->other_table = NULL; it->offset = 0; it->count = count; it->up_fields = 1; ECS_CONST_CAST(int16_t*, it->columns)[0] = -1; if (count) { it->entities = &ecs_table_entities(table)[offset]; } it->event_cur = ++world->event_id; int32_t ider_i; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); if (!owned) { /* Owned takes precedence */ flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } if (!table->_->traversable_count) { return; } const ecs_entity_t *entities = it->entities; for (e = 0; e < count; e ++) { ecs_component_record_t *cr_t = flecs_components_get( world, ecs_pair(EcsWildcard, entities[e])); if (cr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate(world, it, cr, cr_t, trav, iders, ider_count); } } } static void flecs_emit_propagate_id( ecs_world_t *world, ecs_iter_t *it, ecs_component_record_t *cr, ecs_component_record_t *cur, ecs_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_table_cache_iter_t idt; if ((trav == EcsChildOf) && (flecs_component_has_non_fragmenting_childof(cur))) { ecs_assert(ECS_PAIR_FIRST(cur->id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); if (!(cur->flags & EcsIdMarkedForDelete)) { int32_t i, count = ecs_vec_count(&cur->pair->ordered_children); ecs_entity_t *children = ecs_vec_first(&cur->pair->ordered_children); int32_t event_cur = it->event_cur; for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, children[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); flecs_emit_propagate_id_for_range( world, it, cr, trav, iders, ider_count, &(ecs_table_range_t){ .table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1 }); } it->event_cur = event_cur; } return; } if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { return; } const ecs_table_record_t *tr; int32_t event_cur = it->event_cur; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table)) { continue; } flecs_emit_propagate_id_for_range( world, it, cr, trav, iders, ider_count, &(ecs_table_range_t){ .table = table, .count = ecs_table_count(table), }); } it->event_cur = event_cur; it->up_fields = 0; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_component_record_t *cr, ecs_component_record_t *tgt_cr, ecs_entity_t propagate_trav, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_assert(tgt_cr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_cr->id); ecs_dbg_3("propagate events/invalidate cache for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Propagate to records of traversable relationships */ ecs_component_record_t *cur = tgt_cr; while ((cur = flecs_component_trav_next(cur))) { cur->pair->reachable.generation ++; /* Invalidate cache */ /* Get traversed relationship */ ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); if (propagate_trav && propagate_trav != trav) { if (propagate_trav != EcsIsA) { continue; } } flecs_emit_propagate_id( world, it, cr, cur, trav, iders, ider_count); } ecs_log_pop_3(); } void flecs_emit_propagate_invalidate_tables( ecs_world_t *world, ecs_component_record_t *tgt_cr) { ecs_assert(tgt_cr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_cr->id); ecs_dbg_3("invalidate reachable cache for %s", idstr); ecs_os_free(idstr); } /* Invalidate records of traversable relationships */ ecs_component_record_t *cur = tgt_cr; 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; } rc->generation ++; if (flecs_component_has_non_fragmenting_childof(cur)) { int32_t i, count = ecs_vec_count(&cur->pair->ordered_children); ecs_entity_t *children = ecs_vec_first(&cur->pair->ordered_children); for (i = 0; i < count; i ++) { ecs_component_record_t *cr_t = flecs_components_get( world, ecs_pair(EcsWildcard, children[i])); if (cr_t) { flecs_emit_propagate_invalidate_tables(world, cr_t); } } } ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { continue; } const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } int32_t e, entity_count = ecs_table_count(table); const ecs_entity_t *entities = ecs_table_entities(table); for (e = 0; e < entity_count; e ++) { ecs_component_record_t *cr_t = flecs_components_get( world, ecs_pair(EcsWildcard, entities[e])); if (cr_t) { flecs_emit_propagate_invalidate_tables(world, cr_t); } } } } } 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_component_record_t *cr_t = flecs_components_get( world, ecs_pair(EcsWildcard, entities[i])); if (cr_t) { /* Entity is used as target in traversable relationship, propagate */ flecs_emit_propagate_invalidate_tables(world, cr_t); } } } static void flecs_propagate_entities( ecs_world_t *world, ecs_iter_t *it, ecs_component_record_t *cr, 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_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; int32_t i; for (i = 0; i < count; i ++) { ecs_component_record_t *cr_t = flecs_components_get( world, ecs_pair(EcsWildcard, entities[i])); if (cr_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, cr, cr_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; } 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 *cr, 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_component_record_t *cr, ecs_entity_t tgt, ecs_table_t *tgt_table, int32_t column, ecs_entity_t trav) { ecs_id_t id = cr->id; ecs_entity_t event = er ? er->event : 0; bool inherit = trav == EcsIsA; bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); ecs_event_id_record_t *iders[5]; ecs_event_id_record_t *iders_onset[5]; /* Skip id if there are no observers for it */ int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); int32_t ider_onset_i, ider_onset_count = 0; if (er_onset) { ider_onset_count = flecs_event_observers_get( er_onset, id, iders_onset); } if (!may_override && (!ider_count && !ider_onset_count)) { return; } ecs_entity_t old_src = it->sources[0]; 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; int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); it->trs[0] = &tgt_table->_->records[column]; ECS_CONST_CAST(int16_t*, it->columns)[0] = -1; if (storage_i != -1) { ecs_column_t *c = &tgt_table->data.columns[storage_i]; ecs_assert(cr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ECS_CONST_CAST(int32_t*, it->sizes)[0] = c->ti->size; /* safe, see above */ } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); bool owned = tr != NULL; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } /* Emit OnSet events for newly inherited components */ if (storage_i != -1) { if (ider_onset_count) { it->event = er_onset->event; for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke( world, &ider->self_up, it, table, trav); } } it->event = event; } } it->sources[0] = old_src; it->up_fields = 0; } 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 *cr, 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) { /* 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 = cr->id; #ifndef FLECS_NDEBUG elem->table = tgt_table; #endif ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, cr, tgt, tgt_table, column, trav); } static int32_t flecs_emit_stack_at( ecs_vec_t *stack, ecs_component_record_t *cr) { 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_component_get_table(cr, elem)) { break; } } return sp; } static bool flecs_emit_stack_has( ecs_vec_t *stack, ecs_component_record_t *cr) { return flecs_emit_stack_at(stack, cr) != ecs_vec_count(stack); } static void flecs_emit_forward_cached_ids( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_reachable_cache_t *rc, ecs_vec_t *reachable_ids, ecs_vec_t *stack, ecs_entity_t trav) { ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t i, count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *rc_elem = &elems[i]; const ecs_table_record_t *rc_tr = rc_elem->tr; ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *rc_cr = rc_tr->hdr.cr; ecs_record_t *rc_record = rc_elem->record; ecs_assert(rc_cr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, rc_elem->src) == rc_record, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(rc_record->table == rc_elem->table, ECS_INTERNAL_ERROR, NULL); if (flecs_emit_stack_has(stack, rc_cr)) { continue; } flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, rc_cr, rc_elem->src, rc_record, rc_record->table, rc_tr, rc_tr->index, reachable_ids, trav); } } static void flecs_emit_dump_cache( ecs_world_t *world, const ecs_vec_t *vec) { ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); for (int i = 0; i < ecs_vec_count(vec); i ++) { ecs_reachable_elem_t *elem = &elems[i]; char *idstr = ecs_id_str(world, elem->id); char *estr = ecs_id_str(world, elem->src); #ifndef FLECS_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 (!ecs_vec_count(vec)) { ecs_dbg_3("- no entries"); } } static void flecs_emit_forward_table_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t tgt, ecs_table_t *tgt_table, ecs_record_t *tgt_record, ecs_component_record_t *tgt_cr, ecs_vec_t *stack, ecs_vec_t *reachable_ids, int32_t depth) { 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); /* If tgt_cr 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_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_reachable_cache_t *rc = &tgt_cr->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 (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_cr->id); ecs_dbg_3("forward events from %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Function may have to copy values from overridden components if an IsA * relationship was added together with other components. */ ecs_entity_t trav = ECS_PAIR_FIRST(tgt_cr->id); bool inherit = trav == EcsIsA; for (i = 0; i < id_count; i ++) { ecs_id_t id = ids[i]; ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; ecs_component_record_t *cr = tgt_tr->hdr.cr; if (inherit && !(cr->flags & EcsIdOnInstantiateInherit)) { continue; } if (cr == tgt_cr) { char *idstr = ecs_id_str(world, cr->id); ecs_assert(cr != tgt_cr, ECS_CYCLE_DETECTED, "%s", idstr); ecs_os_free(idstr); return; } /* Id has the same relationship, traverse to find ids for forwarding */ if ((ECS_IS_PAIR(id) && (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA)) || ((trav == EcsChildOf) && id == ecs_id(EcsParent))) { ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, ecs_table_t*); t[0] = tgt_table; if (id == ecs_id(EcsParent)) { const EcsParent *parent = ecs_get(world, tgt, EcsParent); ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(parent->value != 0, ECS_INTERNAL_ERROR, NULL); cr = flecs_components_get(world, ecs_childof(parent->value)); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_reachable_cache_t *cr_rc = &cr->pair->reachable; if (cr_rc->current == cr_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, cr_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, cr, stack, reachable_ids, depth); if (++i >= id_count) { break; } id = ids[i]; if (ECS_PAIR_FIRST(id) != trav) { break; } cr = tgt_table->_->records[i].hdr.cr; } while (true); } ecs_vec_remove_last(stack); continue; } int32_t stack_at = flecs_emit_stack_at(stack, cr); 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 = cr->id; #ifndef FLECS_NDEBUG elem->table = tgt_table; #endif } /* Skip id if it's masked by a lower table in the tree */ if (stack_at != stack_count) { continue; } flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, cr, tgt, tgt_record, tgt_table, tgt_tr, i, reachable_ids, trav); } if (parent_revalidate) { /* If this is not the current cache being updated, but it's marked * as out of date, use intermediate results to populate cache. */ int32_t rc_parent_offset = ecs_vec_count(&rc->ids); /* Only add ids that were added for this table */ int32_t count = ecs_vec_count(reachable_ids); count -= rc_child_offset; /* Append ids to any ids that already were added */ if (count) { ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, ecs_reachable_elem_t, rc_parent_offset); ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, ecs_reachable_elem_t, rc_child_offset); ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); } rc->current = rc->generation; if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_cr->id); ecs_dbg_3("cache revalidated for %s:", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } } ecs_log_pop_3(); } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_component_record_t *cr, ecs_vec_t *stack, ecs_vec_t *reachable_ids, int32_t depth) { if (depth >= FLECS_DAG_DEPTH_MAX) { char *idstr = ecs_id_str(world, cr->id); ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, "%s", idstr); ecs_os_free(idstr); return; } ecs_id_t id = cr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); if (!tgt) { return; } 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, cr, stack, reachable_ids, depth + 1); } 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_component_record_t *cr) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_reachable_cache_t *rc = &cr->pair->reachable; if (rc->current != rc->generation) { /* Cache miss, iterate the tree to find ids to forward */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, cr->id); ecs_dbg_3("reachable cache miss for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); ecs_vec_t stack; ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, cr, &stack, &rc->ids, 0); it->sources[0] = 0; ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); if (it->event == EcsOnAdd || it->event == EcsOnRemove) { /* Only OnAdd/OnRemove events can validate top-level cache, which * is for the id for which the event is emitted. * The reason for this is that we don't want to validate the cache * while the administration for the mutated entity isn't up to * date yet. */ rc->current = rc->generation; } if (ecs_should_log_3()) { ecs_dbg_3("cache after rebuild:"); flecs_emit_dump_cache(world, &rc->ids); } ecs_log_pop_3(); } else { /* Cache hit, use cached values instead of walking the tree */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, cr->id); ecs_dbg_3("reachable cache hit for %s", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } ecs_entity_t trav = ECS_PAIR_FIRST(cr->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_cr = tr->hdr.cr; ecs_record_t *r = elem->record; ecs_assert(rc_cr->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_cr, elem->src, r->table, tr->index, trav); } } /* 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->row & EcsEntityIsTraversable)) { 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_component_record_t *rc_cr = tr->hdr.cr; ecs_record_t *r = elem->record; ecs_assert(rc_cr->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; /* If entities already have the component, don't propagate */ if (flecs_component_get_table(rc_cr, it->table)) { continue; } ecs_event_id_record_t *iders[5] = {0}; int32_t ider_count = flecs_event_observers_get( er, rc_cr->id, iders); it->ids[0] = rc_cr->id; it->event_id = rc_cr->id; it->trs[0] = tr; ECS_CONST_CAST(int16_t*, it->columns)[0] = tr ? tr->column : -1; ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; if (rc_cr->type_info) { ECS_CONST_CAST(int32_t*, it->sizes)[0] = rc_cr->type_info->size; } flecs_propagate_entities(world, it, rc_cr, it->entities, it->count, elem->src, iders, ider_count); } } } } static void flecs_emit_on_set_for_override_on_add( ecs_world_t *world, const ecs_event_record_t *er_onset, int32_t evtx, ecs_iter_t *it, ecs_id_t id, ecs_component_record_t *cr, ecs_table_t *table) { (void)evtx; ecs_ref_t storage; const ecs_ref_t *o = flecs_table_get_override( world, table, id, cr, &storage); if (!o) { return; } /* Table has override for component. If this overrides a * component that was already reachable for the table, we * don't need to emit since the value didn't change. */ ecs_entity_t base = o->entity; ecs_table_t *other = it->other_table; if (other) { if (ecs_table_has_id(world, other, ecs_pair(EcsIsA, base))) { /* If previous table already had (IsA, base), entity already * inherited the component, so no new value needs to be emitted. */ return; } } 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) { /* No OnSet observers for component */ return; } it->ids[0] = id; it->event_id = id; it->trs[0] = flecs_component_get_table(cr, table); ECS_CONST_CAST(int16_t*, it->columns)[0] = it->trs[0] ? it->trs[0]->column : -1; it->sources[0] = 0; /* Only valid for components, so type info must exist */ ecs_assert(cr && cr->type_info, ECS_INTERNAL_ERROR, NULL); ECS_CONST_CAST(ecs_size_t*, it->sizes)[0] = cr->type_info->size; /* Invoke OnSet observers for new inherited component value. */ 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); } } static void flecs_emit_on_set_for_override_on_remove( ecs_world_t *world, const ecs_event_record_t *er_onset, int32_t evtx, ecs_iter_t *it, ecs_id_t id, ecs_component_record_t *cr, ecs_table_t *table) { (void)evtx; ecs_ref_t storage; const ecs_ref_t *o = flecs_table_get_override(world, table, id, cr, &storage); if (!o) { return; } 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) { /* No OnSet observers for component */ return; } /* We're removing, so emit an OnSet for the base component. */ ecs_entity_t base = o->entity; ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *base_r = flecs_entities_get(world, base); const ecs_table_record_t *base_tr = flecs_component_get_table(cr, base_r->table); it->ids[0] = id; it->event_id = id; it->sources[0] = base; it->trs[0] = base_tr; ECS_CONST_CAST(int16_t*, it->columns)[0] = -1; it->up_fields = 1; /* Only valid for components, so type info must exist */ ecs_assert(cr && cr->type_info, ECS_INTERNAL_ERROR, NULL); ECS_CONST_CAST(ecs_size_t*, it->sizes)[0] = cr->type_info->size; /* Invoke OnSet observers for previous inherited component value. */ 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); } } /* The emit function is responsible for finding and invoking the observers * matching the emitted event. The function is also capable of forwarding events * for newly reachable ids (after adding a relationship) and propagating events * downwards. Both capabilities are not just useful in application logic, but * are also an important building block for keeping query caches in sync. */ void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_event_desc_t *desc) { 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); ecs_os_perf_trace_push("flecs.emit"); ecs_time_t t = {0}; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&t); } const ecs_type_t *ids = desc->ids; ecs_entity_t event = desc->event; ecs_table_t *table = desc->table, *other_table = desc->other_table; int32_t offset = desc->offset; int32_t i, count = desc->count; ecs_flags32_t table_flags = table->flags; /* Deferring cannot be suspended for observers */ int32_t defer = world->stages[0]->defer; if (defer < 0) { world->stages[0]->defer *= -1; } /* Table events are emitted for internal table operations only, and do not * provide component data and/or entity ids. */ bool table_event = desc->flags & EcsEventTableOnly; if (!count && !table_event) { /* If no count is provided, forward event for all entities in table */ count = ecs_table_count(table) - offset; } /* The world event id is used to determine if an observer has already been * triggered for an event. Observers for multiple components are split up * into multiple observers for a single component, and this counter is used * to make sure a multi observer only triggers once, even if multiple of its * single-component observers trigger. */ int32_t evtx = ++world->event_id; ecs_id_t ids_cache = 0; ecs_size_t sizes_cache = 0; const ecs_table_record_t* trs_cache = 0; ecs_entity_t sources_cache = 0; int16_t columns_cache = -1; 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, .columns = &columns_cache, .sources = &sources_cache, .other_table = other_table, .offset = offset, .count = count, .param = ECS_CONST_CAST(void*, desc->param), .flags = desc->flags | EcsIterIsValid }; ecs_observable_t *observable = flecs_get_observable(desc->observable); ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); /* Event records contain all observers for a specific event. In addition to * the emitted event, also request data for the Wildcard event (for * observers subscribing to the wildcard event), and OnSet events. The * latter is 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); if (count) { it.entities = &ecs_table_entities(table)[offset]; } int32_t id_count = ids->count; ecs_id_t *id_array = ids->array; bool do_on_set = !(desc->flags & EcsEventNoOnSet); /* When we add an (IsA, b) pair we need to emit OnSet events for any new * component values that are reachable through the instance, either * inherited or overridden. OnSet events for inherited components are * emitted by the event forwarding logic. For overriding, we only need to * emit an OnSet if both the IsA pair and the component were added in the * same event. If a new override is added for an existing base component, * it changes the ownership of the component, but not the value, so no OnSet * is needed. */ bool can_override_on_add = count && do_on_set && (event == EcsOnAdd) && (table_flags & EcsTableHasIsA); /* If we remove an override, this reexposes the component from the base. * Since the override could have a different value from the base, this * effectively changes the value of the component for the entity, so an * OnSet event must be emitted. */ bool can_override_on_remove = count && do_on_set && (event == EcsOnRemove) && (it.other_table) && (it.other_table->flags & EcsTableHasIsA); /* 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 through the relationship. */ bool can_forward = true; /* Does table have observed entities */ bool has_observed = table_flags & EcsTableHasTraversable; ecs_event_id_record_t *iders[5] = {0}; ecs_table_record_t dummy_tr; 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 event. */ 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 * when inheriting from a prefab with overrides. In these cases an entity is * moved directly to the archetype with the additional components. */ ecs_id_t id = id_array[i]; /* If id is wildcard this could be a remove(Rel, *) call for a * DontFragment component (for regular components this gets handled by * the table graph which returns a vector with removed ids). * This will be handled at a higher level than flecs_emit, so we can * ignore the wildcard */ if (id != EcsAny && ecs_id_is_wildcard(id)) { continue; } /* Forward events for Parent component as ChildOf pairs. */ if (id == ecs_id(EcsParent) && !table_event && can_forward && (table->flags & EcsTableHasParent)) { ecs_event_desc_t pdesc = *desc; pdesc.event = event; if (event == EcsOnSet) { /* If the Parent component is set, forward it as an OnAdd event * for a ChildOf pair. */ pdesc.event = EcsOnAdd; } /* Iterate over entities, forward for their specific parent. */ const EcsParent *parents = ecs_table_get_column(table, table->component_map[ecs_id(EcsParent)] - 1, it.offset); int32_t p, parent_count = pdesc.count; for (p = 0; p < parent_count; p ++) { ecs_entity_t parent = parents[p].value; if (parent && flecs_entities_is_alive(world, parent)) { ecs_id_t pair = ecs_childof(parent); ecs_type_t type = { .count = 1, .array = &pair }; pdesc.ids = &type; flecs_emit(world, stage, &pdesc); } } } int32_t ider_i, ider_count = 0; ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { continue; } ecs_flags32_t cr_flags = cr->flags; /* Check if this id is a pair of a traversable relationship. If so, we * may have to forward ids from the pair's target. */ if (can_forward && ECS_IS_PAIR(id) && (cr_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; const ecs_entity_t *instances = ecs_table_entities(table); int32_t e; for (e = 0; e < count; e ++) { flecs_instantiate( world, tgt, instances[offset + e], NULL, 0); } world->stages[0]->base = 0; } /* 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, cr); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } 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); } if (!ider_count && !(can_override_on_add || can_override_on_remove)) { /* If nothing more to do for this id, early out */ continue; } ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table(cr, table); dummy_tr = (ecs_table_record_t){ .hdr.cr = cr, .hdr.table = table, .index = -1, .column = -1, .count = 0 }; bool dont_fragment = cr_flags & EcsIdDontFragment; if (!dont_fragment && id != EcsAny && !(ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf)) { if (tr == NULL) { /* When a single batch contains multiple adds 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; } it.trs[0] = tr; ECS_CONST_CAST(int16_t*, it.columns)[0] = tr ? tr->column : -1; it.event_id = id; it.ids[0] = id; const ecs_type_info_t *ti = cr->type_info; if (ti) { /* safe, owned by observer */ ECS_CONST_CAST(int32_t*, it.sizes)[0] = ti->size; } else { ECS_CONST_CAST(int32_t*, it.sizes)[0] = 0; } /* Actually invoke observers for this event/id */ for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->self, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); flecs_observers_invoke(world, &ider->self_up, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } if (!ider_count || !count || !has_observed) { continue; } /* The table->traversable_count value indicates if the table contains any * entities that are used as targets of traversable relationships. If the * entity/entities for which the event was generated are used as such a * target, events must be propagated downwards. */ flecs_propagate_entities( world, &it, cr, it.entities, count, 0, iders, ider_count); } 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; } /* Invoke OnSet observers for component overrides if necessary */ if (count && (can_override_on_add|can_override_on_remove)) { for (i = 0; i < id_count; i ++) { ecs_id_t id = id_array[i]; bool non_trivial_set = true; if (id < FLECS_HI_COMPONENT_ID) { non_trivial_set = world->non_trivial_set[id]; } if (non_trivial_set) { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { continue; } const ecs_type_info_t *ti = cr->type_info;; ecs_flags32_t cr_flags = cr->flags; /* Can only override components that don't have DontInherit trait. */ bool id_can_override_on_add = can_override_on_add; bool id_can_override_on_remove = can_override_on_remove; id_can_override_on_add &= !(cr_flags & EcsIdOnInstantiateDontInherit); id_can_override_on_remove &= !(cr_flags & EcsIdOnInstantiateDontInherit); id_can_override_on_add &= ti != NULL; id_can_override_on_remove &= ti != NULL; if (id_can_override_on_add) { flecs_emit_on_set_for_override_on_add( world, er_onset, evtx, &it, id, cr, table); } else if (id_can_override_on_remove) { flecs_emit_on_set_for_override_on_remove( world, er_onset, evtx, &it, id, cr, table); } } } } 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; } void ecs_emit( ecs_world_t *stage, ecs_event_desc_t *desc) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, "cannot set param and const_param at the same time"); if (desc->entity) { ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, desc->entity); desc->table = r->table; desc->offset = ECS_RECORD_TO_ROW(r->row); desc->count = 1; } if (!desc->observable) { desc->observable = world; } ecs_type_t default_ids = (ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ EcsAny } }; if (!desc->ids || !desc->ids->count) { desc->ids = &default_ids; } if (desc->const_param) { desc->param = ECS_CONST_CAST(void*, desc->const_param); desc->const_param = NULL; } ecs_defer_begin(world); flecs_emit(world, stage, desc); ecs_defer_end(world); if (desc->ids == &default_ids) { desc->ids = NULL; } error: return; } void ecs_enqueue( ecs_world_t *world, ecs_event_desc_t *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); } 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 && term->oper == EcsNot) { if (event == EcsOnAdd || event == EcsOnSet) { event = EcsOnRemove; } else if (event == EcsOnRemove) { event = EcsOnAdd; } } return event; } static ecs_flags32_t flecs_id_flag_for_event( ecs_entity_t e) { if (e == EcsOnAdd) { return EcsIdHasOnAdd; } if (e == EcsOnRemove) { return EcsIdHasOnRemove; } if (e == EcsOnSet) { return EcsIdHasOnSet; } if (e == EcsOnTableCreate) { return EcsIdHasOnTableCreate; } if (e == EcsOnTableDelete) { return EcsIdHasOnTableDelete; } if (e == EcsWildcard) { return EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet| EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; } return 0; } static void flecs_inc_observer_count( ecs_world_t *world, ecs_entity_t event, ecs_event_record_t *evt, ecs_id_t id, int32_t value) { ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); int32_t result = idt->observer_count += value; if (result == 1) { /* Notify framework that there are observers for the event/id. This * allows parts of the code to skip event evaluation early */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_component_record_t *cr = flecs_components_get(world, id); if (cr) { cr->flags |= flags; } /* Track that we've created an OnSet observer so we know not to take * fast code path when doing a set operation. */ if (event == EcsOnSet || event == EcsWildcard) { if (id < FLECS_HI_COMPONENT_ID) { world->non_trivial_set[id] = true; } if (id == EcsWildcard || id == EcsAny) { ecs_os_memset_n(world->non_trivial_set, true, bool, FLECS_HI_COMPONENT_ID); } } } } else if (result == 0) { /* Ditto, but the reverse */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableNoTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_component_record_t *cr = flecs_components_get(world, id); if (cr) { cr->flags &= ~flags; } } flecs_event_id_record_remove(evt, id); ecs_os_free(idt); } if (ECS_PAIR_FIRST(id) == EcsChildOf) { ecs_observable_t *observable = &world->observable; if (event == EcsOnAdd) { ecs_event_record_t *er_onset = flecs_event_record_ensure(observable, EcsOnSet); flecs_inc_observer_count( world, EcsOnSet, er_onset, ecs_id(EcsParent), value); } else { flecs_inc_observer_count( world, event, evt, ecs_id(EcsParent), value); } } } 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); } } return id; } static void flecs_register_observer_for_event_and_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset, const ecs_term_t *term, ecs_entity_t event, ecs_id_t term_id) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_entity_t trav = term ? term->trav : 0; /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_ensure(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id for event */ ecs_event_id_record_t *idt = flecs_event_id_record_ensure( world, er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *observers = ECS_OFFSET(idt, offset); ecs_map_init_if(observers, &world->allocator); ecs_map_insert_ptr(observers, impl->id, o); flecs_inc_observer_count(world, event, er, term_id, 1); if (trav && (term_id != ecs_id(EcsParent))) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), 1); } } static void flecs_register_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset, ecs_id_t term_id) { ecs_term_t *term = o->query ? &o->query->terms[0] : NULL; 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; } flecs_register_observer_for_event_and_id( world, observable, o, offset, term, event, term_id); } } static void flecs_uni_observer_register( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o) { ecs_term_t *term = o->query ? &o->query->terms[0] : NULL; ecs_flags64_t flags = term ? ECS_TERM_REF_FLAGS(&term->src) : EcsSelf; ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_id_t term_id = flecs_observer_id(impl->register_id); if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self_up), term_id); } else if (flags & EcsSelf) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self), term_id); } 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), term_id); } if (term && (term->trav == EcsChildOf)) { ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); if (o->query->flags & EcsQueryTableOnly) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent)); flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self), ecs_pair(EcsChildOf, EcsWildcard)); } } } static void flecs_unregister_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset, ecs_id_t term_id) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_term_t *term = o->query ? &o->query->terms[0] : NULL; ecs_entity_t trav = term ? term->trav : 0; 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; } /* 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); } flecs_inc_observer_count(world, event, er, term_id, -1); if (trav && (term_id != ecs_id(EcsParent))) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), -1); } } } static void flecs_unregister_observer( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_t *q = o->query; if (q && q->term_count == 0) { return; } ecs_term_t *term = q ? &q->terms[0] : NULL; ecs_flags64_t flags = term ? ECS_TERM_REF_FLAGS(&term->src) : EcsSelf; ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_id_t term_id = flecs_observer_id(impl->register_id); if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self_up), term_id); } else if (flags & EcsSelf) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self), term_id); } else if (flags & EcsUp) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, up), term_id); } if (term && (term->trav == EcsChildOf)) { if (o->query->flags & EcsQueryTableOnly) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent)); flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self), ecs_pair(EcsChildOf, EcsWildcard)); } } } static bool flecs_ignore_observer( ecs_observer_t *o, ecs_table_t *table) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_observer_impl_t *impl = flecs_observer_impl(o); if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { return true; } ecs_flags32_t table_flags = table->flags, query_flags = impl->flags; bool result = (table_flags & EcsTableIsPrefab) && !(query_flags & EcsQueryMatchPrefab); result = result || ((table_flags & EcsTableIsDisabled) && !(query_flags & EcsQueryMatchDisabled)); return result; } 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; o->callback(it); } static bool flecs_observer_query_has_range( const ecs_query_t *query, ecs_table_range_t *range, const ecs_term_t *term, ecs_id_t event_id, ecs_iter_t *it) { bool first_var = (term->first.id & EcsIsVariable) && term->first.name; bool second_var = (term->second.id & EcsIsVariable) && term->second.name; if (!first_var && !second_var) { return ecs_query_has_range(query, range, it); } *it = ecs_query_iter(query->world, query); ecs_iter_set_var_as_range(it, 0, range); if (first_var) { ecs_iter_set_var(it, ecs_query_find_var(query, term->first.name), ECS_IS_PAIR(event_id) ? ECS_PAIR_FIRST(event_id) : event_id); } if (second_var) { if (!ECS_IS_PAIR(event_id)) { ecs_iter_fini(it); return false; } ecs_iter_set_var(it, ecs_query_find_var(query, term->second.name), ECS_PAIR_SECOND(event_id)); } return ecs_query_next(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, ecs_observer_t *o, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { if (flecs_ignore_observer(o, table)) { return; } 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_log_push_3(); ecs_observer_impl_t *impl = flecs_observer_impl(o); 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; ecs_entity_t event = it->event; int32_t event_cur = it->event_cur; ecs_entity_t old_system = flecs_stage_set_system( world->stages[0], o->entity); ecs_flags32_t set_fields_cur = it->set_fields; it->set_fields = 1; ecs_query_t *query = o->query; it->query = query; if (!query) { /* Invoke trivial observer */ it->event = event; flecs_observer_invoke(o, it); } else { ecs_term_t *term = &query->terms[0]; ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); if (trav && term->trav != trav) { return; } bool is_filter = term->inout == EcsInOutNone; ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); it->ref_fields = query->fixed_fields | query->row_fields; ecs_termset_t row_fields = it->row_fields; it->row_fields = query->row_fields; it->event = flecs_get_observer_event(term, event); bool match_this = query->flags & EcsQueryMatchThis; if (match_this) { /* Invoke observer for $this field */ flecs_observer_invoke(o, it); ecs_os_inc(&query->eval_count); } else { /* 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; int16_t old_column = it->columns[0]; it->entities = NULL; it->count = 0; it->table = NULL; ECS_CONST_CAST(int16_t*, it->columns)[0] = -1; /* 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_entity_t e = entities[i]; /* 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_observer_invoke(o, it); ecs_os_inc(&query->eval_count); /* Restore source */ it->sources[0] = src; /* Observer can only match one source explicitly, so we don't * have to check any other entities. */ break; } } it->entities = entities; it->count = count; it->table = old_table; ECS_CONST_CAST(int16_t*, it->columns)[0] = old_column; } it->row_fields = row_fields; } flecs_stage_set_system(world->stages[0], old_system); it->event = event; it->event_cur = event_cur; it->set_fields = set_fields_cur; ecs_log_pop_3(); world->info.observers_ran_total ++; } 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); 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_assert(ecs_map_iter_valid(&oit), ECS_INVALID_OPERATION, "observer list modified while notifying: " "cannot create observer from observer"); } ECS_TABLE_UNLOCK(it->world, table); } } static void flecs_multi_observer_invoke( ecs_iter_t *it) { ecs_observer_t *o = it->ctx; flecs_poly_assert(o, ecs_observer_t); 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; } 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; } ecs_table_t *lock_table = table; (void)lock_table; table = table ? table : &world->store.root; prev_table = prev_table ? prev_table : &world->store.root; ecs_iter_t user_it; bool match; if (is_not) { ecs_table_range_t range = { .table = table }; match = flecs_observer_query_has_range( o->query, &range, term, it->event_id, &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. */ user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(&user_it); ecs_table_range_t prev_range = { .table = prev_table }; match = flecs_observer_query_has_range( impl->not_query, &prev_range, term, it->event_id, &user_it); /* 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 { int trivial = -1; if (!(impl->flags & EcsObserverIsMonitor)) { trivial = flecs_query_trivial_has_range(o->query, &user_it, it->world, table, it->offset, it->count); } if (trivial >= 0) { match = trivial != 0; } else { ecs_table_range_t range = { .table = table, .offset = it->offset, .count = it->count }; match = flecs_observer_query_has_range( o->query, &range, term, it->event_id, &user_it); } } 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; ecs_table_range_t prev_range = { .table = prev_table }; if (flecs_observer_query_has_range( o->query, &prev_range, term, it->event_id, &table_it)) { /* Prevent change detection on fini */ user_it.flags |= EcsIterSkip; table_it.flags |= EcsIterSkip; ecs_iter_fini(&table_it); ecs_iter_fini(&user_it); goto done; } } impl->last_event_id[0] = it->event_cur; /* 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.sources[pivot_field] = it->sources[0]; ECS_CONST_CAST(int16_t*, user_it.columns)[pivot_field] = it->sources[0] ? -1 : it->columns[0]; user_it.term_index = pivot_term; 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; ecs_entity_t old_system = flecs_stage_set_system( world->stages[0], o->entity); ECS_TABLE_LOCK(it->world, lock_table); if (o->run) { user_it.next = flecs_default_next_callback; o->run(&user_it); } else { user_it.callback(&user_it); } user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(&user_it); ECS_TABLE_UNLOCK(it->world, lock_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 --; } done: return; } 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); ecs_world_t *world = it->real_world; ecs_iter_t user_it = *it; 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; ecs_entity_t old_system = flecs_stage_set_system( world->stages[0], o->entity); ECS_TABLE_LOCK(it->world, it->table); if (o->run) { user_it.next = flecs_default_next_callback; o->run(&user_it); } else { user_it.callback(&user_it); } ECS_TABLE_UNLOCK(it->world, it->table); flecs_stage_set_system(world->stages[0], old_system); } /* 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; } } /* 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 (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; } } flecs_multi_observer_invoke(it); } 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; } 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_iter_t it; if (o->query) { it = ecs_query_iter(world, o->query); } else { it = ecs_each_id(world, flecs_observer_impl(o)->register_id); } 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 (o->query ? ecs_query_next(&it) : ecs_each_next(&it)) { it.event_id = it.ids[0]; it.event_cur = ++ world->event_id; ecs_iter_next_action_t next = it.next; it.next = flecs_default_next_callback; run(&it); it.next = next; it.interrupted_by = 0; } } ecs_defer_end(world); } static int flecs_uni_observer_init( ecs_world_t *world, ecs_observer_t *o, ecs_id_t component_id, const ecs_observer_desc_t *desc) { ecs_observer_impl_t *impl = flecs_observer_impl(o); 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 = component_id; if (ecs_id_is_tag(world, component_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; } } 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; } } } } 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) { ecs_assert(child_desc->query.flags & EcsQueryNested, ECS_INTERNAL_ERROR, NULL); ecs_observer_t *child_observer = flecs_observer_init( world, 0, child_desc); if (!child_observer) { return -1; } 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 int flecs_multi_observer_init( ecs_world_t *world, ecs_observer_t *o, const ecs_observer_desc_t *desc) { 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); /* 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; } } if ((query->terms[i].oper == EcsNot) && (query->terms[i].inout != EcsInOutFilter)) { has_not = true; } } /* 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 (query->flags & EcsQueryMatchPrefab) { child_desc.query.flags |= EcsQueryMatchPrefab; } if (query->flags & EcsQueryMatchDisabled) { child_desc.query.flags |= EcsQueryMatchDisabled; } if (query->flags & EcsQueryTableOnly) { child_desc.query.flags |= EcsQueryTableOnly; } bool self_term_handled = false; for (i = 0; i < term_count; i ++) { if (query->terms[i].inout == EcsInOutFilter && !only_table_events) { continue; } ecs_term_t *term = &child_desc.query.terms[0]; child_desc.term_index_ = query->terms[i].field_index; *term = query->terms[i]; /* Don't create observers for non-$this terms */ if (!ecs_term_match_this(term) && term->src.id & EcsIsVariable) { continue; } int16_t oper = term->oper; ecs_id_t id = term->id; 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; } } } } /* AndFrom & OrFrom terms insert multiple observers */ if (oper == EcsAndFrom || oper == EcsOrFrom) { const ecs_type_t *type = ecs_get_type(world, id); if (!type) { continue; } 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_component_record_t *cr = flecs_components_get(world, ti_id); if (cr->flags & EcsIdOnInstantiateDontInherit) { continue; } term->first.name = NULL; term->first.id = ti_ids[ti]; term->id = ti_ids[ti]; if (flecs_observer_add_child(world, o, &child_desc)) { goto error; } } continue; } /* Single component observers never use OR */ if (oper == EcsOr) { term->oper = EcsAnd; } /* If observer only contains optional terms, match everything */ if (optional_only) { term->id = EcsAny; term->first.id = EcsAny; term->src.id = EcsThis | 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 (flecs_observer_add_child(world, o, &child_desc)) { goto error; } if (optional_only) { break; } } /* 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_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; } } flecs_observer_impl(o)->not_query = ecs_query_init(world, ¬_desc); } return 0; error: return -1; } static void flecs_observer_poly_fini(void *ptr) { flecs_observer_fini(ptr); } ecs_observer_t* flecs_observer_init( ecs_world_t *world, ecs_entity_t entity, const ecs_observer_desc_t *desc) { 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; o->world = world; impl->dtor = flecs_observer_poly_fini; /* Make writable 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 = NULL; /* Only do optimization when not in sanitized mode. This ensures that the * behavior is consistent between observers with and without queries, as * both paths will be exercised in unit tests. */ #ifndef FLECS_SANITIZE /* Temporary arrays for dummy query */ ecs_term_t terms[FLECS_TERM_COUNT_MAX] = {0}; ecs_size_t sizes[FLECS_TERM_COUNT_MAX] = {0}; ecs_id_t ids[FLECS_TERM_COUNT_MAX] = {0}; ecs_query_t dummy_query = { .terms = terms, .sizes = sizes, .ids = ids }; if (desc->events[0] != EcsMonitor) { if (flecs_query_finalize_simple(world, &dummy_query, &query_desc)) { /* Flag is set if query increased the keep_alive count of the * queried for component, which prevents deleting the component * while queries are still alive. */ bool trivial_observer = (dummy_query.term_count == 1) && (dummy_query.flags & EcsQueryIsTrivial) && (dummy_query.flags & EcsQueryMatchOnlySelf) && !dummy_query.row_fields; if (trivial_observer) { if (ECS_PAIR_FIRST(dummy_query.terms[0].id) != EcsChildOf) { dummy_query.flags |= desc->query.flags; query = &dummy_query; } } else { /* We're going to create an actual query, so undo the keep_alive * increment of the dummy_query. */ int32_t i, count = dummy_query.term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; flecs_component_unlock(world, term->id); } } } } #endif /* Create query */ if (!query) { query = o->query = ecs_query_init( world, &query_desc); if (query == NULL) { flecs_observer_fini(o); return 0; } flecs_poly_assert(query, ecs_query_t); } ecs_check(query->term_count > 0, ECS_INVALID_PARAMETER, "observer must have at least one term"); int i, var_count = 0; for (i = 0; i < query->term_count; i ++) { ecs_term_t *term = &query->terms[i]; if (!ecs_term_match_this(term)) { if (term->src.id & EcsIsVariable) { /* Term has a non-$this variable source */ var_count ++; } } } ecs_check(query->term_count > var_count, ECS_UNSUPPORTED, "observers with only non-$this variable sources are not yet supported"); (void)var_count; 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 = flecs_get_observable(world); o->entity = entity; o->world = world; impl->term_index = desc->term_index_; impl->flags |= desc->flags_ | (query->flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)); ecs_check(!(desc->yield_existing && (desc->flags_ & (EcsObserverYieldOnCreate|EcsObserverYieldOnDelete))), ECS_INVALID_PARAMETER, "cannot set yield_existing and YieldOn* flags at the same time"); /* Check if observer is monitor. Monitors are created as multi observers * since they require pre/post checking of the query 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; } if (event == EcsMonitor) { ecs_check(i == 0, ECS_INVALID_PARAMETER, "monitor observers can only have a single Monitor event"); 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; } } } o->event_count ++; } /* Observer must have at least one event */ ecs_check(o->event_count != 0, ECS_INVALID_PARAMETER, "observer must have at least one event"); bool multi = false; 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; } bool is_monitor = impl->flags & EcsObserverIsMonitor; if (query->term_count == 1 && !is_monitor && !multi) { ecs_term_t *term = &query->terms[0]; term->field_index = flecs_ito(int8_t, desc->term_index_); if (flecs_uni_observer_init(world, o, term->id, desc)) { goto error; } } else { if (flecs_multi_observer_init(world, o, desc)) { goto error; } } if (impl->flags & EcsObserverYieldOnCreate) { flecs_observer_yield_existing(world, o, false); } return o; error: return NULL; } 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"); bool entity_created = false; entity = desc->entity; if (!entity && !desc->global_observer) { entity = ecs_entity(world, {0}); entity_created = true; } if (!entity) { ecs_observer_t *o = flecs_observer_init(world, entity, desc); if (!o) { goto error; } ecs_vec_append_t(NULL, &world->observable.global_observers, ecs_observer_t*)[0] = o; } else { EcsPoly *poly = flecs_poly_bind(world, entity, ecs_observer_t); ecs_check(poly->poly == NULL, ECS_INVALID_OPERATION, "entity %s already is an observer, use ecs_observer_update() " "to modify", flecs_errstr(ecs_get_path(world, entity))); ecs_observer_t *o = flecs_observer_init(world, entity, desc); if (!o) { goto error; } 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)); } flecs_poly_modified(world, entity, ecs_observer_t); } return entity; error: /* Only delete the entity if we created it ourselves; entities provided by * the caller must be preserved on failure. */ if (entity_created) { ecs_delete(world, entity); } return 0; } ecs_entity_t ecs_observer_update( ecs_world_t *world, ecs_entity_t entity, const ecs_observer_desc_t *desc) { 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(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!desc->entity || desc->entity == entity, ECS_INVALID_PARAMETER, "ecs_observer_desc_t::entity does not match observer entity"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot update observer while world is being deleted"); ecs_observer_t *o = flecs_poly_get(world, entity, ecs_observer_t); ecs_check(o != NULL, ECS_INVALID_PARAMETER, "entity %s is not an observer, use ecs_observer_init() to create it", flecs_errstr(ecs_get_path(world, entity))); /* desc->ctx == NULL means "do not touch ctx", not "set ctx to NULL". * Only free the existing ctx when the caller is explicitly replacing it. */ if (desc->ctx && desc->ctx != o->ctx) { if (o->ctx_free && o->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; } } 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; } } if (desc->run) { o->run = desc->run; if (!desc->callback) { o->callback = NULL; } } if (desc->callback) { o->callback = desc->callback; if (!desc->run) { o->run = NULL; } } if (desc->ctx) { o->ctx = desc->ctx; } if (desc->callback_ctx) { o->callback_ctx = desc->callback_ctx; } if (desc->run_ctx) { o->run_ctx = desc->run_ctx; } if (desc->ctx_free) { o->ctx_free = desc->ctx_free; } if (desc->callback_ctx_free) { o->callback_ctx_free = desc->callback_ctx_free; } if (desc->run_ctx_free) { o->run_ctx_free = desc->run_ctx_free; } flecs_poly_modified(world, entity, ecs_observer_t); return entity; error: return 0; } 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); } void flecs_observer_fini( ecs_observer_t *o) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = o->world; flecs_poly_assert(world, ecs_world_t); ecs_observer_impl_t *impl = flecs_observer_impl(o); if (impl->flags & EcsObserverYieldOnDelete) { flecs_observer_yield_existing(world, o, true); } if (impl->flags & EcsObserverIsMulti) { ecs_observer_t **children = ecs_vec_first(&impl->children); int32_t i, children_count = ecs_vec_count(&impl->children); for (i = 0; i < children_count; i ++) { flecs_observer_fini(children[i]); } ecs_os_free(impl->last_event_id); } else { flecs_unregister_observer(world, o->observable, o); } ecs_vec_fini_t(&world->allocator, &impl->children, ecs_observer_t*); /* Cleanup queries */ if (o->query) { ecs_query_fini(o->query); } else if (impl->register_id) { flecs_component_unlock(world, impl->register_id); } if (impl->not_query) { ecs_query_fini(impl->not_query); } /* Cleanup context */ if (o->ctx_free) { o->ctx_free(o->ctx); } if (o->callback_ctx_free) { o->callback_ctx_free(o->callback_ctx); } 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); } 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; } 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); } } static void flecs_marked_id_push( ecs_world_t *world, ecs_component_record_t* cr, 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->cr = cr; m->id = cr->id; m->action = action; m->delete_id = delete_id; cr->flags |= EcsIdMarkedForDelete; flecs_component_claim(world, cr); } static void flecs_component_mark_for_delete( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t action, bool delete_id, bool force_delete); static void flecs_target_mark_for_delete( ecs_world_t *world, ecs_entity_t e, bool force_delete) { ecs_component_record_t *cr; ecs_record_t *r = flecs_entities_get(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, 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))) { return; } if (flags & EcsEntityIsId) { if ((cr = flecs_components_get(world, e))) { flecs_component_mark_for_delete(world, cr, ECS_ID_ON_DELETE(cr->flags), true, force_delete); } if ((cr = flecs_components_get(world, ecs_pair(e, EcsWildcard)))) { flecs_component_mark_for_delete(world, cr, ECS_ID_ON_DELETE(cr->flags), true, force_delete); } } if (flags & EcsEntityIsTarget) { if ((cr = flecs_components_get(world, ecs_pair(EcsWildcard, e)))) { flecs_component_mark_for_delete(world, cr, ECS_ID_ON_DELETE_TARGET(cr->flags), true, force_delete); } if ((cr = flecs_components_get(world, ecs_pair(EcsFlag, e)))) { flecs_component_mark_for_delete(world, cr, ECS_ID_ON_DELETE_TARGET(cr->flags), true, force_delete); } } } static void flecs_targets_mark_for_delete( ecs_world_t *world, ecs_table_t *table, bool force_delete) { const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { flecs_target_mark_for_delete(world, entities[i], force_delete); } } static bool flecs_id_is_delete_target( ecs_id_t id, ecs_entity_t action) { if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { /* If no explicit delete action is provided, and the id we're deleting * has the form (*, Target), use OnDeleteTarget action */ return true; } return false; } static ecs_entity_t flecs_get_delete_action( ecs_table_t *table, const ecs_table_record_t *tr, ecs_entity_t action, bool delete_target) { ecs_entity_t result = action; if (!result && delete_target) { ecs_component_record_t *cr = tr->hdr.cr; ecs_id_t id = cr->id; /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ int32_t i = tr->index, count = tr->count; do { ecs_type_t *type = &table->type; ecs_table_record_t *trr = &table->_->records[i]; ecs_component_record_t *crr = trr->hdr.cr; result = ECS_ID_ON_DELETE_TARGET(crr->flags); if (result == EcsDelete) { /* Delete takes precedence over Remove */ break; } if (count > 1) { /* If table contains multiple pairs for target they are not * guaranteed to occupy consecutive elements in the table's type * vector, so a linear search is needed to find matches. */ for (++ i; i < type->count; i ++) { if (ecs_id_match(type->array[i], id)) { break; } } /* We should always have as many matching ids as tr->count */ ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); } } while (--count); } return result; } static void flecs_simple_delete( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *r) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!ecs_has_pair(world, entity, EcsOnDelete, EcsPanic), ECS_CONSTRAINT_VIOLATED, "cannot delete entity '%s' with (OnDelete, Panic) trait", flecs_errstr(ecs_get_path(world, entity))); flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); /* Entity is still in use by a query */ ecs_assert((world->flags & EcsWorldQuit) || !flecs_component_is_delete_locked(world, entity), ECS_INVALID_OPERATION, "cannot delete '%s' as it is still in use by queries", flecs_errstr(ecs_id_str(world, entity))); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = { .removed = table->type, .removed_flags = table->flags & EcsTableRemoveEdgeFlags }; int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_actions_delete_tree(world, table, row, 1, &diff); flecs_entity_remove_non_fragmenting(world, entity, r); flecs_table_delete(world, table, row, true); flecs_entities_remove(world, entity); flecs_journal_end(); error: return; } static bool flecs_is_childof_tgt_only( const ecs_component_record_t *cr) { ecs_pair_record_t *pr = cr->pair; if (pr->second.next) { return false; } if (ECS_PAIR_FIRST(pr->second.prev->id) != EcsWildcard) { return false; } return true; } static void flecs_component_delete_non_fragmenting_childof( ecs_world_t *world, ecs_component_record_t *cr, bool force_delete) { cr->flags |= EcsIdMarkedForDelete; ecs_pair_record_t *pr = cr->pair; /* Detach ordered_children so observers fired during cleanup don't see * entries for siblings that have already been deleted in the loop. */ ecs_vec_t children_vec = pr->ordered_children; pr->ordered_children = (ecs_vec_t){0}; int32_t i, count = ecs_vec_count(&children_vec); ecs_entity_t *children = ecs_vec_first_t(&children_vec, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_entity_t e = children[i]; ecs_assert(ecs_is_alive(world, e), ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get_any(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if ((r->row & EcsEntityIsTarget)) { ecs_component_record_t *tgt_cr = flecs_components_get( world, ecs_pair(EcsWildcard, e)); if (tgt_cr) { flecs_emit_propagate_invalidate_tables(world, tgt_cr); } ecs_component_record_t *child_cr = flecs_components_get( world, ecs_childof(e)); if (child_cr && !(child_cr->flags & EcsIdMarkedForDelete) && flecs_component_has_non_fragmenting_childof(child_cr)) { if (!flecs_is_childof_tgt_only(child_cr)) { /* Entity is used as target with other relationships, go * through regular cleanup path. */ flecs_target_mark_for_delete(world, e, force_delete); } else { flecs_component_delete_non_fragmenting_childof( world, child_cr, force_delete); } } else { /* Entity is a target but is not a (non-fragmenting) ChildOf * target. Go through regular cleanup path. */ flecs_target_mark_for_delete(world, e, force_delete); } } flecs_simple_delete(world, e, r); } ecs_vec_fini_t(&world->allocator, &children_vec, ecs_entity_t); ecs_component_record_t *tgt_wc = pr->second.prev; ecs_assert(ECS_PAIR_FIRST(tgt_wc->id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_component_release(world, tgt_wc); } static bool flecs_component_mark_non_fragmenting_childof( ecs_world_t *world, ecs_component_record_t *cr, bool force_delete) { ecs_entity_t tgt = ECS_PAIR_SECOND(cr->id); ecs_component_record_t *childof_cr = flecs_components_get( world, ecs_childof(tgt)); if (!childof_cr) { return false; } ecs_flags32_t flags = childof_cr->flags; if (flags & EcsIdMarkedForDelete) { return false; } if (!flecs_component_has_non_fragmenting_childof(childof_cr)) { return false; } childof_cr->flags |= EcsIdMarkedForDelete; ecs_pair_record_t *pr = childof_cr->pair; if (!pr->second.next) { if (ECS_PAIR_FIRST(pr->second.prev->id) == EcsWildcard) { /* Entity is only used as ChildOf target */ flecs_component_delete_non_fragmenting_childof( world, childof_cr, force_delete); return true; } } flecs_marked_id_push(world, childof_cr, EcsDelete, true); int32_t i, count = ecs_vec_count(&pr->ordered_children); ecs_entity_t *children = ecs_vec_first(&pr->ordered_children); for (i = 0; i < count; i ++) { ecs_entity_t e = children[i]; ecs_component_record_t *tgt_cr = flecs_components_get( world, ecs_pair(EcsWildcard, e)); if (!tgt_cr) { continue; } flecs_component_mark_for_delete(world, tgt_cr, 0, true, force_delete); } return false; } static void flecs_component_mark_for_delete( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t action, bool delete_id, bool force_delete) { if (cr->flags & EcsIdMarkedForDelete) { return; } flecs_marked_id_push(world, cr, action, delete_id); ecs_id_t id = cr->id; bool delete_target = flecs_id_is_delete_target(id, action); if (delete_target || (ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard)) { if (flecs_component_mark_non_fragmenting_childof(world, cr, force_delete)) { return; } } /* Mark all tables with the id for delete */ ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&cr->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; } /* Don't mark prefab tables in case of delete_with/remove_all. */ if (!delete_id && !force_delete && (table->flags & EcsTableIsPrefab)) { continue; } ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, delete_target); /* If this is a Delete action, recursively mark ids & tables */ if (cur_action == EcsDelete) { table->flags |= EcsTableMarkedForDelete; ecs_log_push_2(); flecs_targets_mark_for_delete(world, table, force_delete); ecs_log_pop_2(); } else if (cur_action == EcsPanic) { flecs_throw_invalid_delete(world, id); } } } /* Same for empty tables */ if (flecs_table_cache_empty_iter(&cr->cache, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { tr->hdr.table->flags |= EcsTableMarkedForDelete; } } /* Flag component records for deletion */ if (ecs_id_is_wildcard(id)) { ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); ecs_component_record_t *cur = cr; if (ECS_PAIR_SECOND(id) == EcsWildcard) { while ((cur = flecs_component_first_next(cur))) { cur->flags |= EcsIdMarkedForDelete; } } else { /* Iterating all pairs for relationship target */ ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); while ((cur = flecs_component_second_next(cur))) { if (cur->flags & EcsIdOrderedChildren) { continue; } cur->flags |= EcsIdMarkedForDelete; /* If relationship is traversable and is removed upon deletion * of a target, we may have to rematch queries. If a query * matched for example (IsA, A) -> (IsA, B) -> Position, and * B is deleted, Position would no longer be reachable from * tables that have (IsA, B). */ if (cur->flags & EcsIdTraversable) { /* If tables with (R, target) are deleted anyway we don't * need to rematch. Since this will happen recursively it is * guaranteed that queries cannot have tables that reached a * component through the deleted entity. */ if (!(cur->flags & EcsIdOnDeleteTargetDelete)) { /* Only bother if tables have the relationship. */ if (ecs_map_count(&cur->cache.index)) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){cur->id}, .count = 1 }); } } } } } } } static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id, bool force_delete) { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { /* If there's no component 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(cr->flags); } } if (action == EcsPanic) { /* This id is protected from deletion */ flecs_throw_invalid_delete(world, id); return false; } flecs_component_mark_for_delete(world, cr, action, delete_id, force_delete); return true; } static void flecs_remove_from_table( ecs_world_t *world, ecs_table_t *table) { 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_component_record_t *cr = ids[i].cr; if (!(tr = flecs_component_get_table(cr, 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, cr->id, NULL)) != -1); } 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); if (table->flags & EcsTableHasTraversable) { for (i = 0; i < diff.removed.count; i ++) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){td.removed.array[i]}, .count = 1 }); } } flecs_actions_move_remove(world, table, dst_table, 0, table_count, &td); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table); } flecs_table_diff_builder_fini(world, &diff); } static bool flecs_on_delete_clear_entities( ecs_world_t *world, bool force_delete) { /* 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 *cr = ids[i].cr; ecs_entity_t action = ids[i].action; /* Empty all tables for id */ ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&cr->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 contains prefabs and we're not deleting the * prefab entity itself (!force_delete), don't delete table. * This means that delete_with/remove_all can be used safely * for game entities without risking modifying prefabs. * If force_delete is true, it means that one of the * components, relationships or relationship targets is * being deleted in which case the table must go too. */ if (!ids[i].delete_id) { if ((table->flags & EcsTableIsPrefab) && !force_delete) { table->flags &= ~EcsTableMarkedForDelete; continue; } } 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); ecs_table_clear_entities(world, table); } } } /* If component record contains children with Parent components, * delete them. */ if (flecs_component_has_non_fragmenting_childof(cr)) { int32_t c, count = ecs_vec_count(&cr->pair->ordered_children); ecs_entity_t *children = ecs_vec_first(&cr->pair->ordered_children); ecs_defer_suspend(world); for (c = count - 1; c >= 0; c --) { ecs_delete(world, children[c]); } ecs_defer_resume(world); } /* User code (from observers) could have enqueued more ids to delete, * reobtain the array in case it got reallocated */ ids = ecs_vec_first(&world->store.marked_ids); } /* Check if new ids were marked since we started */ int32_t new_last = ecs_vec_count(&world->store.marked_ids); if (new_last != last) { /* Iterate remaining ids */ ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); first = last; last = new_last; } else { break; } } while (true); return true; } static void flecs_on_delete_clear_sparse( ecs_world_t *world, ecs_component_record_t *cr) { ecs_component_record_t *cur = cr; while ((cur = flecs_component_second_next(cur))) { if (!cur->sparse || (!(cur->flags & EcsIdDontFragment))) { continue; } if (cur->flags & EcsIdOnDeleteTargetDelete) { flecs_component_delete_sparse(world, cur); } else if (cur->flags & EcsIdOnDeleteTargetPanic) { flecs_throw_invalid_delete(world, cr->id); } } } static bool flecs_on_delete_clear_ids( ecs_world_t *world, bool force_delete) { 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; (void)force_delete; 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_component_record_t *cr = ids[i].cr; bool delete_id = ids[i].delete_id; /* Run OnDeleteTarget traits for non-fragmenting relationships */ ecs_id_t component_id = cr->id; if (ECS_IS_PAIR(component_id) && (ECS_PAIR_FIRST(component_id) == EcsWildcard) && (cr->flags & EcsIdMatchDontFragment)) { flecs_on_delete_clear_sparse(world, cr); } /* Run OnDelete traits for non-fragmenting components */ if (ids[i].action == EcsDelete) { if (cr->flags & EcsIdDontFragment) { flecs_component_delete_sparse(world, cr); } } if (flecs_component_release_tables(world, cr)) { ecs_assert(!force_delete, ECS_INVALID_OPERATION, "cannot delete component '%s': tables are keeping it alive (likely because of used prefab)", flecs_errstr(ecs_id_str(world, cr->id))); /* There are still tables remaining. This can happen when * flecs_table_keep has been called for a table, which is used * whenever code doesn't want a table to get deleted. */ cr->flags &= ~EcsIdMarkedForDelete; flecs_component_release(world, cr); } else { /* 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, cr); 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, cr); } 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. */ cr->flags &= ~EcsIdMarkedForDelete; } } } } } while (-- twice); return true; } void flecs_throw_invalid_delete( ecs_world_t *world, ecs_id_t id) { (void)id; if (!(world->flags & EcsWorldQuit)) { ecs_throw(ECS_CONSTRAINT_VIOLATED, "(OnDelete, Panic) constraint violated while deleting entities with %s", flecs_errstr(ecs_id_str(world, id))); } error: return; } void flecs_on_delete( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id, bool force_delete) { /* 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, force_delete); /* 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(); /* Delete all entities from the to-be-deleted tables/components */ flecs_on_delete_clear_entities(world, force_delete); /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world, force_delete); /* 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 ecs_delete_with( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { return; } flecs_on_delete(world, id, EcsDelete, false, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_remove_all( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { return; } flecs_on_delete(world, id, EcsRemove, false, false); flecs_defer_end(world, stage); flecs_journal_end(); } #include void ecs_os_api_impl(ecs_os_api_t *api); static bool ecs_os_api_initialized = false; static bool ecs_os_api_initializing = false; static int ecs_os_api_init_count = 0; ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; int64_t ecs_os_api_malloc_count = 0; int64_t ecs_os_api_realloc_count = 0; int64_t ecs_os_api_calloc_count = 0; int64_t ecs_os_api_free_count = 0; void ecs_os_set_api( ecs_os_api_t *os_api) { if (!ecs_os_api_initialized) { ecs_os_api = *os_api; ecs_os_api_initialized = true; } } ecs_os_api_t ecs_os_get_api(void) { return ecs_os_api; } void ecs_os_init(void) { if (!ecs_os_api_initialized) { ecs_os_set_api_defaults(); } if (!(ecs_os_api_init_count ++)) { if (ecs_os_api.init_) { ecs_os_api.init_(); } } } void ecs_os_fini(void) { if (!--ecs_os_api_init_count) { if (ecs_os_api.fini_) { ecs_os_api.fini_(); } } } /* Assume every non-glibc Linux target has no execinfo. This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ #if (defined(ECS_TARGET_LINUX) && !defined(__GLIBC__)) || 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 #define ECS_BT_BUF_SIZE 100 #ifdef ECS_TARGET_WINDOWS #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #endif #include #include #ifdef ECS_TARGET_MSVC #pragma comment(lib, "DbgHelp.lib") #endif void flecs_dump_backtrace( void *stream) { void* stack[ECS_BT_BUF_SIZE]; unsigned short frames; SYMBOL_INFO *symbol; HANDLE hProcess = GetCurrentProcess(); if (!SymInitialize(hProcess, NULL, TRUE)) { return; } frames = CaptureStackBackTrace(0, ECS_BT_BUF_SIZE, stack, NULL); symbol = (SYMBOL_INFO*)ecs_os_calloc( sizeof(SYMBOL_INFO) + 256 * sizeof(char)); symbol->MaxNameLen = 255; symbol->SizeOfStruct = sizeof(SYMBOL_INFO); for (int i = 0; i < frames; i++) { if (SymFromAddr(hProcess, (DWORD64)(uintptr_t)stack[i], NULL, symbol)) { fprintf(stream, "%s\n", symbol->Name); } else { fprintf(stream, "%p\n", stack[i]); } } ecs_os_free(symbol); SymCleanup(hProcess); } #elif HAVE_EXECINFO #include void flecs_dump_backtrace( void *stream) { int nptrs; void *buffer[ECS_BT_BUF_SIZE]; char **strings; nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { return; } for (int j = 1; j < nptrs; j++) { fprintf(stream, "%s\n", strings[j]); } free(strings); } #else void flecs_dump_backtrace( void *stream) { (void)stream; } #endif #undef HAVE_EXECINFO_H static void flecs_log_msg( int32_t level, const char *file, int32_t line, const char *msg) { FILE *stream = ecs_os_api.log_out_; if (!stream) { stream = stdout; } bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; time_t now = 0; if (deltatime) { now = time(NULL); int64_t delta = 0; if (ecs_os_api.log_last_timestamp_) { delta = now - ecs_os_api.log_last_timestamp_; } ecs_os_api.log_last_timestamp_ = (int64_t)now; if (delta) { if (delta < 10) { fputs(" ", stream); } if (delta < 100) { fputs(" ", stream); } char time_buf[20]; ecs_os_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); } if (level >= 4) { if (use_colors) fputs(ECS_NORMAL, stream); fputs("jrnl", stream); } else if (level >= 0) { if (level == 0) { if (use_colors) fputs(ECS_MAGENTA, stream); } else { if (use_colors) fputs(ECS_GREY, stream); } fputs("info", stream); } else if (level == -2) { if (use_colors) fputs(ECS_YELLOW, stream); fputs("warning", stream); } else if (level == -3) { if (use_colors) fputs(ECS_RED, stream); fputs("error", stream); } else if (level == -4) { if (use_colors) fputs(ECS_RED, stream); fputs("fatal", stream); } if (use_colors) fputs(ECS_NORMAL, stream); fputs(": ", stream); if (level >= 0) { if (ecs_os_api.log_indent_) { char indent[32]; int i, indent_count = ecs_os_api.log_indent_; if (indent_count > 15) indent_count = 15; for (i = 0; i < indent_count; i ++) { indent[i * 2] = '|'; indent[i * 2 + 1] = ' '; } if (ecs_os_api.log_indent_ != indent_count) { indent[i * 2 - 2] = '+'; } indent[i * 2] = '\0'; fputs(indent, stream); } } if (level < 0) { if (file) { const char *file_ptr = strrchr(file, '/'); if (!file_ptr) { file_ptr = strrchr(file, '\\'); } if (file_ptr) { file = file_ptr + 1; } fputs(file, stream); fputs(": ", stream); } if (line) { fprintf(stream, "%d: ", line); } } fputs(msg, stream); fputs("\n", stream); if (level == -4) { flecs_dump_backtrace(stream); } } void ecs_os_dbg( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(1, file, line, msg); } } void ecs_os_trace( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(0, file, line, msg); } } void ecs_os_warn( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-2, file, line, msg); } } void ecs_os_err( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-3, file, line, msg); } } void ecs_os_fatal( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-4, file, line, msg); } } static void ecs_os_gettime(ecs_time_t *time) { ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); uint64_t now = ecs_os_now(); uint64_t sec = now / 1000000000; assert(sec < UINT32_MAX); assert((now - sec * 1000000000) < UINT32_MAX); time->sec = (uint32_t)sec; time->nanosec = (uint32_t)(now - sec * 1000000000); } #ifdef FLECS_TRACK_OS_ALLOC ecs_size_t ecs_os_allocated_bytes = 0; static void* ecs_os_api_malloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_malloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); void *result = malloc((size_t)size + 16); *(ecs_size_t*)result = size; ecs_os_allocated_bytes += size; return ECS_OFFSET(result, 16); } 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); void *result = calloc(1, (size_t)size + 16); *(ecs_size_t*)result = size; ecs_os_allocated_bytes += size; return ECS_OFFSET(result, 16); } 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); ptr = ECS_OFFSET(ptr, -16); ecs_os_allocated_bytes -= *(ecs_size_t*)ptr; } else { /* If not actually reallocing, treat as malloc */ ecs_os_linc(&ecs_os_api_malloc_count); } if (!size) { return NULL; } ptr = realloc(ptr, (size_t)size + 16); *(ecs_size_t*)ptr = size; ecs_os_allocated_bytes += size; return ECS_OFFSET(ptr, 16); } static void ecs_os_api_free(void *ptr) { if (ptr) { ptr = ECS_OFFSET(ptr, -16); ecs_size_t size = *(ecs_size_t*)ptr; ecs_os_allocated_bytes -= size; ecs_os_linc(&ecs_os_api_free_count); } free(ptr); } #else static void* ecs_os_api_malloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_malloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); return malloc((size_t)size); } static void* ecs_os_api_calloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_calloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); return calloc(1, (size_t)size); } static void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_os_linc(&ecs_os_api_realloc_count); } else { /* If not actually reallocing, treat as malloc */ ecs_os_linc(&ecs_os_api_malloc_count); } return realloc(ptr, (size_t)size); } static void ecs_os_api_free(void *ptr) { if (ptr) { ecs_os_linc(&ecs_os_api_free_count); } free(ptr); } #endif 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; } } static FILE* ecs_os_api_fopen(const char *file, const char *mode) { #ifndef ECS_TARGET_POSIX FILE *result = NULL; fopen_s(&result, file, mode); return result; #else return fopen(file, mode); #endif } static void ecs_os_api_fclose(FILE *file) { fclose(file); } void ecs_os_strset(char **str, const char *value) { char *old = str[0]; str[0] = ecs_os_strdup(value); ecs_os_free(old); } 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); } } 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); } } /* Replace dots with underscores */ static char *module_file_base(const char *module, char sep) { char *base = ecs_os_strdup(module); ecs_size_t i, len = ecs_os_strlen(base); for (i = 0; i < len; i ++) { if (base[i] == '.') { base[i] = sep; } } return base; } static char* ecs_os_api_module_to_dl(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with underscores + OS library extension */ char *file_base = module_file_base(module, '_'); # if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".so"); # elif defined(ECS_TARGET_DARWIN) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dylib"); # elif defined(ECS_TARGET_WINDOWS) ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dll"); # endif ecs_os_free(file_base); return ecs_strbuf_get(&lib); } static char* ecs_os_api_module_to_etc(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with dashes + /etc */ char *file_base = module_file_base(module, '-'); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, "/etc"); ecs_os_free(file_base); return ecs_strbuf_get(&lib); } void ecs_os_set_api_defaults(void) { /* Don't overwrite if already initialized */ if (ecs_os_api_initialized != 0) { return; } if (ecs_os_api_initializing != 0) { return; } ecs_os_api_initializing = true; /* Memory management */ ecs_os_api.malloc_ = ecs_os_api_malloc; ecs_os_api.free_ = ecs_os_api_free; ecs_os_api.realloc_ = ecs_os_api_realloc; ecs_os_api.calloc_ = ecs_os_api_calloc; /* Strings */ ecs_os_api.strdup_ = ecs_os_api_strdup; /* File I/O */ ecs_os_api.fopen_ = ecs_os_api_fopen; ecs_os_api.fclose_ = ecs_os_api_fclose; /* Time */ ecs_os_api.get_time_ = ecs_os_gettime; /* Logging */ ecs_os_api.log_ = flecs_log_msg; /* Modules */ if (!ecs_os_api.module_to_dl_) { ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; } if (!ecs_os_api.module_to_etc_) { ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } ecs_os_api.abort_ = abort; # ifdef FLECS_OS_API_IMPL /* Initialize defaults to OS API IMPL addon, but still allow for overriding * by the application */ ecs_set_os_api_impl(); ecs_os_api_initialized = false; # endif ecs_os_api_initializing = false; } bool ecs_os_has_heap(void) { return (ecs_os_api.malloc_ != NULL) && (ecs_os_api.calloc_ != NULL) && (ecs_os_api.realloc_ != NULL) && (ecs_os_api.free_ != NULL); } bool ecs_os_has_threading(void) { return (ecs_os_api.mutex_new_ != NULL) && (ecs_os_api.mutex_free_ != NULL) && (ecs_os_api.mutex_lock_ != NULL) && (ecs_os_api.mutex_unlock_ != NULL) && (ecs_os_api.cond_new_ != NULL) && (ecs_os_api.cond_free_ != NULL) && (ecs_os_api.cond_wait_ != NULL) && (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.thread_new_ != NULL) && (ecs_os_api.thread_join_ != NULL) && (ecs_os_api.thread_self_ != NULL); } bool ecs_os_has_task_support(void) { return (ecs_os_api.mutex_new_ != NULL) && (ecs_os_api.mutex_free_ != NULL) && (ecs_os_api.mutex_lock_ != NULL) && (ecs_os_api.mutex_unlock_ != NULL) && (ecs_os_api.cond_new_ != NULL) && (ecs_os_api.cond_free_ != NULL) && (ecs_os_api.cond_wait_ != NULL) && (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.task_new_ != NULL) && (ecs_os_api.task_join_ != NULL); } bool ecs_os_has_time(void) { return (ecs_os_api.get_time_ != NULL) && (ecs_os_api.sleep_ != NULL) && (ecs_os_api.now_ != NULL); } bool ecs_os_has_logging(void) { return (ecs_os_api.log_ != NULL); } bool ecs_os_has_dl(void) { return (ecs_os_api.dlopen_ != NULL) && (ecs_os_api.dlproc_ != NULL) && (ecs_os_api.dlclose_ != NULL); } bool ecs_os_has_modules(void) { return (ecs_os_api.module_to_dl_ != NULL) && (ecs_os_api.module_to_etc_ != NULL); } #if defined(ECS_TARGET_WINDOWS) static char error_str[255]; #endif const char* ecs_os_strerror(int err) { # if defined(ECS_TARGET_WINDOWS) strerror_s(error_str, 255, err); return error_str; # else return strerror(err); # endif } static const char* mixin_kind_str[] = { [EcsMixinWorld] = "world", [EcsMixinEntity] = "entity", [EcsMixinObservable] = "observable", [EcsMixinDtor] = "dtor", [EcsMixinMax] = "max (should never be requested by application)" }; ecs_mixins_t ecs_world_t_mixins = { .type_name = "ecs_world_t", .elems = { [EcsMixinWorld] = offsetof(ecs_world_t, self), [EcsMixinObservable] = offsetof(ecs_world_t, observable), } }; ecs_mixins_t ecs_stage_t_mixins = { .type_name = "ecs_stage_t", .elems = { [EcsMixinWorld] = offsetof(ecs_stage_t, world) } }; 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) } }; 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->type != 0, 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; /* Object has mixin, return its address */ return ECS_OFFSET(hdr, offset); } void* flecs_poly_init_( ecs_poly_t *poly, int32_t type, ecs_size_t size, ecs_mixins_t *mixins) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_os_memset(poly, 0, size); hdr->type = type; hdr->refcount = 1; hdr->mixins = mixins; return poly; } void flecs_poly_fini_( ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); (void)type; ecs_header_t *hdr = poly; /* Don't deinit poly that wasn't initialized */ ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, "incorrect function called to free flecs object"); hdr->type = 0; } 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->type != 0, 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; } } 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->type != 0, 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; } } 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->type != 0, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); return hdr->refcount; } 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); } /* Never defer creation of a poly object */ bool deferred = false; if (ecs_is_deferred(world)) { deferred = true; ecs_defer_suspend(world); } /* If this is a new poly, leave the actual creation up to the caller so they * can tell the difference between a create or an update */ EcsPoly *result = ecs_ensure_pair(world, entity, EcsPoly, tag); if (deferred) { ecs_defer_resume(world); } return result; } void flecs_poly_modified_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); } static const EcsPoly* flecs_poly_bind_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { return ecs_get_pair(world, entity, EcsPoly, tag); } ecs_poly_t* flecs_poly_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { const EcsPoly *p = flecs_poly_bind_get_(world, entity, tag); if (p) { return p->poly; } return NULL; } bool flecs_poly_is_( const ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr->type != 0, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); return hdr->type == type; } ecs_observable_t* flecs_get_observable( const ecs_poly_t *poly) { return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); } const ecs_world_t* ecs_get_world( const ecs_poly_t *poly) { if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { return poly; } return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); } ecs_entity_t ecs_get_entity( const ecs_poly_t *poly) { return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); } flecs_poly_dtor_t* flecs_get_dtor( const ecs_poly_t *poly) { return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); } 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); flecs_check_exclusive_world_access_read(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, #ifdef FLECS_DEBUG .id = id, #endif }; ecs_table_t *table = record->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); result.table_id = table->id; result.table_version_fast = flecs_get_table_version_fast(world, result.table_id); result.table_version = table->version; result.ptr = flecs_get_component( world, 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_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_DEBUG ecs_check(id == ref->id, ECS_INVALID_PARAMETER, "id does not match ref"); #endif flecs_check_exclusive_world_access_read(world); if (ref->table_version_fast == flecs_get_table_version_fast( world, ref->table_id)) { return; } ecs_record_t *r = flecs_entities_get_any(world, ref->entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { /* Table can be NULL, entity could have been deleted */ ref->table_id = 0; ref->table_version_fast = 0; ref->table_version = 0; ref->ptr = NULL; return; } if (!ecs_is_alive(world, ref->entity)) { ref->table_id = 0; ref->table_version_fast = 0; ref->table_version = 0; ref->ptr = NULL; return; } if (ref->table_id == table->id && ref->table_version == table->version) { ref->table_version_fast = flecs_get_table_version_fast(world, ref->table_id); return; } ref->table_id = table->id; ref->table_version_fast = flecs_get_table_version_fast(world, ref->table_id); ref->table_version = table->version; ref->ptr = flecs_get_component(world, table, ECS_RECORD_TO_ROW(r->row), flecs_components_get(world, id)); error: return; } void* ecs_ref_get_id( const ecs_world_t *world, ecs_ref_t *ref, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, "ref not initialized"); ecs_check(id != 0, ECS_INVALID_PARAMETER, "ref not initialized"); #ifdef FLECS_DEBUG ecs_check(id == ref->id, ECS_INVALID_PARAMETER, "id does not match ref"); #endif ecs_ref_update(world, ref, id); return ref->ptr; error: return NULL; } static int32_t flecs_table_search_relation( const ecs_world_t *world, const ecs_record_t *record, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_component_record_t *cr, ecs_id_t rel, ecs_component_record_t *cr_r, bool self, ecs_entity_t *tgt_out, ecs_id_t *id_out, ecs_table_record_t **tr_out); static int32_t flecs_table_search( const ecs_table_t *table, ecs_component_record_t *cr, ecs_id_t *id_out, ecs_table_record_t **tr_out) { ecs_table_record_t *tr = ecs_table_cache_get(&cr->cache, table); if (tr) { int32_t r = tr->index; if (tr_out) tr_out[0] = tr; if (id_out) { id_out[0] = table->type.array[r]; } return r; } return -1; } static int32_t flecs_table_offset_search( const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out) { ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t *ids = table->type.array; int32_t count = table->type.count; 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; } bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_component_record_t *cr, ecs_id_t id) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); if (cr->flags & EcsIdOnInstantiateDontInherit) { return false; } if (cr->flags & EcsIdExclusive) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t er = ECS_PAIR_FIRST(id); ecs_component_record_t *cr_wc = flecs_components_get( world, ecs_pair(er, EcsWildcard)); if (cr_wc && flecs_component_get_table(cr_wc, table)) { return false; } } } return true; } static int32_t flecs_table_search_relation_for_tgt( const ecs_world_t *world, ecs_entity_t tgt, ecs_id_t id, ecs_component_record_t *cr, ecs_id_t rel, ecs_component_record_t *cr_r, ecs_entity_t *tgt_out, ecs_id_t *id_out, ecs_table_record_t **tr_out) { ecs_record_t *r = flecs_entities_get_any(world, tgt); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *tgt_table = r->table; ecs_assert(tgt_table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t result = -1; if (cr->flags & EcsIdDontFragment) { if (flecs_sparse_get(cr->sparse, 0, tgt) != NULL) { result = -2; goto found; } } else { result = flecs_table_search_relation(world, r, tgt_table, 0, id, cr, rel, cr_r, true, tgt_out, id_out, tr_out); if (result != -1) { goto found; } } return -1; found: if (tgt_out && !tgt_out[0]) { tgt_out[0] = ecs_get_alive(world, tgt); } return result; } static int32_t flecs_table_search_relation_pairs( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_component_record_t *cr, ecs_id_t rel, ecs_component_record_t *cr_r, ecs_entity_t *tgt_out, ecs_id_t *id_out, ecs_table_record_t **tr_out) { bool exclusive = cr_r->flags & EcsIdExclusive; int32_t column; ecs_id_t id_r; if (offset) { column = flecs_table_offset_search(table, offset, rel, &id_r); } else { column = flecs_table_search(table, cr_r, &id_r, 0); } while (column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(id_r); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_table_search_relation_for_tgt( world, tgt, id, cr, rel, cr_r, tgt_out, id_out, tr_out) != -1) { return column; } if (exclusive) { break; } column = flecs_table_offset_search(table, column + 1, rel, &id_r); } return -1; } static int32_t flecs_table_search_relation( const ecs_world_t *world, const ecs_record_t *record, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_component_record_t *cr, ecs_id_t rel, ecs_component_record_t *cr_r, bool self, ecs_entity_t *tgt_out, ecs_id_t *id_out, ecs_table_record_t **tr_out) { bool dont_fragment = cr->flags & EcsIdDontFragment; if (self && !dont_fragment) { if (offset) { int32_t r = flecs_table_offset_search(table, offset, id, id_out); if (r != -1) { return r; } } else { int32_t r = flecs_table_search(table, cr, id_out, tr_out); if (r != -1) { return r; } } } ecs_flags32_t flags = table->flags; if (!(flags & EcsTableHasPairs) || !rel) { return -1; } if (flags & EcsTableHasIsA) { if (flecs_type_can_inherit_id(world, table, cr, id)) { int32_t column = flecs_table_search_relation_pairs( world, table, offset, id, cr, rel, world->cr_isa_wildcard, tgt_out, id_out, tr_out); if (column != -1) { return column; } } if (rel == ecs_isa(EcsWildcard)) { return -1; } } if (rel == ecs_childof(EcsWildcard)) { cr_r = world->cr_childof_wildcard; if (table->flags & EcsTableHasParent) { /* Can't resolve parent on table */ ecs_assert(record != NULL, ECS_INVALID_PARAMETER, "cannot traverse ChildOf on table with Parent component, " "search on entity instead"); int32_t column = table->component_map[ecs_id(EcsParent)]; ecs_assert(column > 0, ECS_INTERNAL_ERROR, NULL); const EcsParent *parents = table->data.columns[column - 1].data; ecs_entity_t parent = parents[ECS_RECORD_TO_ROW(record->row)].value; return flecs_table_search_relation_for_tgt(world, parent, id, cr, rel, cr_r, tgt_out, id_out, tr_out); } } if (!cr_r) { cr_r = flecs_components_get(world, rel); if (!cr_r) { return -1; } } return flecs_table_search_relation_pairs(world, table, offset, id, cr, rel, cr_r, tgt_out, id_out, tr_out); } int32_t ecs_search_relation_for_entity( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, ecs_entity_t rel, bool self, ecs_component_record_t *cr, ecs_entity_t *tgt_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (tgt_out) tgt_out[0] = 0; if (!cr) { cr = flecs_components_get(world, id); } int32_t result = flecs_table_search_relation(world, r, r->table, 0, id, cr, rel, NULL, self, tgt_out, id_out, tr_out); if (result != -1) { if (tgt_out && !tgt_out[0]) { tgt_out[0] = entity; } } return result; } int32_t ecs_search_relation( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags64_t flags, ecs_entity_t *tgt_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); flags = flags ? flags : (EcsSelf|EcsUp); if (tgt_out) tgt_out[0] = 0; if (!(flags & EcsUp)) { return ecs_search_offset(world, table, offset, id, id_out); } ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return -1; } int32_t result = flecs_table_search_relation(world, NULL, table, offset, id, cr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, tgt_out, id_out, tr_out); return result; } 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); ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return -1; } return flecs_table_search(table, cr, id_out, 0); } int32_t ecs_search_offset( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out) { if (!offset) { flecs_poly_assert(world, ecs_world_t); return ecs_search(world, table, id, id_out); } return flecs_table_offset_search(table, offset, id, id_out); } static int32_t flecs_relation_depth_walk( const ecs_world_t *world, const ecs_component_record_t *cr, const ecs_table_t *first, const ecs_table_t *table) { int32_t result = 0; const ecs_table_record_t *tr = flecs_component_get_table(cr, 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]); if (!o) { /* Rare, but can happen during cleanup when an intermediate table is * created that contains a pair that is about to be removed but * hasn't been yet, where the target is not alive. * Would be better if this intermediate table wouldn't get created, * but that requires a refactor of the cleanup logic. */ return 0; } 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, cr, first, ot); if (cur > result) { result = cur; } } return result + 1; } int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table) { if (r == EcsChildOf) { if (table->flags & EcsTableHasChildOf) { ecs_component_record_t *cr_wc = world->cr_childof_wildcard; const ecs_table_record_t *tr_wc = flecs_component_get_table(cr_wc, table); ecs_assert(tr_wc != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = &table->_->records[tr_wc->index]; ecs_component_record_t *cr = tr->hdr.cr; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); return cr->pair->depth; } else if (table->flags & EcsTableHasParent) { ecs_component_record_t *cr_wc = flecs_components_get( world, ecs_pair(EcsParentDepth, EcsWildcard)); if (!cr_wc) { return 0; } const ecs_table_record_t *tr_wc = flecs_component_get_table( cr_wc, table); if (!tr_wc) { return 0; } ecs_id_t depth_pair = table->type.array[tr_wc->index]; ecs_assert(ECS_PAIR_FIRST(depth_pair) == EcsParentDepth, ECS_INTERNAL_ERROR, NULL); return flecs_uto(int32_t, ECS_PAIR_SECOND(depth_pair)); } else { return 0; } } ecs_component_record_t *cr = flecs_components_get( world, ecs_pair(r, EcsWildcard)); if (!cr) { return 0; } return flecs_relation_depth_walk(world, cr, table, table); } static void flecs_stage_merge( ecs_world_t *world) { bool is_stage = flecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); bool measure_frame_time = ECS_BIT_IS_SET(ecs_world_get_flags(world), EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; if (measure_frame_time) { ecs_os_get_time(&t_start); } ecs_dbg_3("#[magenta]merge"); ecs_log_push_3(); if (is_stage) { /* Check for consistency when merging a single stage. */ ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, "mismatching defer_begin/defer_end detected"); flecs_defer_end(world, stage); } else { /* Merge all stages */ 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); } } flecs_eval_component_monitors(world); if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; /* If stage is unmanaged, deferring is always enabled */ if (stage->id == -1) { flecs_defer_begin(world, stage); } ecs_log_pop_3(); } void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage) { /* Execute post frame actions */ int32_t i, count = ecs_vec_count(&stage->post_frame_actions); ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_clear(&stage->post_frame_actions); } 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 ecs_stage_t* flecs_stage_new( ecs_world_t *world) { 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_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); int32_t i; for (i = 0; i < 2; i ++) { flecs_commands_init(stage, &stage->cmd_stack[i]); } stage->cmd = &stage->cmd_stack[0]; return stage; } static void flecs_stage_free( ecs_world_t *world, ecs_stage_t *stage) { (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]); } flecs_stack_fini(&stage->allocators.iter_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); } 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; } 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; } ecs_world_t* ecs_stage_new( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_new(world); stage->id = -1; flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } 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; } void ecs_set_stage_count( ecs_world_t *world, int32_t stage_count) { flecs_poly_assert(world, ecs_world_t); /* World must have at least one default stage */ ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), ECS_INTERNAL_ERROR, NULL); 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 * lookup_path 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; } ecs_world_t* flecs_suspend_readonly( const ecs_world_t *stage_world, ecs_suspend_readonly_state_t *state) { ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (!is_readonly && !stage->defer) { state->is_readonly = false; state->is_deferred = false; return world; } ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ ecs_assert(!(world->flags & EcsWorldReadonly) || !(world->flags & EcsWorldMultiThreaded), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = stage->defer != 0; state->cmd_flushing = stage->cmd_flushing; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; stage->cmd_flushing = false; /* Hack around safety checks (this ought to look ugly) */ state->defer_count = stage->defer; state->cmd_stack[0] = stage->cmd_stack[0]; state->cmd_stack[1] = stage->cmd_stack[1]; state->cmd = stage->cmd; flecs_commands_init(stage, &stage->cmd_stack[0]); flecs_commands_init(stage, &stage->cmd_stack[1]); stage->cmd = &stage->cmd_stack[0]; state->scope = stage->scope; state->with = stage->with; stage->defer = 0; return world; } void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state) { flecs_poly_assert(world, ecs_world_t); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (state->is_readonly || state->is_deferred) { ecs_dbg_3("resuming readonly mode"); ecs_run_aperiodic(world, 0); /* Restore readonly state / defer count */ ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); stage->defer = state->defer_count; stage->cmd_flushing = state->cmd_flushing; flecs_commands_fini(stage, &stage->cmd_stack[0]); flecs_commands_fini(stage, &stage->cmd_stack[1]); stage->cmd_stack[0] = state->cmd_stack[0]; stage->cmd_stack[1] = state->cmd_stack[1]; stage->cmd = state->cmd; stage->scope = state->scope; stage->with = state->with; } } void ecs_merge( ecs_world_t *stage) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_poly_is(stage, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); flecs_stage_merge(stage); 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; } void ecs_stage_shrink( ecs_stage_t *stage) { flecs_sparse_shrink(&stage->cmd_stack[0].entries); flecs_sparse_shrink(&stage->cmd_stack[1].entries); ecs_vec_reclaim_t(&stage->allocator, &stage->cmd_stack[0].queue, ecs_cmd_t); ecs_vec_reclaim_t(&stage->allocator, &stage->cmd_stack[1].queue, ecs_cmd_t); } 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; } bool ecs_is_defer_suspended( 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; } static void flecs_tree_spawner_release_tables( ecs_vec_t *v) { int32_t i, count = ecs_vec_count(v); ecs_tree_spawner_child_t *elems = ecs_vec_first(v); for (i = 0; i < count; i ++) { ecs_tree_spawner_child_t *elem = &elems[i]; flecs_table_release(elem->table); } } static void EcsTreeSpawner_free(EcsTreeSpawner *ptr) { int32_t i; for (i = 0; i < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; i ++) { flecs_tree_spawner_release_tables(&ptr->data[i].children); ecs_vec_fini_t(NULL, &ptr->data[i].children, ecs_tree_spawner_child_t); } } static ECS_COPY(EcsTreeSpawner, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "TreeSpawner component cannot be copied"); }) static ECS_MOVE(EcsTreeSpawner, dst, src, { EcsTreeSpawner_free(dst); *dst = *src; ecs_os_zeromem(src); }) static ECS_DTOR(EcsTreeSpawner, ptr, { EcsTreeSpawner_free(ptr); }) static ecs_type_t flecs_prefab_spawner_build_type( ecs_world_t *world, ecs_entity_t child, ecs_table_t *table, int32_t depth) { ecs_type_t dst = {0}; ecs_type_t *src = &table->type; flecs_type_add(world, &dst, ecs_id(EcsParent)); int32_t i, count = src->count; for (i = 0; i < count; i ++) { ecs_id_t id = src->array[i]; ecs_table_record_t *tr = &table->_->records[i]; ecs_component_record_t *cr = tr->hdr.cr; if (cr->flags & (EcsIdOnInstantiateDontInherit|EcsIdOnInstantiateInherit)) { continue; } if (id & ECS_AUTO_OVERRIDE) { /* If AUTO_OVERRIDE flag is set, add component to instances. This * allows instances to end up with an owned component, even if the * component has the (OnInstantiate, Inherit) trait. * Additionally, this also allows for adding components to instances * that aren't copyable, since a prefab can have a component with * AUTO_OVERRIDE flag, but not have the actual component (which then * isn't copied). */ flecs_type_add(world, &dst, id & ~ECS_AUTO_OVERRIDE); continue; } ecs_entity_t rel = ECS_PAIR_FIRST(id); if (rel == EcsIsA) { /* If prefab child has IsA relationships, they will be inherited * through the (IsA, prefab_child) relationship (added below). */ continue; } if (rel == EcsParentDepth) { /* Replace depth value with the normalized depth for spawner. */ id = ecs_value_pair(EcsParentDepth, depth); } flecs_type_add(world, &dst, id); } flecs_type_add(world, &dst, ecs_isa(child)); return dst; } static void flecs_prefab_spawner_build_from_cr( ecs_world_t *world, ecs_component_record_t *cr, ecs_vec_t *spawner, int32_t parent_index, int32_t depth) { ecs_vec_t *children_vec = &cr->pair->ordered_children; int32_t i, count = ecs_vec_count(children_vec); ecs_entity_t *children = ecs_vec_first(children_vec); for (i = 0; i < count; i ++) { ecs_entity_t child = children[i]; ecs_record_t *r = flecs_entities_get(world, child); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!(table->flags & EcsTableHasParent)) { continue; } ecs_tree_spawner_child_t *elem = ecs_vec_append_t( NULL, spawner, ecs_tree_spawner_child_t); elem->parent_index = parent_index; elem->child_name = NULL; elem->child = (uint32_t)child; ecs_type_t type = flecs_prefab_spawner_build_type( world, child, table, depth); elem->table = flecs_table_find_or_create(world, &type); ecs_assert(elem->table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_free(world, &type); /* Make sure table doesn't get freed by shrink() */ flecs_table_keep(elem->table); if (!(r->row & EcsEntityIsTraversable)) { continue; } ecs_component_record_t *child_cr = flecs_components_get( world, ecs_childof(child)); if (!child_cr) { continue; } flecs_prefab_spawner_build_from_cr( world, child_cr, spawner, ecs_vec_count(spawner), depth + 1); } } static void flecs_spawner_transpose_depth( ecs_world_t *world, EcsTreeSpawner *spawner, ecs_vec_t *dst, int32_t depth) { ecs_vec_t *src = &spawner->data[0].children; int32_t i, count = ecs_vec_count(src); ecs_vec_set_count_t(NULL, dst, ecs_tree_spawner_child_t, count); for (i = 0; i < count; i ++) { ecs_tree_spawner_child_t *src_elem = ecs_vec_get_t( src, ecs_tree_spawner_child_t, i); ecs_tree_spawner_child_t *dst_elem = ecs_vec_get_t( dst, ecs_tree_spawner_child_t, i); dst_elem->child_name = src_elem->child_name; dst_elem->parent_index = src_elem->parent_index; dst_elem->child = src_elem->child; /* Get depth for source element at depth 0 */ int32_t src_depth = flecs_relation_depth( world, EcsChildOf, src_elem->table); /* Get table for correct depth */ ecs_id_t depth_pair = ecs_value_pair(EcsParentDepth, src_depth + depth); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; dst_elem->table = flecs_table_traverse_add( world, src_elem->table, &depth_pair, &diff); flecs_table_keep(dst_elem->table); } } #ifdef FLECS_DEBUG void flecs_tree_spawner_assert_not_instantiated( ecs_world_t *world, ecs_entity_t parent) { if (world->flags & EcsWorldFini) { return; } ecs_record_t *r = flecs_entities_get(world, parent); if (!r || !r->table || !(r->table->flags & EcsTableIsPrefab)) { return; } ecs_entity_t cur = parent; while (cur) { r = flecs_entities_get(world, cur); if (!r || !r->table) { break; } if (ecs_get_id(world, cur, ecs_id(EcsTreeSpawner)) != NULL) { char *path = ecs_get_path(world, cur); ecs_abort(ECS_INVALID_OPERATION, "cannot change children of prefab '%s' after it has been " "instantiated", path); } cur = ecs_get_parent(world, cur); } } #endif EcsTreeSpawner* flecs_prefab_spawner_build( ecs_world_t *world, ecs_entity_t base) { ecs_component_record_t *cr = flecs_components_get(world, ecs_childof(base)); if (!cr) { return NULL; } ecs_vec_t spawner; ecs_vec_init_t(NULL, &spawner, ecs_tree_spawner_child_t, 0); flecs_prefab_spawner_build_from_cr(world, cr, &spawner, 0, 1); base = flecs_entities_get_alive(world, base); EcsTreeSpawner *ts = ecs_ensure(world, base, EcsTreeSpawner); ts->data[0].children = spawner; /* Initialize remaining vectors */ int32_t i; for (i = 1; i < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; i ++) { ecs_vec_init_t( NULL, &ts->data[i].children, ecs_tree_spawner_child_t, 0); } return ts; } void flecs_spawner_instantiate( ecs_world_t *world, EcsTreeSpawner *spawner, ecs_entity_t base, ecs_entity_t instance, const ecs_instantiate_ctx_t *ctx) { ecs_record_t *r_instance = flecs_entities_get(world, instance); int32_t depth = flecs_relation_depth(world, EcsChildOf, r_instance->table); int32_t i, child_count = ecs_vec_count(&spawner->data[0].children); bool is_prefab = r_instance->table->flags & EcsTableIsPrefab; ecs_instantiate_ctx_t ctx_cur = {base, instance}; if (ctx) { ctx_cur = *ctx; } /* Use cached spawner for depth if available. */ ecs_vec_t *vec, tmp_vec; if (depth < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE) { vec = &spawner->data[depth].children; } else { vec = &tmp_vec; ecs_vec_init_t(NULL, vec, ecs_tree_spawner_child_t, 0); } if (depth && ecs_vec_count(vec) != child_count) { /* Vector for depth is not yet initialized, create it. */ flecs_spawner_transpose_depth(world, spawner, vec, depth); } ecs_tree_spawner_child_t *spawn_children = ecs_vec_first(vec); ecs_vec_set_min_count_t(&world->allocator, &world->allocators.tree_spawner, ecs_entity_t, child_count + 1); ecs_entity_t *parents = ecs_vec_first(&world->allocators.tree_spawner); parents[0] = instance; ecs_component_record_t *cr = NULL; ecs_entity_t old_parent = 0; ecs_assert(ecs_vec_count(vec) == child_count, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < child_count; i ++) { ecs_tree_spawner_child_t *spawn_child = &spawn_children[i]; ecs_entity_t entity = parents[i + 1] = flecs_instantiate_alloc_child_id( world, spawn_child->child, ctx_cur.root_prefab, ctx_cur.root_instance); ecs_table_t *table = spawn_child->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); const char *child_name = NULL; if (is_prefab) { ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_id_t id = EcsPrefab; table = flecs_table_traverse_add(world, table, &id, &diff); child_name = ecs_get_name(world, flecs_entities_get_alive(world, spawn_child->child)); if (child_name) { id = ecs_pair_t(EcsIdentifier, EcsName); table = flecs_table_traverse_add(world, table, &id, &diff); } } ecs_record_t *r = flecs_entities_get(world, entity); ecs_flags32_t flags = table->flags & EcsTableAddEdgeFlags; ecs_table_diff_t table_diff = { .added = table->type, .added_flags = flags }; ecs_entity_t parent = parents[spawn_child->parent_index]; ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); if (parent != old_parent) { cr = flecs_components_ensure(world, ecs_childof(parent)); old_parent = parent; } ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = ecs_table_count(table); r->table = table; r->row = (uint32_t)row; flecs_table_append(world, table, entity, true, true); int32_t parent_column = table->component_map[ecs_id(EcsParent)]; ecs_assert(parent_column != 0, ECS_INTERNAL_ERROR, NULL); EcsParent *parent_ptr = table->data.columns[parent_column - 1].data; parent_ptr = &parent_ptr[row]; parent_ptr->value = parent; flecs_actions_new(world, table, row, 1, &table_diff, EcsEventNoOnSet, true, EcsWildcard); if (is_prefab && child_name) { ecs_set_name(world, entity, child_name); } flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r); ecs_entity_t base_child = spawn_child->child; ecs_record_t *spawn_r = flecs_entities_get_any( world, spawn_child->child); ecs_assert(spawn_r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_range_t base_range = { .table = spawn_r->table, .offset = 0, .count = 1 }; flecs_instantiate_sparse(world, &base_range, &base_child, r->table, &entity, ECS_RECORD_TO_ROW(r->row), true); if (spawn_r->row & EcsEntityHasDontFragment) { flecs_instantiate_dont_fragment( world, spawn_child->child, entity); } } if (vec == &tmp_vec) { ecs_vec_fini_t(NULL, vec, ecs_tree_spawner_child_t); } } void flecs_fini_tree_spawners( ecs_world_t *world) { ecs_iter_t it = ecs_each(world, EcsTreeSpawner); while (ecs_each_next(&it)) { EcsTreeSpawner *t = ecs_field(&it, EcsTreeSpawner, 0); int32_t i; for (i = 0; i < it.count; i ++) { EcsTreeSpawner_free(&t[i]); } } } void flecs_bootstrap_spawner( ecs_world_t *world) { flecs_type_info_init(world, EcsTreeSpawner, { .ctor = flecs_default_ctor, .copy = ecs_copy(EcsTreeSpawner), .move = ecs_move(EcsTreeSpawner), .dtor = ecs_dtor(EcsTreeSpawner) }); ecs_add_pair(world, ecs_id(EcsTreeSpawner), EcsOnInstantiate, EcsDontInherit); } #ifdef FLECS_DEBUG static void flecs_type_info_mark_in_use( const ecs_type_info_t *ti) { ecs_type_info_t *ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); ti_mut->hooks.flags |= ECS_TYPE_HOOK_IN_USE; } #else #define flecs_type_info_mark_in_use(ti) ((void)ti) #endif void flecs_default_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_os_memset(ptr, 0, ti->size * count); } bool flecs_type_info_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(ptr, count, ti); return true; } return false; } bool flecs_type_info_dtor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_DTOR_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { dtor(ptr, count, ti); return true; } return false; } void flecs_type_info_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_COPY_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst_ptr, src_ptr, count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } } void flecs_type_info_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_move_t move = ti->hooks.move; if (move) { move(dst_ptr, src_ptr, count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } } void flecs_type_info_copy_ctor( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(dst_ptr, src_ptr, count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } } void flecs_type_info_move_ctor( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_move_t move = ti->hooks.move_ctor; if (move) { move(dst_ptr, src_ptr, count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } } void flecs_type_info_ctor_move_dtor( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_move_t move = ti->hooks.ctor_move_dtor; if (move) { move(dst_ptr, src_ptr, count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } } void flecs_type_info_move_dtor( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_move_t move = ti->hooks.move_dtor; if (move) { move(dst_ptr, src_ptr, count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } } int flecs_type_info_cmp( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_CMP_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_cmp_t cmp = ti->hooks.cmp; ecs_assert(cmp != NULL, ECS_INTERNAL_ERROR, NULL); return cmp(a_ptr, b_ptr, ti); } bool flecs_type_info_equals( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { ecs_assert(!(ti->hooks.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL), ECS_INVALID_OPERATION, "%s", ti->name); flecs_type_info_mark_in_use(ti); ecs_equals_t equals = ti->hooks.equals; ecs_assert(equals != NULL, ECS_INTERNAL_ERROR, NULL); return equals(a_ptr, b_ptr, ti); } static void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->copy(dst_ptr, src_ptr, count, ti); } static void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->move(dst_ptr, src_ptr, count, ti); } static void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->move(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } static void flecs_default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->move_ctor(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } static void flecs_default_move(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->move(dst_ptr, src_ptr, count, ti); } static void flecs_default_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { /* When there is no move, destruct the destination component & memcpy the * component to dst. The src component does not have to be destructed when * a component has a trivial move. */ const ecs_type_hooks_t *cl = &ti->hooks; cl->dtor(dst_ptr, count, ti); ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } static void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { /* If a component has a move, the move will take care of memcpying the data * and destroying any data in dst. Because this is not a trivial move, the * src component must also be destructed. */ const ecs_type_hooks_t *cl = &ti->hooks; cl->move(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } 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, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid constructor for %s", ti->name); } ECS_NORETURN static void flecs_dtor_illegal( void *dst, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid destructor for %s", ti->name); } ECS_NORETURN static void flecs_copy_illegal( void *dst, const void *src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid copy assignment for %s", ti->name); } ECS_NORETURN static void flecs_move_illegal( void * dst, void * src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid move assignment for %s", ti->name); } ECS_NORETURN static void flecs_copy_ctor_illegal( void *dst, const void *src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid copy construct for %s", ti->name); } ECS_NORETURN static void flecs_move_ctor_illegal( void *dst, void *src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; 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); } static bool flecs_type_hooks_storage_equal( const ecs_type_hooks_t *a, const ecs_type_hooks_t *b) { ecs_flags32_t flags = ECS_TYPE_HOOKS|ECS_TYPE_HOOKS_ILLEGAL; if ((a->flags & flags) != (b->flags & flags)) { return false; } if ((a->on_add != NULL) != (b->on_add != NULL)) { return false; } if ((a->on_set != NULL) != (b->on_set != NULL)) { return false; } if ((a->on_remove != NULL) != (b->on_remove != NULL)) { return false; } if ((a->on_replace != NULL) != (b->on_replace != NULL)) { return false; } return true; } void ecs_set_hooks_id( ecs_world_t *world, ecs_entity_t component, const ecs_type_hooks_t *h) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, component), ECS_INVALID_PARAMETER, NULL); /* TODO: Refactor to enforce flags consistency: */ ecs_flags32_t flags = h->flags; flags &= ~((ecs_flags32_t)ECS_TYPE_HOOKS); ecs_check(!(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL && h->ctor != NULL && h->ctor != flecs_ctor_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both ctor hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL && h->dtor != NULL && h->dtor != flecs_dtor_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both dtor hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_COPY_ILLEGAL && h->copy != NULL && h->copy != flecs_copy_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both copy hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL && h->move != NULL && h->move != flecs_move_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both move hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL && h->copy_ctor != NULL && h->copy_ctor != flecs_copy_ctor_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both copy ctor hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL && h->move_ctor != NULL && h->move_ctor != flecs_move_ctor_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both move ctor hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); 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, "illegal call to set_hooks() for component '%s': " "cannot specify both ctor move dtor hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL && h->move_dtor != NULL && h->move_dtor != flecs_move_ctor_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both move dtor hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_CMP_ILLEGAL && h->cmp != NULL && h->cmp != flecs_comp_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both compare hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); ecs_check(!(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL && h->equals != NULL && h->equals != flecs_equals_illegal), ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify both equals hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); flecs_stage_from_world(&world); bool in_use = ecs_id_in_use(world, component) || ecs_id_in_use(world, ecs_pair(component, EcsWildcard)); ecs_type_info_t *ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!ti->component || ti->component == component, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); ecs_type_hooks_t prev_hooks = ti->hooks; if (!ti->size) { const EcsComponent *component_ptr = ecs_get( world, component, EcsComponent); /* Cannot register lifecycle actions for things that aren't a component */ ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, "illegal call to set_hooks() for '%s': component cannot be a tag/zero sized", flecs_errstr(ecs_get_path(world, component))); ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, "illegal call to set_hooks() for '%s': " "component cannot be a tag/zero sized", flecs_errstr(ecs_get_path(world, component))); ti->size = component_ptr->size; ti->alignment = component_ptr->alignment; } if (h->ctor) ti->hooks.ctor = h->ctor; if (h->dtor) ti->hooks.dtor = h->dtor; if (h->copy) ti->hooks.copy = h->copy; if (h->move) ti->hooks.move = h->move; if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; if (h->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; if (h->on_set) ti->hooks.on_set = h->on_set; if (h->on_replace) ti->hooks.on_replace = h->on_replace; if (h->ctx) ti->hooks.ctx = h->ctx; if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; if (h->lifecycle_ctx) ti->hooks.lifecycle_ctx = h->lifecycle_ctx; if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; if (h->lifecycle_ctx_free) ti->hooks.lifecycle_ctx_free = h->lifecycle_ctx_free; /* If no constructor is set, invoking any of the other lifecycle actions * is not safe as they will potentially access uninitialized memory. For * ease of use, if no constructor is specified, set a default one that * initializes the component to 0. */ if (!h->ctor && (h->dtor || h->copy || h->move)) { ti->hooks.ctor = flecs_default_ctor; } /* Set default copy ctor, move ctor and merge */ if (!h->copy_ctor && !(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL)) { if (h->copy) { ti->hooks.copy_ctor = flecs_default_copy_ctor; } } if (!h->move_ctor && !(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL)) { if (h->move) { ti->hooks.move_ctor = flecs_default_move_ctor; } } if (!h->ctor_move_dtor) { ecs_flags32_t illegal_check = 0; if (h->move) { illegal_check |= ECS_TYPE_HOOK_MOVE_ILLEGAL; if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; if (h->move_ctor) { illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; /* If an explicit move ctor has been set, use callback * that uses the move ctor vs. using a ctor+move */ ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { illegal_check |= ECS_TYPE_HOOK_CTOR_ILLEGAL; /* If no explicit move_ctor has been set, use * combination of ctor + move + dtor */ ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; } } else { illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; /* If no dtor has been set, this is just a move ctor */ ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } else { /* If move is not set but move_ctor and dtor is, we can still set * ctor_move_dtor. */ if (h->move_ctor) { illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } } if(flags & illegal_check) { flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL; } } if (!h->move_dtor) { ecs_flags32_t illegal_check = 0; if (h->move) { illegal_check |= ECS_TYPE_HOOK_MOVE_ILLEGAL; if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; ti->hooks.move_dtor = flecs_default_move_w_dtor; } else { ti->hooks.move_dtor = flecs_default_move; } } else { if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; ti->hooks.move_dtor = flecs_default_dtor; } } if(flags & illegal_check) { flags |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; } } 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; if (ti->hooks.dtor) ti->hooks.flags |= ECS_TYPE_HOOK_DTOR; if (ti->hooks.move) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE; if (ti->hooks.move_ctor) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE_CTOR; if (ti->hooks.ctor_move_dtor) ti->hooks.flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR; 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; ti->hooks.flags |= prev_hooks.flags & ECS_TYPE_HOOK_IN_USE; 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; } if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) { ti->hooks.move_ctor = flecs_move_ctor_illegal; } if(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) { ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; } if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) { ti->hooks.move_dtor = flecs_move_ctor_illegal; } if (in_use && !flecs_type_hooks_storage_equal(&ti->hooks, &prev_hooks)) { ti->hooks = prev_hooks; ecs_throw(ECS_ALREADY_IN_USE, "illegal call to set_hooks() for " "component '%s': cannot change which hooks are set while the " "component is in use", flecs_errstr(ecs_get_path(world, component))); } if (component < FLECS_HI_COMPONENT_ID) { if (ti->hooks.on_set || ti->hooks.copy || ti->hooks.move || ti->hooks.on_replace) { world->non_trivial_set[component] = true; } } error: return; } static void flecs_type_info_fini( ecs_type_info_t *ti) { if (ti->hooks.ctx_free) { ti->hooks.ctx_free(ti->hooks.ctx); } if (ti->hooks.binding_ctx_free) { ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); } if (ti->hooks.lifecycle_ctx_free) { ti->hooks.lifecycle_ctx_free(ti->hooks.lifecycle_ctx); } if (ti->name) { /* Safe to cast away const, world has ownership over string */ ecs_os_free(ECS_CONST_CAST(char*, ti->name)); ti->name = NULL; } ti->size = 0; ti->alignment = 0; } void flecs_fini_type_info( ecs_world_t *world) { ecs_map_iter_t it = ecs_map_iter(&world->type_info); while (ecs_map_next(&it)) { ecs_type_info_t *ti = ecs_map_ptr(&it); flecs_type_info_fini(ti); ecs_os_free(ti); } ecs_map_fini(&world->type_info); } static const ecs_type_info_t* flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component) { flecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); return ecs_map_get_deref(&world->type_info, ecs_type_info_t, component); } ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component) { flecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = flecs_type_info_get(world, component); ecs_type_info_t *ti_mut = NULL; if (!ti) { ti_mut = ecs_map_ensure_alloc_t( &world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); ti_mut->component = component; } else { ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); } if (!ti_mut->name) { const char *sym = ecs_get_symbol(world, component); if (sym) { ti_mut->name = ecs_os_strdup(sym); } else { const char *name = ecs_get_name(world, component); if (name) { ti_mut->name = ecs_os_strdup(name); } } } return ti_mut; } bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li) { bool changed = false; flecs_entities_ensure(world, component); ecs_type_info_t *ti = NULL; if (!size || !alignment) { ecs_assert(size == 0 && alignment == 0, ECS_INVALID_COMPONENT_SIZE, NULL); ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); ecs_map_remove_free(&world->type_info, component); } else { ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); changed |= ti->size != size; changed |= ti->alignment != alignment; ti->size = size; ti->alignment = alignment; if (li) { ecs_set_hooks_id(world, component, li); } } /* Set type info for component record of component */ ecs_component_record_t *cr = flecs_components_get(world, component); if (cr) { changed |= flecs_component_set_type_info(world, cr, ti); } bool is_tag = ecs_has_id(world, component, EcsPairIsTag); /* All id records with component as relationship inherit type info */ cr = flecs_components_get(world, ecs_pair(component, EcsWildcard)); if (cr) { while ((cr = flecs_component_first_next(cr))) { if (is_tag) { changed |= flecs_component_set_type_info(world, cr, NULL); } else if (ti) { changed |= flecs_component_set_type_info(world, cr, ti); } else if ((cr->type_info != NULL) && (cr->type_info->component == component)) { changed |= flecs_component_set_type_info(world, cr, NULL); } } } /* All non-tag id records with component as target inherit type info, * if relationship doesn't have type info */ cr = flecs_components_get(world, ecs_pair(EcsWildcard, component)); if (cr) { while ((cr = flecs_component_second_next(cr))) { if (!(cr->flags & EcsIdPairIsTag) && !cr->type_info) { changed |= flecs_component_set_type_info(world, cr, ti); } } } return changed; } void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not * deleted before entities that use it. The remaining type info elements * will be deleted after the store is finalized. */ return; } ecs_type_info_t *ti = ecs_map_get_deref( &world->type_info, ecs_type_info_t, component); if (ti) { flecs_type_info_fini(ti); ecs_map_remove_free(&world->type_info, component); } } ecs_size_t flecs_type_size( ecs_world_t *world, ecs_entity_t type) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); return comp->size; } const ecs_type_hooks_t* ecs_get_hooks_id( const ecs_world_t *world, ecs_entity_t id) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return &ti->hooks; } return NULL; } const ecs_type_info_t* flecs_determine_type_info_for_component( const ecs_world_t *world, ecs_id_t id) { if (!ECS_IS_PAIR(id)) { if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_type_info_get(world, id); } } else { ecs_entity_t rel = ECS_PAIR_FIRST(id); rel = flecs_entities_get_alive(world, rel); ecs_assert(ecs_is_alive(world, rel), ECS_INTERNAL_ERROR, NULL); if (ecs_owns_id(world, rel, EcsPairIsTag)) { return NULL; } const ecs_type_info_t *ti = flecs_type_info_get(world, rel); if (ti) { return ti; } if (!ECS_IS_VALUE_PAIR(id)) { ecs_entity_t tgt = ecs_pair_second(world, id); if (tgt) { return flecs_type_info_get(world, tgt); } } } return NULL; } const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_component_record_t *cr = flecs_components_get(world, id); if (cr) { return cr->type_info; } else { return flecs_determine_type_info_for_component(world, id); } error: return NULL; } 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; if (!flecs_type_info_ctor(ptr, 1, ti)) { 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 '%s' is not a type", flecs_errstr(ecs_get_path(world, 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; flecs_type_info_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; flecs_type_info_copy(dst, src, 1, ti); 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; flecs_type_info_move(dst, src, 1, ti); 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; flecs_type_info_move_ctor(dst, src, 1, ti); 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_ctor_w_type_info(world, ti, dst, src); error: return -1; } #include /* 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); const ecs_id_t ECS_VALUE_PAIR = ((1ull << 60) | (1ull << 63)); /** 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; const ecs_entity_t ecs_id(EcsParent) = 4; const ecs_entity_t ecs_id(EcsTreeSpawner) = 5; const ecs_entity_t EcsParentDepth = 6; /* Poly target components */ const ecs_entity_t EcsQuery = FLECS_HI_COMPONENT_ID + 0; const ecs_entity_t EcsObserver = FLECS_HI_COMPONENT_ID + 1; const ecs_entity_t EcsSystem = FLECS_HI_COMPONENT_ID + 2; /* Core scopes & entities */ const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 3; const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 4; const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 5; const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 7; const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 9; const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 10; const ecs_entity_t EcsNotQueryable = FLECS_HI_COMPONENT_ID + 11; const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 12; const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 13; /* Marker entities for query encoding */ const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 14; const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 15; const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 17; /* Traits */ const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 19; const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 21; const ecs_entity_t EcsInheritable = FLECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsSingleton = FLECS_HI_COMPONENT_ID + 23; const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 24; const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 26; const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 29; const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 32; const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 35; const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 37; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 38; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 40; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 42; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 43; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 46; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 47; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 48; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 49; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 50; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 51; const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 52; const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 53; /* Actions */ const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 54; const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 55; const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 56; /* Storage */ const ecs_entity_t EcsSparse = FLECS_HI_COMPONENT_ID + 57; const ecs_entity_t EcsDontFragment = FLECS_HI_COMPONENT_ID + 58; /* Misc */ const ecs_entity_t ecs_id(EcsDefaultChildComponent) = FLECS_HI_COMPONENT_ID + 59; const ecs_entity_t EcsOrderedChildren = FLECS_HI_COMPONENT_ID + 60; /* Builtin predicate ids (used by query engine) */ const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 61; const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 62; const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 63; const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 64; const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 65; /* Systems */ const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 67; const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 68; const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 69; const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 70; const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 71; const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 72; const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 73; const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 74; const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 75; const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 76; const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 77; const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 78; const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 79; const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 80; /* Meta primitive components (don't use low ids to save id space) */ const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 114; /* Doc module components */ /* REST module components */ /* Max static id: * #define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) */ /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; /* Declarations for addons. Located in world.c to avoid issues during linking of * a static library */ ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world) { ecs_assert(flecs_poly_is(world, ecs_world_t) || flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (flecs_poly_is(world, ecs_world_t)) { return ECS_CONST_CAST(ecs_stage_t*, world->stages[0]); } else if (flecs_poly_is(world, ecs_stage_t)) { return ECS_CONST_CAST(ecs_stage_t*, world); } return NULL; } ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr) { ecs_world_t *world = *world_ptr; ecs_assert(flecs_poly_is(world, ecs_world_t) || flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (flecs_poly_is(world, ecs_world_t)) { return world->stages[0]; } *world_ptr = ((ecs_stage_t*)world)->world; return ECS_CONST_CAST(ecs_stage_t*, world); } /* Evaluate component monitor. If a monitored entity changed, it will have set a * flag in one of the world's component monitors. Queries can register * themselves with component monitors to determine whether they need to rematch * with tables. */ static void flecs_eval_component_monitor( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); if (!world->monitors.is_dirty) { return; } world->info.eval_comp_monitors_total ++; ecs_os_perf_trace_push("flecs.component_monitor.eval"); world->monitors.is_dirty = false; ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); while (ecs_map_next(&it)) { ecs_monitor_t *m = ecs_map_ptr(&it); if (!m->is_dirty) { continue; } m->is_dirty = false; int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **elems = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { ecs_query_t *q = elems[i]; flecs_poly_assert(q, ecs_query_t); flecs_query_rematch(world, q); } } ecs_os_perf_trace_pop("flecs.component_monitor.eval"); } static void flecs_monitor_mark_dirty( ecs_world_t *world, ecs_entity_t id) { ecs_map_t *monitors = &world->monitors.monitors; /* Only flag if there are actually monitors registered, so that we * don't waste cycles evaluating monitors if there's no interest */ if (ecs_map_is_init(monitors)) { ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (m) { if (!world->monitors.is_dirty) { world->monitor_generation ++; } m->is_dirty = true; world->monitors.is_dirty = true; } } } void flecs_monitor_register( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(query, ecs_query_t); ecs_map_t *monitors = &world->monitors.monitors; ecs_map_init_if(monitors, &world->allocator); ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); ecs_vec_init_if_t(&m->queries, ecs_query_t*); ecs_query_t **q = ecs_vec_append_t( &world->allocator, &m->queries, ecs_query_t*); *q = query; } void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(query, ecs_query_t); ecs_map_t *monitors = &world->monitors.monitors; if (!ecs_map_is_init(monitors)) { return; } ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (!m) { return; } int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **queries = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { if (queries[i] == query) { ecs_vec_remove_t(&m->queries, ecs_query_t*, i); count --; break; } } if (!count) { ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); ecs_map_remove_free(monitors, id); } if (!ecs_map_count(monitors)) { ecs_map_fini(monitors); } } /* Updating component monitors is a relatively expensive operation that only * happens for entities that are monitored. The approach balances the amount of * processing between the operation on the entity vs the amount of work that * needs to be done to rematch queries, as a simple brute force approach does * not scale when there are many tables / queries. Therefore we need to do a bit * of bookkeeping that is more intelligent than simply flipping a flag */ static void flecs_update_component_monitor_w_array( ecs_world_t *world, ecs_type_t *ids) { if (!ids) { return; } int i; for (i = 0; i < ids->count; i ++) { ecs_entity_t id = ids->array[i]; if (ECS_HAS_ID_FLAG(id, PAIR)) { flecs_monitor_mark_dirty(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); } flecs_monitor_mark_dirty(world, id); } } void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed) { flecs_update_component_monitor_w_array(world, added); flecs_update_component_monitor_w_array(world, removed); } static void flecs_init_store( ecs_world_t *world) { ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); ecs_vec_init_t(a, &world->store.deleted_components, ecs_entity_t, 0); /* Initialize entity index */ flecs_entities_init(world); /* Initialize table sparse set */ flecs_sparse_init_t(&world->store.tables, a, &world->allocators.sparse_chunk, ecs_table_t); /* Initialize table map */ flecs_table_hashmap_init(world, &world->store.table_map); } static void flecs_clean_tables( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->store.tables); for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_fini(world, t); } /* Free table types separately so that if application destructors rely on * a type, it's still valid. */ for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free_type(world, t); } /* Clear the root table */ if (count) { flecs_table_reset(world, &world->store.root); } } static void flecs_fini_root_tables( ecs_world_t *world, ecs_component_record_t *cr, bool fini_targets) { ecs_stage_t *stage0 = world->stages[0]; bool finished = false; const ecs_size_t MAX_DEFERRED_DELETE_QUEUE_SIZE = 4096; while (!finished) { ecs_table_cache_iter_t it; ecs_size_t queue_size = 0; finished = true; bool has_roots = flecs_table_cache_iter(&cr->cache, &it); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableHasBuiltins|EcsTableHasModule)) { continue; /* Skip modules */ } int32_t i, count = ecs_table_count(table); const ecs_entity_t *entities = ecs_table_entities(table); if (fini_targets) { /* Only delete entities that are used as pair target. Iterate * backwards to minimize moving entities around in table. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { ecs_delete(world, entities[i]); queue_size++; /* Flush the queue before it grows too big: */ if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { finished = false; break; /* restart iteration */ } } } } else { /* Delete remaining entities that are not in use (added to another * entity). This limits table moves during cleanup and delays * cleanup of tags. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); if (!(ECS_RECORD_TO_ROW_FLAGS(r->row) & ~EcsEntityHasDontFragment)) { ecs_delete(world, entities[i]); queue_size++; /* Flush the queue before it grows too big: */ if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { finished = false; break; /* restart iteration */ } } } } if(!finished) { /* flush queue and restart iteration */ flecs_defer_end(world, stage0); flecs_defer_begin(world, stage0); break; } } } } static void flecs_fini_roots( ecs_world_t *world) { ecs_component_record_t *cr = 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 they got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, world->stages[0]); flecs_fini_root_tables(world, cr, true); flecs_defer_end(world, world->stages[0]); flecs_defer_begin(world, world->stages[0]); flecs_fini_root_tables(world, cr, false); flecs_defer_end(world, world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_fini(world, &world->store.root); flecs_entities_clear(world); flecs_hashmap_fini(&world->store.table_map); ecs_assert(ecs_vec_count(&world->store.marked_ids) == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&world->store.deleted_components) == 0, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); ecs_vec_fini_t(a, &world->store.deleted_components, ecs_entity_t); } static void flecs_world_allocators_init( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; flecs_allocator_init(&world->allocator); 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->component_record, ecs_component_record_t); flecs_ballocator_init_t(&a->pair_record, ecs_pair_record_t); 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_table_diff_builder_init(world, &world->allocators.diff_builder); ecs_vec_init_t(&world->allocator, &world->allocators.tree_spawner, ecs_entity_t, 0); } static void flecs_world_allocators_fini( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->component_record); flecs_ballocator_fini(&a->pair_record); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); ecs_vec_fini_t( &world->allocator, &world->allocators.tree_spawner, ecs_entity_t); flecs_allocator_fini(&world->allocator); } #define ECS_STRINGIFY_INNER(x) #x #define ECS_STRINGIFY(x) ECS_STRINGIFY_INNER(x) static const char flecs_compiler_info[] #if defined(__clang__) = "clang " __clang_version__; #elif defined(__GNUC__) = "gcc " ECS_STRINGIFY(__GNUC__) "." ECS_STRINGIFY(__GNUC_MINOR__); #elif defined(_MSC_VER) = "msvc " ECS_STRINGIFY(_MSC_VER); #elif defined(__TINYC__) = "tcc " ECS_STRINGIFY(__TINYC__); #else = "unknown compiler"; #endif static const char *flecs_addons_info[] = { #ifdef FLECS_OS_API_IMPL "FLECS_OS_API_IMPL", #endif NULL }; static const char *flecs_compiler_flags[] = { #ifdef FLECS_DEBUG "FLECS_DEBUG", #endif #ifdef FLECS_NDEBUG "FLECS_NDEBUG", #endif #ifdef FLECS_SANITIZE "FLECS_SANITIZE", #endif #ifdef FLECS_CONFIG_HEADER "FLECS_CONFIG_HEADER", #endif #ifdef FLECS_ACCURATE_COUNTERS "FLECS_ACCURATE_COUNTERS", #endif #ifdef FLECS_DISABLE_COUNTERS "FLECS_DISABLE_COUNTERS", #endif #ifdef FLECS_DEBUG_INFO "FLECS_DEBUG_INFO", #endif #ifdef FLECS_DEFAULT_TO_UNCACHED_QUERIES "FLECS_DEFAULT_TO_UNCACHED_QUERIES", #endif #ifdef FLECS_SOFT_ASSERT "FLECS_SOFT_ASSERT", #endif #ifdef FLECS_KEEP_ASSERT "FLECS_KEEP_ASSERT", #endif #ifdef FLECS_CPP_NO_AUTO_REGISTRATION "FLECS_CPP_NO_AUTO_REGISTRATION", #endif #ifdef FLECS_CPP_NO_ENUM_REFLECTION "FLECS_CPP_NO_ENUM_REFLECTION", #endif #ifdef FLECS_NO_ALWAYS_INLINE "FLECS_NO_ALWAYS_INLINE", #endif #ifdef FLECS_CUSTOM_BUILD "FLECS_CUSTOM_BUILD", #endif #ifdef FLECS_LOW_FOOTPRINT "FLECS_LOW_FOOTPRINT", #endif #ifdef FLECS_USE_OS_ALLOC "FLECS_USE_OS_ALLOC", #endif #ifdef FLECS_HI_COMPONENT_ID "FLECS_HI_COMPONENT_ID=" ECS_STRINGIFY(FLECS_HI_COMPONENT_ID), #endif #ifdef FLECS_HI_ID_RECORD_ID "FLECS_HI_ID_RECORD_ID=" ECS_STRINGIFY(FLECS_HI_ID_RECORD_ID), #endif #ifdef FLECS_ENTITY_PAGE_BITS "FLECS_ENTITY_PAGE_BITS=" ECS_STRINGIFY(FLECS_ENTITY_PAGE_BITS), #endif #ifdef FLECS_SPARSE_PAGE_BITS "FLECS_SPARSE_PAGE_BITS=" ECS_STRINGIFY(FLECS_SPARSE_PAGE_BITS), #endif #ifdef FLECS_ID_DESC_MAX "FLECS_ID_DESC_MAX=" ECS_STRINGIFY(FLECS_ID_DESC_MAX), #endif #ifdef FLECS_EVENT_DESC_MAX "FLECS_EVENT_DESC_MAX=" ECS_STRINGIFY(FLECS_EVENT_DESC_MAX), #endif #ifdef FLECS_VARIABLE_COUNT_MAX "FLECS_VARIABLE_COUNT_MAX=" ECS_STRINGIFY(FLECS_VARIABLE_COUNT_MAX), #endif #ifdef FLECS_TERM_COUNT_MAX "FLECS_TERM_COUNT_MAX=" ECS_STRINGIFY(FLECS_TERM_COUNT_MAX), #endif #ifdef FLECS_TERM_ARG_COUNT_MAX "FLECS_TERM_ARG_COUNT_MAX=" ECS_STRINGIFY(FLECS_TERM_ARG_COUNT_MAX), #endif #ifdef FLECS_QUERY_VARIABLE_COUNT_MAX "FLECS_QUERY_VARIABLE_COUNT_MAX=" ECS_STRINGIFY(FLECS_QUERY_VARIABLE_COUNT_MAX), #endif #ifdef FLECS_QUERY_SCOPE_NESTING_MAX "FLECS_QUERY_SCOPE_NESTING_MAX=" ECS_STRINGIFY(FLECS_QUERY_SCOPE_NESTING_MAX), #endif #ifdef FLECS_DAG_DEPTH_MAX "FLECS_DAG_DEPTH_MAX=" ECS_STRINGIFY(FLECS_DAG_DEPTH_MAX), #endif NULL }; static const ecs_build_info_t flecs_build_info = { .compiler = flecs_compiler_info, .addons = flecs_addons_info, .flags = flecs_compiler_flags, #ifdef FLECS_DEBUG .debug = true, #endif #ifdef FLECS_SANITIZE .sanitize = true, #endif .version = FLECS_VERSION, .version_major = FLECS_VERSION_MAJOR, .version_minor = FLECS_VERSION_MINOR, .version_patch = FLECS_VERSION_PATCH }; static void flecs_log_build_info(void) { const ecs_build_info_t *bi = ecs_get_build_info(); ecs_assert(bi != NULL, ECS_INTERNAL_ERROR, NULL); ecs_trace("flecs version %s", bi->version); ecs_trace("addons included in build:"); ecs_log_push(); const char **addon = bi->addons; do { ecs_trace("%s", addon[0]); } while ((++ addon)[0]); ecs_log_pop(); if (bi->sanitize) { ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); } else if (bi->debug) { ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for " "improved performance"); } else { ecs_trace("#[green]release#[reset] build"); } ecs_trace("compiled with %s", bi->compiler); } /* -- Public functions -- */ const ecs_build_info_t* ecs_get_build_info(void) { return &flecs_build_info; } const ecs_world_info_t* ecs_get_world_info( const ecs_world_t *world) { world = ecs_get_world(world); return &world->info; } ecs_world_t *ecs_mini(void) { #ifdef FLECS_OS_API_IMPL ecs_set_os_api_impl(); #endif ecs_os_init(); ecs_assert(ECS_ALIGNOF(ecs_query_triv_cache_match_t) == ECS_ALIGNOF(ecs_query_cache_match_t), ECS_INTERNAL_ERROR, NULL); ecs_assert(EcsQueryNothing < 256, ECS_INTERNAL_ERROR, NULL); ecs_trace("#[bold]bootstrapping world"); ecs_log_push(); ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); if (!ecs_os_has_heap()) { ecs_abort(ECS_MISSING_OS_API, NULL); } if (!ecs_os_has_threading()) { ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } if (!ecs_os_has_time()) { ecs_trace("time management not available"); } flecs_log_build_info(); ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); flecs_poly_init(world, ecs_world_t); if (ecs_os_has_time()) { ecs_time_t now; ecs_os_get_time(&now); world->info.creation_time = now.sec; } world->flags |= EcsWorldInit; flecs_world_allocators_init(world); ecs_allocator_t *a = &world->allocator; ecs_map_init(&world->type_info, a); #ifdef FLECS_DEBUG ecs_map_init(&world->locked_components, a); ecs_map_init(&world->locked_entities, a); #endif ecs_map_init(&world->id_index_hi, &world->allocator); world->id_index_lo = ecs_os_calloc_n( ecs_component_record_t*, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); ecs_vec_init_t(a, &world->component_ids, ecs_id_t, 0); world->info.time_scale = 1.0; if (ecs_os_has_time()) { ecs_os_get_time(&world->world_start_time); } ecs_map_init(&world->prefab_child_indices, a); ecs_set_stage_count(world, 1); ecs_default_lookup_path[0] = EcsFlecsCore; ecs_set_lookup_path(world, ecs_default_lookup_path); flecs_init_store(world); flecs_bootstrap(world); world->flags &= ~EcsWorldInit; #ifdef FLECS_LOW_FOOTPRINT ecs_shrink(world); #endif ecs_trace("world ready!"); ecs_log_pop(); return world; } ecs_world_t *ecs_init(void) { ecs_world_t *world = ecs_mini(); #ifdef FLECS_MODULE_H ecs_trace("#[bold]import addons"); ecs_log_push(); ecs_trace("use ecs_mini to create world without importing addons"); ecs_trace("addons imported!"); ecs_log_pop(); #endif #ifdef FLECS_LOW_FOOTPRINT ecs_shrink(world); #endif return world; } ecs_world_t* ecs_init_w_args( int argc, char *argv[]) { ecs_world_t *world = ecs_init(); (void)argc; (void)argv; return world; } void ecs_quit( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); world->flags |= EcsWorldQuit; error: return; } bool ecs_should_quit( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return true; } void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event) { flecs_poly_assert(world, ecs_world_t); /* If no id is specified, broadcast to all tables */ 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 ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_notify(world, table, id, event); } /* If id is specified, only broadcast to tables with id */ } else { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return; } ecs_table_cache_iter_t it; const ecs_table_record_t *tr; flecs_table_cache_all_iter(&cr->cache, &it); while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_table_notify(world, tr->hdr.table, id, event); } } } void ecs_atfini( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { flecs_poly_assert(world, ecs_world_t); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } void ecs_run_post_frame( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, "cannot register post frame action while frame is not in progress"); ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, &stage->post_frame_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } /* Unset data in tables */ static void flecs_fini_unset_tables( ecs_world_t *world) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_remove_actions(world, table); } } /* Invoke fini actions */ static void flecs_fini_actions( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->fini_actions); ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); } ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e) { if (ecs_is_alive(world, e)) { if (ecs_has_id(world, e, EcsOneOf)) { return e; } else { return ecs_get_target(world, e, EcsOneOf, 0); } } else { return 0; } } /* The destroyer of worlds */ int ecs_fini( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot fini world while it is in readonly mode"); ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot fini world when it is already being deleted"); ecs_assert(world->stages[0]->defer == 0, ECS_INVALID_OPERATION, "call defer_end before destroying world"); ecs_trace("#[bold]shutting down world"); ecs_log_push(); world->flags |= EcsWorldQuit; /* Tree spawners can keep tables alive, which can conflict with entity * cleanup. Cleanup treespawners first before cleaning up other entities. * This means that prefab spawning does not work during world cleanup. */ flecs_fini_tree_spawners(world); /* Delete root entities first using regular APIs. This ensures that cleanup * policies get a chance to execute. */ ecs_dbg_1("#[bold]cleanup root entities"); ecs_log_push_1(); flecs_fini_roots(world); ecs_log_pop_1(); world->flags |= EcsWorldFini; /* Run fini actions (simple callbacks run when world is deleted) before * destroying the storage */ ecs_dbg_1("#[bold]run fini actions"); ecs_log_push_1(); flecs_fini_actions(world); ecs_log_pop_1(); ecs_dbg_1("#[bold]cleanup remaining entities"); ecs_log_push_1(); /* Operations invoked during OnRemove/destructors are deferred and * will be discarded after world cleanup */ flecs_defer_begin(world, world->stages[0]); /* Run OnRemove actions for components while the store is still * unmodified by cleanup. */ flecs_fini_unset_tables(world); /* This will destroy all entities and components. */ flecs_fini_store(world); /* Purge deferred operations from the queue. This discards operations but * makes sure that any resources in the queue are freed */ flecs_defer_purge(world, world->stages[0]); ecs_log_pop_1(); /* All queries are cleaned up, so monitors should've been cleaned up too */ ecs_assert(!ecs_map_is_init(&world->monitors.monitors), ECS_INTERNAL_ERROR, NULL); /* Cleanup world ctx and binding_ctx */ if (world->ctx_free) { world->ctx_free(world->ctx); } if (world->binding_ctx_free) { world->binding_ctx_free(world->binding_ctx); } /* After this point no more user code is invoked */ ecs_dbg_1("#[bold]cleanup world data structures"); ecs_log_push_1(); flecs_observable_fini(&world->observable); flecs_entities_fini(world); flecs_components_fini(world); flecs_fini_type_info(world); #ifdef FLECS_DEBUG ecs_map_fini(&world->locked_components); ecs_map_fini(&world->locked_entities); #endif flecs_name_index_fini(&world->aliases); flecs_name_index_fini(&world->symbols); ecs_set_stage_count(world, 0); ecs_map_fini(&world->prefab_child_indices); ecs_vec_fini_t(&world->allocator, &world->component_ids, ecs_id_t); ecs_log_pop_1(); flecs_world_allocators_fini(world); /* End of the world */ flecs_poly_free(world, ecs_world_t); ecs_os_fini(); ecs_trace("world destroyed, bye!"); ecs_log_pop(); return 0; } bool ecs_is_fini( const ecs_world_t *world) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldFini); } void ecs_dim( ecs_world_t *world, int32_t entity_count) { flecs_poly_assert(world, ecs_world_t); flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); } void flecs_eval_component_monitors( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); flecs_eval_component_monitor(world); } void ecs_measure_frame_time( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); if (ECS_EQZERO(world->info.target_fps) || enable) { ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } error: return; } void ecs_measure_system_time( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); error: return; } void ecs_set_target_fps( ecs_world_t *world, ecs_ftime_t fps) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ecs_measure_frame_time(world, true); world->info.target_fps = fps; error: return; } void ecs_set_default_query_flags( ecs_world_t *world, ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); world->default_query_flags = flags; } void* ecs_get_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->ctx; error: return NULL; } void* ecs_get_binding_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->binding_ctx; error: return NULL; } void ecs_set_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { flecs_poly_assert(world, ecs_world_t); world->ctx = ctx; world->ctx_free = ctx_free; } void ecs_set_binding_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { flecs_poly_assert(world, ecs_world_t); world->binding_ctx = ctx; world->binding_ctx_free = ctx_free; } const ecs_entity_range_t* ecs_entity_range_new( ecs_world_t *world, uint32_t min, uint32_t max) { flecs_poly_assert(world, ecs_world_t); ecs_check(min > 0, ECS_INVALID_PARAMETER, "min must be > 0"); ecs_check(!max || max >= min, ECS_INVALID_PARAMETER, "max must be >= min or 0"); /* Validate no overlap with existing ranges */ ecs_entity_index_t *index = ecs_eis(world); ecs_check(flecs_entity_index_not_alive_count(index) == 0, ECS_INVALID_OPERATION, "cannot create entity range after entities have been deleted"); bool first_range = ecs_vec_count(&index->ranges) == 0; /* If this is the first range and it covers the default [1, ...] id space, * it must be large enough to contain all currently alive entities. */ if (first_range && min == 1) { ecs_check(!max || max >= index->max_id, ECS_INVALID_OPERATION, "range [%u, %u] cannot be below last alive entity id %u", min, max, (uint32_t)index->max_id); } /* If this is the first range to be created and it does not start at 1, * create a default range for the [1, min) id space and activate it, so * that the default id allocation behavior is preserved. */ if (first_range && min > 1) { ecs_entity_range_t *default_range = ECS_CONST_CAST(ecs_entity_range_t*, ecs_entity_range_new(world, 1, min - 1)); default_range->cur = index->max_id; flecs_entity_index_set_range(index, default_range); } ecs_allocator_t *a = &world->allocator; int32_t count = ecs_vec_count(&index->ranges); if (count > 0) { ecs_entity_range_t **ranges = ecs_vec_first_t(&index->ranges, ecs_entity_range_t*); int32_t i; for (i = 0; i < count; i ++) { ecs_entity_range_t *existing = ranges[i]; /* Two ranges overlap if one starts before the other ends */ bool overlap; if (!existing->max && !max) { overlap = true; } else if (!existing->max) { overlap = max >= existing->min; } else if (!max) { overlap = min <= existing->max; } else { overlap = min <= existing->max && max >= existing->min; } ecs_check(!overlap, ECS_INVALID_PARAMETER, "range [%u, %u] overlaps with existing range [%u, %u]", min, max, existing->min, existing->max); (void)overlap; } } ecs_entity_range_t *range = flecs_walloc_t(world, ecs_entity_range_t); range->min = min; range->max = max; range->cur = min - 1; ecs_vec_init_t(a, &range->recycled, uint64_t, 0); /* Insert into sorted ranges vec (sorted by min) */ ecs_vec_append_t(a, &index->ranges, ecs_entity_range_t*)[0] = range; if (count > 0) { ecs_entity_range_t **ranges = ecs_vec_first_t(&index->ranges, ecs_entity_range_t*); /* Find insertion point and shift elements */ int32_t i; for (i = count; i > 0; i --) { if (ranges[i - 1]->min <= min) { break; } ranges[i] = ranges[i - 1]; } ranges[i] = range; } /* If this is the first range and it covers the default [1, ...] id space, * activate it so the default id allocation behavior is preserved. */ if (first_range && min == 1) { range->cur = index->max_id; flecs_entity_index_set_range(index, range); } return range; error: return NULL; } void ecs_entity_range_set( ecs_world_t *world, const ecs_entity_range_t *range) { flecs_poly_assert(world, ecs_world_t); ecs_check(range != NULL, ECS_INVALID_PARAMETER, NULL); flecs_entity_index_set_range(ecs_eis(world), ECS_CONST_CAST(ecs_entity_range_t*, range)); error: return; } const ecs_entity_range_t* ecs_entity_range_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ecs_eis(world)->active_range; error: return NULL; } ecs_entity_t ecs_get_max_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_max_id(world); error: return 0; } void 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] ++; table->version ++; } uint32_t flecs_get_table_version_fast( 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]; } static int32_t flecs_component_ids_last_index = 0; int32_t flecs_component_ids_index_get(void) { if (ecs_os_api.ainc_) { return ecs_os_ainc(&flecs_component_ids_last_index); } else { return ++ flecs_component_ids_last_index; } } ecs_entity_t flecs_component_ids_get( const ecs_world_t *stage_world, int32_t index) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); if (index >= ecs_vec_count(&world->component_ids)) { return 0; } return ecs_vec_get_t( &world->component_ids, ecs_entity_t, index)[0]; } ecs_entity_t flecs_component_ids_get_alive( const ecs_world_t *stage_world, int32_t index) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); if (index >= ecs_vec_count(&world->component_ids)) { return 0; } ecs_entity_t result = ecs_vec_get_t( &world->component_ids, ecs_entity_t, index)[0]; if (!flecs_entities_is_alive(world, result)) { return 0; } return result; } void flecs_component_ids_set( ecs_world_t *stage_world, int32_t index, ecs_entity_t component) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); ecs_vec_set_min_count_zeromem_t( &world->allocator, &world->component_ids, ecs_entity_t, index + 1); ecs_vec_get_t(&world->component_ids, ecs_entity_t, index)[0] = component; } static void flecs_process_empty_queries( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); /* Make sure that we defer adding the inactive tags until after iterating * the query */ flecs_defer_begin(world, world->stages[0]); FLECS_EACH_QUERY(query, { if (!ecs_query_is_true(query)) { ecs_add_id(world, query->entity, EcsEmpty); } }) flecs_defer_end(world, world->stages[0]); } void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); } if (!flags || (flags & EcsAperiodicComponentMonitors)) { flecs_eval_component_monitors(world); } } int32_t ecs_delete_empty_tables( ecs_world_t *world, const ecs_delete_empty_tables_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_os_perf_trace_push("flecs.delete_empty_tables"); ecs_time_t start = {0}, cur = {0}; bool time_budget = false; int32_t measure_budget_after = 100; int32_t result = 0; uint16_t clear_generation = desc->clear_generation; uint16_t delete_generation = desc->delete_generation; double time_budget_seconds = desc->time_budget_seconds; int32_t offset = desc->offset; if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } if (ECS_NEQZERO(time_budget_seconds)) { time_budget = true; } int32_t count = flecs_sparse_count(&world->store.tables); if (!count) { goto done; } if (offset >= count || offset < 0) { offset = 0; } int32_t remaining = count; int32_t i = offset; while (remaining > 0) { count = flecs_sparse_count(&world->store.tables); if (!count) { break; } if (i >= count) { i = 0; } ecs_table_t *table = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); if (table->keep) { i ++; remaining --; continue; } measure_budget_after --; if (time_budget && !measure_budget_after) { cur = start; if (ecs_time_measure(&cur) > time_budget_seconds) { count = flecs_sparse_count(&world->store.tables); result = i + 1; if (result >= count) { result = 0; } goto done; } measure_budget_after = 100; } if (!table->id || ecs_table_count(table) != 0) { i ++; remaining --; continue; } uint16_t gen = ++ table->_->generation; if (delete_generation && (gen > delete_generation)) { flecs_table_fini(world, table); measure_budget_after = 1; remaining --; continue; } else if (clear_generation && (gen > clear_generation)) { flecs_table_shrink(world, table); measure_budget_after = 1; } i ++; remaining --; } done: ecs_os_perf_trace_pop("flecs.delete_empty_tables"); return result; } ecs_entities_t ecs_get_entities( const ecs_world_t *world) { ecs_entities_t result; result.ids = flecs_entities_ids(world); result.count = flecs_entities_size(world); result.alive_count = flecs_entities_count(world); return result; } ecs_flags32_t ecs_world_get_flags( const ecs_world_t *world) { if (flecs_poly_is(world, ecs_world_t)) { return world->flags; } else { flecs_poly_assert(world, ecs_stage_t); const ecs_stage_t *stage = (const ecs_stage_t*)world; return stage->world->flags; } } static bool flecs_component_record_in_use( const ecs_component_record_t *cr) { if (cr->flags & EcsIdDontFragment) { return flecs_sparse_count(cr->sparse) != 0; } else if (cr->flags & EcsIdOrderedChildren) { return ecs_vec_count(&cr->pair->ordered_children) != 0; } else { return cr->cache.tables.count != 0; } } void ecs_shrink( ecs_world_t *world) { /* This can invalidate ecs_record_t pointers for entities that are no longer * alive. If you're sure an application doesn't store any ecs_record_t ptrs * or ecs_ref_t's for not-alive entities, you can uncomment this line. */ // 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 if (!table->keep) { flecs_table_fini(world, table); } } flecs_table_shrink(world, &world->store.root); ecs_map_reclaim(&world->store.table_map.impl); flecs_sparse_shrink(&world->store.tables); ecs_vec_t cr_to_release; ecs_vec_init_t( &world->allocator, &cr_to_release, ecs_component_record_t*, 0); FLECS_EACH_COMPONENT_RECORD(cr, { if (flecs_component_record_in_use(cr)) { flecs_component_shrink(cr); } else { if (!ecs_id_is_wildcard(cr->id)) { ecs_vec_append_t( &world->allocator, &cr_to_release, ecs_component_record_t*)[0] = cr; } } }) count = ecs_vec_count(&cr_to_release); for (i = 0; i < count; i ++) { ecs_component_record_t *cr = ecs_vec_get_t( &cr_to_release, ecs_component_record_t*, i)[0]; flecs_component_release(world, cr); } ecs_vec_fini_t(&world->allocator, &cr_to_release, ecs_component_record_t*); FLECS_EACH_QUERY(query, { flecs_query_reclaim(query); }) ecs_map_reclaim(&world->id_index_hi); ecs_map_reclaim(&world->type_info); for (i = 0; i < world->stage_count; i ++) { ecs_stage_shrink(world->stages[i]); } } void ecs_exclusive_access_begin( ecs_world_t *world, const char *thread_name) { flecs_poly_assert(world, ecs_world_t); /* If world was locked, one thread can get exclusive access */ if (world->exclusive_access == UINT64_MAX) { world->exclusive_access = 0; } ecs_assert(!world->exclusive_access, ECS_INVALID_OPERATION, "cannot begin exclusive access: world already in exclusive mode " "by thread %" PRIu64 " (%s)", world->exclusive_access, world->exclusive_thread_name ? world->exclusive_thread_name : "no thread name"); world->exclusive_access = ecs_os_thread_self(); world->exclusive_thread_name = thread_name; } void ecs_exclusive_access_end( ecs_world_t *world, bool lock_world) { flecs_poly_assert(world, ecs_world_t); /* If the world is locked (not exclusively accessed by a specific thread) * this allows for unlocking the world without first calling access_begin */ if (world->exclusive_access == UINT64_MAX) { world->exclusive_access = 0; return; } ecs_assert(world->exclusive_access != 0, ECS_INVALID_OPERATION, "cannot end exclusive access: world is not in exclusive mode"); ecs_os_thread_id_t thr_self = ecs_os_thread_self(); (void)thr_self; ecs_assert(world->exclusive_access == thr_self, ECS_INVALID_OPERATION, "cannot end exclusive access from thread that does not have exclusive access"); if (!lock_world) { world->exclusive_access = 0; } else { /* Prevent any mutations on the world */ world->exclusive_access = UINT64_MAX; world->exclusive_thread_name = "locked world"; } } ecs_entity_t ecs_set_with( ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_id_t prev = stage->with; stage->with = id; return prev; error: return 0; } ecs_id_t ecs_get_with( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->with; error: return 0; } #ifdef FLECS_DEBUG static void flecs_component_lock_inc( ecs_map_t *locked_map, ecs_id_t component) { if (!component) { return; } int32_t *rc = (int32_t*)ecs_map_ensure(locked_map, component); if (ecs_os_has_threading()) { ecs_os_ainc(rc); } else { rc[0] ++; } } static void flecs_component_lock_dec( ecs_world_t *world, ecs_map_t *locked_map, ecs_id_t component) { if (!component) { return; } int32_t *rc = (int32_t*)ecs_map_get(locked_map, component); ecs_assert(rc != NULL, ECS_INTERNAL_ERROR, "'%s' is unlocked more times than it was locked", flecs_errstr(ecs_id_str(world, component))); if (ecs_os_has_threading()) { ecs_os_adec(rc); } else { rc[0] --; } ecs_assert(rc[0] >= 0, ECS_INTERNAL_ERROR, "'%s' is unlocked more times than it was locked", flecs_errstr(ecs_id_str(world, component))); if (!rc[0]) { ecs_map_remove(locked_map, component); } } void flecs_component_lock( ecs_world_t *world, ecs_id_t component) { flecs_component_lock_inc(&world->locked_components, component); if (ECS_IS_PAIR(component)) { flecs_component_lock_inc(&world->locked_components, ECS_PAIR_FIRST(component)); flecs_component_lock_inc(&world->locked_entities, ECS_PAIR_SECOND(component)); } } void flecs_component_unlock( ecs_world_t *world, ecs_id_t component) { flecs_component_lock_dec(world, &world->locked_components, component); if (ECS_IS_PAIR(component)) { flecs_component_lock_dec(world, &world->locked_components, ECS_PAIR_FIRST(component)); flecs_component_lock_dec(world, &world->locked_entities, ECS_PAIR_SECOND(component)); } } bool flecs_component_is_trait_locked( ecs_world_t *world, ecs_id_t component) { return ecs_map_get(&world->locked_components, component) != NULL; } bool flecs_component_is_delete_locked( ecs_world_t *world, ecs_id_t component) { return (ecs_map_get(&world->locked_components, component) != NULL) || (ecs_map_get(&world->locked_entities, component) != NULL); } #endif /* Utilities for C++ API */ /* Empty bodies for when logging is disabled */ void ecs_log_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_parser_warning_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_warningv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_abort_( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } void ecs_assert_log_( int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; } void ecs_log_start_capture(bool try) { (void)try; } char* ecs_log_stop_capture(void) { return NULL; } void flecs_log_get_captured_error_pos( int32_t *line, int32_t *column) { if (line) { *line = 0; } if (column) { *column = 0; } } int ecs_log_get_level(void) { return ecs_os_api.log_level_; } int ecs_log_set_level( int level) { int prev = ecs_os_api.log_level_; ecs_os_api.log_level_ = level; return prev; } bool ecs_log_enable_colors( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); return prev; } bool ecs_log_enable_timestamp( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); return prev; } bool ecs_log_enable_timedelta( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); return prev; } int ecs_log_last_error(void) { int result = ecs_os_api.log_last_error_; ecs_os_api.log_last_error_ = 0; return result; } #ifndef FLECS_QUERY_DSL_H #define FLECS_QUERY_DSL_H 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); const char* flecs_term_parse( ecs_world_t *world, const char *name, const char *expr, char *token_buffer, ecs_term_t *term); const char* flecs_id_parse( const ecs_world_t *world, const char *name, const char *expr, ecs_id_t *id); #endif #ifndef FLECS_PIPELINE_PRIVATE_H #define FLECS_PIPELINE_PRIVATE_H /** Instruction data for pipeline. * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { int32_t offset; /* Offset in systems vector */ int32_t count; /* Number of systems to run before next op */ double time_spent; /* Time spent merging commands for sync point */ int64_t commands_enqueued; /* Number of commands enqueued for sync point */ bool multi_threaded; /* Whether systems can be run multi-threaded */ bool immediate; /* Whether systems run in immediate mode */ } ecs_pipeline_op_t; struct ecs_pipeline_state_t { ecs_query_t *query; /* Pipeline query */ ecs_vec_t ops; /* Pipeline schedule */ ecs_vec_t systems; /* Vector with system ids */ ecs_entity_t last_system; /* Last system run by pipeline */ int32_t match_count; /* Used to track if rebuild is necessary */ int32_t rebuild_count; /* Number of pipeline rebuilds */ ecs_iter_t *iters; /* Iterator for worker(s) */ int32_t iter_count; /* Members for continuing pipeline iteration after pipeline rebuild */ ecs_pipeline_op_t *cur_op; /* Current pipeline op */ int32_t cur_i; /* Index in current result */ int32_t ran_since_merge; /* Index in current op */ bool immediate; /* Is pipeline in immediate mode */ }; typedef struct EcsPipeline { /* Stable ptr so threads can safely access while entities/components move */ ecs_pipeline_state_t *state; } EcsPipeline; //////////////////////////////////////////////////////////////////////////////// //// Pipeline API //////////////////////////////////////////////////////////////////////////////// void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); void flecs_create_worker_threads( ecs_world_t *world); void flecs_join_worker_threads( ecs_world_t *world); void flecs_signal_workers( ecs_world_t *world); void flecs_wait_for_sync( ecs_world_t *world); #endif #ifndef FLECS_JSON_PRIVATE_H #define FLECS_JSON_PRIVATE_H #endif /* FLECS_JSON_PRIVATE_H */ #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #endif #ifndef FLECS_USE_OS_ALLOC 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; } #endif void flecs_allocator_init( ecs_allocator_t *a) { (void)a; #ifndef FLECS_USE_OS_ALLOC 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); #endif } void flecs_allocator_fini( ecs_allocator_t *a) { (void)a; #ifndef FLECS_USE_OS_ALLOC 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); #endif } ecs_block_allocator_t* flecs_allocator_get( ecs_allocator_t *a, ecs_size_t size) { #ifndef FLECS_USE_OS_ALLOC 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_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; #else (void)a; (void)size; ecs_err("invalid call to flecs_allocator_get in FLECS_USE_OS_ALLOC build"); return NULL; #endif } char* flecs_strdup( ecs_allocator_t *a, const char* str) { #ifndef FLECS_USE_OS_ALLOC 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; #else (void)a; return ecs_os_strdup(str); #endif } void flecs_strfree( ecs_allocator_t *a, char* str) { #ifndef FLECS_USE_OS_ALLOC ecs_size_t len = ecs_os_strlen(str); flecs_free_n(a, char, len + 1, str); #else (void)a; ecs_os_free(str); #endif } void* flecs_dup( ecs_allocator_t *a, ecs_size_t size, const void *src) { if (!size) { return NULL; } #ifndef FLECS_USE_OS_ALLOC ecs_block_allocator_t *ba = flecs_allocator_get(a, size); void *dst = flecs_balloc(ba); ecs_os_memcpy(dst, src, size); return dst; #else (void)a; return ecs_os_memdup(src, size); #endif } #ifdef FLECS_USE_OS_ALLOC void* flecs_alloc( ecs_allocator_t *a, ecs_size_t size) { (void)a; return ecs_os_malloc(size); } void* flecs_calloc( ecs_allocator_t *a, ecs_size_t size) { (void)a; return ecs_os_calloc(size); } void* flecs_realloc( ecs_allocator_t *a, ecs_size_t dst_size, ecs_size_t src_size, void *ptr) { (void)a; (void)src_size; return ecs_os_realloc(ptr, dst_size); } void flecs_free( ecs_allocator_t *a, ecs_size_t size, void *ptr) { (void)a; (void)size; ecs_os_free(ptr); } #endif static void flecs_bitset_ensure_size( 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; flecs_bitset_ensure_size(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; flecs_bitset_ensure_size(bs, elem); } void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value) { ecs_check(elem >= 0, ECS_INVALID_PARAMETER, NULL); 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 >= 0, ECS_INVALID_PARAMETER, NULL); 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 >= 0, ECS_INVALID_PARAMETER, NULL); 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 >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(elem_b >= 0, ECS_INVALID_PARAMETER, NULL); 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; } // #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; #ifndef FLECS_USE_OS_ALLOC #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) * 2; /* 16 byte aligned */ #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; #endif } 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); (void)ba; #ifndef FLECS_USE_OS_ALLOC #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; #endif } 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) * 2); #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) * 2); 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->data_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 #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_); } 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; } static ecs_hm_bucket_t* flecs_hm_bucket_new( ecs_hashmap_t *map) { if (map->impl.allocator) { return flecs_calloc_t(map->impl.allocator, ecs_hm_bucket_t); } else { return ecs_os_calloc_t(ecs_hm_bucket_t); } } static void flecs_hm_bucket_free( ecs_hashmap_t *map, ecs_hm_bucket_t *bucket) { if (map->impl.allocator) { flecs_free_t(map->impl.allocator, ecs_hm_bucket_t, bucket); } else { ecs_os_free(bucket); } } 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; 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); flecs_hm_bucket_free(map, bucket); } 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_hm_bucket_new(dst); 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_hm_bucket_new(map); } 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_hm_bucket_free(map, 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)) { if (!ecs_map_next(&it->it)) { it->bucket = NULL; return NULL; } bucket = it->bucket = ecs_map_ptr(&it->it); 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); } /* The ratio used to determine whether the map should 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; } 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_init( ecs_map_t *result, ecs_allocator_t *allocator) { ecs_os_zeromem(result); result->allocator = allocator; flecs_map_rehash(result, 0); } 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; #ifdef FLECS_DEBUG ecs_os_linc(&map->change_count); #endif } 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; #ifdef FLECS_DEBUG ecs_os_linc(&map->change_count); #endif 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) { #ifdef FLECS_DEBUG if (map->last_iterated != key) { ecs_os_linc(&map->change_count); } #endif return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); } void ecs_map_reclaim( ecs_map_t *map) { int32_t tgt_bucket_count = flecs_map_get_bucket_count(map->count - 1); if (tgt_bucket_count != map->bucket_count) { flecs_map_rehash(map, tgt_bucket_count); } } 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); #ifdef FLECS_DEBUG ecs_os_linc(&map->change_count); #endif } 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, #ifdef FLECS_DEBUG .change_count = map->change_count #endif }; } else { return (ecs_map_iter_t){ 0 }; } } bool ecs_map_iter_valid( ecs_map_iter_t *iter) { const ecs_map_t *map = iter->map; if (!map) { return false; } #ifdef FLECS_DEBUG if (map->change_count != iter->change_count) { return false; } #endif return true; } bool ecs_map_next( ecs_map_iter_t *iter) { const ecs_map_t *map = iter->map; if (!map) { return false; } ecs_dbg_assert(map->change_count == iter->change_count, ECS_INVALID_PARAMETER, "map cannot be modified while it is being iterated"); ecs_bucket_t *end; if (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; #ifdef FLECS_DEBUG /* Safe, only used for detecting if an element got removed that's not the * currently iterated element. */ ECS_CONST_CAST(ecs_map_t*, map)->last_iterated = entry->key; #endif return true; } void ecs_map_copy( ecs_map_t *dst, const ecs_map_t *src) { if (ecs_map_is_init(dst)) { ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); ecs_map_fini(dst); } if (!ecs_map_is_init(src)) { return; } ecs_map_init(dst, src->allocator); ecs_map_iter_t it = ecs_map_iter(src); while (ecs_map_next(&it)) { ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); } } static uint64_t flecs_name_index_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } static int flecs_name_index_compare( const void *ptr1, const void *ptr2) { const ecs_hashed_string_t *str1 = ptr1; const ecs_hashed_string_t *str2 = ptr2; ecs_size_t len1 = str1->length; ecs_size_t len2 = str2->length; if (len1 != len2) { return (len1 > len2) - (len1 < len2); } return ecs_os_memcmp(str1->value, str2->value, len1); } void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { flecs_hashmap_init_(hm, ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), flecs_name_index_hash, flecs_name_index_compare, allocator); } void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { if (!hm->compare) { flecs_name_index_init(hm, allocator); } } bool flecs_name_index_is_init( const ecs_hashmap_t *hm) { return hm->compare != NULL; } ecs_hashmap_t* flecs_name_index_new( ecs_allocator_t *allocator) { ecs_hashmap_t *result = flecs_alloc_t(allocator, ecs_hashmap_t); flecs_name_index_init(result, allocator); return result; } void flecs_name_index_fini( ecs_hashmap_t *map) { flecs_hashmap_fini(map); } void flecs_name_index_free( ecs_hashmap_t *map) { if (map) { ecs_allocator_t *a = map->impl.allocator; flecs_name_index_fini(map); flecs_free_t(a, ecs_hashmap_t, map); } } ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *map) { ecs_hashmap_t *result = flecs_alloc_t(map->impl.allocator, ecs_hashmap_t); flecs_hashmap_copy(result, map); return result; } static ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash) { if (!length) { length = ecs_os_strlen(name); } else { ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); } if (!hash) { hash = flecs_hash(name, length); } else { ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); } return (ecs_hashed_string_t) { .value = ECS_CONST_CAST(char*, name), .length = length, .hash = hash }; } const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); if (!b) { return NULL; } ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); int32_t i, count = ecs_vec_count(&b->keys); for (i = 0; i < count; i ++) { ecs_hashed_string_t *key = &keys[i]; ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); if (hs.length != key->length) { continue; } if (!ecs_os_strcmp(name, key->value)) { uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); return e; } } return NULL; } uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); if (id) { return id[0]; } return 0; } void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t e, uint64_t hash) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { flecs_hm_bucket_remove(map, b, hash, i); break; } } } bool flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return 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 true; } } return false; } void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash) { ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); flecs_hashmap_result_t hmr = flecs_hashmap_ensure( map, &key, uint64_t); *((uint64_t*)hmr.value) = id; ((ecs_hashed_string_t*)hmr.key)->value = ECS_CONST_CAST(char*, name); error: return; } /* Utility to get a pointer to the payload */ #define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) static ecs_sparse_page_t* flecs_sparse_page_new( ecs_sparse_t *sparse, int32_t page_index) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; int32_t count = ecs_vec_count(&sparse->pages); ecs_sparse_page_t *pages; 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 { pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); } ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); 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); /* Initialize sparse array with zeros, as zero is used to indicate that the * sparse element has not been paired with a dense element. Using zero * as the sentinel means we can take advantage of calloc, which can have * better performance than malloc + memset. */ result->sparse = ca ? flecs_bcalloc(ca) : ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); /* Initialize the data array with zeros 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. */ if (sparse->size) { result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); } else { result->data = NULL; } ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); return result; } static void flecs_sparse_page_free( ecs_sparse_t *sparse, ecs_sparse_page_t *page) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; if (ca) { flecs_bfree(ca, page->sparse); } else { ecs_os_free(page->sparse); } if (a) { flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); } else { ecs_os_free(page->data); } page->sparse = NULL; page->data = NULL; } 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; } return ecs_vec_get_t(&sparse->pages, ecs_sparse_page_t, page_index); } 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(!sparse->size || page->data != NULL, ECS_INTERNAL_ERROR, NULL); return page; } return flecs_sparse_page_new(sparse, page_index); } static void flecs_sparse_grow_dense( ecs_sparse_t *sparse) { ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } static void flecs_sparse_assign_index( ecs_sparse_page_t * page, uint64_t * dense_array, uint64_t id, int32_t dense) { /* Initialize sparse-dense pair. This assigns the dense index to the sparse * array, and the sparse index to the dense array. */ page->sparse[FLECS_SPARSE_OFFSET(id)] = dense; dense_array[dense] = id; } static uint64_t flecs_sparse_inc_id( ecs_sparse_t *sparse) { /* Generate a new id. The last issued id could be stored in an external * variable, such as is the case with the last issued entity id, which is * stored on the world. */ return ++ sparse->max_id; } static uint64_t flecs_sparse_get_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); return sparse->max_id; } static void flecs_sparse_set_id( ecs_sparse_t *sparse, uint64_t value) { /* Sometimes the max id needs to be assigned directly, which typically * happens when the API calls get_or_create for an id that hasn't been * issued before. */ sparse->max_id = value; } /* Pair dense id with new sparse id */ static uint64_t flecs_sparse_create_id( ecs_sparse_t *sparse, int32_t dense) { uint64_t id = 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(id)); ecs_assert(page->sparse[FLECS_SPARSE_OFFSET(id)] == 0, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, id, dense); return id; } /* Create new id */ static uint64_t flecs_sparse_new_index( ecs_sparse_t *sparse) { int32_t dense_count = ecs_vec_count(&sparse->dense); int32_t count = sparse->count ++; ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); if (count < dense_count) { /* If there are unused elements in the dense array, return the first */ uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[count]; } else { return flecs_sparse_create_id(sparse, count); } } /* Get value from sparse set when it is guaranteed that the value exists. This * function is used when values are obtained using a dense index */ static void* flecs_sparse_get_sparse( const ecs_sparse_t *sparse, int32_t dense, uint64_t id) { uint64_t index = (uint32_t)id; 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); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); (void)dense; return DATA(page->data, sparse->size, offset); } /* Swap dense elements. A swap occurs when an element is removed, or when a * removed element is recycled. */ static void flecs_sparse_swap_dense( ecs_sparse_t * sparse, ecs_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 id_a = dense_array[a]; uint64_t id_b = dense_array[b]; ecs_sparse_page_t *page_b = flecs_sparse_get_or_create_page( sparse, FLECS_SPARSE_PAGE(id_b)); flecs_sparse_assign_index(page_a, dense_array, id_a, b); flecs_sparse_assign_index(page_b, dense_array, id_b, a); } void flecs_sparse_init( ecs_sparse_t *result, struct ecs_allocator_t *allocator, ecs_block_allocator_t *page_allocator, ecs_size_t size) { ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); result->size = size; result->max_id = UINT64_MAX; result->allocator = allocator; result->page_allocator = page_allocator; ecs_vec_init_t(allocator, &result->pages, ecs_sparse_page_t, 0); ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); result->dense.count = 1; /* Consume first value in dense array as 0 is used in the sparse array to * indicate that a sparse element hasn't been paired yet. */ ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; result->count = 1; } void flecs_sparse_clear( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_sparse_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_sparse_page_t); for (i = 0; i < count; i ++) { int32_t *indices = pages[i].sparse; if (indices) { ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); } } ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); sparse->count = 1; sparse->max_id = 0; } void flecs_sparse_fini( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_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]); } ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_sparse_page_t); ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } uint64_t flecs_sparse_new_id( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_sparse_new_index(sparse); } void* flecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t size) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); uint64_t id = flecs_sparse_new_index(sparse); ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(id)); 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(id)); } uint64_t flecs_sparse_last_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[sparse->count - 1]; } void* flecs_sparse_insert( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { bool is_new = true; void *result = flecs_sparse_ensure(sparse, size, id, &is_new); if (!is_new) { result = NULL; } return result; } void* flecs_sparse_ensure( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id, bool *is_new) { 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); /* Make sure is_new is initialized to true */ ecs_assert(!is_new || *is_new, ECS_INVALID_PARAMETER, NULL); (void)size; uint64_t index = (uint32_t)id; 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]; 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] = id; } else { if (is_new) *is_new = false; } } 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, id, count); } return DATA(page->data, sparse->size, offset); } void* flecs_sparse_ensure_fast( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint32_t index = (uint32_t)id; 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; if (!dense) { /* Element is not paired yet. Must add a new element to dense array */ sparse->count = count + 1; if (count == ecs_vec_count(&sparse->dense)) { flecs_sparse_grow_dense(sparse); } uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, count); } return DATA(page->data, sparse->size, offset); } bool flecs_sparse_remove( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { 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(id)); if (!page || !page->sparse) { return false; } uint64_t index = (uint32_t)id; 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 */ if (sparse->size) { void *ptr = DATA(page->data, sparse->size, offset); ecs_os_memset(ptr, 0, sparse->size); } return true; } else { /* Element is not paired and thus not alive, nothing to be done */ return false; } } static uint64_t flecs_sparse_inc_gen( uint64_t index) { /* When an index is deleted, its generation is increased so that we can do * liveness checking while recycling ids */ return ECS_GENERATION_INC(index); } bool flecs_sparse_remove_w_gen( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { 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(id)); if (!page || !page->sparse) { return false; } uint64_t index = (uint32_t)id; int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Increase generation */ uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); ecs_assert(dense_array[dense] == id, ECS_INVALID_PARAMETER, NULL); dense_array[dense] = flecs_sparse_inc_gen(id); 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 { return false; } /* Reset memory to zero on remove */ if (sparse->size) { void *ptr = DATA(page->data, sparse->size, offset); ecs_os_memset(ptr, 0, sparse->size); } return true; } else { /* Element is not paired and thus not alive, nothing to be done */ return false; } } void* flecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t size, int32_t dense_index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); (void)size; dense_index ++; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return flecs_sparse_get_sparse( sparse, dense_index, dense_array[dense_index]); } bool flecs_sparse_is_alive( const ecs_sparse_t *sparse, uint64_t id) { ecs_sparse_page_t *page = flecs_sparse_get_page( sparse, FLECS_SPARSE_PAGE(id)); if (!page || !page->sparse) { return false; } int32_t offset = FLECS_SPARSE_OFFSET(id); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return false; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return true; } void* flecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; uint64_t index = (uint32_t)id; 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); } bool flecs_sparse_has( const ecs_sparse_t *sparse, uint64_t id) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); uint64_t index = (uint32_t)id; ecs_sparse_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return false; } int32_t offset = FLECS_SPARSE_OFFSET(id); int32_t dense = page->sparse[offset]; return dense && (dense < sparse->count); } int32_t flecs_sparse_count( const ecs_sparse_t *sparse) { if (!sparse || !sparse->count) { return 0; } return sparse->count - 1; } const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); if (sparse->dense.array) { return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); } else { return NULL; } } void flecs_sparse_shrink( ecs_sparse_t *sparse) { int32_t i, e, max_page_index = -1, 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; } bool has_alive = false; for (e = 0; e < FLECS_SPARSE_PAGE_SIZE; e ++) { uint64_t id = flecs_ito(uint64_t, (i * FLECS_SPARSE_PAGE_SIZE) + e); if (flecs_sparse_is_alive(sparse, id)) { has_alive = true; break; } } 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); ecs_vec_set_count_t( sparse->allocator, &sparse->dense, uint64_t, sparse->count); ecs_vec_reclaim_t(sparse->allocator, &sparse->dense, uint64_t); } void ecs_sparse_init( ecs_sparse_t *sparse, ecs_size_t elem_size) { flecs_sparse_init(sparse, NULL, NULL, elem_size); } void* ecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t elem_size) { return flecs_sparse_add(sparse, elem_size); } uint64_t ecs_sparse_last_id( const ecs_sparse_t *sparse) { return flecs_sparse_last_id(sparse); } int32_t ecs_sparse_count( const ecs_sparse_t *sparse) { return flecs_sparse_count(sparse); } void* ecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t elem_size, int32_t index) { return flecs_sparse_get_dense(sparse, elem_size, index); } void* ecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t elem_size, uint64_t id) { return flecs_sparse_get(sparse, elem_size, id); } 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 + FLECS_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) { ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); void *result = NULL; if (size > FLECS_STACK_PAGE_SIZE) { result = ecs_os_malloc(size); /* Too large for page */ goto done; } ecs_stack_page_t *page = stack->tail_page; if (!page) { page = stack->first = flecs_stack_page_new(0); stack->tail_page = page; } ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(align != 0, ECS_INTERNAL_ERROR, NULL); int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); if (next_sp > FLECS_STACK_PAGE_SIZE) { if (page->next) { page = page->next; } else { page = page->next = flecs_stack_page_new(page->id); } sp = 0; next_sp = flecs_ito(int16_t, size); stack->tail_page = page; } page->sp = next_sp; result = ECS_OFFSET(page->data, sp); done: ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif return result; } void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { void *ptr = flecs_stack_alloc(stack, size, align); ecs_os_memset(ptr, 0, size); return ptr; } void flecs_stack_free( void *ptr, ecs_size_t size) { if (size > FLECS_STACK_PAGE_SIZE) { ecs_os_free(ptr); } } ecs_stack_cursor_t* flecs_stack_get_cursor( ecs_stack_t *stack) { ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; if (!page) { page = stack->first = flecs_stack_page_new(0); stack->tail_page = page; } int16_t sp = stack->tail_page->sp; ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); result->page = page; result->sp = sp; result->is_free = false; #ifdef FLECS_DEBUG ++ stack->cursor_count; result->owner = stack; #endif result->prev = stack->tail_cursor; stack->tail_cursor = result; return result; } #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) { if (!cursor) { return; } 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; } /* 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, the stack should be empty. * If the cursor count is non-zero, the 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 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; if (stack->first) { stack->first->sp = 0; } stack->tail_cursor = NULL; } void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); stack->first = NULL; stack->tail_page = NULL; } void flecs_stack_fini( ecs_stack_t *stack) { ecs_stack_page_t *next, *cur = stack->first; ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page == stack->first, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(!stack->tail_page || stack->tail_page->sp == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); if (cur) { do { next = cur->next; ecs_os_linc(&ecs_stack_allocator_free_count); ecs_os_free(cur); } while ((cur = next)); } } #include // isnan, isinf /** * stm32tpl -- STM32 C++ Template Peripheral Library * Visit https://github.com/antongus/stm32tpl for new versions * * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA */ #define MAX_PRECISION (10) #define EXP_THRESHOLD (3) #define INT64_MAX_F ((double)INT64_MAX) static const double rounders[MAX_PRECISION + 1] = { 0.5, // 0 0.05, // 1 0.005, // 2 0.0005, // 3 0.00005, // 4 0.000005, // 5 0.0000005, // 6 0.00000005, // 7 0.000000005, // 8 0.0000000005, // 9 0.00000000005 // 10 }; static char* flecs_strbuf_itoa( char *buf, int64_t v) { char *ptr = buf; char * p1; char c; uint64_t uv; if (v < 0) { ptr[0] = '-'; ptr ++; uv = (uint64_t)0 - (uint64_t)v; } else { uv = (uint64_t)v; } if (!uv) { *ptr++ = '0'; } else { char *p = ptr; while (uv) { uint64_t vdiv = uv / 10; uint64_t vmod = uv - (vdiv * 10); p[0] = (char)('0' + (char)vmod); p ++; uv = vdiv; } p1 = p; while (p > ptr) { c = *--p; *p = *ptr; *ptr++ = c; } ptr = p1; } return ptr; } static void flecs_strbuf_ftoa( ecs_strbuf_t *out, double f, int precision, char nan_delim) { char buf[64]; char * ptr = buf; char c; int64_t intPart; int64_t exp = 0; if (ecs_os_isnan(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "NaN"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "NaN"); return; } } if (ecs_os_isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "Inf"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "Inf"); return; } } if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } if (f < 0) { f = -f; *ptr++ = '-'; } if (precision < 0) { if (f < 1.0) precision = 6; else if (f < 10.0) precision = 5; else if (f < 100.0) precision = 4; else if (f < 1000.0) precision = 3; else if (f < 10000.0) precision = 2; else if (f < 100000.0) precision = 1; else precision = 0; } if (precision) { f += rounders[precision]; } /* Make sure that number can be represented as 64bit int, increase exp */ while (f >= INT64_MAX_F) { f /= 1000 * 1000 * 1000; exp += 9; } intPart = (int64_t)f; f -= (double)intPart; ptr = flecs_strbuf_itoa(ptr, intPart); if (precision) { *ptr++ = '.'; while (precision--) { f *= 10.0; c = (char)f; *ptr++ = (char)('0' + c); f -= c; } } *ptr = 0; /* Remove trailing 0s */ while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { ptr[-1] = '\0'; ptr --; } if (ptr != buf && ptr[-1] == '.') { ptr[-1] = '\0'; ptr --; } /* If trailing zeros exceed the threshold, convert to exponent notation to * save space without losing precision. */ char *cur = ptr; while ((&cur[-1] != buf) && (cur[-1] == '0')) { cur --; } if (exp || ((ptr - cur) > EXP_THRESHOLD)) { cur[0] = '\0'; exp += (ptr - cur); ptr = cur; } if (exp) { char *p1 = &buf[1]; if (nan_delim) { ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); buf[0] = nan_delim; p1 ++; } /* Place decimal point after the first digit */ c = p1[0]; if (c) { p1[0] = '.'; do { char t = (++p1)[0]; if (t == '.') { exp ++; p1 --; break; } p1[0] = c; c = t; exp ++; } while (c); ptr = p1 + 1; } else { ptr = p1; } ptr[0] = 'e'; ptr = flecs_strbuf_itoa(ptr + 1, exp); if (nan_delim) { ptr[0] = nan_delim; ptr ++; } ptr[0] = '\0'; } ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } /* Grow the buffer */ static void flecs_strbuf_grow( ecs_strbuf_t *b) { if (!b->content) { b->content = b->small_string; b->size = ECS_STRBUF_SMALL_STRING_SIZE; } else if (b->content == b->small_string) { b->size *= 2; b->content = ecs_os_malloc_n(char, b->size); ecs_os_memcpy(b->content, b->small_string, b->length); } else { b->size *= 2; if (b->size < 16) b->size = 16; b->content = ecs_os_realloc_n(b->content, char, b->size); } } static char* flecs_strbuf_ptr( ecs_strbuf_t *b) { ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); return &b->content[b->length]; } /* Append a format string to a buffer */ static void flecs_strbuf_vappend( ecs_strbuf_t *b, const char* str, va_list args) { va_list arg_cpy; if (!str) { return; } /* Compute the memory required to add the string to the buffer. If the * buffer has already been allocated, use space left in buffer, otherwise * use zero. */ int32_t mem_left = b->size - b->length; int32_t mem_required; va_copy(arg_cpy, args); 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; } ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); if ((mem_required + 1) >= mem_left) { while ((mem_required + 1) >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_vsnprintf(flecs_strbuf_ptr(b), flecs_itosize(mem_required + 1), str, arg_cpy); } b->length += mem_required; va_end(arg_cpy); } static void flecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str, int n) { int32_t mem_left = b->size - b->length; while (n >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); b->length += n; } static void flecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { if (b->size == b->length) { flecs_strbuf_grow(b); } flecs_strbuf_ptr(b)[0] = ch; b->length ++; } void ecs_strbuf_vappend( ecs_strbuf_t *b, const char* fmt, va_list args) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_vappend(b, fmt, args); } void ecs_strbuf_append( ecs_strbuf_t *b, const char* fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_appendstrn( ecs_strbuf_t *b, const char* str, int32_t len) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, len); } void ecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_appendint( ecs_strbuf_t *b, int64_t v) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char numbuf[32]; char *ptr = flecs_strbuf_itoa(numbuf, v); ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } void ecs_strbuf_appendflt( ecs_strbuf_t *b, double flt, char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_ftoa(b, flt, 10, nan_delim); } void ecs_strbuf_appendbool( ecs_strbuf_t *buffer, bool v) { ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); if (v) { ecs_strbuf_appendlit(buffer, "true"); } else { ecs_strbuf_appendlit(buffer, "false"); } } void ecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } void ecs_strbuf_mergebuff( ecs_strbuf_t *b, ecs_strbuf_t *src) { if (src->content && src->length) { flecs_strbuf_appendstr(b, src->content, src->length); } ecs_strbuf_reset(src); } char* ecs_strbuf_get( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; if (!result) { return NULL; } ecs_strbuf_appendch(b, '\0'); result = b->content; #ifdef FLECS_SANITIZE ecs_assert(ecs_os_strlen(result) <= (b->length - 1), ECS_INTERNAL_ERROR, NULL); #endif if (result == b->small_string) { result = ecs_os_memdup_n(result, char, b->length); } b->length = 0; b->content = NULL; b->size = 0; b->list_sp = 0; return result; } char* ecs_strbuf_get_small( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; result[b->length] = '\0'; b->length = 0; b->content = NULL; b->size = 0; return result; } void ecs_strbuf_reset( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->content && b->content != b->small_string) { ecs_os_free(b->content); } *b = ECS_STRBUF_INIT; } void ecs_strbuf_list_push( ecs_strbuf_t *b, const char *list_open, const char *separator) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, "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); } } } 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"); b->list_sp --; if (list_close) { char ch = list_close[0]; if (ch && !list_close[1]) { ecs_strbuf_appendch(b, list_close[0]); } else { ecs_strbuf_appendstr(b, list_close); } } } void ecs_strbuf_list_next( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t list_sp = b->list_sp; if (b->list_stack[list_sp].count != 0) { const char *sep = b->list_stack[list_sp].separator; if (sep && !sep[1]) { ecs_strbuf_appendch(b, sep[0]); } else { ecs_strbuf_appendstr(b, sep); } } b->list_stack[list_sp].count ++; } void ecs_strbuf_list_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_list_append( ecs_strbuf_t *b, const char *fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_list_appendstr( ecs_strbuf_t *b, const char *str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstr(b, str); } void ecs_strbuf_list_appendstrn( ecs_strbuf_t *b, const char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstrn(b, str, n); } int32_t ecs_strbuf_written( const ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return b->length; } 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); } static void* flecs_vec_alloc( struct ecs_allocator_t *allocator, ecs_size_t size, int32_t elem_count, const char *type_name) { (void)type_name; if (elem_count) { if (allocator) { return flecs_alloc_w_dbg_info( allocator, size * elem_count, type_name); } else { return ecs_os_malloc(size * elem_count); } } return NULL; } static void flecs_vec_free( struct ecs_allocator_t *allocator, ecs_size_t elem_size, int32_t size, void *ptr) { if (allocator) { flecs_free(allocator, elem_size * size, ptr); } else { ecs_os_free(ptr); } } 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 = flecs_vec_alloc(allocator, size, elem_count, type_name); v->count = 0; 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 } void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (v->array) { ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (allocator) { flecs_free(allocator, size * v->size, v->array); } else { ecs_os_free(v->array); } v->array = NULL; v->count = 0; v->size = 0; } } ecs_vec_t* ecs_vec_reset( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (!v->size) { ecs_vec_init(allocator, v, size, 0); } else { ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); ecs_vec_clear(v); } return v; } void ecs_vec_clear( ecs_vec_t *vec) { vec->count = 0; } ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); void *array; if (allocator) { array = flecs_dup(allocator, size * v->size, v->array); } else { array = ecs_os_memdup(v->array, size * v->size); } return (ecs_vec_t) { .count = v->count, .size = v->size, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } ecs_vec_t ecs_vec_copy_shrink( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; void *array = NULL; if (count) { if (allocator) { array = flecs_dup(allocator, size * count, v->array); } else { array = ecs_os_memdup(v->array, size * count); } } return (ecs_vec_t) { .count = count, .size = count, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (count < v->size) { if (count) { /* Don't use realloc as it can return the same size buffer when the * new size is smaller than the existing size, which defeats the * purpose of reclaim. */ if (allocator) { void *new_array = flecs_alloc(allocator, count * size); ecs_os_memcpy(new_array, v->array, size * count); flecs_free(allocator, v->size * size, v->array); v->array = new_array; } else { void *new_array = ecs_os_malloc(size * count); ecs_os_memcpy(new_array, v->array, size * count); ecs_os_free(v->array); v->array = new_array; } v->size = count; } else { ecs_vec_fini(allocator, v, size); } } } void ecs_vec_set_size( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->size != elem_count) { if (elem_count < v->count) { elem_count = v->count; } elem_count = flecs_next_pow_of_2(elem_count); if (elem_count < 2) { elem_count = 2; } if (elem_count != v->size) { if (allocator) { #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 ecs_vec_set_min_size( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { if (elem_count > vec->size) { ecs_vec_set_size(allocator, vec, size, elem_count); } } void ecs_vec_set_min_size_w_type_info( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count, const ecs_type_info_t *ti) { ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); ecs_vec_init_if(vec, size); #ifdef FLECS_SANITIZE if (!vec->type_name) { vec->type_name = ti ? ti->name : NULL; } #else (void)ti; #endif if (elem_count > vec->size) { ecs_vec_set_size(allocator, vec, size, elem_count); } } void ecs_vec_set_min_count( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { ecs_vec_set_min_size(allocator, vec, size, elem_count); if (vec->count < elem_count) { vec->count = elem_count; } } void ecs_vec_set_min_count_zeromem( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { int32_t count = vec->count; if (count < elem_count) { ecs_vec_set_min_count(allocator, vec, size, elem_count); ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, size * (elem_count - count)); } } void ecs_vec_set_min_count_w_type_info( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count, const ecs_type_info_t *ti) { int32_t count = vec->count; if (count < elem_count) { ecs_vec_set_count_w_type_info(allocator, vec, size, elem_count, ti); } } void ecs_vec_set_count( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); ecs_san_assert(v->elem_size != 0, ECS_INVALID_PARAMETER, "vector is not initialized"); ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->count != elem_count) { if (v->size < elem_count) { ecs_vec_set_size(allocator, v, size, elem_count); } v->count = elem_count; } } void ecs_vec_set_count_w_type_info( struct ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count, const ecs_type_info_t *ti) { ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); ecs_san_assert(v->elem_size != 0, ECS_INVALID_PARAMETER, "vector is not initialized"); ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (v->count == elem_count) { return; } if (!ti->hooks.ctor_move_dtor) { /* Trivial type, use regular set_count */ ecs_vec_set_count(allocator, v, size, elem_count); return; } /* If array is large enough, we don't need to realloc. */ if (v->size > elem_count) { if (elem_count > v->count) { void *ptr = ECS_ELEM(v->array, size, v->count); flecs_type_info_ctor(ptr, elem_count - v->count, ti); } if (elem_count < v->count) { void *ptr = ECS_ELEM(v->array, size, elem_count); flecs_type_info_dtor(ptr, v->count - elem_count, ti); } v->count = elem_count; return; } /* Resize array. We can't use realloc because we need to call the move hook * from the old to the new memory. */ /* Round up to next power of 2 so we don't allocate for each new element */ ecs_size_t new_size = flecs_next_pow_of_2(elem_count); void *array = NULL; #ifdef FLECS_SANITIZE array = flecs_vec_alloc(allocator, size, new_size, v->type_name); #else array = flecs_vec_alloc(allocator, size, new_size, NULL); #endif int32_t move_count = elem_count; if (move_count > v->count) { move_count = v->count; } /* Move elements over to new array */ flecs_type_info_ctor_move_dtor(array, v->array, move_count, ti); /* Destruct remaining elements in old array, if any */ if (move_count < v->count) { void *ptr = ECS_ELEM(v->array, size, move_count); flecs_type_info_dtor(ptr, v->count - move_count, ti); } /* Construct new elements, if any */ if (move_count < elem_count) { void *ptr = ECS_ELEM(array, size, move_count); flecs_type_info_ctor(ptr, elem_count - move_count, ti); } flecs_vec_free(allocator, size, v->size, v->array); v->array = array; v->size = new_size; v->count = elem_count; } void* ecs_vec_grow( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem_count >= 0, ECS_INTERNAL_ERROR, NULL); int32_t count = v->count; ecs_vec_set_count(allocator, v, size, count + elem_count); return ECS_ELEM(v->array, size, count); } void* ecs_vec_append( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (v->size == count) { ecs_vec_set_size(allocator, v, size, count + 1); } v->count = count + 1; return ECS_ELEM(v->array, size, count); } void ecs_vec_remove( ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); if (index == --v->count) { return; } ecs_os_memcpy( ECS_ELEM(v->array, size, index), ECS_ELEM(v->array, size, v->count), size); } void ecs_vec_remove_last( ecs_vec_t *v) { v->count --; } void ecs_vec_remove_ordered( 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); int32_t new_count = --v->count; if (index == new_count) { return; } ecs_os_memmove( ECS_ELEM(v->array, size, index), ECS_ELEM(v->array, size, index + 1), size * (new_count - index)); } int32_t ecs_vec_count( const ecs_vec_t *v) { return v->count; } int32_t ecs_vec_size( const ecs_vec_t *v) { return v->size; } void* ecs_vec_get( const ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, ECS_OUT_OF_RANGE, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); return ECS_ELEM(v->array, size, index); } void* ecs_vec_last( const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(!v->elem_size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(v->array, size, v->count - 1); } void* ecs_vec_first( const ecs_vec_t *v) { return v->array; } /* Placeholder arrays for queries that only have the $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) } }; int32_t ecs_query_find_var( const ecs_query_t *q, const char *name) { flecs_poly_assert(q, ecs_query_t); 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; } const char* ecs_query_var_name( const ecs_query_t *q, int32_t var_id) { 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; } } bool ecs_query_var_is_entity( const ecs_query_t *q, int32_t var_id) { flecs_poly_assert(q, ecs_query_t); return flecs_query_impl(q)->vars[var_id].kind == EcsVarEntity; } static int flecs_query_set_caching_policy( ecs_query_impl_t *impl, const ecs_query_desc_t *desc) { ecs_query_cache_kind_t kind = desc->cache_kind; bool require_caching = desc->group_by || desc->group_by_callback || desc->order_by || desc->order_by_callback || (desc->flags & EcsQueryDetectChanges); /* 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) { require_caching = true; break; } } #ifdef FLECS_DEFAULT_TO_UNCACHED_QUERIES if (kind == EcsQueryCacheDefault && !require_caching) { kind = EcsQueryCacheNone; } #endif /* If caching policy is default, try to pick a policy that does the right * thing in most cases. */ if (kind == EcsQueryCacheDefault) { if (desc->entity || require_caching) { /* 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 scenarios, 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; } } /* Don't cache query, even if it has cacheable terms */ if (kind == EcsQueryCacheNone) { impl->pub.cache_kind = EcsQueryCacheNone; if (require_caching && !(impl->pub.flags & EcsQueryNested)) { ecs_err("cannot create uncached query with " "group_by/order_by/change detection"); return -1; } return 0; } /* 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; } } /* 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. */ if (!(impl->pub.flags & EcsQueryCacheWithFilter)) { impl->pub.cache_kind = EcsQueryCacheAll; } else { impl->pub.cache_kind = EcsQueryCacheAuto; } } 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 more than just * Not/Optional terms, as this would build a cache that contains * all tables. */ int32_t not_optional_terms = 0, cacheable_terms = 0; if (!require_caching) { 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 ++; } } } } if (require_caching || cacheable_terms != not_optional_terms) { impl->pub.cache_kind = EcsQueryCacheAuto; } else { impl->pub.cache_kind = EcsQueryCacheNone; } } } return 0; } 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 (q->cache_kind != EcsQueryCacheNone) { flecs_check_exclusive_world_access_write(impl->pub.real_world); } else { flecs_check_exclusive_world_access_read(impl->pub.real_world); } 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; } } 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); } } 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 created 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; } void flecs_query_copy_arrays( ecs_query_t *q) { ecs_allocator_t *a = &flecs_query_impl(q)->stage->allocator; q->terms = flecs_dup_n(a, ecs_term_t, q->term_count, q->terms); q->sizes = flecs_dup_n(a, ecs_size_t, q->term_count, q->sizes); q->ids = flecs_dup_n(a, ecs_id_t, q->term_count, q->ids); } static void flecs_query_free_arrays( ecs_query_t *q) { ecs_allocator_t *a = &flecs_query_impl(q)->stage->allocator; flecs_free_n(a, ecs_term_t, q->term_count, q->terms); flecs_free_n(a, ecs_size_t, q->term_count, q->sizes); flecs_free_n(a, ecs_id_t, q->term_count, q->ids); } 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; if (impl->ctx_free) { impl->ctx_free(impl->pub.ctx); } if (impl->binding_ctx_free) { impl->binding_ctx_free(impl->pub.binding_ctx); } 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); } 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); ecs_query_t *q = &impl->pub; if (q->flags & EcsQueryValid) { int i, count = q->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &q->terms[i]; if (!(ecs_world_get_flags(q->world) & EcsWorldQuit)) { if (!ecs_term_match_0(term)) { flecs_component_unlock(q->real_world, term->id); } } } } if (impl->tokens) { flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); } if (impl->cache) { flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->cache->field_map); flecs_query_cache_fini(impl); } flecs_query_free_arrays(q); 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); } 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); } } } } void ecs_query_fini( ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); 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)); } } static ecs_query_t* flecs_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; /* Initialize the query */ result->pub.entity = entity; result->pub.real_world = world; result->pub.world = world_arg; result->stage = stage; 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); /* Validate input, translate input to canonical query representation */ if (flecs_query_finalize_query(world, &result->pub, &desc)) { goto error; } /* If query terms have the query itself as source, add term ids to it. 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); /* 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 (flecs_query_compile(world, stage, result)) { goto error; } /* Entity could've been set during query finalization 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); } return &result->pub; error: result->pub.entity = 0; ecs_query_fini(&result->pub); return NULL; } ecs_query_t* ecs_query_init( ecs_world_t *world, const ecs_query_desc_t *const_desc) { ecs_os_perf_trace_push("flecs.query_init"); ecs_query_t *result = NULL; ecs_entity_t entity = const_desc->entity; if (entity) { flecs_check_exclusive_world_access_write(world); ecs_check(!ecs_has_pair(world, entity, ecs_id(EcsPoly), EcsQuery), ECS_INVALID_OPERATION, "entity %s already is a query, use ecs_query_update() to modify", flecs_errstr(ecs_get_path(world, entity))); } result = flecs_query_init(world, const_desc); error: ecs_os_perf_trace_pop("flecs.query_init"); return result; } ecs_query_t* ecs_query_update( ecs_world_t *world, ecs_entity_t entity, const ecs_query_desc_t *const_desc) { ecs_os_perf_trace_push("flecs.query_update"); ecs_query_t *result = NULL; ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!const_desc->entity || const_desc->entity == entity, ECS_INVALID_PARAMETER, "ecs_query_desc_t::entity does not match query entity"); flecs_check_exclusive_world_access_write(world); /* Remove the existing query if any. */ 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); } ecs_query_desc_t desc = *const_desc; desc.entity = entity; result = flecs_query_init(world, &desc); error: ecs_os_perf_trace_pop("flecs.query_update"); return result; } bool ecs_query_has( const 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); *it = ecs_query_iter(q->world, q); ecs_iter_set_var(it, 0, entity); return ecs_query_next(it); error: return false; } bool ecs_query_has_table( const 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 (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { /* Safe, only used for statistics */ ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; return false; } *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( const ecs_query_t *q, ecs_table_range_t *range, ecs_iter_t *it) { flecs_poly_assert(q, ecs_query_t); ecs_table_t *table = range->table; if (q->flags & EcsQueryMatchThis) { if (table) { if ((range->offset + range->count) > ecs_table_count(table)) { return false; } } } if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { /* Safe, only used for statistics */ ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; return false; } *it = ecs_query_iter(q->world, q); if (q->flags & EcsQueryMatchThis) { ecs_iter_set_var_as_range(it, 0, range); } return ecs_query_next(it); } 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}; 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); } if ((q->flags & EcsQueryMatchOnlySelf) && !(q->flags & EcsQueryMatchWildcards)) { result.tables = result.results; } else if (q->flags & EcsQueryIsCacheable) { ecs_query_impl_t *impl = flecs_query_impl(q); if (impl->cache) { result.tables = ecs_map_count(&impl->cache->tables); } } return result; } bool ecs_query_is_true( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_iter_t it = flecs_query_iter(q->world, q); return ecs_iter_is_true(&it); } int32_t ecs_query_match_count( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); 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; } } 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; } } void ecs_iter_set_group( ecs_iter_t *it, uint64_t group_id) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, "cannot set group during iteration"); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *q = flecs_query_impl(it->query); ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); flecs_poly_assert(q, ecs_query_t); ecs_query_cache_t *cache = q->cache; ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); static ecs_vec_t empty_table = {0}; ecs_query_cache_group_t *group = flecs_query_cache_get_group( cache, group_id); if (!group) { qit->tables = &empty_table; /* Dummy table to indicate empty result */ qit->all_tables = &empty_table; qit->cur = 0; qit->group = NULL; qit->iter_single_group = true; return; } qit->tables = &group->tables; qit->all_tables = &group->tables; qit->cur = 0; qit->group = group; qit->iter_single_group = true; /* Prevent iterating next group */ error: return; } const ecs_query_group_info_t* ecs_query_get_group_info( const ecs_query_t *query, uint64_t group_id) { flecs_poly_assert(query, ecs_query_t); ecs_query_cache_group_t *node = flecs_query_cache_get_group( flecs_query_impl(query)->cache, group_id); if (!node) { return NULL; } return &node->info; } void* ecs_query_get_group_ctx( const ecs_query_t *query, uint64_t group_id) { flecs_poly_assert(query, ecs_query_t); const ecs_query_group_info_t *info = ecs_query_get_group_info( query, group_id); if (!info) { return NULL; } else { return info->ctx; } } const ecs_map_t* ecs_query_get_groups( const ecs_query_t *query) { flecs_poly_assert(query, ecs_query_t); ecs_query_impl_t *q = flecs_query_impl(query); ecs_check(q->cache != NULL, ECS_INVALID_PARAMETER, "ecs_query_get_groups is not valid for queries that don't use group_by"); ecs_query_cache_t *cache = q->cache; ecs_check(cache->group_by != 0, ECS_INVALID_PARAMETER, "ecs_query_get_groups is not valid for queries that don't use group_by"); return &cache->groups; error: return NULL; } const char* flecs_query_op_str( uint16_t kind) { switch(kind) { case EcsQueryAll: return "all "; case EcsQueryAnd: return "and "; case EcsQueryAndAny: return "and_any "; case EcsQueryAndWcTgt: return "and_wct "; 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 EcsQueryWithWcTgt: return "with_wct "; 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 EcsQueryIdsAll: return "idsa "; 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 EcsQuerySparse: return "sparse "; case EcsQuerySparseWith: return "sparse_w "; case EcsQuerySparseNot: return "sparse_not "; case EcsQuerySparseSelfUp: return "sparse_sup "; case EcsQuerySparseUp: return "sparse_up "; case EcsQueryTree: return "tree "; case EcsQueryTreeWildcard: return "tree_wc "; case EcsQueryTreePre: return "tree_pre "; case EcsQueryTreePost: return "tree_post "; case EcsQueryTreeUpPre: return "treeup_pre "; case EcsQueryTreeSelfUpPre: return "treesup_pre "; case EcsQueryTreeUpPost: return "treeup_post "; case EcsQueryTreeSelfUpPost: return "treesup_post"; case EcsQueryTreeUp: return "tree_up "; case EcsQueryTreeSelfUp: return "tree_selfup "; case EcsQueryTreeWith: return "tree_w "; case EcsQueryChildren: return "children "; case EcsQueryChildrenWc: return "children_wc "; 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_lbl_t flecs_itolbl(int64_t val) { return flecs_ito(int16_t, val); } ecs_var_id_t flecs_itovar(int64_t val) { return flecs_ito(uint8_t, val); } 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) { if (term->first.id & EcsIsEntity) { ecs_entity_t id = ECS_TERM_REF_ID(&term->first); if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { return true; } } return false; } const char* flecs_term_ref_var_name( ecs_term_ref_t *ref) { if (!(ref->id & EcsIsVariable)) { return NULL; } if (ECS_TERM_REF_ID(ref) == EcsThis) { return EcsThisName; } return ref->name; } 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; } 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; } /* 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; } 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); } ecs_flags16_t flecs_query_ref_flags( ecs_flags16_t flags, ecs_flags16_t kind) { return (flags >> kind) & (EcsQueryIsVar | EcsQueryIsEntity); } bool flecs_query_is_written( ecs_var_id_t var_id, uint64_t written) { if (var_id == EcsVarNone) { return true; } ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); return (written & (1ull << var_id)) != 0; } void flecs_query_write( ecs_var_id_t var_id, uint64_t *written) { ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); *written |= (1ull << var_id); } void flecs_query_write_ctx( ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx, bool cond_write) { 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; } ecs_allocator_t* flecs_query_get_allocator( const ecs_iter_t *it) { 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; } } 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) { 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]"); } return color_chars; } static void flecs_query_str_append_bitset( ecs_strbuf_t *buf, ecs_flags64_t bitset) { 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, "}"); } static void flecs_query_plan_w_profile( const ecs_query_t *q, const ecs_iter_t *it, ecs_strbuf_t *buf) { 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 } 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_strbuf_append(buf, "%*s", indent, ""); ecs_strbuf_appendstr(buf, flecs_query_op_str(op->kind)); ecs_strbuf_appendstr(buf, " "); int32_t written = ecs_strbuf_written(buf); for (int32_t j = 0; j < (12 - (written - start)); j ++) { ecs_strbuf_appendch(buf, ' '); } hidden_chars = flecs_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 ++; } if (op->kind == EcsQueryTriv) { flecs_query_str_append_bitset(buf, op->src.entity); } if (op->kind == EcsQueryIfSet) { ecs_strbuf_append(buf, "[%d]\n", op->other); continue; } bool is_toggle = op->kind == EcsQueryToggle || op->kind == EcsQueryToggleOption; if (!first_flags && !second_flags && !is_toggle) { ecs_strbuf_appendstr(buf, "\n"); continue; } written = ecs_strbuf_written(buf) - hidden_chars; for (int32_t j = 0; j < (30 - (written - start)); j ++) { ecs_strbuf_appendch(buf, ' '); } if (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; } 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; } default: break; } } 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); return ecs_strbuf_get(&buf); } char* ecs_query_plan( const ecs_query_t *q) { return ecs_query_plan_w_profile(q, NULL); } char* ecs_query_plans( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_query_plan_w_profile(q, NULL, &buf); ecs_query_impl_t *impl = flecs_query_impl(q); if (impl->cache) { ecs_strbuf_appendstr(&buf, "---\n"); flecs_query_plan_w_profile(impl->cache->query, NULL, &buf); } return ecs_strbuf_get(&buf); } 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 (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; 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 ((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"); } if (flags & EcsCascade) { ecs_strbuf_list_appendstr(buf, "cascade"); } else if (flags & EcsUp) { ecs_strbuf_list_appendstr(buf, "up"); } 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); } } 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 *first = &term->first; const ecs_term_ref_t *second = &term->second; ecs_entity_t first_id = ECS_TERM_REF_ID(first); bool src_set = !ecs_term_match_0(term); bool second_set = ecs_term_ref_is_set(second); 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 */ } 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 (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; } if (!t || !(term[-1].oper == EcsOr)) { if (term->inout == EcsIn) { ecs_strbuf_appendlit(buf, "[in] "); } else if (term->inout == EcsInOut) { ecs_strbuf_appendlit(buf, "[inout] "); } else if (term->inout == EcsOut) { ecs_strbuf_appendlit(buf, "[out] "); } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { ecs_strbuf_appendlit(buf, "[none] "); } } if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, "!"); } else if (term->oper == EcsOptional) { ecs_strbuf_appendlit(buf, "?"); } if (!src_set) { flecs_query_str_add_id(world, buf, term, &term->first, false); if (!second_set) { ecs_strbuf_appendlit(buf, "()"); } else { 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, '|'); } flecs_query_str_add_id(world, buf, term, &term->first, false); ecs_strbuf_appendlit(buf, "("); 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, ")"); } } 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); } char* ecs_query_str( const ecs_query_t *q) { ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); ecs_world_t *world = q->world; ecs_strbuf_t buf = ECS_STRBUF_INIT; const ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = 0; i < count; i ++) { const ecs_term_t *term = &terms[i]; flecs_term_to_buf(world, term, &buf, i); 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, ", "); } } } } } return ecs_strbuf_get(&buf); error: return NULL; } 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); } void flecs_query_reclaim( ecs_query_t *query) { ecs_query_impl_t *impl = flecs_query_impl(query); ecs_query_cache_t *cache = impl->cache; if (cache) { ecs_map_reclaim(&cache->tables); ecs_map_reclaim(&cache->groups); } } ecs_id_t flecs_query_iter_set_id( ecs_iter_t *it, int8_t field, ecs_id_t id) { ecs_assert(!(it->flags & EcsIterImmutableCacheData), ECS_INTERNAL_ERROR, NULL); it->ids[field] = id; return id; } static void flecs_query_validator_error( const ecs_query_validator_ctx_t *ctx, const char *fmt, ...) { 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"); } 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 { 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"); } } 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); } static int flecs_term_ref_finalize_flags( ecs_term_ref_t *ref, ecs_query_validator_ctx_t *ctx, const char* refname) { (void)refname; if ((ref->id & EcsIsEntity) && (ref->id & EcsIsVariable)) { flecs_query_validator_error(ctx, "cannot set both EcsIsEntity and EcsIsVariable for term.%s", refname); return -1; } if (ref->name && ref->name[0] == '$') { if (!ref->name[1]) { if (!(ref->id & EcsIsName)) { flecs_query_validator_error(ctx, "invalid variable name for term.%s" " ('$' syntax support is removed, use new Singleton trait)", refname); return -1; } } else { ref->name = &ref->name[1]; ref->id |= EcsIsVariable; } } 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; } } } return 0; } static bool flecs_identifier_is_0( const char *id) { return id[0] == '#' && id[1] == '0' && !id[2]; } 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 (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; } 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; } ecs_entity_t e = 0; if (scope) { e = ecs_lookup_child(world, scope, name); } if (!e) { e = ecs_lookup(world, name); } if (!e) { e = ecs_lookup_symbol(world, name, false, false); } 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_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; } ref->id = e | ECS_TERM_REF_FLAGS(ref); ref_id = ECS_TERM_REF_ID(ref); if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || !ecs_os_strcmp(name, "$")) { ref->id &= ~EcsIsEntity; ref->id |= EcsIsVariable; } /* 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; } ref->name = NULL; return 0; } return 0; } static int flecs_term_refs_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { 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; } /* 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; } } /* 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; } /* Initialize term identifier flags */ if (flecs_term_ref_finalize_flags(src, ctx, "src")) { return -1; } if (flecs_term_ref_finalize_flags(first, ctx, "first")) { return -1; } if (flecs_term_ref_finalize_flags(second, ctx, "second")) { 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); } 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 a wildcard, term won't return any data */ if ((src->id & EcsIsVariable) && ecs_id_is_wildcard(ECS_TERM_REF_ID(src))) { term->inout = EcsInOutNone; } return 0; } static ecs_entity_t flecs_term_ref_get_entity( const ecs_term_ref_t *ref) { 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 */ } } static int flecs_term_populate_id( ecs_term_t *term) { 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; 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_term_populate_from_id( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { ecs_entity_t first = 0; ecs_entity_t second = 0; bool pair_w_0_tgt = false; 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; } } 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); } } 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; } 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 ((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 (first_id == EcsPredEq) { if (second_id == EcsPredEq || second_id == EcsPredMatch) { flecs_query_validator_error(ctx, "invalid right-hand side for equality operator"); goto error; } } return 0; error: return -1; } static int flecs_term_verify( const ecs_world_t *world, const ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { 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 ((src->id & EcsIsName) && (second->id & EcsIsName)) { flecs_query_validator_error(ctx, "mismatch between term.cr_flags & term.id"); return -1; } ecs_entity_t src_id = ECS_TERM_REF_ID(src); if (first->id & EcsIsEntity) { first_id = ECS_TERM_REF_ID(first); } if (second->id & EcsIsEntity) { second_id = ECS_TERM_REF_ID(second); } if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { return flecs_term_verify_eq_pred(term, ctx); } 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 */ } } 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) { if (src_id) { 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)) { flecs_query_validator_error(ctx, "term with acyclic " "relationship cannot have the same source and target"); return -1; } } } if (second_id && !ecs_id_is_wildcard(second_id)) { ecs_entity_t oneof = flecs_get_oneof(world, first_id); if (oneof) { if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { char *second_str = ecs_get_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 (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; } } return 0; } int flecs_term_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { ctx->term = term; 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 (%s) and first.id have competing values", first->name); return -1; } if (src->name && (src->id & ~EcsTermRefFlags)) { flecs_query_validator_error(ctx, "src.name (%s) and src.id have competing values", src->name); return -1; } if (second->name && (second->id & ~EcsTermRefFlags)) { flecs_query_validator_error(ctx, "second.name (%s) and second.id have competing values", second->name); return -1; } if (term->id & ~ECS_ID_FLAGS_MASK) { if (flecs_term_populate_from_id(world, term, ctx)) { return -1; } } if (flecs_term_refs_finalize(world, term, ctx)) { return -1; } 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 ((first->id & EcsIsVariable) && (first_id == EcsAny)) { term->flags_ |= EcsTermMatchAny; } if ((second->id & EcsIsVariable) && (second_id == EcsAny)) { term->flags_ |= EcsTermMatchAny; } if ((src->id & EcsIsVariable) && (src_id == EcsAny)) { term->flags_ |= EcsTermMatchAnySrc; } ecs_flags64_t ent_var_mask = EcsIsEntity | EcsIsVariable; /* If EcsVariable is used by itself, assign to first (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 ((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; } if (!(term->id & ~ECS_ID_FLAGS_MASK)) { if (flecs_term_populate_id(term)) { return -1; } } /* If term queries for !(ChildOf, _), translate it to the builtin * (ChildOf, 0) index which is a cheaper way to find root entities */ if (term->oper == EcsNot && ( (term->id == ecs_pair(EcsChildOf, EcsAny)) || (term->id == ecs_pair(EcsChildOf, EcsWildcard)) )) { /* 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 (term->id == ecs_pair(EcsChildOf, 0)) { /* Ensure same behavior for (ChildOf, #0) and !(ChildOf, _) terms */ term->flags_ |= EcsTermMatchAny; } ecs_entity_t first_entity = 0; if ((first->id & EcsIsEntity)) { first_entity = first_id; } ecs_flags32_t cr_flags = 0; if (term->id) { cr_flags = flecs_component_get_flags(world, term->id); } if (src_id || src->name) { if (!(term->src.id & EcsTraverseFlags)) { if (cr_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, "traversal relationship (term::trav) '%s' specified for " "component '%s' which can't be inherited", flecs_errstr(ecs_id_str(world, term->trav)), idstr); ecs_os_free(idstr); return -1; } } } if (term->first.id & EcsCascade) { flecs_query_validator_error(ctx, "cascade modifier invalid for term.first"); return -1; } if (term->second.id & EcsCascade) { flecs_query_validator_error(ctx, "cascade modifier invalid for term.second"); return -1; } if (term->first.id & EcsDesc) { flecs_query_validator_error(ctx, "desc modifier invalid for term.first"); return -1; } if (term->second.id & EcsDesc) { flecs_query_validator_error(ctx, "desc modifier invalid for term.second"); return -1; } if (term->src.id & EcsDesc && !(term->src.id & EcsCascade)) { flecs_query_validator_error(ctx, "desc modifier invalid without cascade"); return -1; } if (term->src.id & EcsCascade) { /* Cascade always implies up traversal */ term->src.id |= EcsUp; } 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 (!(cr_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' because the component doesn't have the " "(OnInstantiate, Inherit) trait", idstr); ecs_os_free(idstr); return -1; } } 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); } } /* 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)) || (cr_flags & EcsIdInheritable) || 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 } 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 (!((second_id == EcsWildcard) && (term->oper == EcsNot))) { if (ecs_table_has_id(world, first_table, EcsTransitive)) { term->flags_ |= EcsTermTransitive; } if (ecs_table_has_id(world, first_table, EcsReflexive)) { term->flags_ |= EcsTermReflexive; } } } } } } if (ecs_table_has_id(world, first_table, EcsDontFragment)) { term->flags_ |= EcsTermDontFragment; } } /* Check if term has toggleable component */ if (cr_flags & EcsIdCanToggle) { /* If the term isn't matched on a #0 source */ if (term->src.id != EcsIsEntity) { term->flags_ |= EcsTermIsToggle; } } /* Check if this is a member query */ } if (ECS_TERM_REF_ID(first) == EcsVariable) { flecs_query_validator_error(ctx, "invalid $ for term.first"); return -1; } 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; } } /* Is term trivial/cacheable */ bool cacheable_term = true; bool trivial_term = true; if (term->oper != EcsAnd || term->flags_ & EcsTermIsOr) { trivial_term = false; } if (ECS_IS_PAIR(term->id) && (ECS_PAIR_FIRST(term->id) == EcsChildOf)) { if (term->oper == EcsAnd) { term->flags_ |= EcsTermNonFragmentingChildOf; } if (ECS_PAIR_SECOND(term->id)) { trivial_term = false; } } if (ecs_id_is_wildcard(term->id)) { if (ECS_PAIR_FIRST(term->id) == EcsWildcard) { cacheable_term = false; trivial_term = false; } if (!(cr_flags & EcsIdExclusive)) { trivial_term = false; } if (first->id & EcsIsVariable) { if (!ecs_id_is_wildcard(first_id) || first_id == EcsAny) { trivial_term = false; cacheable_term = false; } } if (second->id & EcsIsVariable) { if (!ecs_id_is_wildcard(second_id) || second_id == EcsAny) { trivial_term = false; if (second_id != EcsAny) { cacheable_term = false; } } } } if (!ecs_term_match_this(term)) { trivial_term = false; } if (term->flags_ & EcsTermTransitive) { trivial_term = false; cacheable_term = false; } if (term->flags_ & EcsTermIdInherited) { trivial_term = false; cacheable_term = false; } if (term->flags_ & EcsTermReflexive) { trivial_term = false; cacheable_term = false; } if (term->trav && term->trav != EcsIsA) { trivial_term = false; } if (!(src->id & EcsSelf)) { trivial_term = 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; } if (ECS_TERM_REF_ID(src) != EcsThis) { cacheable_term = false; } if (term->id == ecs_childof(0)) { cacheable_term = false; } if (term->flags_ & EcsTermIsMember) { trivial_term = false; cacheable_term = false; } if (term->flags_ & EcsTermIsToggle) { trivial_term = false; } if (term->flags_ & EcsTermDontFragment) { trivial_term = false; cacheable_term = false; } ECS_BIT_COND16(term->flags_, EcsTermIsTrivial, trivial_term); ECS_BIT_COND16(term->flags_, EcsTermIsCacheable, cacheable_term); if (flecs_term_verify(world, term, ctx)) { return -1; } return 0; } bool ecs_term_ref_is_set( const ecs_term_ref_t *ref) { return ECS_TERM_REF_ID(ref) != 0 || ref->name != NULL || (ref->id & EcsIsEntity) != 0; } bool ecs_term_is_initialized( const ecs_term_t *term) { return term->id != 0 || ecs_term_ref_is_set(&term->first); } bool ecs_term_match_this( const ecs_term_t *term) { return (term->src.id & EcsIsVariable) && (ECS_TERM_REF_ID(&term->src) == EcsThis); } bool ecs_term_match_0( const ecs_term_t *term) { return (!ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)); } static ecs_term_t* flecs_query_or_other_type( ecs_query_t *q, int32_t t) { 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 (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; } return first; } else { return NULL; } } 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]; if (!ecs_os_strcmp(ref->name, "this")) { ref->name = NULL; ref->id |= EcsThis; } } } static int flecs_query_finalize_terms( 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_query_validator_ctx_t ctx = {0}; ctx.world = world; ctx.query = q; ctx.desc = desc; q->flags |= EcsQueryMatchOnlyThis; 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; } } } bool cacheable = true; bool match_nothing = true; /* If a query has ChildOf terms we need to prevent it from being marked as * IsCacheable (meaning the query can be evaluated by just iterating the * cache). A ChildOf term must be marked as cacheable, but also needs an * instruction to filter tables that use non-fragmenting relationships. */ bool has_childof = false; 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; bool default_src = term->src.id == 0 && term->src.name == NULL; ctx.term_index = i; if (flecs_term_finalize(world, term, &ctx)) { return -1; } if (term->flags_ & EcsTermNonFragmentingChildOf) { if (!i) { /* If the first term is a ChildOf pair, the query result should * respect the order of parents with the OrderedChildren trait. * This cannot be cached, so unset the IsCacheable bit. */ ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } if (ECS_PAIR_SECOND(term->id) != EcsAny) { has_childof = true; } } if (term->trav == EcsChildOf && (term->oper == EcsAnd || term->oper == EcsOptional)) { if (!(term->flags_ & EcsTermIsOr)) { has_childof = true; } } if (term->src.id != EcsIsEntity) { /* If term doesn't match the 0 entity, query doesn't match nothing */ match_nothing = false; } if (scope_nesting) { /* Terms inside a scope are not cacheable */ ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } /* 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 ++; } /* 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); } } } if (!prev_is_or) { field_count ++; } term->field_index = flecs_ito(int8_t, field_count - 1); if (ecs_id_is_wildcard(term->id)) { if (term->oper != EcsNot || ecs_id_is_any(term->id)) { q->flags |= EcsQueryMatchWildcards; } } else if (!(term->flags_ & EcsTermIsOr)) { ECS_TERMSET_SET(q->static_id_fields, 1u << term->field_index); } 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; } if ((term->id == EcsWildcard) || (term->id == ecs_pair(EcsWildcard, EcsWildcard))) { /* If term type is unknown beforehand, default the inout type to * none. This prevents accidentally requesting lots of components, * which can put stress on serializer code. */ if (term->inout == EcsInOutDefault) { term->inout = EcsInOutNone; } } if (term->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_ & 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; } } } 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; } bool match_non_this = !ecs_term_match_this(term) || (term->src.id & EcsUp); if (match_non_this && term->inout != EcsInOutDefault) { q->flags |= EcsQueryHasNonThisOutTerms; } } 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); } } if (term->flags_ & EcsTermIsMember) { nodata_term = false; } if (!nodata_term && term->oper != EcsNot) { ECS_TERMSET_SET(q->data_fields, 1u << term->field_index); 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); } } bool is_sparse = false; ecs_component_record_t *cr = flecs_components_get(world, term->id); ecs_flags32_t cr_flags = 0; if (cr) { cr_flags = cr->flags; } else if (term->id) { cr_flags = flecs_component_get_flags(world, term->id); } if (cr_flags & EcsIdSparse) { is_sparse = true; } if (cr_flags & EcsIdSingleton) { if (default_src) { term->src.id = term->first.id|EcsSelf|EcsIsEntity; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); if (term->flags_ & EcsTermIsCacheable) { cacheable_terms --; ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } } } /* If this is a static field, we need to assume that we might have * to do change detection. */ if (term->src.id & EcsIsEntity) { if (term->id < FLECS_HI_COMPONENT_ID) { world->non_trivial_set[term->id] = true; } } if (ecs_term_match_this(term)) { ECS_BIT_SET(q->flags, EcsQueryMatchThis); } else { ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlyThis); } if (ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)) { ECS_TERMSET_SET(q->fixed_fields, 1u << term->field_index); } if ((term->src.id & EcsIsVariable) && (ECS_TERM_REF_ID(&term->src) != EcsThis)) { ECS_TERMSET_SET(q->var_fields, 1u << term->field_index); } if (prev_is_or) { if (ECS_TERM_REF_ID(&term[-1].src) != ECS_TERM_REF_ID(&term->src)) { flecs_query_validator_error(&ctx, "mismatching sources in OR expression (all terms in OR " "expression must have the same source)"); return -1; } if (term->oper != EcsOr && term->oper != EcsAnd) { flecs_query_validator_error(&ctx, "term after OR expression cannot use operators"); return -1; } } if (is_sparse) { term->flags_ |= EcsTermIsSparse; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); if (term->flags_ & EcsTermIsCacheable) { cacheable_terms --; ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } /* Sparse component fields must be accessed with ecs_field_at */ if (!nodata_term) { q->row_fields |= flecs_uto(uint32_t, 1llu << term->field_index); } } if (term->oper == EcsOptional || term->oper == EcsNot) { cond_set = true; } 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); } } if (first_id == EcsScopeOpen) { q->flags |= EcsQueryHasScopes; scope_nesting ++; } if (scope_nesting) { term->flags_ |= EcsTermIsScope; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); if (term->flags_ & EcsTermIsCacheable) { cacheable_terms --; ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } } 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 (scope_nesting < 0) { flecs_query_validator_error(&ctx, "'}' without matching '{'"); return -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; } q->field_count = flecs_ito(int8_t, field_count); 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; if (!ecs_term_match_0(term)) { flecs_component_lock(world, term->id); } if (term->flags_ & EcsTermIsOr) { if (flecs_query_or_other_type(q, i)) { q->sizes[field] = 0; q->ids[field] = 0; continue; } } ecs_component_record_t *cr = flecs_components_get(world, term->id); if (cr) { if (!ECS_IS_PAIR(cr->id) || ECS_PAIR_FIRST(cr->id) != EcsWildcard) { if (cr->type_info) { q->sizes[field] = cr->type_info->size; q->ids[field] = cr->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; } } } } 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); bool is_trivial = true; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *src = &term->src; if (src->id & EcsUp) { ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlySelf); } if (ECS_TERM_REF_ID(&term->first) == EcsChildOf) { if (ECS_TERM_REF_ID(&term->second) != 0) { is_trivial = false; continue; } } if (!(term->flags_ & EcsTermIsTrivial)) { is_trivial = false; continue; } if ((term->src.id & EcsTraverseFlags) == EcsSelf) { if (!ecs_id_is_wildcard(term->id)) { q->bloom_filter = flecs_table_bloom_filter_add( q->bloom_filter, term->id); } } } if (term_count && is_trivial) { ECS_BIT_SET(q->flags, EcsQueryIsTrivial); } } } /* Set cacheable flags */ ECS_BIT_COND(q->flags, EcsQueryHasCacheable, cacheable_terms != 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 && !has_childof); ECS_BIT_COND(q->flags, EcsQueryCacheWithFilter, has_childof); /* If none of the terms match a source, the query matches nothing */ ECS_BIT_COND(q->flags, EcsQueryMatchNothing, match_nothing); 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); } return 0; } static int flecs_query_query_populate_terms( ecs_world_t *world, ecs_stage_t *stage, ecs_query_t *q, const ecs_query_desc_t *desc) { /* 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 ++; } /* Copy terms from array to query */ if (term_count) { ecs_os_memcpy_n(q->terms, desc->terms, ecs_term_t, term_count); } /* Parse query expression if set */ const char *expr = desc->expr; if (expr && expr[0]) { (void)world; (void)stage; ecs_err("parsing query expressions requires the FLECS_QUERY_DSL addon"); goto error; } q->term_count = flecs_ito(int8_t, term_count); return 0; error: return -1; } bool flecs_query_finalize_simple( ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc) { ecs_assert(q->terms != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(q->sizes != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(q->ids != NULL, ECS_INTERNAL_ERROR, NULL); /* Filter out queries that aren't simple enough */ if (desc->expr) { return false; } if (desc->order_by_callback || desc->group_by_callback) { return false; } int8_t i, term_count; for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { const ecs_term_t *term = &desc->terms[i]; if (!ecs_term_is_initialized(&desc->terms[i])) { break; } ecs_id_t id = term->id; if (ecs_id_is_wildcard(id)) { return false; } if (id == EcsThis || ECS_PAIR_FIRST(id) == EcsThis || ECS_PAIR_SECOND(id) == EcsThis) { return false; } if (id == EcsVariable || ECS_PAIR_FIRST(id) == EcsVariable || ECS_PAIR_SECOND(id) == EcsVariable) { return false; } if (id == EcsPrefab || id == EcsDisabled) { return false; } ecs_term_t cmp_term = { .id = id, .flags_ = term->flags_, .field_index = term->field_index }; if (term->src.id == (EcsThis|EcsSelf|EcsIsVariable)) { cmp_term.src.id = EcsThis|EcsSelf|EcsIsVariable; } else if (term->src.id == EcsSelf) { cmp_term.src.id = EcsSelf; } if (term->first.id == (term->id|EcsSelf|EcsIsEntity)) { cmp_term.first.id = term->id|EcsSelf|EcsIsEntity; } if (ecs_os_memcmp_t(&cmp_term, term, ecs_term_t)) { return false; } } if (!i) { return false; /* No terms */ } term_count = i; ecs_os_memcpy_n(q->terms, desc->terms, ecs_term_t, term_count); /* All fields are InOut */ q->write_fields = (1u << term_count) - 1; /* Simple query that only queries for component ids */ /* Populate terms */ bool has_this = false, has_only_this = true; 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; } bool is_self = term->src.id == EcsSelf; bool default_src = term->src.id == 0; term->field_index = i; term->first.id = first | EcsIsEntity | EcsSelf; term->src.id = EcsThis | EcsIsVariable | EcsSelf; q->ids[i] = id; ecs_component_record_t *cr = NULL; #ifndef FLECS_SANITIZE cr = flecs_components_get(world, id); #else /* In sanitized mode, always compute the component flags on the spot * instead of using the flags cached on the component record. This * ensures that both code paths get the same kind of test coverage. */ #endif const ecs_type_info_t *type_info = NULL; ecs_flags32_t cr_flags = 0; if (cr) { cr_flags = cr->flags; type_info = cr->type_info; } else { cr_flags = flecs_component_get_flags(world, id); type_info = flecs_determine_type_info_for_component(world, id); } bool cacheable = true, trivial = true; if (type_info) { q->sizes[i] = type_info->size; q->flags |= EcsQueryHasOutTerms; q->data_fields |= (ecs_termset_t)(1llu << i); } if (!is_self && cr_flags & EcsIdOnInstantiateInherit) { term->src.id |= EcsUp; term->trav = EcsIsA; up_count ++; } if (cr_flags & EcsIdCanToggle) { term->flags_ |= EcsTermIsToggle; trivial = false; } if (cr_flags & EcsIdDontFragment) { term->flags_ |= EcsTermDontFragment; trivial = false; } if (cr_flags & EcsIdSparse) { term->flags_ |= EcsTermIsSparse; cacheable = false; trivial = false; q->row_fields |= flecs_uto(uint32_t, 1llu << i); } if (cr_flags & EcsIdSingleton) { if (default_src) { term->src.id = term->first.id|EcsSelf|EcsIsEntity; has_only_this = false; cacheable = false; trivial = false; } } else { has_this = true; } if (ECS_IS_PAIR(id)) { if (first == EcsChildOf) { if (term->oper == EcsAnd) { term->flags_ |= EcsTermNonFragmentingChildOf; } ecs_entity_t second = ECS_PAIR_SECOND(id); if (second) { trivial = false; if (ECS_PAIR_SECOND(id) != EcsAny) { if (!i) { /* If first query term is ChildOf, return children * in order if possible, which can't be cached. */ cacheable = false; } } } } 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 ++; if ((term->src.id & EcsTraverseFlags) == EcsSelf) { q->bloom_filter = flecs_table_bloom_filter_add( q->bloom_filter, id); } } flecs_component_lock(world, id); } /* 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); if (has_this) { q->flags |= EcsQueryHasTableThisVar|EcsQueryMatchThis; } if (has_only_this) { q->flags |= EcsQueryMatchOnlyThis; } if (cacheable_count) { q->flags |= EcsQueryHasCacheable; } if (cacheable_count == term_count && trivial_count == term_count) { q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; } if (!up_count) { q->flags |= EcsQueryMatchOnlySelf; } return true; } 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; char *old_tokens = impl->tokens; int32_t old_tokens_len = impl->tokens_len; impl->tokens = NULL; impl->tokens_len = 0; /* 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; } } /* Step 2: reassign term tokens to buffer */ if (len) { impl->tokens = flecs_alloc(&impl->stage->allocator, len); impl->tokens_len = len; char *token = impl->tokens, *next; 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; } } } 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); if ((desc->flags & EcsQueryDetectChanges) && (desc->order_by || desc->order_by_callback)) { ecs_query_validator_ctx_t ctx = {0}; ctx.world = world; ctx.query = q; ctx.desc = desc; flecs_query_validator_error(&ctx, "change detection is not supported for queries with order_by"); goto error; } q->flags |= desc->flags | world->default_query_flags; ecs_term_t terms[FLECS_TERM_COUNT_MAX] = {0}; ecs_size_t sizes[FLECS_TERM_COUNT_MAX] = {0}; ecs_id_t ids[FLECS_TERM_COUNT_MAX] = {0}; q->terms = terms; q->sizes = sizes; q->ids = ids; /* 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)) { goto done; } #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 a single * token buffer which simplifies memory management & reduces allocations. */ flecs_query_populate_tokens(flecs_query_impl(q)); #ifndef FLECS_SANITIZE done: #endif flecs_query_copy_arrays(q); q->flags |= EcsQueryValid; return 0; error: flecs_query_copy_arrays(q); return -1; } static ecs_id_record_elem_t* flecs_component_elem( ecs_component_record_t *head, ecs_id_record_elem_t *list, ecs_component_record_t *cr) { return ECS_OFFSET(cr->pair, (uintptr_t)list - (uintptr_t)head->pair); } static void flecs_component_elem_insert( ecs_component_record_t *head, ecs_component_record_t *cr, ecs_id_record_elem_t *elem) { ecs_id_record_elem_t *head_elem = flecs_component_elem(cr, 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(cr, elem, cur); cur_elem->prev = cr; } head_elem->next = cr; } static void flecs_component_elem_remove( ecs_component_record_t *cr, ecs_id_record_elem_t *elem) { ecs_component_record_t *prev = elem->prev; ecs_component_record_t *next = elem->next; ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_elem_t *prev_elem = flecs_component_elem(cr, elem, prev); prev_elem->next = next; if (next) { ecs_id_record_elem_t *next_elem = flecs_component_elem(cr, elem, next); next_elem->prev = prev; } } static void flecs_insert_id_elem( ecs_world_t *world, ecs_component_record_t *cr, ecs_id_t wildcard, ecs_component_record_t *wcr) { ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); if (!wcr) { wcr = flecs_components_ensure(world, wildcard); } ecs_assert(wcr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *pair = cr->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(wcr, cr, &pair->first); } else { ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_component_elem_insert(wcr, cr, &pair->second); if (cr->flags & EcsIdTraversable) { flecs_component_elem_insert(wcr, cr, &pair->trav); } } } static void flecs_remove_id_elem( ecs_component_record_t *cr, ecs_id_t wildcard) { ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *pair = cr->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_remove(cr, &pair->first); } else { ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); if (pair->second.prev) { flecs_component_elem_remove(cr, &pair->second); } if (cr->flags & EcsIdTraversable) { flecs_component_elem_remove(cr, &pair->trav); } } } 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 t = ECS_PAIR_SECOND(id); if (ECS_IS_VALUE_PAIR(id)) { id = ecs_value_pair(r, t); } else { if (r == EcsAny) { r = EcsWildcard; } if (t == EcsAny) { t = EcsWildcard; } id = ecs_pair(r, t); } } return id; } void flecs_component_init_sparse( ecs_world_t *world, ecs_component_record_t *cr) { if (!ecs_id_is_wildcard(cr->id)) { if (!cr->sparse) { if (cr->flags & EcsIdSparse) { cr->sparse = flecs_walloc_t(world, ecs_sparse_t); if (cr->type_info) { flecs_sparse_init(cr->sparse, NULL, NULL, cr->type_info->size); } else { flecs_sparse_init(cr->sparse, NULL, NULL, 0); } } } if (cr->id < FLECS_HI_COMPONENT_ID) { world->non_trivial_lookup[cr->id] |= EcsNonTrivialIdSparse; } } else if (ECS_IS_PAIR(cr->id)) { if (cr->flags & EcsIdDontFragment) { if (cr->sparse) { ecs_assert(flecs_sparse_count(cr->sparse) == 0, ECS_INTERNAL_ERROR, NULL); flecs_sparse_fini(cr->sparse); } else { cr->sparse = flecs_walloc_t(world, ecs_sparse_t); } if (cr->flags & EcsIdExclusive) { flecs_sparse_init_t(cr->sparse, NULL, NULL, ecs_entity_t); } else { flecs_sparse_init_t(cr->sparse, NULL, NULL, ecs_type_t); } } } } void flecs_component_record_init_dont_fragment( ecs_world_t *world, ecs_component_record_t *cr) { if (world->cr_non_fragmenting_head) { world->cr_non_fragmenting_head->non_fragmenting.prev = cr; } cr->non_fragmenting.next = world->cr_non_fragmenting_head; world->cr_non_fragmenting_head = cr; if (cr->id < FLECS_HI_COMPONENT_ID) { world->non_trivial_lookup[cr->id] |= EcsNonTrivialIdNonFragmenting; world->non_trivial_set[cr->id] = true; } flecs_component_init_sparse(world, cr); ecs_vec_init_t(&world->allocator, &cr->dont_fragment_tables, uint64_t, 0); } static void flecs_component_record_fini_dont_fragment( ecs_world_t *world, ecs_component_record_t *cr) { if (world->cr_non_fragmenting_head == cr) { world->cr_non_fragmenting_head = cr->non_fragmenting.next; } if (cr->non_fragmenting.prev) { cr->non_fragmenting.prev->non_fragmenting.next = cr->non_fragmenting.next; } if (cr->non_fragmenting.next) { cr->non_fragmenting.next->non_fragmenting.prev = cr->non_fragmenting.prev; } int32_t i, count = ecs_vec_count(&cr->dont_fragment_tables); uint64_t *tables = ecs_vec_first(&cr->dont_fragment_tables); for (i = 0; i < count; i ++) { uint64_t table_id = tables[i]; ecs_table_t *table = NULL; if (table_id) { table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, table_id); } else { table = &world->store.root; } if (table) { flecs_table_clear_edges_for_id(world, table, cr->id); } else { /* Table was deleted */ } } ecs_vec_fini_t(&world->allocator, &cr->dont_fragment_tables, uint64_t); } void flecs_component_record_init_exclusive( ecs_world_t *world, ecs_component_record_t *cr) { flecs_component_init_sparse(world, cr); } static void flecs_component_fini_sparse( ecs_world_t *world, ecs_component_record_t *cr) { if (cr->sparse) { ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); if (cr->flags & EcsIdDontFragment) { flecs_component_sparse_remove_all(world, cr); } flecs_sparse_fini(cr->sparse); flecs_wfree_t(world, ecs_sparse_t, cr->sparse); } } static ecs_flags32_t flecs_component_event_flags( const ecs_world_t *world, ecs_id_t id) { const 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; result |= flecs_observers_exist(o, id, EcsWildcard) * ( EcsIdHasOnAdd |EcsIdHasOnRemove |EcsIdHasOnSet |EcsIdHasOnTableCreate |EcsIdHasOnTableDelete); return result; } void flecs_component_ordered_children_init( ecs_world_t *world, ecs_component_record_t *cr) { cr->flags |= EcsIdOrderedChildren; flecs_ordered_children_init(world, cr); ecs_table_cache_iter_t it; if (flecs_table_cache_all_iter(&cr->cache, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { tr->hdr.table->flags |= EcsTableHasOrderedChildren; } } } static ecs_flags32_t flecs_component_get_flags_intern( const ecs_world_t *world, ecs_id_t id, ecs_entity_t rel, ecs_entity_t tgt, const ecs_type_info_t *ti) { ecs_flags32_t result = 0; if (id & ECS_ID_FLAGS_MASK) { if ((id & ECS_ID_FLAGS_MASK) != ECS_PAIR) { return 0; } } ecs_record_t *r = flecs_entities_get_any(world, rel); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; result = table->trait_flags; if (tgt) { if (ti && (ti->component == tgt)) { /* Target determines the type, so unset storage flags */ result &= ~EcsIdSparse; result &= ~EcsIdDontFragment; if (ecs_owns_id(world, tgt, EcsSparse)) { result |= EcsIdSparse; } if (ecs_owns_id(world, tgt, EcsDontFragment)) { result |= EcsIdDontFragment; } } } else { /* Disable flags that only apply to pairs */ result &= ~(EcsIdExclusive|EcsIdTraversable|EcsIdPairIsTag|EcsIdIsTransitive); } return result; } ecs_flags32_t flecs_component_get_flags( const ecs_world_t *world, ecs_id_t id) { const ecs_component_record_t *cr = flecs_components_get(world, id); if (cr) { return cr->flags; } const ecs_type_info_t *ti = flecs_determine_type_info_for_component(world, id); ecs_entity_t rel = 0, tgt = 0; if (ECS_IS_PAIR(id)) { rel = ECS_PAIR_FIRST(id); rel = flecs_entities_get_alive(world, rel); tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); } else { rel = id & ECS_COMPONENT_MASK; ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); } return flecs_component_get_flags_intern(world, id, rel, tgt, ti); } static void flecs_component_record_check_constraints( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t rel, ecs_entity_t tgt) { (void)world; (void)cr; (void)rel; (void)tgt; #ifdef FLECS_DEBUG if (ECS_HAS_ID_FLAG(cr->id, PAIR) && !ECS_IS_PAIR(cr->id)) { return; } if (ECS_IS_PAIR(cr->id)) { /* Internal flag records use (EcsFlag, X). These should not be * validated as regular relationship/target pairs. */ if (rel == EcsFlag) { return; } if (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)) { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' as target" " in pair '%s': '%s' has the Relationship trait", flecs_errstr(ecs_get_path(world, tgt)), flecs_errstr_1(ecs_id_str(world, cr->id)), flecs_errstr_2(ecs_get_path(world, tgt))); } } } if (ecs_has_id(world, rel, EcsTarget)) { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' as relationship " "in pair '%s': '%s' has the Target trait", flecs_errstr(ecs_get_path(world, rel)), flecs_errstr_1(ecs_id_str(world, cr->id)), flecs_errstr_2(ecs_get_path(world, rel))); } if (tgt && !ecs_id_is_wildcard(tgt)) { /* 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)) { if (oneof == rel) { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' as target in pair '%s': " "relationship '%s' has the OneOf trait and '%s' " "is not a child of '%s'", flecs_errstr(ecs_get_path(world, tgt)), flecs_errstr_1(ecs_id_str(world, cr->id)), flecs_errstr_2(ecs_get_path(world, rel)), flecs_errstr_3(ecs_get_path(world, tgt)), flecs_errstr_4(ecs_get_path(world, rel))); } else { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' as target in pair '%s': " "relationship '%s' has (OneOf, %s) and '%s' " "is not a child of '%s'", flecs_errstr(ecs_get_path(world, tgt)), flecs_errstr_1(ecs_id_str(world, cr->id)), flecs_errstr_2(ecs_get_path(world, rel)), flecs_errstr_3(ecs_get_path(world, oneof)), flecs_errstr_4(ecs_get_path(world, tgt)), flecs_errstr_5(ecs_get_path(world, oneof))); } } } /* Check if we're not trying to inherit from a final target */ if (rel == EcsIsA) { if (ecs_has_id(world, tgt, EcsFinal)) { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot add '(IsA, %s)': '%s' has the Final trait", flecs_errstr(ecs_get_path(world, tgt)), flecs_errstr_1(ecs_get_path(world, tgt))); } if (flecs_component_is_trait_locked(world, tgt)) { if (!ecs_has_id(world, tgt, EcsInheritable) && !ecs_has_pair(world, tgt, EcsIsA, EcsWildcard)) { ecs_throw(ECS_INVALID_OPERATION, "cannot add '(IsA, %s)': '%s' is already queried for", flecs_errstr(ecs_get_path(world, tgt)), flecs_errstr_1(ecs_get_path(world, tgt))); } } } } } else { bool is_tgt = false; if (ecs_has_id(world, rel, EcsRelationship) || (is_tgt = ecs_has_id(world, rel, EcsTarget))) { if (is_tgt) { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' by itself: it has the Target trait and " "must be used in pair with relationship", flecs_errstr(ecs_get_path(world, rel))); } else { ecs_throw(ECS_CONSTRAINT_VIOLATED, "cannot use '%s' by itself: it has the Relationship trait " "and must be used in pair with target", flecs_errstr(ecs_get_path(world, rel))); } } } error: return; #endif } static ecs_component_record_t* flecs_component_new( ecs_world_t *world, ecs_id_t id) { ecs_component_record_t *cr, *cr_t = NULL; ecs_id_t hash = flecs_component_hash(id); cr = flecs_bcalloc_w_dbg_info( &world->allocators.component_record, "ecs_component_record_t"); if (hash >= FLECS_HI_ID_RECORD_ID) { ecs_map_insert_ptr(&world->id_index_hi, hash, cr); } else { world->id_index_lo[hash] = cr; } ecs_table_cache_init(world, &cr->cache); cr->id = id; cr->refcount = 1; bool is_wildcard = ecs_id_is_wildcard(id); bool is_pair = ECS_IS_PAIR(id); bool is_value_pair = ECS_IS_VALUE_PAIR(id); ecs_entity_t rel = 0, tgt = 0; ecs_table_t *tgt_table = NULL; ecs_record_t *tgt_r = NULL; if (is_pair) { cr->pair = flecs_bcalloc_w_dbg_info( &world->allocators.pair_record, "ecs_pair_record_t"); cr->pair->reachable.current = -1; rel = ECS_PAIR_FIRST(id); if (!is_value_pair) { tgt = ECS_PAIR_SECOND(id); } /* Link with (R, *) record so we can easily find all matching pairs. */ if (!is_wildcard && (rel != EcsFlag)) { ecs_id_t parent_id = ecs_pair(rel, EcsWildcard); ecs_component_record_t *cr_r = flecs_components_ensure( world, parent_id); cr->pair->parent = cr_r; cr->flags = cr_r->flags; /* If pair is not a wildcard, append it to wildcard lists. These * allow for quickly enumerating all relationships for a target, * or all targets for a relationship. */ flecs_insert_id_elem(world, cr, parent_id, cr_r); if (tgt) { ecs_id_t wc_tgt = ecs_pair(EcsWildcard, tgt); cr_t = flecs_components_ensure(world, wc_tgt); flecs_insert_id_elem(world, cr, wc_tgt, cr_t); } } /* Relationship target can be 0, as tables without a ChildOf * relationship are added to the (ChildOf, 0) component record */ if (tgt) { ecs_entity_t alive_tgt = flecs_entities_get_alive(world, tgt); ecs_assert(alive_tgt != 0, ECS_INVALID_PARAMETER, "target '%s' of pair '%s' is not alive", flecs_errstr(ecs_id_str(world, tgt)), flecs_errstr_1(ecs_id_str(world, cr->id))); tgt = alive_tgt; if (tgt != EcsWildcard) { tgt_r = flecs_entities_get(world, tgt); ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); tgt_table = tgt_r->table; /* Enable flag that indicates entity is a target. */ flecs_record_add_flag(tgt_r, EcsEntityIsTarget); /* ChildOf records can be created often and since we already know * the traits a ChildOf record will have, set them directly. */ if (rel == EcsChildOf) { /* Add flag to indicate that children are prefab children. * This is used to determine whether to build the lookup map * that allows for: * instance_child = instance.target(prefab_child); */ if (tgt_table->flags & EcsTableIsPrefab) { cr->flags |= EcsIdPrefabChildren; } /* Check if we should keep a list of ordered children for * parent */ if (ecs_table_has_id( world, tgt_table, EcsOrderedChildren)) { flecs_component_ordered_children_init(world, cr); } flecs_component_update_childof_depth( world, cr, tgt, tgt_r); cr->flags |= EcsIdOnDeleteTargetDelete | EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdPairIsTag | EcsIdExclusive; if (tgt && tgt != EcsWildcard) { /* (ChildOf, tgt) pairs are always traversable. */ ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); } } } } } else { rel = id & ECS_COMPONENT_MASK; ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); } /* Wildcards don't need trait flags, ChildOf is already initialized */ if (rel != EcsWildcard && rel != EcsChildOf) { rel = flecs_entities_get_alive(world, rel); cr->type_info = flecs_determine_type_info_for_component(world, id); cr->flags |= flecs_component_get_flags_intern( world, id, rel, tgt, cr->type_info); /* Set flag that indicates entity is used as component/relationship. */ flecs_add_flag(world, rel, EcsEntityIsId); if (tgt && tgt != EcsWildcard) { if (cr->flags & EcsIdTraversable) { /* Flag used to determine if target should be traversed when * propagating events or with traversal queries */ ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); } } /* Initialize Sparse storage */ if (cr->flags & EcsIdSparse) { flecs_component_init_sparse(world, cr); } /* Initialize DontFragment storage */ if (cr->flags & EcsIdDontFragment) { flecs_component_record_init_dont_fragment(world, cr); if (cr_t) { /* Mark (*, tgt) record with HasDontFragment so that queries * can quickly detect if there are any non-fragmenting * records to consider for a (*, tgt) query. */ cr_t->flags |= EcsIdMatchDontFragment; } } } /* Set flags that indicate whether there are observers for component */ cr->flags |= flecs_component_event_flags(world, id); flecs_component_record_check_constraints(world, cr, rel, tgt); /* 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 (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); ecs_dbg_1("#[green]component record#[normal] %s #[green]created", id_str); ecs_os_free(id_str); } #ifdef FLECS_DEBUG_INFO cr->str = ecs_id_str(world, cr->id); #endif /* Update counters */ world->info.id_create_total ++; world->info.component_id_count += cr->type_info != NULL; world->info.tag_id_count += cr->type_info == NULL; world->info.pair_id_count += is_pair; return cr; } static void flecs_component_assert_empty( ecs_component_record_t *cr) { (void)cr; ecs_assert(flecs_table_cache_count(&cr->cache) == 0, ECS_INTERNAL_ERROR, NULL); } static void flecs_component_free( ecs_world_t *world, ecs_component_record_t *cr) { flecs_poly_assert(world, ecs_world_t); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = cr->id; flecs_component_assert_empty(cr); 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(cr, ecs_pair(rel, EcsWildcard)); if (!ECS_IS_VALUE_PAIR(id)) { flecs_remove_id_elem(cr, 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 = cr->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 = cr->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(); } } /* Cleanup sparse storage */ flecs_component_fini_sparse(world, cr); if (cr->flags & EcsIdDontFragment) { flecs_component_record_fini_dont_fragment(world, cr); } /* Update counters */ world->info.id_delete_total ++; world->info.pair_id_count -= ECS_IS_PAIR(id); world->info.component_id_count -= cr->type_info != NULL; world->info.tag_id_count -= cr->type_info == NULL; /* Unregister the component record from the world & free resources */ ecs_table_cache_fini(&cr->cache); if (cr->pair) { flecs_ordered_children_fini(world, cr); flecs_name_index_free(cr->pair->name_index); ecs_vec_fini_t(&world->allocator, &cr->pair->reachable.ids, ecs_reachable_elem_t); flecs_bfree_w_dbg_info(&world->allocators.pair_record, cr->pair, "ecs_pair_record_t"); } 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; } #ifdef FLECS_DEBUG_INFO ecs_os_free(cr->str); #endif flecs_bfree_w_dbg_info(&world->allocators.component_record, cr, "ecs_component_record_t"); if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); ecs_dbg_1("#[green]component record#[normal] %s #[red]deleted", id_str); ecs_os_free(id_str); } } ecs_component_record_t* flecs_components_ensure( ecs_world_t *world, ecs_id_t id) { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { cr = flecs_component_new(world, id); } return cr; } 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->cr_isa_wildcard; } else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { return world->cr_childof_wildcard; } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { return world->cr_identifier_name; } ecs_id_t hash = flecs_component_hash(id); ecs_component_record_t *cr = NULL; if (hash >= FLECS_HI_ID_RECORD_ID) { cr = ecs_map_get_deref(&world->id_index_hi, ecs_component_record_t, hash); } else { cr = world->id_index_lo[hash]; } return cr; } void flecs_component_claim( ecs_world_t *world, ecs_component_record_t *cr) { (void)world; cr->refcount ++; } int32_t flecs_component_release( ecs_world_t *world, ecs_component_record_t *cr) { int32_t rc = -- cr->refcount; ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, flecs_errstr(ecs_id_str(world, cr->id))); if (!rc) { flecs_component_free(world, cr); } return rc; } bool flecs_component_release_tables( ecs_world_t *world, ecs_component_record_t *cr) { bool remaining = false; ecs_table_cache_iter_t it; if (flecs_table_cache_all_iter(&cr->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->keep) { remaining = true; continue; } if (ecs_table_count(table)) { remaining = true; continue; } /* Release current table */ flecs_table_fini(world, tr->hdr.table); } } return remaining; } bool flecs_component_set_type_info( ecs_world_t *world, ecs_component_record_t *cr, const ecs_type_info_t *ti) { bool is_wildcard = ecs_id_is_wildcard(cr->id); if (!is_wildcard) { if (ti) { if (!cr->type_info) { world->info.tag_id_count --; world->info.component_id_count ++; } } else { if (cr->type_info) { world->info.tag_id_count ++; world->info.component_id_count --; } } } bool changed = cr->type_info != ti; cr->type_info = ti; return changed; } const ecs_type_info_t* flecs_component_get_type_info( const ecs_component_record_t *cr) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); return cr->type_info; } ecs_hashmap_t* flecs_component_name_index_ensure( ecs_world_t *world, ecs_component_record_t *cr) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_hashmap_t *map = cr->pair->name_index; if (!map) { map = cr->pair->name_index = flecs_name_index_new(&world->allocator); } return map; } ecs_hashmap_t* flecs_component_name_index_get( const ecs_world_t *world, ecs_component_record_t *cr) { flecs_poly_assert(world, ecs_world_t); (void)world; return cr->pair->name_index; } const ecs_table_record_t* flecs_component_get_table( const ecs_component_record_t *cr, const ecs_table_t *table) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_table_cache_get(&cr->cache, table); } void flecs_components_init( ecs_world_t *world) { /* Cache often used id records on world */ world->cr_wildcard = flecs_components_ensure(world, EcsWildcard); world->cr_wildcard_wildcard = flecs_components_ensure(world, ecs_pair(EcsWildcard, EcsWildcard)); world->cr_any = flecs_components_ensure(world, EcsAny); world->cr_isa_wildcard = flecs_components_ensure(world, ecs_pair(EcsIsA, EcsWildcard)); } 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, so 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)); } int32_t i; for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { ecs_component_record_t *cr = world->id_index_lo[i]; if (cr) { flecs_component_release(world, cr); } } ecs_assert(ecs_map_count(&world->id_index_hi) == 0, ECS_INTERNAL_ERROR, NULL); ecs_map_fini(&world->id_index_hi); ecs_os_free(world->id_index_lo); } static ecs_flags32_t flecs_id_flags( ecs_world_t *world, ecs_id_t id) { ecs_flags32_t cr_flags = flecs_component_get_flags(world, id); ecs_flags32_t extra_flags = 0; if (cr_flags & EcsIdOnInstantiateInherit) { extra_flags |= EcsIdHasOnAdd|EcsIdHasOnRemove; } return cr_flags|extra_flags; } 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)); } if (first == EcsIsA) { result |= EcsIdHasOnAdd|EcsIdHasOnRemove; } } else if (id != EcsWildcard) { result |= flecs_id_flags(world, EcsWildcard); } return result; } void flecs_component_shrink( ecs_component_record_t *cr) { ecs_map_reclaim(&cr->cache.index); ecs_pair_record_t *pr = cr->pair; if (pr) { if (pr->name_index) { ecs_map_reclaim(&pr->name_index->impl); } } } void flecs_component_delete_sparse( ecs_world_t *world, ecs_component_record_t *cr) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); int32_t i, count = flecs_sparse_count(cr->sparse); if (!count) { return; } const uint64_t *entities = flecs_sparse_ids(cr->sparse); ecs_entity_t *to_delete = ecs_os_malloc_n(ecs_entity_t, count); ecs_os_memcpy_n(to_delete, entities, ecs_entity_t, count); for (i = 0; i < count; i ++) { ecs_delete(world, to_delete[i]); } ecs_os_free(to_delete); } ecs_component_record_t* flecs_component_first_next( ecs_component_record_t *cr) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); return cr->pair->first.next; } ecs_component_record_t* flecs_component_second_next( ecs_component_record_t *cr) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); return cr->pair->second.next; } ecs_component_record_t* flecs_component_trav_next( ecs_component_record_t *cr) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); return cr->pair->trav.next; } ecs_component_record_t* flecs_components_next( const ecs_world_t *world, ecs_components_iter_t *it) { if (!it->hi) { while (it->lo < FLECS_HI_ID_RECORD_ID) { ecs_component_record_t *cur = world->id_index_lo[it->lo ++]; if (cur) { return cur; } } it->hi = true; it->map_it = ecs_map_iter(&world->id_index_hi); } if (!ecs_map_next(&it->map_it)) { return NULL; } return ecs_map_ptr(&it->map_it); } bool flecs_component_iter( const ecs_component_record_t *cr, ecs_table_cache_iter_t *iter_out) { return flecs_table_cache_all_iter(&cr->cache, iter_out); } const ecs_table_record_t* flecs_component_next( ecs_table_cache_iter_t *iter) { return flecs_table_cache_next(iter, ecs_table_record_t); } ecs_id_t flecs_component_get_id( const ecs_component_record_t *cr) { ecs_assert(cr != NULL, ECS_INVALID_PARAMETER, NULL); return cr->id; } ecs_parent_record_t* flecs_component_get_parent_record( const ecs_component_record_t *cr, const ecs_table_t *table) { ecs_assert(cr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *pair = cr->pair; if (!pair) { return NULL; } if (!ecs_map_is_init(&pair->children_tables)) { return NULL; } return (ecs_parent_record_t*)ecs_map_get(&pair->children_tables, table->id); } int32_t flecs_component_get_childof_depth( const ecs_component_record_t *cr) { ecs_assert(ECS_IS_PAIR(cr->id), ECS_INVALID_PARAMETER, NULL); ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsChildOf, ECS_INVALID_PARAMETER, NULL); ecs_assert(cr->pair != NULL, ECS_INVALID_PARAMETER, NULL); return cr->pair->depth; } static void flecs_entities_update_childof_depth( ecs_world_t *world, ecs_component_record_t *cr) { if (cr->flags & EcsIdOrderedChildren) { ecs_entity_t *entities = ecs_vec_first(&cr->pair->ordered_children); int32_t i, count = ecs_vec_count(&cr->pair->ordered_children); for (i = 0; i < count; i ++) { ecs_entity_t tgt = entities[i]; ecs_record_t *r = flecs_entities_try(world, tgt); if (!r) { continue; } ecs_table_t *table = r->table; if (table->flags & EcsTableHasParent) { ecs_add_id(world, tgt, ecs_value_pair(EcsParentDepth, cr->pair->depth)); } if (!(r->row & EcsEntityIsTraversable)) { continue; } ecs_component_record_t *tgt_cr = flecs_components_get( world, ecs_childof(tgt)); if (!tgt_cr) { continue; } flecs_component_update_childof_depth(world, tgt_cr, tgt, r); } return; } /* If the component record doesn't have a children vector, iterate tables. */ ecs_table_cache_iter_t it; flecs_component_iter(cr, &it); const ecs_table_record_t *tr; while ((tr = flecs_component_next(&it))) { ecs_table_t *table = tr->hdr.table; if (!(table->flags & EcsTableHasTraversable)) { continue; } const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t tgt = entities[i]; ecs_component_record_t *tgt_cr = flecs_components_get( world, ecs_childof(tgt)); if (!tgt_cr) { continue; } ecs_record_t *r = flecs_entities_get(world, tgt); flecs_component_update_childof_depth(world, tgt_cr, tgt, r); } } } void flecs_component_update_childof_w_depth( ecs_world_t *world, ecs_component_record_t *cr, int32_t depth) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *pair = cr->pair; ecs_assert(pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(depth < (FLECS_DAG_DEPTH_MAX + 1), ECS_INVALID_OPERATION, "possible cycle detected in ChildOf/Parent hierarchy"); if (cr->flags & EcsIdMarkedForDelete) { return; } /* If depth changed, propagate downwards */ if (depth != pair->depth) { pair->depth = depth; flecs_entities_update_childof_depth(world, cr); } } void flecs_component_update_childof_depth( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t tgt, const ecs_record_t *tgt_r) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_SECOND(cr->id) == (uint32_t)tgt, ECS_INTERNAL_ERROR, NULL); if (cr->flags & EcsIdMarkedForDelete) { return; } int32_t new_depth; if (tgt) { ecs_table_t *tgt_table = tgt_r->table; if (tgt_table->flags & EcsTableHasChildOf) { ecs_pair_record_t *tgt_childof_pr = flecs_table_get_childof_pr( world, tgt_table); new_depth = tgt_childof_pr->depth + 1; } else if (tgt_table->flags & EcsTableHasParent) { int32_t column = tgt_table->component_map[ecs_id(EcsParent)]; ecs_assert(column > 0, ECS_INTERNAL_ERROR, NULL); EcsParent *data = tgt_table->data.columns[column - 1].data; ecs_entity_t parent = data[ECS_RECORD_TO_ROW(tgt_r->row)].value; if (!parent) { new_depth = 0; } else { ecs_component_record_t *cr_parent = flecs_components_get(world, ecs_childof(parent)); ecs_assert(cr_parent != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr_parent->pair != NULL, ECS_INTERNAL_ERROR, NULL); new_depth = cr_parent->pair->depth + 1; } } else { new_depth = 1; } } else { new_depth = 0; } flecs_component_update_childof_w_depth(world, cr, new_depth); } #include ecs_entity_index_page_t* flecs_entity_index_ensure_page( ecs_entity_index_t *index, uint32_t id) { int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); if (page_index >= ecs_vec_count(&index->pages)) { ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages, ecs_entity_index_page_t*, page_index + 1); } ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index); ecs_entity_index_page_t *page = *page_ptr; if (!page) { page = *page_ptr = ecs_os_calloc_t(ecs_entity_index_page_t); ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL); } return page; } void flecs_entity_index_init( ecs_allocator_t *allocator, ecs_entity_index_t *index) { index->allocator = allocator; index->alive_count = 1; index->active_range = NULL; 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); ecs_vec_init_t(allocator, &index->ranges, ecs_entity_range_t*, 0); } 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 ++) { ecs_os_free(pages[i]); } ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); /* Free entity id ranges */ { int32_t r, range_count = ecs_vec_count(&index->ranges); ecs_entity_range_t **ranges = ecs_vec_first_t(&index->ranges, ecs_entity_range_t*); for (r = 0; r < range_count; r ++) { ecs_vec_fini_t(index->allocator, &ranges[r]->recycled, uint64_t); flecs_free_t(index->allocator, ecs_entity_range_t, ranges[r]); } ecs_vec_fini_t(index->allocator, &index->ranges, ecs_entity_range_t*); } } 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, "entity is not alive"); ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, ECS_INVALID_PARAMETER, "mismatching liveness 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; } ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index)[0]; if (!page) { return NULL; } ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; if (!r->dense) { return NULL; } return r; } ecs_record_t* flecs_entity_index_try_get( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { if (r->dense >= index->alive_count) { return NULL; } if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { return NULL; } } return r; } ecs_record_t* flecs_entity_index_ensure( ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; int32_t dense = r->dense; if (dense) { /* Entity is already alive, nothing to be done */ if (dense < index->alive_count) { ecs_assert( ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, ECS_INTERNAL_ERROR, NULL); return r; } } else { /* Entity doesn't have a dense index yet */ ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; r->dense = dense = ecs_vec_count(&index->dense) - 1; index->max_id = id > index->max_id ? id : index->max_id; } ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); /* Entity is not alive, swap with the first not-alive element */ uint64_t *ids = ecs_vec_first(&index->dense); uint64_t e_swap = ids[index->alive_count]; ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); ecs_assert(r_swap->dense == index->alive_count, ECS_INTERNAL_ERROR, NULL); r_swap->dense = dense; r->dense = index->alive_count; ids[dense] = e_swap; ids[index->alive_count ++] = entity; ecs_assert(flecs_entity_index_is_alive(index, entity), ECS_INTERNAL_ERROR, NULL); return r; } static void flecs_entity_index_remove_not_alive( ecs_entity_index_t *index, ecs_record_t *r) { int32_t not_alive = index->alive_count; int32_t last = ecs_vec_count(&index->dense) - 1; if (not_alive != last) { uint64_t *ids = ecs_vec_first_t(&index->dense, uint64_t); uint64_t e_last = ids[last]; ecs_record_t *r_last = flecs_entity_index_get_any(index, e_last); r_last->dense = not_alive; ids[not_alive] = e_last; } ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, last); r->dense = 0; } static ecs_entity_range_t* flecs_entity_index_find_range( ecs_entity_index_t *index, uint32_t id) { int32_t count = ecs_vec_count(&index->ranges); if (!count) { return NULL; } ecs_entity_range_t **ranges = ecs_vec_first_t(&index->ranges, ecs_entity_range_t*); int32_t lo = 0, hi = count - 1; while (lo <= hi) { int32_t mid = (lo + hi) / 2; ecs_entity_range_t *r = ranges[mid]; if (id < r->min) { hi = mid - 1; } else if (r->max && id > r->max) { lo = mid + 1; } else { return r; } } return NULL; } static ecs_record_t* flecs_entity_index_remove_intern( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get(index, entity); if (!r) { return NULL; } int32_t dense = r->dense; int32_t i_swap = -- index->alive_count; uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); uint64_t e_swap = e_swap_ptr[0]; ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); r_swap->dense = dense; r->table = 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); return r; } void flecs_entity_index_remove( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_remove_intern(index, entity); if (!r) { /* Entity was not alive, nothing else to be done. */ return; } ecs_entity_range_t *active = index->active_range; if (!active) { /* If no entity range is active, we're done. */ return; } uint32_t id = (uint32_t)entity; if (id >= active->min && (!active->max || id <= active->max)) { /* Id falls within the active range, also nothing else to be done. */ return; } /* Entity falls outside of the active range. We now need to remove the * recycled id from the dense vector, and move it to the recycled id vector * of the correct range. */ flecs_entity_index_remove_not_alive(index, r); ecs_entity_range_t *range = flecs_entity_index_find_range(index, id); if (range) { ecs_vec_append_t( index->allocator, &range->recycled, uint64_t)[0] = ECS_GENERATION_INC(entity); } } void flecs_entity_index_set_range( ecs_entity_index_t *index, ecs_entity_range_t *range) { #ifdef FLECS_DEBUG /* Verify that the range was created by ecs_entity_range_new */ { int32_t i, count = ecs_vec_count(&index->ranges); ecs_entity_range_t **ranges = ecs_vec_first_t(&index->ranges, ecs_entity_range_t*); bool found = false; for (i = 0; i < count; i ++) { if (ranges[i] == range) { found = true; break; } } ecs_assert(found, ECS_INVALID_PARAMETER, "range was not created with ecs_entity_range_new"); (void)found; } #endif ecs_allocator_t *a = index->allocator; ecs_entity_range_t *prev = index->active_range; int32_t alive_count = index->alive_count; int32_t dense_count = ecs_vec_count(&index->dense); int32_t not_alive_count = dense_count - alive_count; uint64_t *ids = ecs_vec_first_t(&index->dense, uint64_t); /* Save current not-alive entries to previous range (if any) */ if (prev) { if (not_alive_count > 0) { ecs_vec_set_count_t(a, &prev->recycled, uint64_t, not_alive_count); uint64_t *dst = ecs_vec_first_t(&prev->recycled, uint64_t); ecs_os_memcpy_n(dst, &ids[alive_count], uint64_t, not_alive_count); } else { ecs_vec_set_count_t(a, &prev->recycled, uint64_t, 0); } prev->cur = index->max_id; } /* Clear not-alive entries from entity index */ { int32_t i; for (i = alive_count; i < dense_count; i ++) { uint32_t id = (uint32_t)ids[i]; int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); if (page_index < ecs_vec_count(&index->pages)) { ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index)[0]; if (page) { page->records[id & FLECS_ENTITY_PAGE_MASK].dense = 0; } } } ecs_vec_set_count_t(a, &index->dense, uint64_t, alive_count); } /* Load new range's recycled entries into entity index not-alive section */ { int32_t recycled_count = ecs_vec_count(&range->recycled); if (recycled_count > 0) { int32_t new_dense_count = alive_count + recycled_count; ecs_vec_set_count_t(a, &index->dense, uint64_t, new_dense_count); ids = ecs_vec_first_t(&index->dense, uint64_t); uint64_t *src = ecs_vec_first_t(&range->recycled, uint64_t); ecs_os_memcpy_n(&ids[alive_count], src, uint64_t, recycled_count); int32_t i; for (i = 0; i < recycled_count; i ++) { uint32_t id = (uint32_t)src[i]; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = alive_count + i; ecs_assert(r->table == NULL, ECS_INTERNAL_ERROR, NULL); } ecs_vec_set_count_t(a, &range->recycled, uint64_t, 0); } } index->max_id = range->cur; index->active_range = range; } void flecs_entity_index_make_alive( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity; } } uint64_t flecs_entity_index_get_alive( 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 ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; } } return 0; } bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity) { return flecs_entity_index_try_get(index, entity) != NULL; } bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; ecs_record_t *r = flecs_entity_index_try_get_any(index, id); if (!r || !r->dense) { /* Doesn't exist yet, so is valid */ return true; } /* If the id exists, it must be alive */ return r->dense < index->alive_count; } bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity) { return flecs_entity_index_try_get_any(index, entity) != NULL; } uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index) { if (index->alive_count != ecs_vec_count(&index->dense)) { /* Recycle id */ return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; } /* Create new id */ uint32_t id = (uint32_t)++ index->max_id; ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, "entity id overflow after creating new entity " "(value is %" PRIu64 ", cannot exceed %u)", index->max_id, UINT32_MAX); /* 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); ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = index->alive_count ++; ecs_assert(index->alive_count == ecs_vec_count(&index->dense), ECS_INTERNAL_ERROR, NULL); return id; } uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count) { int32_t alive_count = index->alive_count; int32_t new_count = alive_count + count; int32_t dense_count = ecs_vec_count(&index->dense); if (new_count < dense_count) { /* Recycle ids */ index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } /* Allocate new ids */ ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); int32_t i, to_add = new_count - dense_count; for (i = 0; i < to_add; i ++) { uint32_t id = (uint32_t)++ index->max_id; ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, "entity id overflow after creating new entity " "(value is %" PRIu64 ", cannot exceed %u)", index->max_id, UINT32_MAX); /* 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); int32_t dense = dense_count + i; ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = dense; } index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size) { ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); } int32_t flecs_entity_index_count( const ecs_entity_index_t *index) { return index->alive_count - 1; } int32_t flecs_entity_index_size( const ecs_entity_index_t *index) { return ecs_vec_count(&index->dense) - 1; } int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index) { return ecs_vec_count(&index->dense) - index->alive_count; } void flecs_entity_index_clear( ecs_entity_index_t *index) { int32_t i, count = ecs_vec_count(&index->pages); ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, ecs_entity_index_page_t*); for (i = 0; i < count; i ++) { ecs_entity_index_page_t *page = pages[i]; if (page) { ecs_os_zeromem(page); } } ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); index->alive_count = 1; index->max_id = 0; } 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; } 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 * FLECS_ENTITY_PAGE_SIZE) + 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; } } } if (!has_alive) { ecs_os_free(pages[i]); pages[i] = NULL; } else { max_page_index = i; } } 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*); } const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index) { return ecs_vec_get_t(&index->dense, uint64_t, 1); } static void flecs_add_non_fragmenting_child_to_table( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t entity, const ecs_table_t *table) { ecs_map_init_if(&cr->pair->children_tables, &world->allocator); ecs_parent_record_t *elem = (ecs_parent_record_t*) ecs_map_ensure(&cr->pair->children_tables, table->id); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); /* Store id of first entity in table + the total number of entities in the * table for this parent so everything fits in a map element without having * to allocate. */ if (!elem->count) { elem->entity = (uint32_t)entity; if (table->flags & EcsTableIsDisabled) { cr->pair->disabled_tables ++; } if (table->flags & EcsTableIsPrefab) { cr->pair->prefab_tables ++; } } else { elem->entity = 0; } elem->count ++; } static void flecs_remove_non_fragmenting_child_from_table( ecs_component_record_t *cr, const ecs_table_t *table) { ecs_parent_record_t *elem = flecs_component_get_parent_record(cr, table); if (!elem) { return; } elem->count --; if (!elem->count) { ecs_map_remove(&cr->pair->children_tables, table->id); if (table->flags & EcsTableIsDisabled) { cr->pair->disabled_tables --; ecs_assert(cr->pair->disabled_tables >= 0, ECS_INTERNAL_ERROR, NULL); } if (table->flags & EcsTableIsPrefab) { cr->pair->prefab_tables --; ecs_assert(cr->pair->prefab_tables >= 0, ECS_INTERNAL_ERROR, NULL); } } } int flecs_add_non_fragmenting_child_w_records( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t entity, ecs_component_record_t *cr, const ecs_record_t *r) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); (void)parent; if (!(cr->flags & EcsIdOrderedChildren)) { flecs_component_ordered_children_init(world, cr); flecs_ordered_children_populate(world, cr); } ecs_check(parent != 0, ECS_INVALID_OPERATION, "cannot set Parent component with 0 entity"); ecs_check(ecs_is_alive(world, parent), ECS_INVALID_OPERATION, "cannot set Parent component to entity that is not alive"); flecs_ordered_entities_append(world, cr, entity); flecs_add_non_fragmenting_child_to_table(world, cr, entity, r->table); flecs_tree_spawner_assert_not_instantiated(world, parent); ecs_record_t *r_parent = flecs_entities_get(world, parent); if (r_parent->table->flags & EcsTableIsPrefab) { ecs_add_id(world, entity, EcsPrefab); } return 0; error: return -1; } static ecs_component_record_t* flecs_add_non_fragmenting_child( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t entity) { ecs_component_record_t *cr = flecs_components_ensure(world, ecs_pair(EcsChildOf, parent)); ecs_record_t *r = flecs_entities_get(world, entity); if (flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r)) { return NULL; } return cr; } static ecs_component_record_t* flecs_remove_non_fragmenting_child( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t entity) { if (!parent) { return NULL; } ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(EcsChildOf, parent)); if (!cr || (cr->flags & EcsIdMarkedForDelete)) { return NULL; } flecs_ordered_entities_remove(world, cr, entity); 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); flecs_remove_non_fragmenting_child_from_table(cr, table); flecs_tree_spawner_assert_not_instantiated(world, parent); return cr; } static void flecs_on_reparent_update_name( ecs_world_t *world, ecs_entity_t e, EcsIdentifier *name, ecs_entity_t parent_old, ecs_component_record_t *cr_parent_new) { if (name->value && name->index_hash) { /* Remove entity from name index of old parent */ ecs_component_record_t *old_cr = flecs_components_get( world, ecs_childof(parent_old)); if (old_cr) { ecs_hashmap_t *old_index = flecs_component_name_index_get(world, old_cr); if (old_index) { flecs_name_index_remove(old_index, e, name->index_hash); } } if (cr_parent_new) { /* Add entity to name index of new parent */ ecs_hashmap_t *new_index = flecs_component_name_index_ensure(world, cr_parent_new); flecs_name_index_ensure( new_index, e, name->value, name->length, name->hash); name->index = new_index; } else { name->index = NULL; } } } static void flecs_on_replace_parent(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsParent *old = ecs_field(it, EcsParent, 0); EcsParent *new = ecs_field(it, EcsParent, 1); ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(new != NULL, ECS_INTERNAL_ERROR, NULL); EcsIdentifier *names = NULL; if (it->table->flags & EcsTableHasName) { names = ecs_table_get_pair( world, it->table, EcsIdentifier, EcsName, it->offset); ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); } int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t old_parent = old[i].value; ecs_entity_t new_parent = new[i].value; /* This can happen when a child is parented to a parent that is deleted * in the same command queue. */ if (!flecs_entities_is_alive(world, new_parent)) { /* So cleanup code can see this is a child of a deleted parent */ old[i].value = new_parent; ecs_delete(world, e); continue; } #ifdef FLECS_DEBUG ecs_entity_t cur = new_parent; while (cur) { ecs_assert(cur != e, ECS_CYCLE_DETECTED, "cycle detected in Parent hierarchy"); cur = ecs_get_parent(world, cur); } #endif flecs_journal_begin(world, EcsJournalSetParent, e, &(ecs_type_t){ .count = 1, .array = &new_parent }, NULL); ecs_component_record_t *cr_old = flecs_remove_non_fragmenting_child(world, old_parent, e); ecs_component_record_t *cr_parent = flecs_add_non_fragmenting_child(world, new_parent, e); if (!cr_parent) { continue; } if (names) { flecs_on_reparent_update_name( world, e, &names[i], old_parent, cr_parent); } /* Write new parent value to component storage before ecs_add_id, as * it can trigger a table move that reads the parent value. */ old[i].value = new_parent; ecs_record_t *r = flecs_entities_get(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); int32_t depth = cr_parent->pair->depth; /* If the entity had a parent before, it has a ParentDepth pair that * matches the depth of the old parent. If the depth of the new parent * is the same, the pair doesn't have to be updated and neither do the * cached depths for the entity's children. */ if (!cr_old || cr_old->pair->depth != depth) { ecs_add_id(world, e, ecs_value_pair(EcsParentDepth, depth)); ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(e)); if (cr) { flecs_component_update_childof_w_depth(world, cr, depth + 1); } } if (r->row & EcsEntityIsTraversable) { ecs_id_t added = ecs_childof(new_parent); ecs_id_t removed = ecs_childof(old_parent); flecs_update_component_monitors(world, &(ecs_type_t){ .count = 1, .array = &added }, &(ecs_type_t) { .count = 1, .array = &removed }); } flecs_journal_end(); } } void flecs_on_non_fragmenting_child_move_add( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count) { ecs_assert(dst->flags & EcsTableHasParent, ECS_INTERNAL_ERROR, NULL); EcsParent *parents = ecs_table_get(world, dst, EcsParent, 0); int32_t i = row, end = i + count; for (; i < end; i ++) { ecs_entity_t e = ecs_table_entities(dst)[i]; ecs_entity_t p = parents[i].value; ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(p)); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); if (src && (src->flags & EcsTableHasParent)) { flecs_remove_non_fragmenting_child_from_table(cr, src); } flecs_add_non_fragmenting_child_to_table(world, cr, e, dst); } } void flecs_on_non_fragmenting_child_move_remove( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count, bool update_parent_records) { EcsIdentifier *names = NULL; if (src->flags & EcsTableHasName) { names = ecs_table_get_pair(world, src, EcsIdentifier, EcsName, 0); } EcsParent *parents = ecs_table_get(world, src, EcsParent, 0); int32_t i = row, end = i + count; for (; i < end; i ++) { ecs_entity_t e = ecs_table_entities(src)[i]; ecs_entity_t p = parents[i].value; if (!ecs_is_alive(world, p)) { continue; } ecs_component_record_t *cr = flecs_components_ensure( world, ecs_childof(p)); if (update_parent_records) { flecs_remove_non_fragmenting_child_from_table(cr, src); } if (dst && (dst->flags & EcsTableHasParent)) { if (update_parent_records) { flecs_add_non_fragmenting_child_to_table(world, cr, e, dst); } } else { flecs_ordered_entities_remove(world, cr, e); flecs_tree_spawner_assert_not_instantiated(world, p); if (names) { flecs_on_reparent_update_name(world, e, &names[i], p, NULL); } ecs_component_record_t *cr_e = flecs_components_get( world, ecs_childof(e)); if (cr_e) { flecs_component_update_childof_w_depth(world, cr_e, 1); } } } } void flecs_non_fragmenting_childof_reparent( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count) { ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); if (!src) { return; } ecs_pair_record_t *dst_pair = flecs_table_get_childof_pr(world, dst); ecs_assert(dst_pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *src_pair = flecs_table_get_childof_pr(world, src); int32_t dst_depth = dst_pair->depth; int32_t src_depth = 0; if (src_pair) { src_depth = src_pair->depth; } if (dst_depth == src_depth) { /* If src depth is dst depth, no need to update cached depth values */ return; } if (!ecs_table_has_traversable(src)) { /* If table doesn't contain any traversable entities (meaning there * can't be any parents in the table) there can't be any cached depth * values to update. */ return; } const ecs_entity_t *entities = ecs_table_entities(dst); int32_t i = row, end = i + count; for (; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(e)); if (!cr) { continue; } ecs_record_t *r = flecs_entities_get(world, e); flecs_component_update_childof_depth(world, cr, e, r); } } void flecs_non_fragmenting_childof_unparent( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count) { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *dst_pair = flecs_table_get_childof_pr(world, dst); ecs_pair_record_t *src_pair = flecs_table_get_childof_pr(world, src); ecs_assert(src_pair != NULL, ECS_INTERNAL_ERROR, NULL); int32_t dst_depth = dst_pair ? dst_pair->depth : 0; int32_t src_depth = src_pair->depth; /* If unparent is called there has to be a difference in depth, since the * parent is removed from an entity. */ ecs_assert(src_depth != dst_depth, ECS_INTERNAL_ERROR, NULL); (void)src_depth; (void)dst_depth; if (!ecs_table_has_traversable(src)) { /* If table doesn't contain any traversable entities (meaning there * aren't any parents in the table) there can't be any cached depth * values to update. */ return; } const ecs_entity_t *entities = ecs_table_entities(src); int32_t i = row, end = i + count; for (; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(e)); if (!cr || (cr->flags & EcsIdMarkedForDelete)) { continue; } else { /* Entity is a parent */ } /* Update depth to 1 if parent is removed */ flecs_component_update_childof_w_depth(world, cr, 1); } } bool flecs_component_has_non_fragmenting_childof( ecs_component_record_t *cr) { if (cr->flags & EcsIdOrderedChildren) { return ecs_map_count(&cr->pair->children_tables) != 0; } return false; } void flecs_bootstrap_parent_component( ecs_world_t *world) { flecs_type_info_init(world, EcsParent, { .ctor = flecs_default_ctor, .on_replace = flecs_on_replace_parent }); ecs_add_pair(world, ecs_id(EcsParent), EcsOnInstantiate, EcsDontInherit); } void flecs_ordered_children_init( ecs_world_t *world, ecs_component_record_t *cr) { ecs_vec_init_t( &world->allocator, &cr->pair->ordered_children, ecs_entity_t, 0); } void flecs_ordered_children_fini( ecs_world_t *world, ecs_component_record_t *cr) { ecs_vec_fini_t( &world->allocator, &cr->pair->ordered_children, ecs_entity_t); ecs_map_fini(&cr->pair->children_tables); } void flecs_ordered_children_populate( ecs_world_t *world, ecs_component_record_t *cr) { ecs_assert(ecs_vec_count(&cr->pair->ordered_children) == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); ecs_iter_t it = ecs_each_id(world, cr->id); while (ecs_each_next(&it)) { int32_t i; for (i = 0; i < it.count; i ++) { flecs_ordered_entities_append(world, cr, it.entities[i]); } } } void flecs_ordered_children_clear( ecs_component_record_t *cr) { ecs_vec_t *v = &cr->pair->ordered_children; ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); if (!(cr->flags & EcsIdMarkedForDelete)) { ecs_assert(!ecs_map_count(&cr->pair->children_tables), ECS_UNSUPPORTED, "cannot remove OrderedChildren trait from parent that has " "children which use the Parent component"); ecs_vec_clear(v); } } void flecs_ordered_entities_append( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t e) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_pair_record_t *pr = cr->pair; ecs_assert(pr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_append_t( &world->allocator, &pr->ordered_children, ecs_entity_t)[0] = e; if (cr->flags & EcsIdPrefabChildren) { /* Register index of prefab child so that it can be used to lookup * corresponding instance child. */ ecs_map_ensure(&world->prefab_child_indices, e)[0] = flecs_ito(uint64_t, ecs_vec_count(&pr->ordered_children) - 1); } else { ecs_assert( !ecs_owns_id(world, ecs_pair_second(world, cr->id), EcsPrefab), ECS_INTERNAL_ERROR, NULL); } } void flecs_ordered_children_set_prefab( ecs_world_t *world, ecs_component_record_t *cr) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); if (cr->flags & EcsIdPrefabChildren) { return; } cr->flags |= EcsIdPrefabChildren; ecs_vec_t *vec = &cr->pair->ordered_children; int32_t i, count = ecs_vec_count(vec); ecs_entity_t *entities = ecs_vec_first_t(vec, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_map_ensure(&world->prefab_child_indices, entities[i])[0] = flecs_ito(uint64_t, i); } } void flecs_ordered_entities_remove( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t e) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *vec = &cr->pair->ordered_children; int32_t i, count = ecs_vec_count(vec); ecs_entity_t *entities = ecs_vec_first_t(vec, ecs_entity_t); for (i = 0; i < count; i ++) { if (entities[i] == e) { ecs_vec_remove_ordered_t(vec, ecs_entity_t, i); if (cr->flags & EcsIdPrefabChildren) { ecs_map_remove(&world->prefab_child_indices, e); } break; } } } static void flecs_ordered_entities_unparent_internal( ecs_world_t *world, const ecs_table_t *entities_table, const ecs_table_t *table, int32_t row, int32_t count) { if (table && (table->flags & EcsTableHasOrderedChildren)) { ecs_component_record_t *cr = flecs_table_get_childof_cr(world, table); const ecs_entity_t *entities = ecs_table_entities(entities_table); int32_t i = row, end = row + count; for (; i < end; i ++) { ecs_entity_t e = entities[i]; flecs_ordered_entities_remove(world, cr, e); } } } void flecs_ordered_children_reparent( ecs_world_t *world, const ecs_table_t *dst, const ecs_table_t *src, int32_t row, int32_t count) { flecs_ordered_entities_unparent_internal(world, dst, src, row, count); if (dst->flags & EcsTableHasOrderedChildren) { ecs_component_record_t *cr = flecs_table_get_childof_cr(world, dst); const ecs_entity_t *entities = ecs_table_entities(dst); int32_t i = row, end = row + count; for (; i < end; i ++) { ecs_entity_t e = entities[i]; flecs_ordered_entities_append(world, cr, e); } } } void flecs_ordered_children_unparent( ecs_world_t *world, const ecs_table_t *src, int32_t row, int32_t count) { (void)world; flecs_ordered_entities_unparent_internal(world, src, src, row, count); } void flecs_ordered_children_reorder( ecs_world_t *world, ecs_entity_t parent, const ecs_entity_t *children, int32_t child_count) { (void)world; ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(parent)); ecs_check(cr != NULL, ECS_INVALID_PARAMETER, "ecs_set_child_order() is called for parent '%s' which does not have " "the OrderedChildren trait", flecs_errstr(ecs_get_path(world, parent))); ecs_check(cr->flags & EcsIdOrderedChildren, ECS_INVALID_PARAMETER, "ecs_set_child_order() is called for parent '%s' which does not have " "the OrderedChildren trait", flecs_errstr(ecs_get_path(world, parent))); ecs_vec_t *vec = &cr->pair->ordered_children; ecs_entity_t *parent_children = ecs_vec_first_t(vec, ecs_entity_t); int32_t parent_child_count = ecs_vec_count(vec); ecs_check(parent_child_count == child_count, ECS_INVALID_PARAMETER, "children provided to set_child_order() for parent '%s' do not match " "existing children", flecs_errstr(ecs_get_path(world, parent))); (void)parent_child_count; if (parent_children == children) { return; /* Application is passing the existing children array. */ } ecs_check(children != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(parent_children != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_DEBUG /* Make sure that the provided child ids equal the existing children */ int i, j; for (i = 0; i < child_count; i ++) { ecs_entity_t child = parent_children[i]; for (j = 0; j < child_count; j ++) { ecs_entity_t child_arg = children[j]; if (child == child_arg) { break; } } if (j == child_count) { ecs_throw(ECS_INVALID_PARAMETER, "children provided to set_child_order() for parent '%s' do not " "match existing children (child '%s' is missing in provided " "children vector)", flecs_errstr(ecs_get_path(world, parent)), flecs_errstr_2(ecs_get_path(world, child))); } } #endif /* The actual operation. */ ecs_os_memcpy_n(parent_children, children, ecs_entity_t, child_count); error: return; } bool flecs_component_sparse_has( ecs_component_record_t *cr, ecs_entity_t entity) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = cr->id; if (ecs_id_is_wildcard(id)) { if (ECS_IS_PAIR(id)) { if ((ECS_PAIR_SECOND(id) == EcsWildcard) && (cr->flags & EcsIdDontFragment)) { ecs_component_record_t *cur = cr; while ((cur = flecs_component_first_next(cur))) { if (!cur->sparse) { continue; } if (flecs_sparse_has(cur->sparse, entity)) { return true; } } } if ((ECS_PAIR_FIRST(id) == EcsWildcard) && (cr->flags & EcsIdMatchDontFragment)) { ecs_component_record_t *cur = cr; while ((cur = flecs_component_second_next(cur))) { if (!cur->sparse) { continue; } if (flecs_sparse_has(cur->sparse, entity)) { return true; } } } return false; } return false; } else { ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_sparse_has(cr->sparse, entity); } } void* flecs_component_sparse_get( const ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, ecs_entity_t entity) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); if (!ecs_id_is_wildcard(cr->id)) { return flecs_sparse_get(cr->sparse, 0, entity); } /* Table should always be provided from context where wildcard is allowed */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!(cr->flags & EcsIdDontFragment)) { const ecs_table_record_t *tr = flecs_component_get_table(cr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* Get the non-wildcard record */ const ecs_table_record_t *ttr = &table->_->records[tr->index]; cr = ttr->hdr.cr; } else { /* Find the target entity to replace the wildcard with */ ecs_entity_t tgt = 0; if (cr->flags & EcsIdExclusive) { ecs_entity_t *tgt_ptr = flecs_sparse_get_t(cr->sparse, ecs_entity_t, entity); if (!tgt_ptr) { return NULL; } tgt = *tgt_ptr; } else { ecs_type_t *type = flecs_sparse_get_t( cr->sparse, ecs_type_t, entity); if (!type) { return NULL; } tgt = type->array[0]; } /* Find component record for the non-wildcard component */ if (ECS_PAIR_FIRST(cr->id) == EcsWildcard) { cr = flecs_components_get(world, ecs_pair(tgt, ECS_PAIR_SECOND(cr->id))); } else { /* Component record for (*, *) doesn't exist. */ ecs_assert(ECS_PAIR_SECOND(cr->id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); cr = flecs_components_get(world, ecs_pair(ECS_PAIR_FIRST(cr->id), tgt)); } } return flecs_sparse_get(cr->sparse, 0, entity); } static ecs_entity_t flecs_component_sparse_remove_intern( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = ecs_table_entities(table)[row]; const ecs_type_info_t *ti = cr->type_info; if (!ti) { if (flecs_sparse_remove(cr->sparse, 0, entity)) { return entity; } return 0; } void *ptr = flecs_sparse_get(cr->sparse, 0, entity); if (!ptr) { return 0; } ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { const ecs_table_record_t *tr = NULL; if (!(cr->flags & EcsIdDontFragment)) { tr = flecs_component_get_table(cr, table); } flecs_invoke_hook(world, table, cr, tr, 1, row, &entity, cr->id, ti, EcsOnRemove, on_remove); } flecs_type_info_dtor(ptr, 1, ti); flecs_sparse_remove(cr->sparse, 0, entity); return entity; } static void flecs_component_sparse_dont_fragment_pair_remove( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t entity) { ecs_component_record_t *parent = cr->pair->parent; ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); if (!parent->sparse) { /* It's not the relationship that's non-fragmenting, but the target */ return; } ecs_type_t *type = flecs_sparse_get_t( parent->sparse, ecs_type_t, entity); if (!type) { return; } ecs_assert(type->count > 0, ECS_INTERNAL_ERROR, NULL); int32_t old_type_count = type->count; flecs_type_remove_ignoring_generation(world, type, ECS_PAIR_SECOND(cr->id)); ecs_assert(type->count != old_type_count, ECS_INTERNAL_ERROR, NULL); (void)old_type_count; if (!type->count) { flecs_sparse_remove(parent->sparse, 0, entity); } } static void flecs_component_sparse_dont_fragment_exclusive_remove( ecs_component_record_t *cr, ecs_entity_t entity) { ecs_component_record_t *parent = cr->pair->parent; ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(parent->sparse != NULL, ECS_INTERNAL_ERROR, NULL); flecs_sparse_remove( parent->sparse, ECS_SIZEOF(ecs_entity_t), entity); } void flecs_component_sparse_remove( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row) { ecs_id_t id = cr->id; ecs_flags32_t flags = cr->flags; bool dont_fragment = flags & EcsIdDontFragment; /* If id is a wildcard, remove entity from all matching ids. */ if (dont_fragment && ecs_id_is_wildcard(cr->id)) { /* A wildcard by itself can't be marked sparse, so it must be a pair. */ ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_UNSUPPORTED, "remove(*, T) not supported for DontFragment targets"); ecs_entity_t entity = ecs_table_entities(table)[row]; ecs_component_record_t *cur = cr; while ((cur = flecs_component_first_next(cur))) { if (flecs_component_sparse_has(cur, entity)) { ecs_type_t type = { .array = &cur->id, .count = 1 }; flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = &type, .table = table, .other_table = NULL, .offset = row, .count = 1, .observable = world }); flecs_component_sparse_remove(world, cur, table, row); } } return; } ecs_entity_t entity = flecs_component_sparse_remove_intern(world, cr, table, row); if (entity) { if (dont_fragment) { if (ECS_IS_PAIR(id)) { if (flags & EcsIdExclusive) { flecs_component_sparse_dont_fragment_exclusive_remove( cr, entity); } else { flecs_component_sparse_dont_fragment_pair_remove( world, cr, entity); } } } } } static void flecs_component_sparse_remove_all_id( ecs_world_t *world, ecs_component_record_t *cr) { ecs_id_t component_id = cr->id; ecs_sparse_t *sparse = cr->sparse; const ecs_entity_t *entities = flecs_sparse_ids(sparse); int32_t i, count = flecs_sparse_count(sparse); const ecs_type_info_t *ti = cr->type_info; if (ti) { ecs_iter_action_t on_remove = ti->hooks.on_remove; bool dtor = ti->hooks.dtor != NULL; if (on_remove) { for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *r = flecs_entities_get(world, e); flecs_invoke_hook(world, r->table, cr, NULL, 1, ECS_RECORD_TO_ROW(r->row), &entities[i], component_id, ti, EcsOnRemove, on_remove); } } if (dtor) { for (i = 0; i < count; i ++) { void *ptr = flecs_sparse_get_dense(sparse, 0, i); flecs_type_info_dtor(ptr, 1, ti); } } } if (ECS_IS_PAIR(component_id)) { if (cr->flags & EcsIdExclusive) { for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; flecs_component_sparse_dont_fragment_exclusive_remove(cr, e); } } else { for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; flecs_component_sparse_dont_fragment_pair_remove(world, cr, e); } } } } static void flecs_component_sparse_remove_all_wildcard( ecs_world_t *world, ecs_component_record_t *cr) { ecs_id_t component_id = cr->id; if (!ECS_IS_PAIR(component_id)) { return; } if (cr->flags & EcsIdExclusive) { return; } ecs_sparse_t *sparse = cr->sparse; int32_t i, count = flecs_sparse_count(sparse); for (i = 0; i < count; i ++) { ecs_type_t *type = flecs_sparse_get_dense_t(sparse, ecs_type_t, i); flecs_type_free(world, type); } } void flecs_component_sparse_remove_all( ecs_world_t *world, ecs_component_record_t *cr) { ecs_assert(cr->flags & EcsIdDontFragment, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t component_id = cr->id; if (ecs_id_is_wildcard(component_id)) { flecs_component_sparse_remove_all_wildcard(world, cr); } else { flecs_component_sparse_remove_all_id(world, cr); } } static void flecs_component_sparse_dont_fragment_pair_insert( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t entity) { ecs_component_record_t *parent = cr->pair->parent; ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); if (!parent->sparse) { /* It's not the relationship that's non-fragmenting, but the target */ return; } ecs_type_t *type = flecs_sparse_ensure_t( parent->sparse, ecs_type_t, entity, NULL); flecs_type_add(world, type, ecs_pair_second(world, cr->id)); } static void flecs_component_sparse_dont_fragment_exclusive_insert( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row, ecs_entity_t entity) { ecs_component_record_t *parent = cr->pair->parent; ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(parent->sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t component_id = cr->id; ecs_entity_t tgt, *tgt_ptr = flecs_sparse_ensure_t( parent->sparse, ecs_entity_t, entity, NULL); ecs_assert(tgt_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_sparse_has(parent->sparse, entity), ECS_INTERNAL_ERROR, NULL); if ((tgt = *tgt_ptr)) { ecs_component_record_t *other = flecs_components_get(world, ecs_pair(ECS_PAIR_FIRST(component_id), tgt)); ecs_assert(other != NULL, ECS_INTERNAL_ERROR, NULL); if (other != cr) { ecs_type_t type = { .array = &other->id, .count = 1 }; flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = &type, .table = table, .other_table = NULL, .offset = row, .count = 1, .observable = world }); flecs_component_sparse_remove_intern(world, other, table, row); } } ecs_assert(flecs_sparse_has(parent->sparse, entity), ECS_INTERNAL_ERROR, NULL); *tgt_ptr = flecs_entities_get_alive(world, ECS_PAIR_SECOND(component_id)); } static void flecs_component_sparse_override( ecs_world_t *world, ecs_table_t *table, ecs_id_t component_id, void *ptr, const ecs_type_info_t *ti) { const void *override_ptr = NULL; if (table->flags & EcsTableHasIsA) { ecs_entity_t base = 0; if (ecs_search_relation(world, table, 0, component_id, EcsIsA, EcsUp, &base, NULL, NULL) != -1) { override_ptr = ecs_get_id(world, base, component_id); ecs_assert(override_ptr != NULL, ECS_INTERNAL_ERROR, NULL); } } if (!override_ptr) { flecs_type_info_ctor(ptr, 1, ti); } else { if (ti->hooks.copy_ctor) { flecs_type_info_copy_ctor(ptr, override_ptr, 1, ti); } else { flecs_type_info_ctor(ptr, 1, ti); ecs_os_memcpy(ptr, override_ptr, ti->size); } } } void* flecs_component_sparse_insert( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = ecs_table_entities(table)[row]; bool is_new = true; void *ptr = flecs_sparse_ensure(cr->sparse, 0, entity, &is_new); ecs_id_t component_id = cr->id; if (ECS_IS_PAIR(component_id)) { ecs_flags32_t flags = cr->flags; if (flags & EcsIdDontFragment) { if (flags & EcsIdExclusive) { flecs_component_sparse_dont_fragment_exclusive_insert( world, cr, table, row, entity); } else { flecs_component_sparse_dont_fragment_pair_insert( world, cr, entity); } } } if (!ptr || !is_new) { return ptr; } const ecs_type_info_t *ti = cr->type_info; if (!ti) { return ptr; } flecs_component_sparse_override(world, table, component_id, ptr, ti); ecs_iter_action_t on_add = ti->hooks.on_add; if (!on_add) { return ptr; } const ecs_table_record_t *tr = NULL; if (!(cr->flags & EcsIdDontFragment)) { tr = flecs_component_get_table(cr, table); } flecs_invoke_hook(world, table, cr, tr, 1, row, &entity, component_id, ti, EcsOnAdd, on_add); return ptr; } void* flecs_component_sparse_emplace( ecs_world_t *world, ecs_component_record_t *cr, ecs_table_t *table, int32_t row) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = ecs_table_entities(table)[row]; void *ptr = flecs_sparse_ensure(cr->sparse, 0, entity, NULL); if (!ptr) { return NULL; } const ecs_type_info_t *ti = cr->type_info; if (!ti) { return ptr; } ecs_iter_action_t on_add = ti->hooks.on_add; if (!on_add) { return ptr; } const ecs_table_record_t *tr = NULL; if (!(cr->flags & EcsIdDontFragment)) { tr = flecs_component_get_table(cr, table); } flecs_invoke_hook(world, table, cr, tr, 1, row, &entity, cr->id, ti, EcsOnAdd, on_add); return ptr; } /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severely slow down many ECS operations. */ #ifdef FLECS_SANITIZE static void flecs_table_check_sanity( ecs_table_t *table) { int32_t i, count = ecs_table_count(table); int32_t size = ecs_table_size(table); ecs_assert(count <= size, ECS_INTERNAL_ERROR, NULL); int32_t bs_offset = table->_ ? table->_->bs_offset : 0; int32_t bs_count = table->_ ? table->_->bs_count : 0; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); if (size) { ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { ecs_assert(table->data.entities == NULL, ECS_INTERNAL_ERROR, NULL); } if (table->column_count) { int32_t column_count = table->column_count; ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); 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 (bs_count) { ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); int32_t bs_i = 0; for (i = bs_offset; i < type_count; i ++) { if (!ECS_HAS_ID_FLAG(ids[i], TOGGLE)) { continue; } ecs_bitset_t *bs = &table->_->bs_columns[bs_i]; ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); bs_i ++; } ecs_assert(bs_i == bs_count, ECS_INTERNAL_ERROR, NULL); } ecs_assert((table->_->traversable_count == 0) || (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif /* Set flags for type hooks so table operations can quickly check whether a * fast or complex operation that invokes hooks is required. */ static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) { ecs_flags32_t flags = 0; if (ti->hooks.ctor) { flags |= EcsTableHasCtors; } if (ti->hooks.on_add) { flags |= EcsTableHasCtors; } if (ti->hooks.dtor) { flags |= EcsTableHasDtors; } if (ti->hooks.on_remove) { flags |= EcsTableHasDtors; } if (ti->hooks.copy) { flags |= EcsTableHasCopy; } if (ti->hooks.move) { flags |= EcsTableHasMove; } return flags; } static void flecs_table_init_columns( ecs_world_t *world, ecs_table_t *table, int32_t column_count) { int16_t i, cur = 0, ids_count = flecs_ito(int16_t, table->type.count); 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 (!column_count) { return; } ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); table->data.columns = columns; ecs_id_t *ids = table->type.array; ecs_table_record_t *records = table->_->records; int16_t *t2s = table->column_map; int16_t *s2t = &table->column_map[ids_count]; for (i = 0; i < ids_count; i ++) { ecs_id_t id = ids[i]; ecs_table_record_t *tr = &records[i]; ecs_component_record_t *cr = tr->hdr.cr; const ecs_type_info_t *ti = cr->type_info; if (!ti || (cr->flags & EcsIdSparse)) { t2s[i] = -1; continue; } t2s[i] = cur; s2t[cur] = i; tr->column = flecs_ito(int16_t, cur); columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); if (id < FLECS_HI_COMPONENT_ID) { table->component_map[id] = flecs_ito(int16_t, cur + 1); } table->flags |= flecs_type_info_flags(ti); cur ++; } int32_t record_count = table->_->record_count; for (; i < record_count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_component_record_t *cr = tr->hdr.cr; ecs_id_t id = cr->id; if (ecs_id_is_wildcard(id)) { ecs_table_record_t *first_tr = &records[tr->index]; tr->column = first_tr->column; } } /* 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 } /* Initialize table storage */ static void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { flecs_table_init_columns(world, table, table->column_count); ecs_table__t *meta = table->_; int32_t i, bs_count = meta->bs_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]); } } } /* Initialize table flags. Table flags are used in lots of scenarios to quickly * check the features of a table without having to inspect the table type. Table * flags are typically used to early-out of potentially expensive operations. */ static void flecs_table_init_flags( ecs_world_t *world, ecs_table_t *table) { ecs_id_t *ids = table->type.array; int32_t count = table->type.count; #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 table->childof_index = -1; int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; #if !defined(FLECS_NDEBUG) || defined(FLECS_KEEP_ASSERT) { ecs_id_t check_id = ECS_IS_PAIR(id) ? ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard) : id; ecs_component_record_t *id_cr = flecs_components_ensure( world, check_id); ecs_assert(!(id_cr->flags & EcsIdDontFragment), ECS_INVALID_OPERATION, "table type cannot contain DontFragment components"); } #endif if (id <= EcsLastInternalComponentId) { table->flags |= EcsTableHasModule; } if (id == EcsModule) { 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 (id == ecs_id(EcsParent)) { table->flags |= EcsTableHasParent; table->trait_flags |= EcsIdTraversable; } else if (ECS_PAIR_FIRST(id) == EcsWith) { table->trait_flags |= EcsIdWith; } else if (ECS_PAIR_FIRST(id) == EcsOnDelete) { ecs_entity_t on_delete_kind = ECS_PAIR_SECOND(id); if (on_delete_kind == EcsRemove) { table->trait_flags |= EcsIdOnDeleteRemove; } else if (on_delete_kind == EcsDelete) { table->trait_flags |= EcsIdOnDeleteDelete; } else if (on_delete_kind == EcsPanic) { table->trait_flags |= EcsIdOnDeletePanic; } } else if (ECS_PAIR_FIRST(id) == EcsOnDeleteTarget) { ecs_entity_t on_delete_kind = ECS_PAIR_SECOND(id); if (on_delete_kind == EcsRemove) { table->trait_flags |= EcsIdOnDeleteTargetRemove; } else if (on_delete_kind == EcsDelete) { table->trait_flags |= EcsIdOnDeleteTargetDelete; } else if (on_delete_kind == EcsPanic) { table->trait_flags |= EcsIdOnDeleteTargetPanic; } } else if (ECS_PAIR_FIRST(id) == EcsOnInstantiate) { ecs_entity_t inherit_kind = ECS_PAIR_SECOND(id); if (inherit_kind == EcsInherit) { table->trait_flags |= EcsIdOnInstantiateInherit; } else if (inherit_kind == EcsDontInherit) { table->trait_flags |= EcsIdOnInstantiateDontInherit; } else if (inherit_kind == EcsOverride) { table->trait_flags |= EcsIdOnInstantiateOverride; } } else if (id == EcsCanToggle) { table->trait_flags |= EcsIdCanToggle; } else if (id == EcsInheritable) { table->trait_flags |= EcsIdInheritable; } else if (id == EcsSingleton) { table->trait_flags |= EcsIdSingleton; } else if (id == EcsSparse) { table->trait_flags |= EcsIdSparse; } else if (id == EcsDontFragment) { table->trait_flags |= EcsIdDontFragment; } else if (id == EcsExclusive) { table->trait_flags |= EcsIdExclusive; } else if (id == EcsTraversable) { table->trait_flags |= EcsIdTraversable; } else if (id == EcsPairIsTag) { table->trait_flags |= EcsIdPairIsTag; } else if (id == EcsTransitive) { table->trait_flags |= EcsIdIsTransitive; } else if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); table->flags |= EcsTableHasPairs; if (r == EcsIsA) { table->flags |= EcsTableHasIsA; } else if (r == EcsChildOf) { table->flags |= EcsTableHasChildOf; table->childof_index = flecs_ito(int16_t, i); ecs_entity_t tgt = ecs_pair_second(world, id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); /* If table contains entities that are inside one of the * builtin modules, it contains builtin entities */ if (tgt == EcsFlecsCore) { table->flags |= EcsTableHasBuiltins; } if (ecs_has_id(world, tgt, EcsModule)) { table->flags |= EcsTableHasModule; } #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 |= EcsTableHasModule; } #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 *cr = 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(cr, table)); if (!tr) { tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); tr->index = flecs_ito(int16_t, column); tr->count = 1; ecs_table_cache_insert(&cr->cache, table, &tr->hdr); } else { tr->count ++; } ecs_assert(tr->hdr.cr != NULL, ECS_INTERNAL_ERROR, NULL); } static void flecs_table_init_overrides( ecs_world_t *world, ecs_table_t *table, const ecs_table_record_t *tr) { ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->column_count) { return; } ecs_table_overrides_t *o = flecs_walloc_t(world, ecs_table_overrides_t); if (tr->count > 1) { table->flags |= EcsTableHasMultiIsA; o->is._n.tr = tr; o->is._n.generations = flecs_wcalloc_n(world, int32_t, tr->count); int32_t i; for (i = 0; i < tr->count; i ++) { o->is._n.generations[i] = -1; } } else { const ecs_table_record_t *first = &table->_->records[tr->index]; const ecs_component_record_t *cr = first->hdr.cr; ecs_assert(ECS_IS_PAIR(cr->id), ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(cr->id) == EcsIsA, ECS_INTERNAL_ERROR, NULL); ecs_assert(first->count == 1, ECS_INTERNAL_ERROR, NULL); o->is._1.generation = -1; o->is._1.pair = cr->pair; } o->refs = flecs_wcalloc_n(world, ecs_ref_t, table->column_count); table->data.overrides = o; } static void flecs_table_fini_overrides( ecs_world_t *world, ecs_table_t *table) { if (!table->column_count) { return; } ecs_table_overrides_t *o = table->data.overrides; if (!o) { return; } if (table->flags & EcsTableHasMultiIsA) { const ecs_table_record_t *tr = o->is._n.tr; flecs_wfree_n(world, int32_t, tr->count, o->is._n.generations); } flecs_wfree_n(world, ecs_ref_t, table->column_count, o->refs); flecs_wfree_t(world, ecs_table_overrides_t, o); } static void flecs_table_update_overrides( ecs_world_t *world, ecs_table_t *table) { if (!(table->flags & EcsTableHasIsA)) { return; } ecs_table_overrides_t *o = table->data.overrides; if (!o) { return; } if (table->flags & EcsTableHasMultiIsA) { const ecs_table_record_t *tr = o->is._n.tr; const ecs_table_record_t *records = table->_->records; int32_t *generations = o->is._n.generations; int32_t i = tr->index, end = i + tr->count; for (; i < end; i ++) { ecs_component_record_t *cr = records[i].hdr.cr; if (cr->pair->reachable.generation != *generations) { break; } generations ++; } if (i == end) { /* Cache is up to date */ return; } generations = o->is._n.generations; i = tr->index; end = i + tr->count; for (; i < end; i ++) { ecs_component_record_t *cr = records[i].hdr.cr; generations[0] = cr->pair->reachable.generation; generations ++; } } else { /* Fast cache validation for tables with single IsA pair */ int32_t generation = o->is._1.pair->reachable.generation; if (o->is._1.generation == generation) { /* Cache is up to date */ return; } o->is._1.generation = generation; } int16_t *map = &table->column_map[table->type.count]; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_id_t id = table->type.array[map[i]]; ecs_entity_t base = 0; if (ecs_search_relation( world, table, 0, id, EcsIsA, EcsUp, &base, NULL, NULL) != -1) { o->refs[i] = ecs_ref_init_id(world, base, id); } else { ecs_os_zeromem(&o->refs[i]); } } } static void flecs_table_emit( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { ecs_defer_begin(world); flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &table->type, .event = event, .table = table, .flags = EcsEventTableOnly, .observable = world }); ecs_defer_end(world); } /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { /* Make sure table->flags is initialized */ flecs_table_init_flags(world, table); /* The following code walks the table type to discover which id records the * table needs to register table records with. * * In addition to registering itself with id records for each id in the * table type, a table also registers itself with wildcard id records. For * example, if a table contains (Eats, Apples), it will register itself with * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it * easier for wildcard queries to find the relevant tables. */ int32_t dst_i = 0, dst_count = table->type.count; int32_t src_i = 0, src_count = 0; ecs_id_t *dst_ids = table->type.array; ecs_id_t *src_ids = NULL; ecs_table_record_t *tr = NULL, *src_tr = NULL; if (from) { src_count = from->type.count; src_ids = from->type.array; src_tr = from->_->records; } /* We don't know in advance how large the records array will be, so use * cached vector. This eliminates unnecessary allocations, and/or expensive * iterations to determine how many records we need. */ ecs_allocator_t *a = &world->allocator; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(a, records, ecs_table_record_t); ecs_component_record_t *cr, *childof_cr = NULL; int32_t last_id = -1; /* Track last regular (non-pair) id */ int32_t first_pair = -1; /* Track the first pair in the table */ int32_t first_role = -1; /* Track first id with role */ /* Scan to find boundaries of regular ids, pairs and roles */ for (dst_i = 0; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { first_pair = dst_i; } if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { last_id = dst_i; } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { first_role = dst_i; } /* Build bloom filter for table */ table->bloom_filter = flecs_table_bloom_filter_add(table->bloom_filter, dst_id); } /* 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]; cr = NULL; if (dst_id == src_id) { ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); cr = (ecs_component_record_t*)src_tr[src_i].hdr.cr; } else if (dst_id < src_id) { cr = flecs_components_ensure(world, dst_id); } if (cr) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = cr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } dst_i += dst_id <= src_id; src_i += dst_id >= src_id; } /* Add remaining ids that the "from" table didn't have */ for (; (dst_i < dst_count); dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; tr = ecs_vec_append_t(a, records, ecs_table_record_t); cr = flecs_components_ensure(world, dst_id); tr->hdr.cr = cr; ecs_assert(tr->hdr.cr != NULL, ECS_INTERNAL_ERROR, NULL); tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } /* We're going to insert records from the vector into the index that * will get patched up later. To ensure the record pointers don't get * invalidated we need to grow the vector so that it won't realloc as * we're adding the next set of records */ if (first_role != -1 || first_pair != -1) { int32_t start = first_role; if (first_pair != -1 && (start == -1 || first_pair < start)) { start = first_pair; } /* Total number of records can never be higher than * - number of regular (non-pair) ids + * - three records for pairs: (R,T), (R,*), (*,T) * - one wildcard (*), one any (_) and one pair wildcard (*,*) record * - one record for (ChildOf, 0) */ int32_t flag_id_count = dst_count - start; int32_t record_count = start + 3 * flag_id_count + 3 + 1; ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); } /* Get records size now so we can check that array did not resize */ int32_t records_size = ecs_vec_size(records); (void)records_size; /* Add records for ids with roles (used by cleanup logic) */ if (first_role != -1) { for (dst_i = first_role; dst_i < dst_count; dst_i ++) { ecs_id_t id = dst_ids[dst_i]; if (!ECS_IS_PAIR(id)) { ecs_entity_t first = 0; ecs_entity_t second = 0; if (ECS_HAS_ID_FLAG(id, PAIR)) { first = ECS_PAIR_FIRST(id); second = ECS_PAIR_SECOND(id); } else { first = id & ECS_COMPONENT_MASK; } if (first) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, first), dst_i); } if (second) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, second), dst_i); } } } } int32_t last_pair = -1; bool has_childof = !!(table->flags & (EcsTableHasChildOf|EcsTableHasParent)); 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_component_record_t *p_cr = tr->hdr.cr; r = ECS_PAIR_FIRST(dst_id); if (r == EcsChildOf) { childof_cr = p_cr; ecs_assert(childof_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_assert(p_cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); cr = p_cr->pair->parent; /* (R, *) */ ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = cr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 0; } ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); tr->count ++; } last_pair = dst_i; /* Add a (*, Target) record for each relationship target. Type * ids are sorted relationship-first, so we can't simply do a single * linear scan to find all occurrences for a target. */ for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (ECS_IS_VALUE_PAIR(dst_id)) { continue; } ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); flecs_table_append_to_records( world, table, records, tgt_id, dst_i); } } /* Lastly, add records for all-wildcard ids */ if (last_id >= 0) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cr = world->cr_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.cr = world->cr_wildcard_wildcard; tr->index = flecs_ito(int16_t, first_pair); tr->count = flecs_ito(int16_t, last_pair - first_pair); } if (!has_childof) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); childof_cr = world->cr_childof_0; tr->hdr.cr = childof_cr; tr->index = -1; /* The table doesn't have a (ChildOf, 0) component */ tr->count = 0; table->bloom_filter = flecs_table_bloom_filter_add( table->bloom_filter, ecs_pair(EcsChildOf, 0)); } /* 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; ecs_table_record_t *isa_tr = NULL; /* Register & patch up records */ for (i = 0; i < dst_record_count; i ++) { tr = &dst_tr[i]; cr = dst_tr[i].hdr.cr; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_cache_get(&cr->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 */ /* Ensure that record array hasn't been reallocated */ ecs_assert(records_size == ecs_vec_size(records), ECS_INTERNAL_ERROR, NULL); ecs_table_cache_replace(&cr->cache, table, &tr->hdr); } else { /* Other records are not registered yet */ ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_insert(&cr->cache, table, &tr->hdr); } /* Claim component record so it stays alive as long as the table exists */ flecs_component_claim(world, cr); /* Initialize event flags */ table->flags |= cr->flags & EcsIdEventMask; /* Initialize column index (will be overwritten by init_data) */ tr->column = -1; if (ECS_ID_ON_INSTANTIATE(cr->flags) == EcsOverride) { table->flags |= EcsTableHasOverrides; } if ((i < table->type.count) && (cr->type_info != NULL)) { if (!(cr->flags & EcsIdSparse)) { column_count ++; } } if (cr->id == ecs_pair(EcsIsA, EcsWildcard)) { isa_tr = tr; } } /* Initialize event flags for any record */ table->flags |= world->cr_any->flags & EcsIdEventMask; table->component_map = ecs_os_calloc_n(int16_t, FLECS_HI_COMPONENT_ID); if (column_count) { table->column_map = ecs_os_calloc_n(int16_t, dst_count + column_count); } table->column_count = flecs_ito(int16_t, column_count); table->version = 1; flecs_table_init_data(world, table); if (childof_cr) { if (table->flags & EcsTableHasName) { flecs_component_name_index_ensure(world, childof_cr); ecs_assert(childof_cr->pair->name_index != NULL, ECS_INTERNAL_ERROR, NULL); } } /* If table has IsA pairs, create overrides cache */ if (isa_tr) { flecs_table_init_overrides(world, table, isa_tr); } if (table->flags & EcsTableHasOnTableCreate) { flecs_table_emit(world, table, EcsOnTableCreate); } } /* Unregister table from id records */ static void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) { uint64_t table_id = table->id; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_component_record_t *cr = tr->hdr.cr; ecs_id_t id = cr->id; ecs_assert(tr->hdr.cr == cr, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_components_get(world, id) == cr, ECS_INTERNAL_ERROR, NULL); (void)id; ecs_table_cache_remove(&cr->cache, table_id, &tr->hdr); flecs_component_release(world, cr); } flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); } /* Keep track of what kind of builtin event 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; ecs_flags32_t flags = 0; 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; } 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); } } /* 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) { 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_actions_move_remove(world, table, &world->store.root, 0, count, &diff); } } /* 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]; ecs_component_record_t *cr = tr->hdr.cr; flecs_invoke_hook(world, table, cr, tr, count, row, entities, table->type.array[type_index], column->ti, event, callback); } static void flecs_table_invoke_ctor_for_array( ecs_world_t *world, ecs_table_t *table, int32_t column_index, void *array, int32_t row, int32_t count, const ecs_type_info_t *ti) { void *ptr = ECS_ELEM(array, ti->size, row); if (table) { ecs_table_overrides_t *o = table->data.overrides; if (o) { ecs_ref_t *r = &o->refs[column_index]; if (r->entity) { ecs_id_t id = table->type.array[ table->column_map[table->type.count + column_index]]; void *base_ptr = ecs_ref_get_id(world, r, id); ecs_assert(base_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_set = ti->hooks.on_set; ecs_size_t size = ti->size; int32_t i; for (i = 0; i < count; i ++) { flecs_type_info_copy_ctor(ptr, base_ptr, 1, ti); ptr = ECS_OFFSET(ptr, size); } if (on_set) { table->data.columns[column_index].data = array; int32_t record_index = table->column_map[table->type.count + column_index]; const ecs_table_record_t *tr = &table->_->records[record_index]; const ecs_entity_t *entities = &ecs_table_entities(table)[row]; flecs_invoke_hook(world, table, tr->hdr.cr, tr, count, row, entities, ti->component, ti, EcsOnSet, on_set); } return; } } } flecs_type_info_ctor(ptr, count, ti); } static void flecs_table_invoke_ctor( ecs_world_t *world, ecs_table_t *table, int32_t column_index, int32_t row, int32_t count) { ecs_column_t *column = &table->data.columns[column_index]; ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_invoke_ctor_for_array(world, table, column_index, column->data, row, count, ti); } /* Destruct components */ static void flecs_table_invoke_dtor( 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); void *ptr = ECS_ELEM(column->data, ti->size, row); flecs_type_info_dtor(ptr, count, ti); } /* Run hooks that get invoked when component is added to entity */ static void flecs_table_invoke_add_hooks( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_entity_t *entities, int32_t row, int32_t count, bool construct) { ecs_column_t *column = &table->data.columns[column_index]; 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(world, table, column_index, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, entities, row, count); } } /* Run hooks that get invoked when component is removed from entity */ static void flecs_table_invoke_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count, bool dtor) { ecs_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); } } static void flecs_table_remove_dont_fragment( ecs_world_t *world, ecs_entity_t e) { ecs_record_t *r = flecs_entities_get(world, e); flecs_entity_remove_non_fragmenting(world, e, r); } /* Destruct all components and/or delete all entities in table */ static void flecs_table_dtor_all( ecs_world_t *world, ecs_table_t *table) { int32_t i, c, count = ecs_table_count(table); if (!count) { return; } const ecs_entity_t *entities = ecs_table_entities(table); int32_t column_count = table->column_count; ecs_assert(!column_count || table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); 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); } /* 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); } } /* Destruct components */ for (c = 0; c < column_count; c++) { flecs_table_invoke_dtor(&table->data.columns[c], 0, count); } 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_table_remove_dont_fragment(world, e); 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_table_remove_dont_fragment(world, e); flecs_entities_remove(world, e); ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); } } } #define FLECS_LOCKED_STORAGE_MSG(operation) \ "a " #operation " operation failed because the table is locked, fix by surrounding the operation 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) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table data cleanup")); if (do_on_remove) { flecs_table_notify_on_remove(world, table); } flecs_table_dtor_all(world, table); 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(NULL, &v, column->ti->size); column->data = NULL; } flecs_wfree_n(world, ecs_column_t, table->column_count, columns); table->data.columns = NULL; } } 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++) { ecs_bitset_t *bs = &bs_columns[c]; if (bs->data) { ecs_os_memset(bs->data, 0, (bs->size >> 6) * ECS_SIZEOF(uint64_t)); } bs->count = 0; } } if (deallocate) { flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); meta->bs_columns = NULL; } } if (deallocate) { ecs_vec_t v = ecs_vec_from_entities(table); ecs_vec_fini_t(NULL, &v, ecs_entity_t); table->data.entities = NULL; table->data.size = 0; } table->data.count = 0; table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; flecs_increment_table_version(world, table); } const ecs_entity_t* ecs_table_entities( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.entities; } void ecs_table_clear_entities( ecs_world_t* world, ecs_table_t* table) { flecs_table_fini_data(world, table, true, false); } /* Remove 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) { flecs_table_notify_on_remove(world, table); } /* Free table resources. */ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table) { 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("table deletion")); ecs_assert((world->flags & EcsWorldFini) || !table->keep, ECS_INVALID_OPERATION, "cannot delete table (still in use): '[%s]'", flecs_errstr(ecs_table_str(world, table))); 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); } } 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_table_fini_overrides(world, table); flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); ecs_os_free(table->column_map); ecs_os_free(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_w_gen_t( &world->store.tables, ecs_table_t, table->id); } ecs_log_pop_2(); ecs_os_perf_trace_pop("flecs.table.free"); } /* Free table type. Do this separately from freeing the table as types can be * in use by application destructors. */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table) { flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } /* Reset a table to its initial state. */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table reset")); flecs_table_clear_edges(world, table); } /* Keep track of number of traversable entities in table. A traversable entity * is an entity used as target in a pair with a traversable relationship. The * traversable count and flag are used by code to early out of mechanisms like * event propagation and recursive cleanup. */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value) { int32_t result = table->_->traversable_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { table->flags &= ~EcsTableHasTraversable; } else if (result == value) { table->flags |= EcsTableHasTraversable; } } /* Mark table column dirty. This usually happens as the result of a set * operation, or iteration of a query with [out] fields. */ static void flecs_table_mark_table_dirty( ecs_world_t *world, ecs_table_t *table, int32_t index) { if (table->dirty_state) { table->dirty_state[index] ++; } if (!index) { flecs_increment_table_version(world, table); } } /* Mark table component dirty */ void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); 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 *cr = flecs_components_get(world, component); if (!cr) { return; } const ecs_table_record_t *tr = flecs_component_get_table(cr, 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] ++; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("dirty marking")); } } /* 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; } /* 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) { int32_t i_old = 0, src_column_count = src_table->_->bs_count; int32_t i_new = 0, dst_column_count = dst_table->_->bs_count; if (!src_column_count && !dst_column_count) { return; } ecs_bitset_t *src_columns = src_table->_->bs_columns; ecs_bitset_t *dst_columns = dst_table->_->bs_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; int32_t dst_type_count = dst_type.count; int32_t src_type_count = src_type.count; int32_t ti_new = dst_table->_->bs_offset; int32_t ti_old = src_table->_->bs_offset; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[ti_new]; ecs_id_t src_id = src_ids[ti_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) { if (clear) { ecs_bitset_t *src_bs = &src_columns[i_old]; flecs_bitset_fini(src_bs); } } if (dst_id <= src_id) { i_new ++; ti_new ++; while ((ti_new < dst_type_count) && !ECS_HAS_ID_FLAG(dst_ids[ti_new], TOGGLE)) { ti_new ++; } } if (dst_id >= src_id) { i_old ++; ti_old ++; while ((ti_old < src_type_count) && !ECS_HAS_ID_FLAG(src_ids[ti_old], TOGGLE)) { ti_old ++; } } } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } } /* Grow table column. When a column needs to be reallocated this function takes * care of correctly invoking ctor/move/dtor hooks. */ static void flecs_table_grow_column( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_vec_t *column, const ecs_type_info_t *ti, int32_t to_add, int32_t dst_size, bool construct) { 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; 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 */ if (count && can_realloc && ti->hooks.ctor_move_dtor) { ecs_assert(ti->hooks.ctor != NULL, ECS_INTERNAL_ERROR, NULL); /* Create vector */ ecs_vec_t dst; ecs_vec_init(NULL, &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 */ flecs_type_info_ctor_move_dtor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ flecs_table_invoke_ctor_for_array( world, table, column_index, dst_buffer, count, to_add, ti); } /* Free old vector */ ecs_vec_fini(NULL, 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(NULL, column, elem_size, dst_size); } ecs_vec_grow(NULL, column, elem_size, to_add); if (construct) { flecs_table_invoke_ctor_for_array( world, table, column_index, column->array, count, to_add, ti); } } ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); } /* Grow all data structures in a table */ static int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, int32_t to_add, int32_t size, const ecs_entity_t *ids) { flecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.count + to_add == size, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_table_count(table); int32_t column_count = table->column_count; /* Add entity to column with entity ids */ ecs_vec_t v_entities = ecs_vec_from_entities(table); ecs_vec_set_size_t(NULL, &v_entities, ecs_entity_t, size); ecs_entity_t *e = NULL; if (size) { e = ECS_ELEM_T(v_entities.array, ecs_entity_t, v_entities.count); } 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 (e) { 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); } } flecs_table_update_overrides(world, table); /* 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, table, i, &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; if (to_add) { flecs_table_invoke_add_hooks( world, table, i, 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_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(NULL, &v, ti->size); column->data = v.array; } } /* Append entity to table */ void 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("table append")); flecs_table_check_sanity(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(NULL, &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); /* Fast path: no toggle columns, no lifecycle actions */ if (!(table->flags & (EcsTableIsComplex|EcsTableHasIsA))) { flecs_table_fast_append(table); table->data.count = v_entities.count; table->data.size = v_entities.size; return; } flecs_table_update_overrides(world, table); int32_t prev_count = table->data.count; int32_t prev_size = table->data.size; 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; /* Reobtain size to ensure that the columns have the same size as the * entities vector. This keeps reasoning about when allocations occur * easier. */ int32_t size = v_entities.size; /* 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, table, i, &v_column, ti, 1, size, construct); column->data = v_column.array; 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); } 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); } flecs_table_check_sanity(table); } /* Delete operation for tables that don't have any complex logic */ static void flecs_table_fast_delete( ecs_table_t *table, int32_t row) { 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; } } /* Delete entity from table */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t row, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table delete")); flecs_table_check_sanity(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 removes an element from the array(s) */ if (!(table->flags & EcsTableIsComplex)) { if (row != count) { flecs_table_fast_delete(table, row); } table->data.count --; flecs_table_check_sanity(table); 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); 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); } /* If neither move nor move_ctor are set, this indicates that * non-destructive move semantics are not supported for this * type. In such cases, use ctor_move_dtor for destructive move * semantics to ensure compatibility with language bindings. */ if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { flecs_type_info_ctor_move_dtor(dst, src, 1, ti); } else { flecs_type_info_move_dtor(dst, src, 1, ti); } } } else { flecs_table_fast_delete(table, row); } } /* 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); } table->data.count --; flecs_table_check_sanity(table); } /* Move operation for tables that don't have any complex logic */ static void flecs_table_fast_move( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = 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); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } } /* Move entity from src to dst table */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, ecs_id_t emplace_id) { 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("move")); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("move")); ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); if (!((dst_table->flags | src_table->flags) & (EcsTableIsComplex|EcsTableHasIsA))) { flecs_table_fast_move(dst_table, dst_index, src_table, src_index); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); return; } flecs_table_update_overrides(world, dst_table); flecs_table_move_bitset_columns( dst_table, dst_index, src_table, src_index, 1, false); /* Call move_dtor for the source storage only if the entity is at the last * index in the source table. If it isn't the last entity, the last entity * in the table will be moved to the src storage, which will take care of * cleaning up resources. */ bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); if (dst_id == src_id) { ecs_type_info_t *ti = dst_column->ti; int32_t size = ti->size; 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); bool use_ctor_move_dtor = use_move_dtor || !ti->hooks.move_ctor; if (use_ctor_move_dtor) { /* Also use ctor_move_dtor if component doesn't have a move_ctor * registered, to ensure that the dtor gets called to * cleanup resources. */ flecs_type_info_ctor_move_dtor(dst, src, 1, ti); } else { flecs_type_info_move_ctor(dst, src, 1, ti); } } else { if (dst_id < src_id) { flecs_table_invoke_add_hooks(world, dst_table, i_new, &dst_entity, dst_index, 1, dst_id != emplace_id); } else { flecs_table_invoke_remove_hooks(world, src_table, src_column, &src_entity, src_index, 1, use_move_dtor); } } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } for (; (i_new < dst_column_count); i_new ++) { flecs_table_invoke_add_hooks(world, dst_table, i_new, &dst_entity, dst_index, 1, flecs_column_id(dst_table, i_new) != emplace_id); } for (; (i_old < src_column_count); i_old ++) { flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], &src_entity, src_index, 1, use_move_dtor); } flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); } /* Append n entities to table */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, int32_t to_add, const ecs_entity_t *ids) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table bulk append")); /* Update entity index before calling hooks. */ int32_t i; for (i = 0; i < to_add; i ++) { ecs_record_t *r = flecs_entities_get(world, ids[i]); r->table = table; r->row = ECS_ROW_TO_RECORD(ecs_table_count(table) + i, 0); } flecs_table_check_sanity(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(table); return result; } /* 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_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table shrink")); (void)world; flecs_table_check_sanity(table); bool has_payload = table->data.entities != NULL; int32_t count = table->data.count; if (count == table->data.size) { return has_payload; } ecs_column_t *columns = table->data.columns; ecs_entity_t *entities = table->data.entities; if (count) { ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); table->data.entities = ecs_os_malloc_n(ecs_entity_t, count); ecs_os_memcpy_n(table->data.entities, entities, ecs_entity_t, count); } else { table->data.entities = NULL; } ecs_os_free(entities); int32_t i, column_count = table->column_count; for (i = 0; i < column_count; i ++) { const ecs_type_info_t *ti = columns[i].ti; ecs_size_t component_size = ti->size; void *data = columns[i].data; if (count) { columns[i].data = ecs_os_malloc(component_size * count); flecs_type_info_ctor_move_dtor(columns[i].data, data, count, ti); } else { columns[i].data = NULL; } ecs_os_free(data); } table->data.size = count; flecs_table_mark_table_dirty(world, table, 0); flecs_table_check_sanity(table); return has_payload; } /* Swap operation for bitset (toggle component) columns */ static void flecs_table_swap_bitset_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->_->bs_count; if (!column_count) { return; } ecs_bitset_t *columns = table->_->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } /* Swap two rows in a table. Used for table sorting. */ static void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2) { (void)world; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table swap")); ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(table); if (row_1 == row_2) { return; } /* If the table is monitored, indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_entity_t *entities = table->data.entities; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of row flags */ uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); /* Swap entities & records */ entities[row_1] = e2; entities[row_2] = e1; record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); flecs_table_swap_bitset_columns(table, row_1, row_2); ecs_column_t *columns = table->data.columns; if (!columns) { flecs_table_check_sanity(table); return; } /* Find the maximum size of column elements * and allocate a temporary buffer for swapping */ int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; for (i = 0; i < column_count; i++) { temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].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); bool move_hook = ti->hooks.move != NULL; if (!move_hook) { ecs_os_memcpy(tmp, el_1, size); ecs_os_memcpy(el_1, el_2, size); ecs_os_memcpy(el_2, tmp, size); } else { ecs_assert(ti->hooks.move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti->hooks.move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_info_move_ctor(tmp, el_1, 1, ti); flecs_type_info_move(el_1, el_2, 1, ti); flecs_type_info_move_dtor(el_2, tmp, 1, ti); } } flecs_table_check_sanity(table); } static void flecs_table_merge_vec( ecs_vec_t *dst, ecs_vec_t *src, int32_t size, int32_t elem_size) { int32_t dst_count = dst->count; if (!dst_count) { ecs_vec_fini(NULL, dst, size); *dst = *src; src->array = NULL; src->count = 0; src->size = 0; } else { int32_t src_count = src->count; if (elem_size) { ecs_vec_set_size(NULL, dst, size, elem_size); } ecs_vec_set_count(NULL, 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(NULL, src, size); } } /* Merge data from one table column into other table column */ static void flecs_table_merge_column( 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) { 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(NULL, dst_vec, elem_size); *dst_vec = *src_vec; /* If the new table is not empty, move the contents from the * src into the dst. */ } else { int32_t src_count = src_vec->count; flecs_table_grow_column(world, NULL, -1, 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); flecs_type_info_ctor_move_dtor(dst_ptr, src_ptr, src_count, ti); ecs_vec_fini(NULL, src_vec, elem_size); } dst->data = dst_vec->array; src->data = NULL; } /* 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; 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( &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; 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(NULL, &dst_vec, dst_elem_size, column_size); dst_column->data = dst_vec.array; flecs_table_invoke_ctor(world, dst_table, i_new, 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(NULL, &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(NULL, &vec, elem_size, column_size); column->data = vec.array; flecs_table_invoke_ctor(world, dst_table, i_new, 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(NULL, &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("table merge")); ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table merge")); flecs_table_check_sanity(src_table); flecs_table_check_sanity(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; } /* 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); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } /* Internal mechanism for propagating information to tables */ void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_event_t *event) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldFini) { return; } switch(event->kind) { case EcsTableTriggersForId: flecs_table_add_trigger_flags(world, table, id, event->event); break; case EcsTableNoTriggersForId: break; /* TODO */ } } static int32_t flecs_table_get_toggle_column( ecs_table_t *table, ecs_id_t id) { ecs_id_t bs_id = ECS_TOGGLE | id; ecs_id_t *ids = table->type.array; int32_t i = table->_->bs_offset, end = table->type.count; int32_t column = 0; for (; i < end; i ++) { ecs_id_t cur = ids[i]; if (!ECS_HAS_ID_FLAG(cur, TOGGLE)) { continue; } if (cur == bs_id) { return column; } column ++; } return -1; } 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; } ecs_assert(toggle_column < table->_->bs_count, ECS_INTERNAL_ERROR, NULL); return &table->_->bs_columns[toggle_column]; } 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]; } int32_t flecs_table_observed_count( const ecs_table_t *table) { return table->_->traversable_count; } uint64_t flecs_table_bloom_filter_add( uint64_t filter, uint64_t value) { filter |= 1llu << (value % 64); return filter; } bool flecs_table_bloom_filter_test( const ecs_table_t *table, uint64_t filter) { return (table->bloom_filter & filter) == filter; } ecs_table_records_t flecs_table_records( ecs_table_t* table) { return (ecs_table_records_t){ .array = table->_->records, .count = table->_->record_count }; } ecs_component_record_t* flecs_table_record_get_component( const ecs_table_record_t *tr) { return tr->hdr.cr; } uint64_t flecs_table_id( ecs_table_t* table) { return table->id; } const ecs_ref_t* flecs_table_get_override( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, const ecs_component_record_t *cr, ecs_ref_t *storage) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); if (!(table->flags & EcsTableHasIsA)) { return NULL; } if (cr->flags & EcsIdSparse) { ecs_entity_t base = 0; if (ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, &base, NULL, NULL) != -1) { *storage = ecs_ref_init_id(world, base, id); return storage; } } ecs_table_overrides_t *o = table->data.overrides; if (!o) { return NULL; } int32_t column_index = ecs_table_get_column_index(world, table, id); if (column_index == -1) { return NULL; } flecs_table_update_overrides(world, table); ecs_ref_t *r = &o->refs[column_index]; if (!r->entity) { return NULL; } return r; } void flecs_table_keep( ecs_table_t *table) { table->keep ++; } void flecs_table_release( ecs_table_t *table) { table->keep --; ecs_assert(table->keep >= 0, ECS_INTERNAL_ERROR, NULL); } ecs_component_record_t* flecs_table_get_childof_cr( const ecs_world_t *world, const ecs_table_t *table) { if (!table) { return NULL; } int16_t index = table->childof_index; if (index == -1) { return world->cr_childof_0; } else { return table->_->records[index].hdr.cr; } } ecs_pair_record_t* flecs_table_get_childof_pr( const ecs_world_t *world, const ecs_table_t *table) { if (!table) { return NULL; } ecs_component_record_t *cr = flecs_table_get_childof_cr(world, table); if (cr) { return cr->pair; } return NULL; } ecs_hashmap_t* flecs_table_get_name_index( const ecs_world_t *world, const ecs_table_t *table) { if (!table) { return NULL; } ecs_pair_record_t *pr = flecs_table_get_childof_pr(world, table); if (pr) { return pr->name_index; } return NULL; } /* -- Public API -- */ 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 ++; } } } 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"); } } } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } int32_t ecs_table_get_type_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, 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)]; } return -res - 1; } ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return -1; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return -1; } return tr->index; error: return -1; } int32_t ecs_table_get_column_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { 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 (id < FLECS_HI_COMPONENT_ID) { int16_t res = table->component_map[id]; if (res > 0) { return res - 1; } return -1; } ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return -1; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return -1; } return tr->column; error: return -1; } int32_t ecs_table_column_count( const ecs_table_t *table) { return table->column_count; } int32_t ecs_table_type_to_column_index( const ecs_table_t *table, int32_t index) { ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int16_t *column_map = table->column_map; if (column_map) { return column_map[index]; } error: return -1; } int32_t ecs_table_column_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->column_map[offset + index]; error: return -1; } void* ecs_table_get_column( const ecs_table_t *table, int32_t index, int32_t offset) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, "column index %d out of range for table", index); ecs_column_t *column = &table->data.columns[index]; void *result = column->data; if (offset) { result = ECS_ELEM(result, column->ti->size, offset); } return result; error: return NULL; } void* ecs_table_get_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, int32_t offset) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t index = ecs_table_get_column_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } size_t ecs_table_get_column_size( const ecs_table_t *table, int32_t column) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_ito(size_t, table->data.columns[column].ti->size); error: return 0; } int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.count; } int32_t ecs_table_size( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.size; } 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; } ecs_entity_t ecs_table_get_target( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t relationship, int32_t index) { flecs_poly_assert(world, ecs_world_t); ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(relationship, EcsWildcard)); if (!cr) { return 0; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return 0; } if ((index < 0) || (index >= tr->count)) { return 0; } ecs_id_t id = table->type.array[tr->index + index]; ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = ECS_PAIR_SECOND(id); return flecs_entities_get_alive(world, tgt); } int32_t ecs_table_get_depth( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t rel) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, "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; } bool ecs_table_has_flags( ecs_table_t *table, ecs_flags32_t flags) { return (table->flags & flags) == flags; } bool ecs_table_has_traversable( const ecs_table_t *table) { return table->_->traversable_count != 0; } 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); } 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; 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; ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } ecs_record_t* ecs_record_find( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); if (r) { return r; } error: return NULL; } char* ecs_table_str( const ecs_world_t *world, const ecs_table_t *table) { if (table) { return ecs_type_str(world, &table->type); } else { return NULL; } } static void flecs_table_cache_list_remove( ecs_table_cache_t *cache, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t *next = elem->next; ecs_table_cache_hdr_t *prev = elem->prev; if (next) { next->prev = prev; } if (prev) { prev->next = next; } cache->tables.count --; if (cache->tables.first == elem) { cache->tables.first = next; } if (cache->tables.last == elem) { cache->tables.last = prev; } 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); } 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; } elem->next = NULL; elem->prev = last; if (last) { last->next = elem; } ecs_assert( cache->tables.count != 1 || cache->tables.first == cache->tables.last, ECS_INTERNAL_ERROR, NULL); } 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(&cache->index, &world->allocator); } void ecs_table_cache_fini( ecs_table_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_fini(&cache->index); } 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); result->cr = (ecs_component_record_t*)cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); flecs_table_cache_list_insert(cache, result); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_insert_ptr(&cache->index, table->id, result); ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t **r = ecs_map_get_ref( &cache->index, ecs_table_cache_hdr_t, table->id); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *old = *r; ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; if (prev) { ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); prev->next = elem; } if (next) { ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); next->prev = elem; } if (cache->tables.first == old) { cache->tables.first = elem; } if (cache->tables.last == old) { cache->tables.last = elem; } *r = elem; elem->prev = prev; elem->next = next; } void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_map_is_init(&cache->index), ECS_INTERNAL_ERROR, NULL); return ecs_map_get_deref(&cache->index, void**, table->id); } 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(elem != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->cr == (ecs_component_record_t*)cache, ECS_INTERNAL_ERROR, NULL); flecs_table_cache_list_remove(cache, elem); ecs_map_remove(&cache->index, table_id); return elem; } bool flecs_table_cache_iter( const 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; } bool flecs_table_cache_empty_iter( const 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; } bool flecs_table_cache_all_iter( const 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; } const ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it) { const ecs_table_cache_hdr_t *next; repeat: next = it->next; it->cur = next; if (next) { it->next = next->next; if (ecs_table_count(next->table)) { if (!it->iter_fill) { goto repeat; } } else { if (!it->iter_empty) { goto repeat; } } } return next; } ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src); /* Id sequence (type) utilities */ static uint64_t flecs_type_hash(const void *ptr) { const ecs_type_t *type = ptr; ecs_id_t *ids = type->array; int32_t count = type->count; return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); } static int flecs_type_compare(const void *ptr_1, const void *ptr_2) { const ecs_type_t *type_1 = ptr_1; const ecs_type_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } const ecs_id_t *ids_1 = type_1->array; const ecs_id_t *ids_2 = type_2->array; int result = 0; int32_t i; for (i = 0; !result && (i < count_1); i ++) { ecs_id_t id_1 = ids_1[i]; ecs_id_t id_2 = ids_2[i]; result = (id_1 > id_2) - (id_1 < id_2); } return result; } void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm) { flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, flecs_type_hash, flecs_type_compare, &world->allocator); } /* Find location where to insert id into type */ static int flecs_type_find_insert( const ecs_type_t *type, int32_t offset, ecs_id_t to_add) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = offset; i < count; i ++) { ecs_id_t id = array[i]; if (id == to_add) { return -1; } if (id > to_add) { return i; } } return i; } /* Find location of id in type */ static int flecs_type_find( const ecs_type_t *type, ecs_id_t id) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t cur = array[i]; if (ecs_id_match(cur, id)) { return i; } if (!ECS_IS_PAIR(id) && (cur > id)) { return -1; } } return -1; } static int flecs_type_find_ignoring_generation( const ecs_type_t *type, ecs_id_t id) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { if ((uint32_t)array[i] == (uint32_t)id) { return i; } } return -1; } /* Count number of matching ids */ static int flecs_type_count_matches( const ecs_type_t *type, ecs_id_t wildcard, int32_t offset) { ecs_id_t *array = type->array; int32_t i = offset, count = type->count; for (; i < count; i ++) { ecs_id_t cur = array[i]; if (!ecs_id_match(cur, wildcard)) { break; } } return i - offset; } /* Create type from source type with id */ static int flecs_type_new_with( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t with) { ecs_id_t *src_array = src->array; int32_t at = flecs_type_find_insert(src, 0, with); if (at == -1) { return -1; } int32_t dst_count = src->count + 1; ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->count = dst_count; dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = src->count - at; if (remain) { ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } dst_array[at] = with; return 0; } /* Create type from source type without ids matching wildcard */ static int flecs_type_new_filtered( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t wildcard, int32_t at) { *dst = flecs_type_copy(world, src); ecs_id_t *dst_array = dst->array; ecs_id_t *src_array = src->array; if (at) { ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t i = at + 1, w = at, count = src->count; for (; i < count; i ++) { ecs_id_t id = src_array[i]; if (!ecs_id_match(id, wildcard)) { dst_array[w] = id; w ++; } } dst->count = w; if (w != count) { if (w) { dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); } else { flecs_wfree_n(world, ecs_id_t, count, dst->array); dst->array = NULL; } } return 0; } /* Create type from source type without id */ static int flecs_type_new_without( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t without) { ecs_id_t *src_array = src->array; int32_t count = 1, at = flecs_type_find(src, without); if (at == -1) { return -1; } int32_t src_count = src->count; if (src_count == 1) { dst->array = NULL; dst->count = 0; return 0; } if (ecs_id_is_wildcard(without)) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = ECS_PAIR_FIRST(without); ecs_entity_t o = ECS_PAIR_SECOND(without); if (r == EcsWildcard && o != EcsWildcard) { return flecs_type_new_filtered(world, dst, src, without, at); } } count += flecs_type_count_matches(src, without, at + 1); } int32_t dst_count = src_count - count; dst->count = dst_count; if (!dst_count) { dst->array = NULL; return 0; } ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->array = dst_array; ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_array != NULL, ECS_INTERNAL_ERROR, NULL); if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = dst_count - at; if (remain) { ecs_os_memcpy_n( &dst_array[at], &src_array[at + count], ecs_id_t, remain); } return 0; } /* Create type from source type without entity id, ignoring generation */ static int flecs_type_new_without_ignoring_generation( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t without) { ecs_assert(!ecs_id_is_wildcard(without), ECS_INVALID_PARAMETER, NULL); ecs_assert(!ECS_IS_PAIR(without), ECS_INVALID_PARAMETER, NULL); ecs_id_t *src_array = src->array; int32_t at = flecs_type_find_ignoring_generation(src, without); if (at == -1) { return -1; } int32_t src_count = src->count; if (src_count == 1) { dst->array = NULL; dst->count = 0; return 0; } int32_t dst_count = src_count - 1; ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->array = dst_array; dst->count = dst_count; ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_array != NULL, ECS_INTERNAL_ERROR, NULL); if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = dst_count - at; if (remain) { ecs_os_memcpy_n( &dst_array[at], &src_array[at + 1], ecs_id_t, remain); } return 0; } /* Copy type */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src) { int32_t src_count = src->count; if (!src_count) { return (ecs_type_t){ 0 }; } ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); return (ecs_type_t) { .array = ids, .count = src_count }; } /* Free type */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type) { int32_t count = type->count; if (count) { flecs_wfree_n(world, ecs_id_t, type->count, type->array); } } /* Add to type */ 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; } } /* Remove from type */ static void flecs_type_remove( ecs_world_t *world, ecs_type_t *type, ecs_id_t remove) { ecs_type_t new_type; int res = flecs_type_new_without(world, &new_type, type, remove); if (res != -1) { flecs_type_free(world, type); type->array = new_type.array; type->count = new_type.count; } } /* Remove from type while ignoring entity id generation */ void flecs_type_remove_ignoring_generation( ecs_world_t *world, ecs_type_t *type, ecs_id_t remove) { ecs_type_t new_type; int res = flecs_type_new_without_ignoring_generation( world, &new_type, type, remove); if (res != -1) { flecs_type_free(world, type); type->array = new_type.array; type->count = new_type.count; } } /* Graph edge utilities */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &builder->added, ecs_id_t, 32); ecs_vec_init_t(a, &builder->removed, ecs_id_t, 32); builder->added_flags = 0; builder->removed_flags = 0; } void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &builder->added, ecs_id_t); ecs_vec_fini_t(a, &builder->removed, ecs_id_t); } void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder) { ecs_vec_clear(&builder->added); ecs_vec_clear(&builder->removed); } static void flecs_table_diff_build_type( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *type, int32_t offset) { int32_t count = vec->count - offset; ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); if (count) { type->array = flecs_wdup_n(world, ecs_id_t, count, ECS_ELEM_T(vec->array, ecs_id_t, offset)); type->count = count; ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); } } static 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; } static void flecs_table_diff_build_add_type_to_vec( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *add) { if (!add || !add->count) { return; } int32_t offset = vec->count; ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), add->array, ecs_id_t, add->count); } void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src) { flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); dst->added_flags |= src->added_flags; dst->removed_flags |= src->removed_flags; } static void flecs_table_diff_free( ecs_world_t *world, ecs_table_diff_t *diff) { flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); flecs_bfree(&world->allocators.table_diff, diff); } static ecs_graph_edge_t* flecs_table_ensure_hi_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { if (!edges->hi) { edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); ecs_map_init(edges->hi, &world->allocator); } ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); ecs_graph_edge_t *edge = r[0]; if (edge) { return edge; } if (id < FLECS_HI_COMPONENT_ID) { edge = &edges->lo[id]; } else { edge = flecs_bcalloc(&world->allocators.graph_edge); } r[0] = edge; return edge; } static ecs_graph_edge_t* flecs_table_ensure_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { ecs_graph_edge_t *edge; if (id < FLECS_HI_COMPONENT_ID) { if (!edges->lo) { edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); } edge = &edges->lo[id]; } else { edge = flecs_table_ensure_hi_edge(world, edges, id); } return edge; } static void flecs_table_disconnect_edge( ecs_world_t *world, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); (void)id; /* Remove backref from destination table */ ecs_graph_edge_hdr_t *next = edge->hdr.next; ecs_graph_edge_hdr_t *prev = edge->hdr.prev; if (next) { next->prev = prev; } if (prev) { prev->next = next; } /* Remove data associated with edge */ ecs_table_diff_t *diff = edge->diff; if (diff) { flecs_table_diff_free(world, diff); } /* If edge id is low, clear it from fast lookup array */ if (id < FLECS_HI_COMPONENT_ID) { ecs_os_memset_t(edge, 0, ecs_graph_edge_t); } else { flecs_bfree(&world->allocators.graph_edge, edge); } } static void flecs_table_remove_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); if (!edge->id) { return; } flecs_table_disconnect_edge(world, id, edge); ecs_map_remove(edges->hi, id); } static void flecs_table_init_edges( ecs_graph_edges_t *edges) { edges->lo = NULL; edges->hi = NULL; } static void flecs_table_init_node( ecs_graph_node_t *node) { flecs_table_init_edges(&node->add); flecs_table_init_edges(&node->remove); } static void flecs_init_table( ecs_world_t *world, ecs_table_t *table, ecs_table_t *prev) { table->flags = 0; table->dirty_state = NULL; table->_->lock = 0; table->_->generation = 0; flecs_table_init_node(&table->node); flecs_table_init(world, table, prev); } static ecs_table_t *flecs_table_new( ecs_world_t *world, ecs_type_t *type, flecs_hashmap_result_t table_elem, ecs_table_t *prev) { flecs_check_exclusive_world_access_write(world); ecs_os_perf_trace_push("flecs.table.create"); 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); #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_err(" %d: %s", j, str); } ecs_os_free(str); } ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); } } #endif result->id = flecs_sparse_last_id(&world->store.tables); result->type = *type; if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &result->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", expr, result->id); ecs_os_free(expr); } ecs_log_push_2(); /* Store table in table hashmap */ *(ecs_table_t**)table_elem.value = result; /* Set keyvalue to one that has the same lifecycle as the table */ *(ecs_type_t*)table_elem.key = result->type; result->_->hash = table_elem.hash; flecs_init_table(world, result, prev); /* Update counters */ world->info.table_count ++; world->info.table_create_total ++; ecs_log_pop_2(); ecs_os_perf_trace_pop("flecs.table.create"); return result; } 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); int32_t id_count = type->count; if (!id_count) { return &world->store.root; } ecs_table_t *table; flecs_hashmap_result_t elem = flecs_hashmap_ensure( &world->store.table_map, type, ecs_table_t*); if ((table = *(ecs_table_t**)elem.value)) { if (own_type) { flecs_type_free(world, type); } return table; } /* If we get here, table needs to be created which is only allowed when the * application is not currently in progress */ ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); /* If we get here, the table has not been found, so create it. */ if (own_type) { return flecs_table_new(world, type, elem, prev); } ecs_type_t copy = flecs_type_copy(world, type); return flecs_table_new(world, ©, elem, prev); } static void flecs_diff_insert_added( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; } static void flecs_diff_insert_removed( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_allocator_t *a = &world->allocator; ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; } static bool flecs_id_is_alive( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { if (!flecs_entities_get_alive(world, ECS_PAIR_FIRST(id))) { return false; } if (!flecs_entities_get_alive(world, ECS_PAIR_SECOND(id))) { return false; } return true; } else { return flecs_entities_get_alive(world, id & ECS_COMPONENT_MASK) != 0; } } 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, bool is_remove) { ecs_type_t node_type = node->type; ecs_type_t next_type = next->type; bool childof = false; if (ECS_IS_PAIR(id)) { childof = ECS_PAIR_FIRST(id) == EcsChildOf; } ecs_component_record_t *cr = NULL; if (!flecs_id_is_alive(world, id)) { return; } bool dont_fragment = false; if (id < FLECS_HI_COMPONENT_ID) { dont_fragment = (world->non_trivial_lookup[id] & EcsNonTrivialIdNonFragmenting) != 0; if (dont_fragment) { cr = flecs_components_ensure(world, id); } } else { cr = flecs_components_ensure(world, id); dont_fragment = cr->flags & EcsIdDontFragment; } if (dont_fragment) { ecs_table_diff_t *diff = flecs_bcalloc( &world->allocators.table_diff); if (is_remove) { diff->removed.count = 1; diff->removed.array = flecs_wdup_n(world, ecs_id_t, 1, &id); diff->removed_flags = EcsTableHasDontFragment|EcsTableHasSparse; } else { diff->added.count = 1; diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); diff->added_flags = EcsTableHasDontFragment|EcsTableHasSparse; } edge->diff = diff; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_append_t(&world->allocator, &cr->dont_fragment_tables, uint64_t)[0] = node->id; return; } 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) && !childof; /* First do a scan to see how big the diff is, so we don't have to realloc * or alloc more memory than required. */ for (; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; bool added = id_next < id_node; bool removed = id_node < id_next; trivial_edge &= !added || id_next == id; trivial_edge &= !removed || id_node == id; if (added) { added_flags |= flecs_id_flags_get(world, id_next) & EcsTableAddEdgeFlags; added_count ++; } if (removed) { removed_flags |= flecs_id_flags_get(world, id_node) & EcsTableRemoveEdgeFlags; removed_count ++; } i_node += id_node <= id_next; i_next += id_next <= id_node; } for (; i_next < next_count; i_next ++) { added_flags |= flecs_id_flags_get(world, ids_next[i_next]) & EcsTableAddEdgeFlags; added_count ++; } for (; i_node < node_count; i_node ++) { removed_flags |= flecs_id_flags_get(world, ids_node[i_node]) & EcsTableRemoveEdgeFlags; removed_count ++; } trivial_edge &= (added_count + removed_count) <= 1 && !ecs_id_is_wildcard(id) && !(added_flags|removed_flags); if (trivial_edge) { /* If edge is trivial, there's no need to create a diff element for it */ return; } ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; int32_t added_offset = builder->added.count; int32_t removed_offset = builder->removed.count; for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; if (id_next < id_node) { flecs_diff_insert_added(world, builder, id_next); } else if (id_node < id_next) { flecs_diff_insert_removed(world, builder, id_node); } i_node += id_node <= id_next; i_next += id_next <= id_node; } for (; i_next < next_count; i_next ++) { flecs_diff_insert_added(world, builder, ids_next[i_next]); } for (; i_node < node_count; i_node ++) { flecs_diff_insert_removed(world, builder, ids_node[i_node]); } ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff = diff; flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); diff->added_flags = added_flags; diff->removed_flags = removed_flags; if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf) { if (added_count) { diff->added_flags |= EcsTableEdgeReparent; } else if (removed_count) { diff->removed_flags |= EcsTableEdgeReparent; } } ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } static void flecs_add_overrides_for_base( ecs_world_t *world, ecs_type_t *dst_type, ecs_id_t pair) { ecs_entity_t base = ecs_pair_second(world, pair); ecs_assert(base != 0, ECS_INVALID_PARAMETER, "target of IsA pair is not alive"); ecs_table_t *base_table = ecs_get_table(world, base); if (!base_table) { return; } ecs_id_t *ids = base_table->type.array; ecs_flags32_t flags = base_table->flags; if (flags & EcsTableHasOverrides) { int32_t i, count = base_table->type.count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; ecs_id_t to_add = 0; if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { to_add = id & ~ECS_AUTO_OVERRIDE; ecs_flags32_t cr_flags = flecs_component_get_flags(world, to_add); if (cr_flags & EcsIdDontFragment) { to_add = 0; /* Add flag to base table. Cheaper to do here vs adding an * observer for (OnAdd, AUTO_OVERRIDE|*) during table * creation. */ base_table->flags |= EcsTableOverrideDontFragment; } } else { ecs_table_record_t *tr = &base_table->_->records[i]; if (ECS_ID_ON_INSTANTIATE(tr->hdr.cr->flags) == EcsOverride) { to_add = id; } } if (to_add) { ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); bool exclusive = false; if (ECS_IS_PAIR(to_add)) { ecs_component_record_t *cr = flecs_components_get(world, wc); if (cr) { exclusive = (cr->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); } } } } } if (flags & EcsTableHasIsA) { const ecs_table_record_t *tr = flecs_component_get_table( world->cr_isa_wildcard, base_table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = tr->index, end = i + tr->count; for (; i != end; i ++) { flecs_add_overrides_for_base(world, dst_type, ids[i]); } } } static void flecs_add_with_property( ecs_world_t *world, ecs_component_record_t *cr_with_wildcard, ecs_type_t *dst_type, ecs_entity_t r, ecs_entity_t o) { r = ecs_get_alive(world, r); /* Check if component/relationship has With pairs, which contain ids * that need to be added to the table. */ ecs_table_t *table = ecs_get_table(world, r); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_component_get_table( cr_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); } ecs_id_t check_id = ECS_IS_PAIR(a) ? ecs_pair(ECS_PAIR_FIRST(a), EcsWildcard) : a; ecs_component_record_t *a_cr = flecs_components_ensure(world, check_id); if (!(a_cr->flags & EcsIdDontFragment)) { flecs_type_add(world, dst_type, a); } flecs_add_with_property(world, cr_with_wildcard, dst_type, ra, o); } } } static ecs_table_t* flecs_find_table_with( ecs_world_t *world, ecs_table_t *node, ecs_id_t with) { ecs_make_alive_id(world, with); ecs_component_record_t *cr = NULL; ecs_entity_t r = 0, o = 0; ecs_type_t dst_type; bool replaced = false; if (ECS_IS_PAIR(with)) { r = ECS_PAIR_FIRST(with); o = ECS_PAIR_SECOND(with); cr = flecs_components_ensure(world, ecs_pair(r, EcsWildcard)); if (cr->flags & EcsIdExclusive) { /* Relationship is exclusive, check if table already has it */ const ecs_table_record_t *tr = flecs_component_get_table(cr, node); if (tr) { /* Table already has an instance of the relationship, create * a new id sequence with the existing id replaced */ dst_type = flecs_type_copy(world, &node->type); ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); dst_type.array[tr->index] = with; replaced = true; } } } else { cr = flecs_components_ensure(world, with); r = with; } if (!replaced) { if (cr->flags & EcsIdDontFragment) { /* Component doesn't fragment tables */ node->flags |= EcsTableHasDontFragment; return node; } /* Create sequence with new id */ int res = flecs_type_new_with(world, &dst_type, &node->type, with); if (res == -1) { return node; /* Current table already has id */ } } if (r == EcsIsA) { /* If adding a prefab, check if prefab has overrides */ flecs_add_overrides_for_base(world, &dst_type, with); } else if (r == EcsChildOf) { o = ecs_get_alive(world, o); if (ecs_has_id(world, o, EcsPrefab)) { flecs_type_add(world, &dst_type, EcsPrefab); } } if (cr->flags & EcsIdWith) { ecs_component_record_t *cr_with_wildcard = flecs_components_get(world, ecs_pair(EcsWith, EcsWildcard)); /* If id has With property, add targets to type */ flecs_add_with_property(world, cr_with_wildcard, &dst_type, r, o); } if (with == ecs_id(EcsParent)) { if (node->flags & EcsTableHasChildOf) { flecs_type_remove(world, &dst_type, ecs_pair(EcsChildOf, EcsWildcard)); } } else if (ECS_PAIR_FIRST(with) == EcsChildOf) { if (node->flags & EcsTableHasParent) { flecs_type_remove(world, &dst_type, ecs_id(EcsParent)); flecs_type_remove(world, &dst_type, ecs_pair(EcsParentDepth, EcsWildcard)); } } return flecs_table_ensure(world, &dst_type, true, node); } static ecs_table_t* flecs_find_table_without( ecs_world_t *world, ecs_table_t *node, ecs_id_t without) { ecs_component_record_t *cr = NULL; if (ECS_IS_PAIR(without)) { ecs_entity_t r = ECS_PAIR_FIRST(without); cr = flecs_components_get(world, ecs_pair(r, EcsWildcard)); if (cr) { if (cr->flags & EcsIdDontFragment) { node->flags |= EcsTableHasDontFragment; /* Component doesn't fragment tables */ return node; } } } else { cr = flecs_components_get(world, without); if (cr && cr->flags & EcsIdDontFragment) { node->flags |= EcsTableHasDontFragment; /* Component doesn't fragment tables */ return node; } } /* Create sequence with new id */ ecs_type_t dst_type; int res = flecs_type_new_without(world, &dst_type, &node->type, without); if (res == -1) { return node; /* Current table does not have id */ } if (without == ecs_id(EcsParent)) { flecs_type_remove(world, &dst_type, ecs_pair(EcsParentDepth, EcsWildcard)); } return flecs_table_ensure(world, &dst_type, true, node); } static void flecs_table_init_edge( ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); edge->from = table; edge->to = to; edge->id = id; } static void flecs_init_edge_for_add( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { flecs_table_init_edge(table, edge, id, to); flecs_table_ensure_hi_edge(world, &table->node.add, id); if ((table != to) || (table->flags & EcsTableHasDontFragment)) { /* 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, false); } } static void flecs_init_edge_for_remove( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { flecs_table_init_edge(table, edge, id, to); flecs_table_ensure_hi_edge(world, &table->node.remove, id); if ((table != to) || (table->flags & EcsTableHasDontFragment)) { /* 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, true); } } static ecs_table_t* flecs_create_edge_for_remove( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { ecs_table_t *to = flecs_find_table_without(world, node, id); flecs_init_edge_for_remove(world, node, edge, id, to); return to; } static ecs_table_t* flecs_create_edge_for_add( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { ecs_table_t *to = flecs_find_table_with(world, node, id); flecs_init_edge_for_add(world, node, edge, id, to); return to; } ecs_table_t* flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *node, ecs_id_t *id_ptr, ecs_table_diff_t *diff) { flecs_poly_assert(world, ecs_world_t); ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); /* Removing 0 from an entity is not valid */ ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t id = id_ptr[0]; ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id); ecs_table_t *to = edge->to; if (!to) { to = flecs_create_edge_for_remove(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } if (node != to || edge->diff) { if (edge->diff) { *diff = *edge->diff; } else { diff->added.count = 0; diff->removed.array = id_ptr; diff->removed.count = 1; } } return to; error: return NULL; } ecs_table_t* flecs_table_traverse_add( ecs_world_t *world, ecs_table_t *node, ecs_id_t *id_ptr, ecs_table_diff_t *diff) { flecs_poly_assert(world, ecs_world_t); ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); /* Adding 0 to an entity is not valid */ ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t id = id_ptr[0]; ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); ecs_table_t *to = edge->to; if (!to) { to = flecs_create_edge_for_add(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } if (node != to || edge->diff) { if (edge->diff) { *diff = *edge->diff; } else { diff->added.array = id_ptr; diff->added.count = 1; diff->removed.count = 0; } } return to; error: return NULL; } ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type) { flecs_poly_assert(world, ecs_world_t); return flecs_table_ensure(world, type, false, NULL); } void flecs_init_root_table( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); world->store.root.type = (ecs_type_t){0}; world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); flecs_init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ uint64_t new_id = flecs_sparse_new_id(&world->store.tables); ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); (void)new_id; } void flecs_table_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)); } } /* 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); ecs_log_push_1(); ecs_map_iter_t it; ecs_graph_node_t *table_node = &table->node; ecs_graph_edges_t *node_add = &table_node->add; ecs_graph_edges_t *node_remove = &table_node->remove; ecs_map_t *add_hi = node_add->hi; ecs_map_t *remove_hi = node_remove->hi; ecs_graph_edge_hdr_t *node_refs = &table_node->refs; /* Cleanup outgoing edges */ it = ecs_map_iter(add_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } it = ecs_map_iter(remove_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } /* Cleanup incoming add edges */ ecs_graph_edge_hdr_t *next, *cur = node_refs->next; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->next; flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); } while ((cur = next)); } /* Cleanup incoming remove edges */ cur = node_refs->prev; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->prev; flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); } while ((cur = next)); } if (node_add->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); } if (node_remove->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); } ecs_map_fini(add_hi); ecs_map_fini(remove_hi); flecs_free_t(&world->allocator, ecs_map_t, add_hi); flecs_free_t(&world->allocator, ecs_map_t, remove_hi); table_node->add.lo = NULL; table_node->remove.lo = NULL; table_node->add.hi = NULL; table_node->remove.hi = NULL; ecs_log_pop_1(); } void flecs_table_clear_edges_for_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t component) { if (component < FLECS_HI_COMPONENT_ID) { if (table->node.add.lo) { ecs_graph_edge_t *add_edge = &table->node.add.lo[component]; if (add_edge->id) { flecs_table_disconnect_edge(world, component, add_edge); ecs_map_remove(table->node.add.hi, component); } } if (table->node.remove.lo) { ecs_graph_edge_t *remove_edge = &table->node.remove.lo[component]; if (remove_edge->id) { flecs_table_disconnect_edge(world, component, remove_edge); ecs_map_remove(table->node.remove.hi, component); } } } else { if (table->node.add.hi) { ecs_graph_edge_t *add_edge = ecs_map_get_ptr( table->node.add.hi, component); if (add_edge) { flecs_table_disconnect_edge(world, component, add_edge); ecs_map_remove(table->node.add.hi, component); } } if (table->node.remove.hi) { ecs_graph_edge_t *remove_edge = ecs_map_get_ptr( table->node.remove.hi, component); if (remove_edge) { flecs_table_disconnect_edge(world, component, remove_edge); ecs_map_remove(table->node.remove.hi, component); } } } } 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; } /* 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; table = table ? table : &world->store.root; return flecs_table_traverse_add(world, table, &id, &diff); } ecs_table_t* ecs_table_remove_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; table = table ? table : &world->store.root; return flecs_table_traverse_remove(world, table, &id, &diff); } ecs_table_t* ecs_table_find( ecs_world_t *world, const ecs_id_t *ids, int32_t id_count) { ecs_type_t type = { .array = ECS_CONST_CAST(ecs_id_t*, ids), .count = id_count }; return flecs_table_ensure(world, &type, false, NULL); } #include #ifndef FLECS_SCRIPT_PRIVATE_H #define FLECS_SCRIPT_PRIVATE_H #ifndef FLECS_PARSER_H #define FLECS_PARSER_H typedef struct ecs_script_impl_t ecs_script_impl_t; typedef struct ecs_script_scope_t ecs_script_scope_t; #ifndef ECS_PARSER_MAX_RECURSION_DEPTH #define ECS_PARSER_MAX_RECURSION_DEPTH (64) #endif typedef struct ecs_parser_t { const char *name; const char *code; const char *pos; const char *fixed_pos; const char *stmt_pos; char *token_cur; char *token_end; char *token_keep; bool significant_newline; bool merge_variable_members; bool function_token; int16_t scope_depth; int16_t expr_depth; ecs_world_t *world; /* For script parser */ ecs_script_impl_t *script; ecs_script_scope_t *scope; /* For term parser */ ecs_term_t *term; ecs_oper_kind_t extra_oper; ecs_term_ref_t *extra_args; } ecs_parser_t; #ifndef FLECS_PARSER_TOKENIZER_H #define FLECS_PARSER_TOKENIZER_H /* 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', EcsTokChar = '\'', 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, EcsTokFunction = 113, EcsTokString = 114, EcsTokNumber = 115, EcsTokKeywordModule = 116, EcsTokKeywordUsing = 117, EcsTokKeywordWith = 118, EcsTokKeywordIf = 119, EcsTokKeywordFor = 120, EcsTokKeywordIn = 121, EcsTokKeywordElse = 122, EcsTokKeywordTemplate = 130, EcsTokKeywordProp = 131, EcsTokKeywordConst = 132, EcsTokKeywordMatch = 133, EcsTokKeywordNew = 134, EcsTokKeywordExport = 135, EcsTokKeywordInclude = 138, EcsTokKeywordFn = 139, EcsTokArrow = 140, EcsTokAddAssign = 136, EcsTokMulAssign = 137, } ecs_token_kind_t; typedef struct ecs_token_t { const char *value; ecs_token_kind_t kind; } ecs_token_t; typedef struct ecs_tokens_t { int32_t count; ecs_token_t tokens[256]; } ecs_tokens_t; typedef struct ecs_tokenizer_t { ecs_tokens_t stack; ecs_token_t *tokens; } ecs_tokenizer_t; const char* flecs_tokenizer_until( ecs_parser_t *parser, const char *ptr, ecs_token_t *out, char until); const char* flecs_token_kind_str( ecs_token_kind_t kind); const char* flecs_token_str( ecs_token_kind_t kind); const char* flecs_token( ecs_parser_t *parser, const char *ptr, ecs_token_t *out, bool is_lookahead); const char* flecs_scan_whitespace( ecs_parser_t *parser, const char *pos); const char* flecs_tokenizer_identifier( ecs_parser_t *parser, const char *pos, ecs_token_t *out); int64_t flecs_parser_errpos( const ecs_parser_t *parser, const char *pos); #endif #endif #endif // FLECS_SCRIPT_PRIVATE_H #ifndef FLECS_META_PRIVATE_H #define FLECS_META_PRIVATE_H #endif #include #include #ifndef FLECS_META_TYPE_SUPPORT_H #define FLECS_META_TYPE_SUPPORT_H #endif #ifdef FLECS_OS_API_IMPL #ifdef ECS_TARGET_WINDOWS #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) { ecs_os_free((CONDITION_VARIABLE*)(uintptr_t)c); } static void win_cond_signal( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeConditionVariable(cond); } static void win_cond_broadcast( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeAllConditionVariable(cond); } static void win_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; SleepConditionVariableCS(cond, mutex, INFINITE); } static bool win_time_initialized; static double win_time_freq; static LARGE_INTEGER win_time_start; static ULONG win_current_resolution; static void win_time_setup(void) { if ( win_time_initialized) { return; } win_time_initialized = true; LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&win_time_start); win_time_freq = (double)freq.QuadPart / 1000000000.0; } static void win_sleep( int32_t sec, int32_t nanosec) { HANDLE timer; LARGE_INTEGER ft; ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); } static void win_enable_high_timer_resolution(bool enable) { HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); if (!hntdll) { return; } union { LONG (__stdcall *f)( ULONG desired, BOOLEAN set, ULONG * current); FARPROC p; } func; func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); if(!func.p) { return; } ULONG current, resolution = 10000; /* 1 ms */ if (!enable && win_current_resolution) { func.f(win_current_resolution, 0, ¤t); win_current_resolution = 0; return; } else if (!enable) { return; } if (resolution == win_current_resolution) { return; } if (win_current_resolution) { func.f(win_current_resolution, 0, ¤t); } if (func.f(resolution, 1, ¤t)) { /* Try setting a lower resolution */ resolution *= 2; if(func.f(resolution, 1, ¤t)) return; } win_current_resolution = resolution; } static uint64_t win_time_now(void) { uint64_t now; LARGE_INTEGER qpc_t; QueryPerformanceCounter(&qpc_t); now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); return now; } static ecs_os_dl_t win_dlopen(const char *libname) { return (ecs_os_dl_t)(uintptr_t)LoadLibraryA(libname); } static ecs_os_proc_t win_dlproc(ecs_os_dl_t lib, const char *procname) { union { FARPROC p; ecs_os_proc_t fn; } u; u.p = GetProcAddress((HMODULE)(uintptr_t)lib, procname); return u.fn; } static void win_dlclose(ecs_os_dl_t lib) { FreeLibrary((HMODULE)(uintptr_t)lib); } static void win_fini(void) { if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(false); } } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = win_thread_new; api.thread_join_ = win_thread_join; api.thread_self_ = win_thread_self; api.task_new_ = win_thread_new; api.task_join_ = win_thread_join; api.ainc_ = win_ainc; api.adec_ = win_adec; api.lainc_ = win_lainc; api.ladec_ = win_ladec; api.mutex_new_ = win_mutex_new; api.mutex_free_ = win_mutex_free; api.mutex_lock_ = win_mutex_lock; api.mutex_unlock_ = win_mutex_unlock; api.cond_new_ = win_cond_new; api.cond_free_ = win_cond_free; api.cond_signal_ = win_cond_signal; api.cond_broadcast_ = win_cond_broadcast; api.cond_wait_ = win_cond_wait; api.sleep_ = win_sleep; api.now_ = win_time_now; api.fini_ = win_fini; api.dlopen_ = win_dlopen; api.dlproc_ = win_dlproc; api.dlclose_ = win_dlclose; win_time_setup(); if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(true); } ecs_os_set_api(&api); } #else #include "pthread.h" #include #if defined(__APPLE__) && defined(__MACH__) #include #elif defined(__EMSCRIPTEN__) #include #else #include #endif /* This mutex is used to emulate atomic operations when the gnu builtins are * not supported. This is probably not very fast but if the compiler doesn't * support the gnu built-ins, then speed is probably not a priority. */ #ifndef __GNUC__ static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; #endif static ecs_os_thread_t posix_thread_new( ecs_os_thread_callback_t callback, void *arg) { pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); if (pthread_create (thread, NULL, callback, arg) != 0) { ecs_os_abort(); } return (ecs_os_thread_t)(uintptr_t)thread; } static void* posix_thread_join( ecs_os_thread_t thread) { void *arg; pthread_t *thr = (pthread_t*)(uintptr_t)thread; pthread_join(*thr, &arg); ecs_os_free(thr); return arg; } static ecs_os_thread_id_t posix_thread_self(void) { return (ecs_os_thread_id_t)pthread_self(); } static int32_t posix_ainc( int32_t *count) { int value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) += 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int32_t posix_adec( int32_t *count) { int32_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) -= 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int64_t posix_lainc( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) += 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int64_t posix_ladec( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) -= 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static ecs_os_mutex_t posix_mutex_new(void) { pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); if (pthread_mutex_init(mutex, NULL)) { abort(); } return (ecs_os_mutex_t)(uintptr_t)mutex; } static void posix_mutex_free( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; pthread_mutex_destroy(mutex); ecs_os_free(mutex); } static void posix_mutex_lock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_lock(mutex)) { abort(); } } static void posix_mutex_unlock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_unlock(mutex)) { abort(); } } static ecs_os_cond_t posix_cond_new(void) { pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); if (pthread_cond_init(cond, NULL)) { abort(); } return (ecs_os_cond_t)(uintptr_t)cond; } static void posix_cond_free( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_destroy(cond)) { abort(); } ecs_os_free(cond); } static void posix_cond_signal( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_signal(cond)) { abort(); } } static void posix_cond_broadcast( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_broadcast(cond)) { abort(); } } static void posix_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_cond_wait(cond, mutex)) { abort(); } } static bool posix_time_initialized; #if defined(__APPLE__) && defined(__MACH__) static mach_timebase_info_data_t posix_osx_timebase; static uint64_t posix_time_start; #else static uint64_t posix_time_start; #endif static void posix_time_setup(void) { if (posix_time_initialized) { return; } posix_time_initialized = true; #if defined(__APPLE__) && defined(__MACH__) mach_timebase_info(&posix_osx_timebase); posix_time_start = mach_absolute_time(); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; #endif } static void posix_sleep( int32_t sec, int32_t nanosec) { struct timespec sleepTime; ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); sleepTime.tv_sec = sec; sleepTime.tv_nsec = nanosec; if (nanosleep(&sleepTime, NULL)) { ecs_err("nanosleep failed"); } } /* prevent 64-bit overflow when computing relative timestamp see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 */ #if defined(ECS_TARGET_DARWIN) static int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { int64_t q = value / denom; int64_t r = value % denom; return q * numer + r * numer / denom; } #endif static uint64_t posix_time_now(void) { ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); uint64_t now; #if defined(ECS_TARGET_DARWIN) now = (uint64_t) posix_int64_muldiv( (int64_t)mach_absolute_time(), (int64_t)posix_osx_timebase.numer, (int64_t)posix_osx_timebase.denom); #elif defined(__EMSCRIPTEN__) now = (long long)(emscripten_get_now() * 1000.0 * 1000); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); #endif return now; } static ecs_os_dl_t posix_dlopen(const char *libname) { return (ecs_os_dl_t)(uintptr_t)dlopen(libname, RTLD_NOW); } static ecs_os_proc_t posix_dlproc(ecs_os_dl_t lib, const char *procname) { union { void *obj; ecs_os_proc_t fn; } u; u.obj = dlsym((void*)(uintptr_t)lib, procname); return u.fn; } static void posix_dlclose(ecs_os_dl_t lib) { dlclose((void*)(uintptr_t)lib); } 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; api.dlopen_ = posix_dlopen; api.dlproc_ = posix_dlproc; api.dlclose_ = posix_dlclose; posix_time_setup(); ecs_os_set_api(&api); } #endif #endif #ifndef FLECS_PARSER_GRAMMAR_H #define FLECS_PARSER_GRAMMAR_H #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 /* 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,\ .token_end = &(tokens)[tokens_len]\ } /* Definitions for parser functions */ #define ParserBegin\ ecs_tokenizer_t _tokenizer;\ ecs_os_zeromem(&_tokenizer);\ _tokenizer.tokens = _tokenizer.stack.tokens;\ ecs_tokenizer_t *tokenizer = &_tokenizer; #define ParserEnd\ Error("unexpected end of rule (parser error)");\ error:\ return NULL /* Get token */ #define Token(n) (tokenizer->tokens[n].value) /* Push/pop token frame (allows token stack reuse in recursive functions) */ #define TokenFramePush() \ tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; #define TokenFramePop() \ tokenizer->tokens = tokenizer->stack.tokens; /* Error */ #define Error(...)\ ecs_parser_error(parser->name, parser->code,\ flecs_parser_errpos(parser, pos - 1), __VA_ARGS__);\ goto error /* Warning */ #define Warning(...)\ ecs_parser_warning(parser->name, parser->code,\ flecs_parser_errpos(parser, pos - 1), __VA_ARGS__);\ /* 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__\ } /* 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) {\ if (until != '\n' || pos[0] != '\0') {\ Error("expected '%c'", until);\ }\ if (pos[0] == '\0') {\ pos --;\ }\ }\ INITIALIZER = (ecs_expr_node_t*)_initializer;\ pos ++;\ __VA_ARGS__\ } /* 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__) /* 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));\ }\ }\ } /* Parse N consecutive tokens */ #define Parse_1(tok, ...)\ Parse(\ case tok: {\ __VA_ARGS__\ }\ ) #define Parse_2(tok1, tok2, ...)\ Parse_1(tok1, \ Parse(\ case tok2: {\ __VA_ARGS__\ }\ )\ ) #define Parse_3(tok1, tok2, tok3, ...)\ Parse_2(tok1, tok2, \ Parse(\ case tok3: {\ __VA_ARGS__\ }\ )\ ) #define Parse_4(tok1, tok2, tok3, tok4, ...)\ Parse_3(tok1, tok2, tok3, \ Parse(\ case tok4: {\ __VA_ARGS__\ }\ )\ ) #define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ Parse_4(tok1, tok2, tok3, tok4, \ Parse(\ case tok5: {\ __VA_ARGS__\ }\ )\ ) #define LookAhead_Keep() \ pos = lookahead;\ parser->token_keep = parser->token_cur /* 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;\ }\ } else {\ if (flecs_token(parser, pos, &lookahead_token, false)) {\ if (lookahead_token.value) {\ Error("unexpected %s'%s'", \ flecs_token_kind_str(lookahead_token.kind), \ lookahead_token.value);\ } else {\ Error("unexpected %s", \ flecs_token_kind_str(lookahead_token.kind));\ }\ }\ goto error;\ } /* Lookahead N consecutive tokens */ #define LookAhead_1(tok, ...)\ LookAhead(\ case tok: {\ __VA_ARGS__\ }\ ) #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;\ }\ ) #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;\ }\ ) /* Open scope */ #define Scope(s, ...) {\ ecs_script_scope_t *old_scope = parser->scope;\ parser->scope = s;\ __VA_ARGS__\ parser->scope = old_scope;\ } /* Parser loop */ #define Loop(...)\ int32_t token_stack_count = tokenizer->stack.count;\ do {\ tokenizer->stack.count = token_stack_count;\ __VA_ARGS__\ } while (true); #define EndOfRule return pos #endif #include #ifndef FLECS_STATS_PRIVATE_H #define FLECS_STATS_PRIVATE_H typedef struct { /* Statistics API interface */ void (*copy_last)(void *stats, void *src); void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); void (*reduce)(void *stats, void *src); void (*reduce_last)(void *stats, void *last, int32_t reduce_count); void (*repeat_last)(void* stats); void (*set_t)(void *stats, int32_t t); void (*fini)(void *stats); /* Size of statistics type */ ecs_size_t stats_size; /* Id of component that contains the statistics */ ecs_entity_t monitor_component_id; /* Id of component used to query for monitored resources (optional) */ ecs_id_t query_component_id; /* Is the stats object inlined or a pointer */ bool is_pointer; } ecs_stats_api_t; void flecs_stats_api_import( ecs_world_t *world, ecs_stats_api_t *api); void FlecsWorldSummaryImport( ecs_world_t *world); void FlecsWorldMonitorImport( ecs_world_t *world); void FlecsSystemMonitorImport( ecs_world_t *world); void FlecsPipelineMonitorImport( ecs_world_t *world); void flecs_stats_memory_register_reflection( ecs_world_t *world); #endif /* Is cache trivial? */ bool flecs_query_cache_is_trivial( const ecs_query_cache_t *cache) { return (cache->query->flags & EcsQueryTrivialCache) != 0; } /* Trivial caches have a significantly smaller cache element size */ ecs_size_t flecs_query_cache_elem_size( const ecs_query_cache_t *cache) { return flecs_query_cache_is_trivial(cache) ? ECS_SIZEOF(ecs_query_triv_cache_match_t) : ECS_SIZEOF(ecs_query_cache_match_t) ; } /* The default group_by function. When an application specifies a relationship * for the ecs_query_desc_t::group_by field but does not provide a * group_by_callback, this function will be automatically used. It will cause * the query cache to be grouped by relationship target. */ static uint64_t flecs_query_cache_default_group_by( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, void *ctx) { (void)ctx; ecs_id_t match; if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { if (ECS_IS_VALUE_PAIR(match)) { return ECS_PAIR_SECOND(match); } else { return ecs_pair_second(world, match); } } return 0; } /* The group_by function that's used for the cascade feature. Groups tables by * hierarchy depth, resulting in breadth-first iteration. */ static uint64_t flecs_query_cache_group_by_cascade( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, void *ctx) { (void)id; ecs_term_t *term = ctx; ecs_entity_t rel = term->trav; int32_t depth = flecs_relation_depth(world, rel, table); return flecs_ito(uint64_t, depth); } /* Initialize cache with matching tables. */ static void flecs_query_cache_match_tables( ecs_world_t *world, ecs_query_cache_t *cache) { ecs_iter_t it = ecs_query_iter(world, cache->query); ECS_BIT_SET(it.flags, EcsIterNoData); ECS_BIT_SET(it.flags, EcsIterTableOnly); if (!ecs_query_next(&it)) { return; } while (flecs_query_cache_match_next(cache, &it)) { } } /* Match new table with cache. */ static bool flecs_query_cache_match_table( ecs_world_t *world, ecs_query_cache_t *cache, ecs_table_t *table) { if (!ecs_map_is_init(&cache->tables)) { return false; } ecs_query_t *q = cache->query; #ifndef FLECS_SANITIZE if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { return false; } #endif if (table->flags & EcsTableNotQueryable) { return false; } /* Iterate uncached query for table to check if it matches. If this is a * wildcard query, a table can match multiple times. */ ecs_iter_t it = flecs_query_iter(world, q); it.flags |= EcsIterNoData; ecs_iter_set_var_as_table(&it, 0, table); if (!ecs_query_next(&it)) { /* Table doesn't match */ return false; } if (flecs_query_cache_match_next(cache, &it)) { /* Should not return for more than one table. */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } #ifdef FLECS_SANITIZE /* Sanity check to make sure bloom filter is correct */ ecs_assert(flecs_table_bloom_filter_test(table, q->bloom_filter), ECS_INTERNAL_ERROR, NULL); #endif return true; } /* Iterate component monitors for cache. Each field that could potentially cause * up traversal will create a monitor. Component monitors are registered with * the world and are used to determine whether a rematch is necessary. */ static void flecs_query_cache_for_each_component_monitor( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache, void(*callback)( ecs_world_t* world, ecs_id_t id, ecs_query_t *q)) { ecs_query_t *q = &impl->pub; ecs_term_t *terms = cache->query->terms; int32_t i, count = cache->query->term_count; for (i = 0; i < count; i++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *src = &term->src; if (src->id & EcsUp) { callback(world, ecs_pair(term->trav, EcsWildcard), q); if (term->trav != EcsIsA) { callback(world, ecs_pair(EcsIsA, EcsWildcard), q); } callback(world, term->id, q); } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { ecs_assert(!(src->id & EcsSelf) || ecs_term_match_this(term), ECS_INTERNAL_ERROR, NULL); } } } /* Iterate over terms in query to initialize necessary bookkeeping. */ static int flecs_query_cache_process_query( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_term_t *terms = cache->query->terms; int32_t i, count = cache->query->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *src = &term->src; /* Find cascade terms if query has any */ if (src->id & EcsCascade) { ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, "query can only have one cascade term"); cache->cascade_by = i + 1; } /* Set the EcsQueryHasRefs flag. Ref fields are fields that can be * matched on another entity, and can require rematching. */ if (src->id & EcsUp) { impl->pub.flags |= EcsQueryHasRefs; } } /* Register component monitor for each ref field. */ flecs_query_cache_for_each_component_monitor( world, impl, cache, flecs_monitor_register); return 0; } /* -- Private API -- */ /* Do bookkeeping after enabling order_by */ static int flecs_query_cache_order_by( ecs_world_t *world, ecs_query_impl_t *impl, ecs_entity_t order_by, ecs_order_by_action_t order_by_callback, ecs_sort_table_action_t action) { ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_cache_t *cache = impl->cache; ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_id_is_wildcard(order_by), ECS_INVALID_PARAMETER, "cannot order by wildcard component"); /* Find order_by term & make sure it is queried for */ const ecs_query_t *query = cache->query; int32_t i, count = query->term_count; int32_t order_by_term = -1; if (order_by) { for (i = 0; i < count; i ++) { const ecs_term_t *term = &query->terms[i]; /* Only And terms are supported */ if (term->id == order_by && term->oper == EcsAnd) { order_by_term = i; break; } } if (order_by_term == -1) { char *id_str = ecs_id_str(world, order_by); ecs_err("order_by component '%s' is not queried for", id_str); ecs_os_free(id_str); goto error; } } cache->order_by = order_by; cache->order_by_callback = order_by_callback; cache->order_by_term = order_by_term; cache->order_by_table_callback = action; ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_match_t); flecs_query_cache_sort_tables(world, impl); if (!cache->table_slices.array) { flecs_query_cache_build_sorted_tables(cache); } return 0; error: return -1; } /* Do bookkeeping after enabling group_by */ static void flecs_query_cache_group_by( ecs_query_cache_t *cache, ecs_entity_t sort_component, ecs_group_by_action_t group_by) { ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, "query is already grouped"); ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, "query is already grouped"); if (!group_by) { /* Builtin function that groups by relationship */ group_by = flecs_query_cache_default_group_by; } cache->group_by = sort_component; cache->group_by_callback = group_by; ecs_map_init(&cache->groups, &cache->query->world->allocator); error: return; } /* Callback for the observer that is subscribed to table events. This function * is the entry point for matching/unmatching new tables with the query. */ static void flecs_query_cache_on_event( ecs_iter_t *it) { /* Because this is the observer::run callback, checking if this event is * already handled is not done for us. */ ecs_world_t *world = it->world; ecs_observer_t *o = it->ctx; ecs_observer_impl_t *o_impl = flecs_observer_impl(o); if (o_impl->last_event_id) { if (o_impl->last_event_id[0] == world->event_id) { return; } o_impl->last_event_id[0] = world->event_id; } ecs_query_impl_t *impl = o->ctx; flecs_poly_assert(impl, ecs_query_t); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = it->table; ecs_entity_t event = it->event; if (event == EcsOnTableCreate) { /* Creation of new table */ if (flecs_query_cache_match_table(world, cache, table)) { if (ecs_should_log_3()) { char *table_str = ecs_table_str(world, table); ecs_dbg_3("query cache event: %s for [%s]", ecs_get_name(world, event), table_str); ecs_os_free(table_str); } } return; } ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); /* The observer isn't doing the matching because the query can do it more * efficiently by checking the table with the query cache. */ if (ecs_map_get(&cache->tables, table->id) == NULL) { return; } if (ecs_should_log_3()) { char *table_str = ecs_table_str(world, table); ecs_dbg_3("query cache event: %s for [%s]", ecs_get_name(world, event), table_str); ecs_os_free(table_str); } if (event == EcsOnTableDelete) { /* Deletion of table */ flecs_query_cache_remove_table(cache, table); return; } } /* Create query-specific allocators. The reason these allocators are * specific to the query is because they depend on the number of terms the * query has. */ static void flecs_query_cache_allocators_init( ecs_query_cache_t *cache) { int32_t field_count = cache->query->field_count; if (field_count) { 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.monitors, (1 + field_count) * ECS_SIZEOF(int32_t)); flecs_ballocator_init(&cache->allocators.columns, field_count * ECS_SIZEOF(int16_t)); } } /* Free query-specific allocators. */ static void flecs_query_cache_allocators_fini( ecs_query_cache_t *cache) { int32_t field_count = cache->query->field_count; if (field_count) { flecs_ballocator_fini(&cache->allocators.pointers); flecs_ballocator_fini(&cache->allocators.ids); flecs_ballocator_fini(&cache->allocators.monitors); flecs_ballocator_fini(&cache->allocators.columns); } } /* Free cache. */ void flecs_query_cache_fini( ecs_query_impl_t *impl) { ecs_world_t *world = impl->pub.world; ecs_stage_t *stage = impl->stage; ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); if (cache->observer) { flecs_observer_fini(cache->observer); } ecs_group_delete_action_t on_delete = cache->on_group_delete; if (on_delete) { ecs_map_iter_t it = ecs_map_iter(&cache->groups); while (ecs_map_next(&it)) { ecs_query_cache_group_t *group = ecs_map_ptr(&it); uint64_t group_id = ecs_map_key(&it); on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); } cache->on_group_delete = NULL; } if (cache->group_by_ctx_free) { if (cache->group_by_ctx) { cache->group_by_ctx_free(cache->group_by_ctx); } } flecs_query_cache_for_each_component_monitor(world, impl, cache, flecs_monitor_unregister); flecs_query_cache_remove_all_tables(cache); ecs_assert(ecs_map_count(&cache->tables) == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_map_count(&cache->groups) == 0, ECS_INTERNAL_ERROR, NULL); ecs_map_fini(&cache->tables); ecs_map_fini(&cache->groups); ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_match_t); if (cache->query->term_count) { flecs_bfree(&cache->allocators.ids, cache->sources); } flecs_query_cache_allocators_fini(cache); ecs_query_fini(cache->query); flecs_bfree(&stage->allocators.query_cache, cache); } /* Create new cache. */ ecs_query_cache_t* flecs_query_cache_init( ecs_query_impl_t *impl, const ecs_query_desc_t *const_desc) { ecs_world_t *world = impl->pub.real_world; flecs_poly_assert(world, ecs_world_t); ecs_stage_t *stage = impl->stage; flecs_poly_assert(stage, ecs_stage_t); ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_query_desc_t was not initialized to zero"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot create query during world fini"); /* Create private version of desc to create the uncached query that will * populate the query cache. */ ecs_query_desc_t desc = *const_desc; ecs_entity_t entity = desc.entity; desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ desc.group_by_callback = NULL; desc.group_by = 0; desc.order_by_callback = NULL; desc.order_by = 0; desc.entity = 0; /* Don't pass ctx/binding_ctx to uncached query */ desc.ctx = NULL; desc.binding_ctx = NULL; desc.ctx_free = NULL; desc.binding_ctx_free = NULL; ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); result->entity = entity; impl->cache = result; ecs_observer_desc_t observer_desc = { .query = desc }; observer_desc.query.flags |= EcsQueryNested; ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; /* order_by is not compatible with matching empty tables, as it causes * a query to return table slices, not entire tables. */ if (const_desc->order_by_callback) { query_flags &= ~EcsQueryMatchEmptyTables; } ecs_query_t *q = result->query = ecs_query_init(world, &desc); if (!q) { goto error; } /* Set flag for trivial caches which allows for faster iteration */ if (impl->pub.flags & EcsQueryIsCacheable) { /* Trivial caches may only contain And/Not/Optional operators. */ int32_t t, count = q->term_count; for (t = 0; t < count; t ++) { if (q->terms[t].oper != EcsAnd && q->terms[t].oper != EcsNot && q->terms[t].oper != EcsOptional) { break; } } if ((t == count) && (q->flags & EcsQueryMatchOnlySelf) && !(q->flags & EcsQueryMatchWildcards) && !(q->flags & EcsQueryCacheWithFilter)) { if (!const_desc->order_by && !const_desc->group_by && !const_desc->order_by_callback && !const_desc->group_by_callback && !(const_desc->flags & EcsQueryDetectChanges)) { q->flags |= EcsQueryTrivialCache; } } } if (const_desc->flags & EcsQueryDetectChanges) { for (int i = 0; i < q->term_count; i ++) { ecs_term_t *term = &q->terms[i]; /* If query has change detection, flag this on the component record. * This allows code to skip calling modified() if there are no OnSet * hooks/observers, and the component isn't used in any queries that use * change detection. */ ecs_component_record_t *cr = flecs_components_ensure(world, term->id); cr->flags |= EcsIdHasOnSet; if (term->id < FLECS_HI_COMPONENT_ID) { world->non_trivial_set[term->id] = true; } } } if (const_desc->order_by) { ecs_component_record_t *cr = flecs_components_ensure(world, const_desc->order_by); if (cr) { cr->flags |= EcsIdHasOnSet; if (const_desc->order_by < FLECS_HI_COMPONENT_ID) { world->non_trivial_set[const_desc->order_by] = true; } } } ecs_size_t elem_size = flecs_query_cache_elem_size(result); ecs_vec_init(&world->allocator, &result->default_group.tables, elem_size, 0); result->first_group = &result->default_group; /* The uncached query used to populate the cache always matches empty * tables. This flag determines whether the empty tables are stored * separately in the cache or are treated as regular tables. This is only * enabled if the user requested that the query matches empty tables. */ ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, !!(query_flags & EcsQueryMatchEmptyTables)); flecs_query_cache_allocators_init(result); /* Zeroed-out sources array that's used for results that only match $this. * 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.ids); } if (q->term_count) { observer_desc.run = flecs_query_cache_on_event; observer_desc.ctx = impl; int32_t event_index = 0; observer_desc.events[event_index ++] = EcsOnTableCreate; observer_desc.events[event_index ++] = EcsOnTableDelete; observer_desc.flags_ = EcsObserverBypassQuery; /* ecs_query_init could have moved away resources from the terms array * in the descriptor, so use the terms array from the query. */ ecs_os_memcpy_n(observer_desc.query.terms, q->terms, ecs_term_t, q->term_count); observer_desc.query.expr = NULL; /* Already parsed */ observer_desc.query.flags |= EcsQueryTableOnly; result->observer = flecs_observer_init(world, entity, &observer_desc); if (!result->observer) { goto error; } } result->prev_match_count = -1; if (ecs_should_log_1()) { char *query_expr = ecs_query_str(result->query); ecs_dbg_1("#[green]query#[normal] [%s] created", query_expr ? query_expr : ""); ecs_os_free(query_expr); } ecs_log_push_1(); if (flecs_query_cache_process_query(world, impl, result)) { goto error; } /* Group before matching so we won't have to move tables around later */ int32_t cascade_by = result->cascade_by; if (cascade_by) { flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, flecs_query_cache_group_by_cascade); result->group_by_ctx = &result->query->terms[cascade_by - 1]; result->query->flags |= EcsQueryGroupByOrdered; if (result->query->terms[cascade_by - 1].src.id & EcsDesc) { result->query->flags |= EcsQueryGroupByDesc; } } if (const_desc->group_by_callback || const_desc->group_by) { ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, "cannot mix cascade and group_by"); flecs_query_cache_group_by(result, const_desc->group_by, const_desc->group_by_callback); result->group_by_ctx = const_desc->group_by_ctx; result->on_group_create = const_desc->on_group_create; result->on_group_delete = const_desc->on_group_delete; result->group_by_ctx_free = const_desc->group_by_ctx_free; } ecs_map_init(&result->tables, &world->allocator); flecs_query_cache_match_tables(world, result); if (const_desc->order_by_callback) { if (flecs_query_cache_order_by(world, impl, const_desc->order_by, const_desc->order_by_callback, const_desc->order_by_table_callback)) { goto error; } } if (entity) { if (!ecs_map_count(&result->tables) && result->query->term_count){ ecs_add_id(world, entity, EcsEmpty); } } ecs_log_pop_1(); return result; error: return NULL; } /* Initialize cached query iterator. */ void flecs_query_cache_iter_init( ecs_iter_t *it, ecs_query_iter_t *qit, ecs_query_impl_t *impl) { ecs_query_cache_t *cache = impl->cache; if (!cache) { return; } qit->group = cache->first_group; qit->tables = &qit->group->tables; qit->all_tables = qit->tables; qit->cur = 0; /* If query uses order_by, iterate the array with ordered table slices. */ if (cache->order_by_callback) { /* Check if query needs sorting. */ flecs_query_cache_sort_tables(it->real_world, impl); qit->tables = &cache->table_slices; qit->all_tables = qit->tables; qit->group = NULL; } cache->prev_match_count = cache->match_count; } /* Find next match in cache. This function is called for non-trivial caches and * handles features like wildcards, up traversal and grouping. */ static ecs_query_cache_match_t* flecs_query_cache_next( const ecs_query_run_ctx_t *ctx, bool always_match_empty) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; repeat: { if (qit->cur >= ecs_vec_count(qit->tables)) { /* We're iterating the table vector of the group */ if (qit->tables == qit->all_tables) { /* If a group is set, we might have to iterate multiple groups */ ecs_query_cache_group_t *group = qit->group; if (!group || qit->iter_single_group) { return NULL; } /* Check if this was the last group to iterate */ qit->group = group->next; if (!qit->group) { return NULL; } /* Prepare iterator for the next group */ qit->all_tables = qit->tables = &qit->group->tables; qit->cur = 0; /* Not common, but can happen if a query uses group_by and there * are no tables in the default group (group id 0). */ if (!ecs_vec_count(qit->tables)) { goto repeat; } /* We're iterating a wildcard table vector */ } else { qit->tables = qit->all_tables; qit->cur = qit->all_cur; goto repeat; } } /* Get currently iterated cache element */ ecs_query_cache_match_t *qm = ecs_vec_get_t(qit->tables, ecs_query_cache_match_t, qit->cur); /* Check if table is empty and whether we need to skip it */ ecs_table_t *table = qm->base.table; if (!ecs_table_count(table)) { if (!(always_match_empty || (it->flags & EcsIterMatchEmptyTables))) { if (ctx->query->pub.flags & EcsQueryHasChangeDetection) { ecs_query_impl_t *impl = flecs_query_impl(it->query); flecs_query_sync_match_monitor(impl, qm); if (qm->wildcard_matches) { ecs_query_cache_match_t *wc_qms = ecs_vec_first(qm->wildcard_matches); int32_t j, wc_count = ecs_vec_count(qm->wildcard_matches); for (j = 0; j < wc_count; j ++) { flecs_query_sync_match_monitor(impl, &wc_qms[j]); } } } qit->cur ++; goto repeat; } } qit->elem = qm; /* If there are multiple matches for table iterate those first. */ if (qm->wildcard_matches) { qit->tables = qm->wildcard_matches; qit->all_cur = qit->cur + 1; qit->cur = 0; } else { qit->cur ++; } ctx->vars[0].range.table = table; return qm; } } /* Find next match in trivial cache. A trivial cache doesn't have to handle * wildcards, multiple groups or fields matched through up traversal. */ static ecs_query_cache_match_t* flecs_query_trivial_cache_next( const ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; repeat: { if (qit->cur == ecs_vec_count(qit->tables)) { return NULL; } ecs_query_triv_cache_match_t *qm = ecs_vec_get_t( qit->tables, ecs_query_triv_cache_match_t, qit->cur); ecs_table_t *table = it->table = qm->table; int32_t count = it->count = ecs_table_count(table); qit->cur ++; if (!count) { if (!(it->flags & EcsIterMatchEmptyTables)) { goto repeat; } } it->entities = ecs_table_entities(table); it->columns = qm->columns; it->set_fields = qm->set_fields; return qit->elem = (ecs_query_cache_match_t*)qm; } } static ecs_query_cache_match_t* flecs_query_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_iter_t *it = ctx->it; if (!redo) { ecs_var_t *var = &ctx->vars[0]; ecs_table_t *table = var->range.table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the variable set on the iterator is missing a table"); ecs_query_cache_table_t *qt = flecs_query_cache_get_table( ctx->query->cache, table); if (!qt) { return NULL; } ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_assert(qt->group != NULL, ECS_INTERNAL_ERROR, NULL); qit->group = qt->group; qit->tables = &qt->group->tables; qit->cur = qt->index; } ecs_query_cache_match_t *qm = flecs_query_cache_next(ctx, true /* always match empty */); if (redo && qm) { if (qm->base.table != it->table) { return NULL; } } return qm; } static void flecs_query_cache_init_mapped_fields( const ecs_query_run_ctx_t *ctx, ecs_query_cache_match_t *node) { ecs_iter_t *it = ctx->it; const ecs_query_impl_t *impl = ctx->query; ecs_query_cache_t *cache = impl->cache; ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INTERNAL_ERROR, NULL); int32_t i, field_count = cache->query->field_count; int8_t *field_map = cache->field_map; int16_t *columns = ECS_CONST_CAST(int16_t*, it->columns); for (i = 0; i < field_count; i ++) { int8_t field_index = field_map[i]; it->trs[field_index] = node->_trs ? node->_trs[i] : NULL; columns[field_index] = node->base.columns[i]; it->ids[field_index] = node->_ids[i]; it->sources[field_index] = node->_sources[i]; ecs_termset_t bit = (ecs_termset_t)(1u << i); ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); ECS_TERMSET_COND(it->set_fields, field_bit, node->base.set_fields & bit); ECS_TERMSET_COND(it->up_fields, field_bit, node->_up_fields & bit); } } /* Reset the cache iteration cursor to the start of the iteration. This is * called when the cache operation is entered fresh (redo == false), which * happens when a preceding operation in the plan yields a new result. */ static void flecs_query_cache_iter_restart( const ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_cache_t *cache = ctx->query->cache; if (qit->iter_single_group) { qit->tables = qit->all_tables; qit->cur = 0; } else if (cache->order_by_callback) { qit->tables = qit->all_tables = &cache->table_slices; qit->group = NULL; qit->cur = 0; } else { qit->group = cache->first_group; qit->tables = qit->all_tables = &qit->group->tables; qit->cur = 0; } } /* Iterate cache for query that's partially cached */ bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_assert(!flecs_query_cache_is_trivial(ctx->query->cache), ECS_INTERNAL_ERROR, NULL); if (!redo) { flecs_query_cache_iter_restart(ctx); } ecs_query_cache_match_t *node = flecs_query_cache_next(ctx, false); if (!node) { return false; } flecs_query_cache_init_mapped_fields(ctx, node); ctx->vars[0].range.count = node->_count; ctx->vars[0].range.offset = node->_offset; return true; } /* Iterate cache for query that's entirely cached */ bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_assert(!flecs_query_cache_is_trivial(ctx->query->cache), ECS_INTERNAL_ERROR, NULL); if (!redo) { flecs_query_cache_iter_restart(ctx); } ecs_query_cache_match_t *node = flecs_query_cache_next(ctx, false); if (!node) { return false; } ctx->vars[0].range.count = node->_count; ctx->vars[0].range.offset = node->_offset; ecs_iter_t *it = ctx->it; it->trs = node->_trs; it->columns = node->base.columns; it->ids = node->_ids; it->sources = node->_sources; it->set_fields = node->base.set_fields; it->up_fields = node->_up_fields; #ifdef FLECS_DEBUG it->flags |= EcsIterImmutableCacheData; #endif return true; } /* Iterate trivial cache for query that's entirely cached */ bool flecs_query_is_trivial_cache_search( const ecs_query_run_ctx_t *ctx) { return flecs_query_trivial_cache_next(ctx) != NULL; } /* Test if query that is partially cached matches constrained $this */ bool flecs_query_cache_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_query_cache_match_t *node = flecs_query_test(ctx, redo); if (!node) { return false; } flecs_query_cache_init_mapped_fields(ctx, node); return true; } /* Test if query that is entirely cached matches constrained $this */ bool flecs_query_is_cache_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_assert(!flecs_query_cache_is_trivial(ctx->query->cache), ECS_INTERNAL_ERROR, NULL); ecs_query_cache_match_t *node = flecs_query_test(ctx, redo); if (!node) { return false; } ecs_iter_t *it = ctx->it; it->trs = node->_trs; it->columns = node->base.columns; it->ids = node->_ids; it->sources = node->_sources; it->set_fields = node->base.set_fields; #ifdef FLECS_DEBUG it->flags |= EcsIterImmutableCacheData; #endif return true; } bool flecs_query_is_trivial_cache_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_assert(flecs_query_cache_is_trivial(ctx->query->cache), ECS_INTERNAL_ERROR, NULL); ecs_iter_t *it = ctx->it; if (!redo) { ecs_var_t *var = &ctx->vars[0]; ecs_table_t *table = var->range.table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the variable set on the iterator is missing a table"); ecs_query_cache_t *cache = ctx->query->cache; ecs_query_cache_table_t *qt = flecs_query_cache_get_table(cache, table); if (!qt) { return false; } ecs_query_cache_match_t *qm = flecs_query_cache_match_from_table(cache, qt); it->columns = qm->base.columns; it->set_fields = qm->base.set_fields; return true; } return false; } typedef struct { ecs_table_t *table; int32_t column; } flecs_table_column_t; /* Look up table record for id in table. */ static const ecs_table_record_t *flecs_query_get_tr( ecs_world_t *world, ecs_id_t id, ecs_table_t *table) { ecs_component_record_t *cr = flecs_components_get(world, id); if (!cr) { return NULL; } return flecs_component_get_table(cr, table); } /* Get table column index for query field. */ static void flecs_query_get_column_for_field( const ecs_query_t *q, ecs_query_cache_match_t *match, int32_t field, flecs_table_column_t *out) { ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); int32_t column = match->base.columns[field]; if (column != -1) { out->table = match->base.table; out->column = column; return; } ecs_assert(!match->_trs || !match->_trs[field] || match->_trs[field]->column == -1, ECS_INTERNAL_ERROR, NULL); ecs_entity_t src = match->_sources ? match->_sources[field] : 0; if (!src) { out->table = NULL; out->column = -1; return; } ecs_record_t *r = flecs_entities_get(q->real_world, src); if (!r || !r->table) { out->table = NULL; out->column = -1; return; } ecs_id_t id = match->_ids ? match->_ids[field] : q->ids[field]; const ecs_table_record_t *tr = flecs_query_get_tr( q->real_world, id, r->table); if (!tr) { out->table = NULL; out->column = -1; return; } out->table = r->table; out->column = tr->column; } /* Get match monitor. Monitors are used to keep track of whether components * matched by the query in a table have changed. */ static bool flecs_query_get_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); bool is_trivial = flecs_query_cache_is_trivial(cache); ecs_assert(!is_trivial, ECS_INVALID_OPERATION, "query was not created with change detection enabled"); if (match->_monitor) { return false; } int32_t *monitor = flecs_balloc(&cache->allocators.monitors); monitor[0] = 0; /* Mark terms that don't need to be monitored. This saves time when reading * and/or updating the monitor. */ const ecs_query_t *q = cache->query; int32_t i, field = -1, term_count = q->term_count; flecs_table_column_t tc; for (i = 0; i < term_count; i ++) { if (field == q->terms[i].field_index) { if (monitor[field + 1] != -1) { continue; } } field = q->terms[i].field_index; monitor[field + 1] = -1; /* If term isn't read, don't monitor */ if (q->terms[i].inout != EcsIn && q->terms[i].inout != EcsInOut && q->terms[i].inout != EcsInOutDefault) { continue; } /* Don't track fields that aren't set */ if (!is_trivial) { if (!(match->base.set_fields & (1llu << field))) { continue; } } flecs_query_get_column_for_field(q, match, field, &tc); if (tc.column == -1) { continue; /* Don't track terms that aren't stored */ } monitor[field + 1] = 0; } match->_monitor = monitor; impl->pub.flags |= EcsQueryHasChangeDetection; return true; } /* Get monitor for fixed query terms. Fixed terms are handled separately as they * don't require a query cache, and fixed terms aren't stored in the cache. */ static bool flecs_query_get_fixed_monitor( ecs_query_impl_t *impl, bool check) { ecs_query_t *q = &impl->pub; ecs_world_t *world = q->real_world; ecs_term_t *terms = q->terms; int32_t i, term_count = q->term_count; if (!impl->monitor) { impl->monitor = flecs_alloc_n(&impl->stage->allocator, int32_t, q->field_count); check = false; /* If the monitor is new, initialize it with dirty state */ } for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int16_t field_index = term->field_index; if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { continue; /* If term doesn't read data there's nothing to track */ } if (!(term->src.id & EcsIsEntity)) { continue; /* Not a term with a fixed source */ } ecs_entity_t src = ECS_TERM_REF_ID(&term->src); ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, src); if (!r || !r->table) { continue; /* Entity is empty, nothing to track */ } ecs_component_record_t *cr = flecs_components_get(world, term->id); if (!cr) { continue; /* If id doesn't exist, entity can't have it */ } const ecs_table_record_t *tr = flecs_component_get_table(cr, r->table); if (!tr) { continue; /* Entity doesn't have the component */ } /* Copy/check column dirty state from table */ int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (!check) { impl->monitor[field_index] = dirty_state[tr->column + 1]; } else { if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { return true; } } } return !check; } /* Synchronize fixed source monitor */ bool flecs_query_update_fixed_monitor( ecs_query_impl_t *impl) { return flecs_query_get_fixed_monitor(impl, false); } /* Compare fixed source monitor */ static bool flecs_query_check_fixed_monitor( ecs_query_impl_t *impl) { return flecs_query_get_fixed_monitor(impl, true); } /* Check if a single match term has changed */ static bool flecs_query_check_match_monitor_term( ecs_query_impl_t *impl, ecs_query_cache_match_t *match, int32_t field) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INVALID_OPERATION, "query was not created with change detection enabled"); if (flecs_query_get_match_monitor(impl, match)) { return true; } int32_t *monitor = match->_monitor; int32_t state = monitor[field]; if (state == -1) { return false; } ecs_table_t *table = match->base.table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( cache->query->world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (!field) { return monitor[0] != dirty_state[0]; } } else if (!field) { return false; } flecs_table_column_t cur; flecs_query_get_column_for_field( &impl->pub, match, field - 1, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); return monitor[field] != flecs_table_get_dirty_state( cache->query->world, cur.table)[cur.column + 1]; } /* Check if any tables in the cache changed. */ static bool flecs_query_check_cache_monitor( ecs_query_impl_t *impl) { ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); /* If the match count changed, tables got matched/unmatched for the * cache, so return that the query has changed. */ if (cache->match_count != cache->prev_match_count) { return true; } const ecs_query_cache_group_t *cur = cache->first_group; do { int32_t i, count = ecs_vec_count(&cur->tables); for (i = 0; i < count; i ++) { ecs_query_cache_match_t *qm = ecs_vec_get_t(&cur->tables, ecs_query_cache_match_t, i); if (flecs_query_check_table_monitor(impl, qm, -1)) { return true; } if (qm->wildcard_matches) { ecs_query_cache_match_t *wc_qms = ecs_vec_first(qm->wildcard_matches); int32_t j, wc_count = ecs_vec_count(qm->wildcard_matches); for (j = 0; j < wc_count; j ++) { if (flecs_query_check_table_monitor(impl, &wc_qms[j], -1)) { return true; } } } } } while ((cur = cur->next)); return false; } /* Initialize monitors for the elements in the query cache. */ static void flecs_query_init_query_monitors( ecs_query_impl_t *impl) { /* Change monitor for cache */ ecs_query_cache_t *cache = impl->cache; if (cache) { const ecs_query_cache_group_t *cur = cache->first_group; do { int32_t i, count = ecs_vec_count(&cur->tables); for (i = 0; i < count; i ++) { ecs_query_cache_match_t *qm = ecs_vec_get_t(&cur->tables, ecs_query_cache_match_t, i); flecs_query_get_match_monitor(impl, qm); if (qm->wildcard_matches) { ecs_query_cache_match_t *wc_qms = ecs_vec_first(qm->wildcard_matches); int32_t j, wc_count = ecs_vec_count(qm->wildcard_matches); for (j = 0; j < wc_count; j ++) { flecs_query_get_match_monitor(impl, &wc_qms[j]); } } } } while ((cur = cur->next)); } } /* Check if a specific match (table) has changed. */ static bool flecs_query_check_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *match, const ecs_iter_t *it) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INVALID_OPERATION, "query was not created with change detection enabled"); if (flecs_query_get_match_monitor(impl, match)) { return true; } int32_t *monitor = match->_monitor; ecs_table_t *table = match->base.table; int32_t *dirty_state = NULL; if (table) { dirty_state = flecs_table_get_dirty_state( cache->query->world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (monitor[0] != dirty_state[0]) { return true; } } const ecs_query_t *query = cache->query; ecs_world_t *world = query->world; int32_t i, field_count = query->field_count; bool trivial_cache = flecs_query_cache_is_trivial(cache); ecs_entity_t *sources = NULL; ecs_flags64_t set_fields = 0; if (it) { set_fields = it->set_fields; } else if (!trivial_cache) { set_fields = match->base.set_fields; } else { set_fields = (1llu << field_count) - 1; } if (!trivial_cache) { sources = match->_sources; } for (i = 0; i < field_count; i ++) { int32_t mon = monitor[i + 1]; if (mon == -1) { continue; } /* When set_fields comes from the iterator it uses the field indices of * the actual query, which can differ from the cache query fields if * the query is partially cached. */ int32_t set_field_index = i; if (it && cache->field_map) { set_field_index = cache->field_map[i]; } if (!(set_fields & (1llu << set_field_index))) { continue; } int32_t column = match->base.columns[i]; ecs_entity_t src = sources ? sources[i] : 0; if (!src) { if (column >= 0) { /* owned component */ ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (mon != dirty_state[column + 1]) { return true; } continue; } else { continue; /* owned but not a component */ } } ecs_assert(!trivial_cache, ECS_INTERNAL_ERROR, NULL); /* Component from non-this source */ ecs_assert(match->_sources != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_column_t tc; flecs_query_get_column_for_field(query, match, i, &tc); if (!tc.table || tc.column < 0) { continue; } int32_t *src_dirty_state = flecs_table_get_dirty_state( world, tc.table); if (mon != src_dirty_state[tc.column + 1]) { return true; } } return false; } /* Check if one or more fields of a specific match have changed. */ static bool flecs_query_check_table_monitor_match( ecs_query_impl_t *impl, ecs_query_cache_match_t *qm, int32_t field) { if (field == -1) { if (flecs_query_check_match_monitor(impl, qm, NULL)) { return true; } } else { if (flecs_query_check_match_monitor_term(impl, qm, field)) { return true; } } return false; } /* Compare cache monitor with table dirty state to detect changes */ bool flecs_query_check_table_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *qm, int32_t field) { if (flecs_query_check_table_monitor_match(impl, qm, field)) { return true; } if (qm->wildcard_matches) { ecs_query_cache_match_t *wc_qms = ecs_vec_first(qm->wildcard_matches); int32_t i, count = ecs_vec_count(qm->wildcard_matches); for (i = 0; i < count; i ++) { if (flecs_query_check_table_monitor_match(impl, &wc_qms[i], field)) { return true; } } } return false; } /* Mark iterated out fields dirty */ void flecs_query_mark_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it) { ecs_query_t *q = &impl->pub; /* Evaluate all writable non-fixed, set fields */ ecs_termset_t write_fields = (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); if (!write_fields || (it->flags & EcsIterNoData)) { return; } ecs_world_t *world = q->real_world; int16_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << i); if (!(write_fields & field_bit)) { continue; /* If term doesn't write data there's nothing to track */ } ecs_entity_t src = it->sources[i]; ecs_table_t *table; if (!src) { table = it->table; } else { ecs_record_t *r = flecs_entities_get(world, src); if (!r || !(table = r->table)) { continue; } if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { /* Shared fields that aren't marked explicitly as out/inout * default to readonly */ continue; } } const ecs_table_record_t *tr = it->trs ? it->trs[i] : NULL; if (!tr) { tr = flecs_query_get_tr(world, it->ids[i], table); if (!tr) { continue; } } int32_t type_index = tr->index; ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *dirty_state = table->dirty_state; if (!dirty_state) { continue; } ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[type_index]; dirty_state[column + 1] ++; } } /* Mark out fields with fixed source dirty */ void flecs_query_mark_fixed_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it) { /* This function marks fields dirty for terms with fixed sources. */ ecs_query_t *q = &impl->pub; ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; if (!fixed_write_fields) { return; } ecs_world_t *world = q->real_world; int32_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { continue; /* If term doesn't write data there's nothing to track */ } ecs_entity_t src = it->sources[i]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, src); ecs_table_t *table; if (!r || !(table = r->table)) { /* If the field is optional, it's possible that it didn't match */ continue; } int32_t *dirty_state = table->dirty_state; if (!dirty_state) { continue; } const ecs_table_record_t *tr = it->trs ? it->trs[i] : NULL; if (!tr) { tr = flecs_query_get_tr(world, it->ids[i], table); if (!tr) { continue; } } ecs_assert(tr->column >= 0, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[tr->index]; dirty_state[column + 1] ++; } } /* Synchronize cache monitor with table dirty state */ void flecs_query_sync_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INVALID_OPERATION, "query was not created with change detection enabled"); if (!match->_monitor) { if (impl->pub.flags & EcsQueryHasChangeDetection) { flecs_query_get_match_monitor(impl, match); } else { return; } } int32_t *monitor = match->_monitor; ecs_table_t *table = match->base.table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( cache->query->world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ } ecs_query_t *q = cache->query; { flecs_table_column_t tc; int32_t t, term_count = q->term_count; for (t = 0; t < term_count; t ++) { int32_t field = q->terms[t].field_index; if (monitor[field + 1] == -1) { continue; } flecs_query_get_column_for_field(q, match, field, &tc); if (!tc.table) { continue; } /* Query for cache should never point to stage */ ecs_assert(q->world == q->real_world, ECS_INTERNAL_ERROR, NULL); monitor[field + 1] = flecs_table_get_dirty_state( q->world, tc.table)[tc.column + 1]; } } cache->prev_match_count = cache->match_count; } /* Public API call to check if any matches in the query have changed. */ bool ecs_query_changed( ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, "change detection is only supported on cached queries"); if (q->read_fields & q->fixed_fields) { if (!impl->monitor) { /* Create change monitor for fixed fields */ flecs_query_get_fixed_monitor(impl, false); } } /* If query reads terms with fixed sources, check those first as that's * cheaper than checking entries in the cache. */ if (impl->monitor) { if (flecs_query_check_fixed_monitor(impl)) { return true; } } /* Check cache for changes. We can't detect changes for terms that are not * cached/cacheable and don't have a fixed source, since that requires * storing state per result, which doesn't happen for uncached queries. */ if (impl->cache) { if (!(impl->pub.flags & EcsQueryHasChangeDetection)) { flecs_query_init_query_monitors(impl); } /* Check cache entries for changes */ return flecs_query_check_cache_monitor(impl); } return false; } /* Public API call to check if the currently iterated result has changed. */ bool ecs_iter_changed( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_query_impl_t *impl = flecs_query_impl(it->query); ecs_query_t *q = &impl->pub; /* First check for changes for terms with fixed sources, if query has any */ if (q->read_fields & q->fixed_fields) { /* Detecting changes for uncached terms is costly, so only do it once * per iteration. */ if (!(it->flags & EcsIterFixedInChangeComputed)) { it->flags |= EcsIterFixedInChangeComputed; ECS_BIT_COND(it->flags, EcsIterFixedInChanged, flecs_query_check_fixed_monitor(impl)); } if (it->flags & EcsIterFixedInChanged) { return true; } } /* If query has a cache, check for changes in current matched result */ if (impl->cache) { ecs_query_cache_match_t *qm = (ecs_query_cache_match_t*)it->priv_.iter.query.elem; ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_query_check_match_monitor(impl, qm, it); } error: return false; } /* Public API call for skipping change detection (don't mark fields dirty) */ void ecs_iter_skip( ecs_iter_t *it) { ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); it->flags |= EcsIterSkip; } /* Check if group is currently linked in the cache group list. */ static bool flecs_query_cache_group_is_linked( const ecs_query_cache_t *cache, const ecs_query_cache_group_t *group) { ecs_query_cache_group_t *cur = cache->first_group; while (cur) { if (cur == group) { return true; } cur = cur->next; } return false; } /* Get group id for table. */ static uint64_t flecs_query_cache_get_group_id( const ecs_query_cache_t *cache, ecs_table_t *table) { if (cache->group_by_callback) { return cache->group_by_callback(cache->query->world, table, cache->group_by, cache->group_by_ctx); } else { return 0; } } /* Get group for group id. */ ecs_query_cache_group_t* flecs_query_cache_get_group( const ecs_query_cache_t *cache, uint64_t group_id) { if (!group_id) { return ECS_CONST_CAST(ecs_query_cache_group_t*, &cache->default_group); } return ecs_map_get_deref( &cache->groups, ecs_query_cache_group_t, group_id); } /* Insert group in list that's ordered by group id */ static void flecs_query_cache_group_insert( ecs_query_cache_t *cache, ecs_query_cache_group_t *group) { if (!(cache->query->flags & EcsQueryGroupByOrdered)) { group->next = cache->first_group; cache->first_group = group; return; } bool desc = (cache->query->flags & EcsQueryGroupByDesc) != 0; ecs_query_cache_group_t *cur = cache->first_group, *prev = NULL; do { ecs_assert(cur->info.id != group->info.id, ECS_INTERNAL_ERROR, NULL); bool insert = cur->info.id > group->info.id; if (desc) { insert = !insert; /* Works since ids can't be the same. */ } if (insert) { if (prev) { prev->next = group; } else { cache->first_group = group; } group->next = cur; return; } prev = cur; } while ((cur = cur->next)); prev->next = group; ecs_assert(group->next == NULL, ECS_INTERNAL_ERROR, NULL); } /* Make sure a group exists for the provided group id. */ static ecs_query_cache_group_t* flecs_query_cache_ensure_group( ecs_query_cache_t *cache, uint64_t group_id) { if (!group_id) { ecs_query_cache_group_t *group = &cache->default_group; if (!group->info.table_count) { if (ecs_map_is_init(&cache->groups)) { ecs_query_cache_group_t **group_ptr = ecs_map_ensure_ref( &cache->groups, ecs_query_cache_group_t, 0); *group_ptr = group; } if (!flecs_query_cache_group_is_linked(cache, group)) { flecs_query_cache_group_insert(cache, group); } if (cache->on_group_create) { group->info.ctx = cache->on_group_create( cache->query->world, 0, cache->group_by_ctx); } } return group; } ecs_query_cache_group_t *group = ecs_map_get_deref(&cache->groups, ecs_query_cache_group_t, group_id); if (!group) { group = ecs_map_insert_alloc_t(&cache->groups, ecs_query_cache_group_t, group_id); ecs_os_zeromem(group); ecs_allocator_t *a = &cache->query->real_world->allocator; if (flecs_query_cache_is_trivial(cache)) { ecs_vec_init_t(a, &group->tables, ecs_query_triv_cache_match_t, 0); } else { ecs_vec_init_t(a, &group->tables, ecs_query_cache_match_t, 0); } group->info.id = group_id; flecs_query_cache_group_insert(cache, group); if (cache->on_group_create) { group->info.ctx = cache->on_group_create( cache->query->world, group_id, cache->group_by_ctx); } } return group; } /* Free group resources. */ static void flecs_query_cache_group_fini( ecs_query_cache_t *cache, ecs_query_cache_group_t *group) { /* Group callbacks are only meaningful for groups that have matched at * least one table. The default group can exist as list head without ever * being materialized through on_group_create (group id 0). */ if (cache->on_group_delete && group->info.table_count) { cache->on_group_delete(cache->query->world, group->info.id, group->info.ctx, cache->group_by_ctx); } ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_allocator_t *a = &cache->query->real_world->allocator; ecs_vec_fini(a, &group->tables, elem_size); group->info.table_count = 0; group->info.match_count = 0; group->info.ctx = NULL; if (group != &cache->default_group) { ecs_map_remove_free(&cache->groups, group->info.id); } else { if (ecs_map_is_init(&cache->groups)) { ecs_map_remove(&cache->groups, 0); } } } /* Remove group from cache. */ static void flecs_query_cache_remove_group( ecs_query_cache_t *cache, ecs_query_cache_group_t *group) { ecs_query_cache_group_t *cur = cache->first_group, *prev = NULL; do { if (cur == group) { if (prev) { prev->next = group->next; } else { cache->first_group = group->next; } break; } prev = cur; } while ((cur = cur->next)); /* If this is the default_group, make sure next is set to NULL since we * never delete the default group. */ group->next = NULL; /* Ensure group was found */ ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); if (!cache->first_group) { cache->first_group = &cache->default_group; } flecs_query_cache_group_fini(cache, group); } /* Get cache entry for table. */ ecs_query_cache_table_t* flecs_query_cache_get_table( const ecs_query_cache_t *cache, ecs_table_t *table) { return ecs_map_get_ptr(&cache->tables, table->id); } /* Get match for table cache entry. */ ecs_query_cache_match_t* flecs_query_cache_match_from_table( const ecs_query_cache_t *cache, const ecs_query_cache_table_t *qt) { ecs_size_t elem_size = flecs_query_cache_elem_size(cache); return ecs_vec_get(&qt->group->tables, elem_size, qt->index); } /* Add table to query group. */ static ecs_query_cache_match_t* flecs_query_cache_add_table_to_group( ecs_query_cache_t *cache, ecs_query_cache_group_t *group, ecs_query_cache_table_t *qt, ecs_table_t *table) { ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_allocator_t *a = &cache->query->real_world->allocator; ecs_query_cache_match_t *result = ecs_vec_append( a, &group->tables, elem_size); ecs_os_memset(result, 0, elem_size); result->base.table = table; qt->group = group; qt->index = ecs_vec_count(&group->tables) - 1; group->info.table_count ++; group->info.match_count ++; cache->match_count ++; return result; } /* Remove table from query group. */ static void flecs_query_cache_remove_table_from_group( ecs_query_cache_t *cache, ecs_query_cache_group_t *group, int32_t index) { cache->match_count ++; ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_vec_remove(&group->tables, elem_size, index); int32_t count = ecs_vec_count(&group->tables); if (!count) { flecs_query_cache_remove_group(cache, group); return; } if (index != count) { /* The element now points to the previously last table in the group. * Update the entry of that table to the new index. */ ecs_query_cache_match_t *match = ecs_vec_get( &group->tables, elem_size, index); ecs_query_cache_table_t *qt_other = flecs_query_cache_get_table( cache, match->base.table); ecs_assert(qt_other != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(qt_other->index == ecs_vec_count(&group->tables), ECS_INTERNAL_ERROR, NULL); qt_other->index = index; } group->info.table_count --; group->info.match_count ++; } /* Add matched table to cache. */ ecs_query_cache_match_t* flecs_query_cache_add_table( ecs_query_cache_t *cache, ecs_table_t *table) { ecs_assert(ecs_map_get(&cache->tables, table->id) == NULL, ECS_INTERNAL_ERROR, NULL); if (!ecs_map_count(&cache->tables) && cache->entity) { ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); } uint64_t group_id = flecs_query_cache_get_group_id(cache, table); ecs_query_cache_group_t *group = flecs_query_cache_ensure_group( cache, group_id); ecs_assert(group != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &cache->query->real_world->allocator; ecs_query_cache_table_t *qt = flecs_alloc_t(a, ecs_query_cache_table_t); qt->group = group; qt->index = ecs_vec_count(&group->tables); ecs_map_insert_ptr(&cache->tables, table->id, qt); return flecs_query_cache_add_table_to_group(cache, group, qt, table); } /* Move table to a different group. This can happen if the value returned by * group_by_callback changed for a table. Typically called during rematch. */ static void flecs_query_cache_move_table_to_group( ecs_query_cache_t *cache, ecs_query_cache_table_t *qt, ecs_query_cache_group_t *group) { ecs_query_cache_match_t *src_match = flecs_query_cache_match_from_table(cache, qt); /* Cache values of previous group since add_table_to_group will modify qt */ ecs_query_cache_group_t *src_group = qt->group; int32_t src_index = qt->index; ecs_table_t *table = src_match->base.table; /* Add table to new group */ ecs_query_cache_match_t *dst_match = flecs_query_cache_add_table_to_group(cache, group, qt, table); ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_os_memcpy(dst_match, src_match, elem_size); ecs_assert(qt->group == group, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_query_cache_match_from_table(cache, qt) == dst_match, ECS_INTERNAL_ERROR, NULL); /* Remove table from old group */ flecs_query_cache_remove_table_from_group(cache, src_group, src_index); } /* Make sure a cache entry exists for table. */ ecs_query_cache_match_t* flecs_query_cache_ensure_table( ecs_query_cache_t *cache, ecs_table_t *table) { ecs_query_cache_table_t *qt = flecs_query_cache_get_table(cache, table); if (qt) { /* Ensure existing table is in the right group */ if (cache->group_by_callback) { uint64_t group_id = flecs_query_cache_get_group_id(cache, table); ecs_query_cache_group_t *group = flecs_query_cache_ensure_group( cache, group_id); ecs_assert(group != NULL, ECS_INTERNAL_ERROR, NULL); if (group != qt->group) { flecs_query_cache_move_table_to_group(cache, qt, group); } } return flecs_query_cache_match_from_table(cache, qt); } return flecs_query_cache_add_table(cache, table); } /* Remove matched table from cache. */ void flecs_query_cache_remove_table( ecs_query_cache_t *cache, ecs_table_t *table) { ecs_query_cache_table_t *qt = flecs_query_cache_get_table(cache, table); if (!qt) { return; } ecs_query_cache_group_t *group = qt->group; ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_query_cache_match_t *match = ecs_vec_get( &group->tables, elem_size, qt->index); ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(match->base.table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(match->base.table->id == table->id, ECS_INTERNAL_ERROR, NULL); flecs_query_cache_match_fini(cache, match); flecs_query_cache_remove_table_from_group(cache, group, qt->index); ecs_allocator_t *a = &cache->query->real_world->allocator; flecs_free_t(a, ecs_query_cache_table_t, qt); ecs_map_remove(&cache->tables, table->id); } /* Remove all groups from the cache. Typically called during query cleanup. */ static void flecs_query_cache_remove_all_groups( ecs_query_cache_t *cache) { ecs_query_cache_group_t *cur = cache->first_group, *next = cur; while ((cur = next)) { next = cur->next; flecs_query_cache_group_fini(cache, cur); } cache->first_group = &cache->default_group; cache->default_group.next = NULL; } /* Remove all tables from the cache. Typically called during query cleanup. */ void flecs_query_cache_remove_all_tables( ecs_query_cache_t *cache) { ecs_allocator_t *a = &cache->query->real_world->allocator; ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_query_cache_group_t *cur = cache->first_group; do { int32_t i, count = ecs_vec_count(&cur->tables); for (i = 0; i < count; i ++) { flecs_query_cache_match_fini( cache, ecs_vec_get(&cur->tables, elem_size, i)); } ecs_vec_fini(a, &cur->tables, elem_size); } while ((cur = cur->next)); ecs_map_iter_t it = ecs_map_iter(&cache->tables); while (ecs_map_next(&it)) { ecs_query_cache_table_t *qt = ecs_map_ptr(&it); flecs_free_t(a, ecs_query_cache_table_t, qt); } ecs_map_clear(&cache->tables); flecs_query_cache_remove_all_groups(cache); ecs_assert(ecs_map_count(&cache->groups) == 0, ECS_INTERNAL_ERROR, NULL); } /* Free cache entry element. */ static void flecs_query_cache_match_elem_fini( ecs_query_cache_t *cache, ecs_query_cache_match_t *qm) { if (qm->base.columns) { flecs_bfree(&cache->allocators.columns, qm->base.columns); } if (!flecs_query_cache_is_trivial(cache)) { if (qm->_trs) { flecs_bfree(&cache->allocators.pointers, ECS_CONST_CAST(void*, qm->_trs)); } if (qm->_ids != cache->query->ids) { flecs_bfree(&cache->allocators.ids, qm->_ids); } if (qm->_sources != cache->sources) { flecs_bfree(&cache->allocators.ids, qm->_sources); } if (qm->_monitor) { flecs_bfree(&cache->allocators.monitors, qm->_monitor); } } } /* Free cache entry element and optional wildcard matches. */ void flecs_query_cache_match_fini( ecs_query_cache_t *cache, ecs_query_cache_match_t *qm) { flecs_query_cache_match_elem_fini(cache, qm); if (!flecs_query_cache_is_trivial(cache)) { if (qm->wildcard_matches) { ecs_query_cache_match_t *elems = ecs_vec_first(qm->wildcard_matches); int32_t i, count = ecs_vec_count(qm->wildcard_matches); for (i = 0; i < count; i ++) { flecs_query_cache_match_elem_fini(cache, &elems[i]); } ecs_allocator_t *a = &cache->query->real_world->allocator; ecs_vec_fini_t(a, qm->wildcard_matches, ecs_query_cache_match_t); flecs_free_t(a, ecs_vec_t, qm->wildcard_matches); } } } /* Initialize cache entry element. */ static void flecs_query_cache_match_set( ecs_query_cache_t *cache, ecs_query_cache_match_t *qm, ecs_iter_t *it) { bool trivial_cache = flecs_query_cache_is_trivial(cache); ecs_query_t *query = cache->query; int8_t i, field_count = query->field_count; ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); qm->base.table = it->table; qm->base.set_fields = it->set_fields; if (!qm->base.columns) { qm->base.columns = flecs_balloc(&cache->allocators.columns); } ecs_os_memcpy_n(qm->base.columns, it->columns, int16_t, field_count); /* Find out whether to store result-specific ids array or fixed array */ ecs_id_t *ids = cache->query->ids; for (i = 0; i < field_count; i ++) { if (it->ids[i] != ids[i]) { break; } } if (!trivial_cache) { if (i != field_count) { if (qm->_ids == ids || !qm->_ids) { qm->_ids = flecs_balloc(&cache->allocators.ids); } ecs_os_memcpy_n(qm->_ids, it->ids, ecs_id_t, field_count); } else { if (qm->_ids != ids) { flecs_bfree(&cache->allocators.ids, qm->_ids); qm->_ids = ids; } } } /* Find out whether to store result-specific sources array or fixed array */ for (i = 0; i < field_count; i ++) { if (it->sources[i]) { break; } } if (!trivial_cache) { if (i != field_count) { if (qm->_sources == cache->sources || !qm->_sources) { qm->_sources = flecs_balloc(&cache->allocators.ids); } ecs_os_memcpy_n(qm->_sources, it->sources, ecs_entity_t, field_count); } else { if (qm->_sources != cache->sources) { flecs_bfree(&cache->allocators.ids, qm->_sources); qm->_sources = cache->sources; } } qm->_up_fields = it->up_fields; if (!qm->_trs) { qm->_trs = flecs_balloc(&cache->allocators.pointers); } for (i = 0; i < field_count; i ++) { if (it->trs[i] && !it->sources[i] && !(it->up_fields & (1llu << i))) { qm->_trs[i] = it->trs[i]; } else { qm->_trs[i] = NULL; } } } else { /* If this is a trivial cache, we shouldn't have any fields with * non-$this sources */ ecs_assert(i == field_count, ECS_INTERNAL_ERROR, NULL); } } /* Iterate the next match for table. This function accepts an iterator for the * cache query and will keep on iterating until a result for a different table * is returned. Typically each table only returns one result, but wildcard * queries can return multiple results for the same table. * * For each iterated result the function will initialize the cache entry for the * matched table. */ bool flecs_query_cache_match_next( ecs_query_cache_t *cache, ecs_iter_t *it) { ecs_table_t *table = it->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_match_t *first = flecs_query_cache_add_table(cache, table); ecs_query_cache_match_t *qm = first; ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_allocator_t *a = &cache->query->real_world->allocator; do { flecs_query_cache_match_set(cache, qm, it); if (!ecs_query_next(it)) { return false; } if (it->table != table) { return true; } /* Another match for the same table (for wildcard queries) */ if (!first->wildcard_matches) { first->wildcard_matches = flecs_alloc_t(a, ecs_vec_t); ecs_vec_init(a, first->wildcard_matches, elem_size, 1); } qm = ecs_vec_append(a, first->wildcard_matches, elem_size); ecs_os_zeromem(qm); } while (true); } /* Same as flecs_query_cache_match_next, but for rematching. This function will * overwrite existing cache entries with new match data. */ static bool flecs_query_cache_rematch_next( ecs_query_cache_t *cache, ecs_iter_t *it) { ecs_table_t *table = it->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_match_t *first = flecs_query_cache_ensure_table(cache, table); ecs_query_cache_match_t *qm = first; ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_allocator_t *a = &cache->query->real_world->allocator; ecs_vec_t *wildcard_matches = first->wildcard_matches; int32_t wildcard_elem = 0; bool result = true, has_more = true; /* So we can tell which tables in the cache got rematched. All tables for * which this counter hasn't changed are no longer matched by the query. */ first->rematch_count = cache->rematch_count; do { flecs_query_cache_match_set(cache, qm, it); if (!ecs_query_next(it)) { has_more = false; result = false; } if (it->table != table) { has_more = false; } /* Are there more results for this table? */ if (!has_more) { /* If existing match had more wildcard matches than new match free * the superfluous ones. */ if (wildcard_matches) { int32_t i, count = ecs_vec_count(wildcard_matches); for (i = wildcard_elem; i < count; i ++) { ecs_query_cache_match_t *qm_elem = ecs_vec_get( wildcard_matches, elem_size, i); flecs_query_cache_match_fini(cache, qm_elem); } if (!wildcard_elem) { ecs_vec_fini(a, wildcard_matches, elem_size); flecs_free_t(a, ecs_vec_t, wildcard_matches); qm->wildcard_matches = NULL; } else { ecs_vec_set_count(a, wildcard_matches, elem_size, wildcard_elem); } } /* Are there more results for other tables? */ return result; } /* Another match for the same table (for wildcard queries) */ if (!wildcard_matches) { first->wildcard_matches = wildcard_matches = flecs_alloc_t(a, ecs_vec_t); ecs_vec_init(a, wildcard_matches, elem_size, 1); } if (ecs_vec_count(wildcard_matches) <= wildcard_elem) { qm = ecs_vec_append(a, wildcard_matches, elem_size); ecs_os_zeromem(qm); } else { qm = ecs_vec_get(wildcard_matches, elem_size, wildcard_elem); } wildcard_elem ++; } while (true); } /* Rematch query cache. This function is called whenever something happened that * could have caused a previously matching table to no longer match with the * query. This function is not called for regular table creation or deletion. * * An example of when this function is called is when a query matched a * component on a parent, and that component was removed from the parent. This * means that tables with (ChildOf, parent) previously matched the query, but * after the component got removed, no longer match. * * This operation is expensive, since it needs to: * - make sure that optional fields matched on parents are updated * - make sure that groups are up to date for all the matched tables * - remove tables that no longer match from the cache. */ void flecs_query_rematch( ecs_world_t *world, ecs_query_t *q) { flecs_poly_assert(world, ecs_world_t); ecs_allocator_t *a = &world->allocator; ecs_iter_t it; ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); /* Queries with trivial caches can't trigger rematching */ ecs_assert(!flecs_query_cache_is_trivial(cache), ECS_INTERNAL_ERROR, NULL); if (cache->monitor_generation == world->monitor_generation) { return; } ecs_os_perf_trace_push("flecs.query.rematch"); cache->monitor_generation = world->monitor_generation; cache->match_count ++; world->info.rematch_count_total ++; int32_t rematch_count = ++ cache->rematch_count; ecs_time_t t = {0}; if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_measure(&t); } it = ecs_query_iter(world, cache->query); ECS_BIT_SET(it.flags, EcsIterNoData); if (!ecs_query_next(&it)) { flecs_query_cache_remove_all_tables(cache); goto done; } while (flecs_query_cache_rematch_next(cache, &it)) { } /* Iterate all tables in cache, remove ones that weren't just matched */ ecs_vec_t unmatched; ecs_vec_init_t(a, &unmatched, ecs_table_t*, 0); ecs_size_t elem_size = flecs_query_cache_elem_size(cache); ecs_query_cache_group_t *cur = cache->first_group; do { int32_t i, count = ecs_vec_count(&cur->tables); for (i = 0; i < count; i ++) { ecs_query_cache_match_t *qm = ecs_vec_get(&cur->tables, elem_size, i); if (qm->rematch_count != rematch_count) { /* Collect tables, don't modify map while updating it */ ecs_vec_append_t(a, &unmatched, ecs_table_t*)[0] = qm->base.table; } } } while ((cur = cur->next)); /* Actually unmatch tables */ int32_t i, count = ecs_vec_count(&unmatched); ecs_table_t **unmatched_tables = ecs_vec_first(&unmatched); for (i = 0; i < count; i ++) { flecs_query_cache_remove_table(cache, unmatched_tables[i]); ecs_assert(flecs_query_cache_get_table( cache, unmatched_tables[i]) == NULL, ECS_INTERNAL_ERROR, NULL); } ecs_vec_fini_t(a, &unmatched, ecs_table_t*); if (world->flags & EcsWorldMeasureFrameTime) { world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); } done: ecs_os_perf_trace_pop("flecs.query.rematch"); } ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) static void flecs_query_cache_sort_table( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_order_by_action_t compare, ecs_sort_table_action_t sort) { int32_t count = ecs_table_count(table); if (!count) { /* Nothing to sort */ return; } if (count < 2) { return; } ecs_entity_t *entities = table->data.entities; void *ptr = NULL; int32_t size = 0; if (column_index != -1) { ecs_column_t *column = &table->data.columns[column_index]; ecs_type_info_t *ti = column->ti; size = ti->size; ptr = column->data; } if (sort) { sort(world, table, entities, ptr, size, 0, count - 1, compare); } else { flecs_query_cache_sort_table_generic( world, table, entities, ptr, size, 0, count - 1, compare); } } /* Helper struct for building sorted table ranges */ typedef struct sort_helper_t { ecs_query_cache_match_t *match; ecs_entity_t *entities; const void *ptr; int32_t row; int32_t elem_size; int32_t count; bool shared; } sort_helper_t; static const void* ptr_from_helper( sort_helper_t *helper) { ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); if (helper->shared) { return helper->ptr; } else { return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); } } static ecs_entity_t e_from_helper( sort_helper_t *helper) { if (helper->row < helper->count) { return helper->entities[helper->row]; } else { return 0; } } static void flecs_query_cache_build_sorted_table_range( ecs_query_cache_t *cache, ecs_query_cache_group_t *group) { ecs_world_t *world = cache->query->world; flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, "cannot sort query in multithreaded mode"); ecs_entity_t id = cache->order_by; ecs_order_by_action_t compare = cache->order_by_callback; int32_t i, table_count = ecs_vec_count(&group->tables); if (!table_count) { return; } ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_match_t); int32_t to_sort = 0; int32_t order_by_term = cache->order_by_term; sort_helper_t *helper = flecs_alloc_n( &world->allocator, sort_helper_t, table_count); for (i = 0; i < table_count; i ++) { ecs_query_cache_match_t *qm = ecs_vec_get_t(&group->tables, ecs_query_cache_match_t, i); ecs_table_t *table = qm->base.table; if (ecs_table_count(table) == 0) { continue; } if (id) { const ecs_term_t *term = &cache->query->terms[order_by_term]; int32_t field = term->field_index; ecs_size_t size = cache->query->sizes[field]; ecs_entity_t src = qm->_sources[field]; if (src == 0) { int32_t column_index = qm->base.columns[field]; ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_column_t *column = &table->data.columns[column_index]; helper[to_sort].ptr = column->data; helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else { ecs_record_t *r = flecs_entities_get(world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); if (term->src.id & EcsUp) { ecs_entity_t base = 0; ecs_search_relation(world, r->table, 0, id, EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); if (base && base != src) { /* Component could be inherited */ r = flecs_entities_get(world, base); } } helper[to_sort].ptr = ecs_table_get_id( world, r->table, id, ECS_RECORD_TO_ROW(r->row)); helper[to_sort].elem_size = size; helper[to_sort].shared = true; } ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); } else { helper[to_sort].ptr = NULL; helper[to_sort].elem_size = 0; helper[to_sort].shared = false; } helper[to_sort].match = qm; helper[to_sort].entities = table->data.entities; helper[to_sort].row = 0; helper[to_sort].count = ecs_table_count(table); to_sort ++; } if (!to_sort) { goto done; } ecs_query_cache_match_t *cur = NULL; bool proceed; do { int32_t j, min = 0; proceed = true; ecs_entity_t e1; while (!(e1 = e_from_helper(&helper[min]))) { min ++; if (min == to_sort) { proceed = false; break; } } if (!proceed) { break; } for (j = min + 1; j < to_sort; j++) { ecs_entity_t e2 = e_from_helper(&helper[j]); if (!e2) { continue; } const void *ptr1 = ptr_from_helper(&helper[min]); const void *ptr2 = ptr_from_helper(&helper[j]); if (compare(e1, ptr1, e2, ptr2) > 0) { min = j; e1 = e_from_helper(&helper[min]); } } sort_helper_t *cur_helper = &helper[min]; if (!cur || cur->base.columns != cur_helper->match->base.columns) { cur = ecs_vec_append_t(NULL, &cache->table_slices, ecs_query_cache_match_t); *cur = *(cur_helper->match); cur->_offset = cur_helper->row; cur->_count = 1; } else { cur->_count ++; } cur_helper->row ++; } while (proceed); done: flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } void flecs_query_cache_build_sorted_tables( ecs_query_cache_t *cache) { ecs_vec_clear(&cache->table_slices); /* Sort tables in group order */ ecs_query_cache_group_t *cur = cache->first_group; do { flecs_query_cache_build_sorted_table_range(cache, cur); } while ((cur = cur->next)); } void flecs_query_cache_sort_tables( ecs_world_t *world, ecs_query_impl_t *impl) { ecs_query_cache_t *cache = impl->cache; ecs_order_by_action_t compare = cache->order_by_callback; if (!compare) { return; } ecs_sort_table_action_t sort = cache->order_by_table_callback; ecs_entity_t order_by = cache->order_by; int32_t order_by_term = cache->order_by_term; ecs_component_record_t *cr = flecs_components_get(world, order_by); /* Iterate over non-empty tables. Don't bother with empty tables as they * have nothing to sort */ bool tables_sorted = false; ecs_query_cache_group_t *cur = cache->first_group; do { int32_t i, count = ecs_vec_count(&cur->tables); for (i = 0; i < count; i ++) { ecs_query_cache_match_t *qm = ecs_vec_get_t(&cur->tables, ecs_query_cache_match_t, i); ecs_table_t *table = qm->base.table; bool dirty = false; if (flecs_query_check_table_monitor(impl, qm, 0)) { tables_sorted = true; dirty = true; if (!ecs_table_count(table)) { /* If table is empty, there's a chance the query won't * iterate it so update the match monitor here. */ flecs_query_sync_match_monitor(impl, qm); ecs_vec_t *matches = qm->wildcard_matches; if (matches) { int32_t j, qms_count = ecs_vec_count(matches); ecs_query_cache_match_t *qms = ecs_vec_first(matches); for (j = 0; j < qms_count; j ++) { flecs_query_sync_match_monitor(impl, &qms[j]); } } } } int32_t column = -1; if (order_by) { int32_t order_by_field = cache->query->terms[order_by_term].field_index; if (flecs_query_check_table_monitor( impl, qm, order_by_field + 1)) { dirty = true; } if (dirty) { column = -1; const ecs_table_record_t *tr = flecs_component_get_table( cr, table); if (tr) { column = tr->column; } if (column == -1) { /* Component is shared, no sorting is needed */ dirty = false; tables_sorted = true; } } } if (!dirty) { continue; } /* Something has changed, sort the table. Prefers using * flecs_query_cache_sort_table when available */ flecs_query_cache_sort_table(world, table, column, compare, sort); tables_sorted = true; } } while ((cur = cur->next)); /* Next group */ if (tables_sorted || cache->match_count != cache->prev_match_count) { flecs_query_cache_build_sorted_tables(cache); cache->match_count ++; /* Increase version if tables changed */ } } static bool flecs_query_var_is_anonymous( const ecs_query_impl_t *query, ecs_var_id_t var_id) { ecs_query_var_t *var = &query->vars[var_id]; return var->anonymous; } ecs_var_id_t flecs_query_add_var( ecs_query_impl_t *query, const char *name, ecs_vec_t *vars, ecs_var_kind_t kind) { const char *dot = NULL; if (name) { dot = strchr(name, '.'); if (dot) { kind = EcsVarEntity; /* lookup variables are always entities */ } } ecs_hashmap_t *var_index = NULL; ecs_var_id_t var_id = EcsVarNone; if (name) { if (kind == EcsVarAny) { var_id = flecs_query_find_var_id(query, name, EcsVarEntity); if (var_id != EcsVarNone) { return var_id; } var_id = flecs_query_find_var_id(query, name, EcsVarTable); if (var_id != EcsVarNone) { return var_id; } kind = EcsVarTable; } else { var_id = flecs_query_find_var_id(query, name, kind); if (var_id != EcsVarNone) { return var_id; } } if (kind == EcsVarTable) { var_index = &query->tvar_index; } else { var_index = &query->evar_index; } /* If we're creating an entity var, check if it has a table variant */ if (kind == EcsVarEntity && var_id == EcsVarNone) { var_id = flecs_query_find_var_id(query, name, EcsVarTable); } } ecs_query_var_t *var; ecs_var_id_t result; if (vars) { var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); result = var->id = flecs_itovar(ecs_vec_count(vars)); } else { ecs_dbg_assert(query->var_count < query->var_size, ECS_INTERNAL_ERROR, NULL); var = &query->vars[query->var_count]; result = var->id = flecs_itovar(query->var_count); query->var_count ++; } var->kind = flecs_ito(int8_t, kind); var->name = name; var->table_id = var_id; var->base_id = 0; var->lookup = NULL; flecs_set_var_label(var, NULL); if (name) { flecs_name_index_init_if(var_index, NULL); flecs_name_index_ensure(var_index, var->id, name, 0, 0); var->anonymous = name[0] == '_'; /* Handle variables that require a by-name lookup, e.g. $this.wheel */ if (dot != NULL) { ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); var->lookup = dot + 1; } } return result; } static ecs_var_id_t flecs_query_add_var_for_term_id( ecs_query_impl_t *query, ecs_term_ref_t *term_id, ecs_vec_t *vars, ecs_var_kind_t kind) { const char *name = flecs_term_ref_var_name(term_id); if (!name) { return EcsVarNone; } return flecs_query_add_var(query, name, vars, kind); } /* This function walks over terms to discover which variables are used in the * query. It needs to provide the following functionality: * - create table vars for all variables used as source * - create entity vars for all variables not used as source * - create entity vars for all non-$this vars * - create anonymous vars to store the content of wildcards * - create anonymous vars to store result of lookups (for $var.child_name) * - create anonymous vars for resolving component inheritance * - create array that stores the source variable for each field * - ensure table vars for non-$this variables are anonymous * - ensure variables created inside scopes are anonymous * - place anonymous variables after public variables in vars array */ static int flecs_query_discover_vars( ecs_stage_t *stage, ecs_query_impl_t *query) { ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ ecs_vec_reset_t(NULL, vars, ecs_query_var_t); ecs_term_t *terms = query->pub.terms; int32_t a, i, anonymous_count = 0, count = query->pub.term_count; int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; bool table_this = false, entity_before_table_this = false; /* For This table lookups during discovery. This will be overwritten after * discovery with whether the query actually has a This table variable. */ query->pub.flags |= EcsQueryHasTableThisVar; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *second = &term->second; ecs_term_ref_t *src = &term->src; if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { /* Keep track of which variables are first used in scope, so that we * can mark them as anonymous. Terms inside a scope are collapsed * into a single result, which means that outside of the scope the * value of those variables is undefined. */ if (!scope) { scoped_var_index = ecs_vec_count(vars); } scope ++; continue; } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { if (!--scope) { /* Any new variables declared after entering a scope should be * marked as anonymous. */ int32_t v; for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; } } continue; } ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( query, first, vars, EcsVarEntity); if (first_var_id == EcsVarNone) { /* If first is not a variable, check if we need to insert anonymous * variable for resolving component inheritance */ if (term->flags_ & EcsTermIdInherited) { anonymous_count += 2; /* table & entity variable */ } /* If first is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(first)) { anonymous_count ++; } } if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { const char *var_name = flecs_term_ref_var_name(src); if (var_name) { ecs_var_id_t var_id = flecs_query_find_var_id( query, var_name, EcsVarEntity); if (var_id == EcsVarNone || var_id == first_var_id) { var_id = flecs_query_add_var( query, var_name, vars, EcsVarEntity); } if (var_id != EcsVarNone) { /* Mark variable as one for which we need to create a table * variable. Don't create table variable now, so that we can * store it in the non-public part of the variable array. */ ecs_query_var_t *var = ecs_vec_get_t( vars, ecs_query_var_t, (int32_t)var_id - 1); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); if (!var->lookup) { var->kind = EcsVarAny; anonymous_table_count ++; } if (((1llu << term->field_index) & query->pub.data_fields)) { /* Can't have an anonymous variable as source of a term * that returns a component. We need to return each * instance of the component, whereas anonymous * variables are not guaranteed to be resolved to * individual entities. */ if (var->anonymous) { ecs_err( "can't use anonymous variable '%s' as source of " "data term", var->name); goto error; } } /* Track which variable ids are used as field source */ if (!query->src_vars) { query->src_vars = flecs_calloc_n(&stage->allocator, ecs_var_id_t, query->pub.field_count); } query->src_vars[term->field_index] = var_id; } } else { if (flecs_term_ref_is_wildcard(src)) { anonymous_count ++; } } } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); } } if (flecs_query_add_var_for_term_id( query, second, vars, EcsVarEntity) == EcsVarNone) { /* If second is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(second)) { anonymous_count ++; } } if (src->id & EcsIsVariable && second->id & EcsIsVariable) { if (term->flags_ & EcsTermTransitive) { /* Anonymous variable to store temporary id for finding * targets for transitive relationship, see compile_term. */ anonymous_count ++; } } /* If member term, make sure source is available as entity */ if (term->flags_ & EcsTermIsMember) { flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); } /* Track if a This entity variable is used before a potential This table * variable. If this happens, the query has no This table variable */ if (ECS_TERM_REF_ID(src) == EcsThis) { table_this = true; } bool first_is_this = (ECS_TERM_REF_ID(first) == EcsThis) && (first->id & EcsIsVariable); bool second_is_this = (ECS_TERM_REF_ID(second) == EcsThis) && (second->id & EcsIsVariable); if (first_is_this || second_is_this) { if (!table_this) { entity_before_table_this = true; } } } int32_t var_count = ecs_vec_count(vars); ecs_var_id_t placeholder = EcsVarNone - 1; bool replace_placeholders = false; /* Ensure lookup variables have table and/or entity variables */ for (i = 0; i < var_count; i ++) { ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); if (var->lookup) { char *var_name = ecs_os_strdup(var->name); var_name[var->lookup - var->name - 1] = '\0'; ecs_var_id_t base_table_id = flecs_query_find_var_id( query, var_name, EcsVarTable); if (base_table_id != EcsVarNone) { var->table_id = base_table_id; } else if (anonymous_table_count) { /* Scan for implicit anonymous table variables that haven't been * inserted yet (happens after this step). Doing this here * ensures that anonymous variables are appended at the end of * the variable array, while also ensuring that variable ids are * stable (no swapping of table var ids that are in use). */ for (a = 0; a < var_count; a ++) { ecs_query_var_t *avar = ecs_vec_get_t( vars, ecs_query_var_t, a); if (avar->kind == EcsVarAny) { if (!ecs_os_strcmp(avar->name, var_name)) { base_table_id = (ecs_var_id_t)(a + 1); break; } } } if (base_table_id != EcsVarNone) { /* Set marker so we can set the new table id afterwards */ var->table_id = placeholder; replace_placeholders = true; } } ecs_var_id_t base_entity_id = flecs_query_find_var_id( query, var_name, EcsVarEntity); if (base_entity_id == EcsVarNone) { /* Get name from table var (must exist). We can't use allocated * name since variables don't own names. */ const char *base_name = NULL; if (base_table_id != EcsVarNone && base_table_id) { ecs_query_var_t *base_table_var = ecs_vec_get_t( vars, ecs_query_var_t, (int32_t)base_table_id - 1); base_name = base_table_var->name; } else { base_name = EcsThisName; } base_entity_id = flecs_query_add_var( query, base_name, vars, EcsVarEntity); var = ecs_vec_get_t(vars, ecs_query_var_t, i); } var->base_id = base_entity_id; ecs_os_free(var_name); } } var_count = ecs_vec_count(vars); /* Add non-This table variables */ if (anonymous_table_count) { anonymous_table_count = 0; for (i = 0; i < var_count; i ++) { ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); if (var->kind == EcsVarAny) { var->kind = EcsVarEntity; ecs_var_id_t var_id = flecs_query_add_var( query, var->name, vars, EcsVarTable); ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; anonymous_table_count ++; } } var_count = ecs_vec_count(vars); } /* If any forward references to newly added anonymous tables exist, replace * them with the actual table variable ids. */ if (replace_placeholders) { for (i = 0; i < var_count; i ++) { ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); if (var->table_id == placeholder) { char *var_name = ecs_os_strdup(var->name); var_name[var->lookup - var->name - 1] = '\0'; var->table_id = flecs_query_find_var_id( query, var_name, EcsVarTable); ecs_assert(var->table_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); ecs_os_free(var_name); } } } /* Always include spot for This variable, even if query doesn't use it */ var_count ++; ecs_query_var_t *query_vars = &flecs_this_array; if ((var_count + anonymous_count) > 1) { query_vars = flecs_alloc(&stage->allocator, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * (var_count + anonymous_count)); } query->vars = query_vars; query->var_count = var_count; query->pub.var_count = flecs_ito(int8_t, var_count); ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, !entity_before_table_this); query->var_size = var_count + anonymous_count; char **var_names; if (query_vars != &flecs_this_array) { query_vars[0].kind = EcsVarTable; query_vars[0].name = NULL; flecs_set_var_label(&query_vars[0], NULL); query_vars[0].id = 0; query_vars[0].table_id = EcsVarNone; query_vars[0].lookup = NULL; var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), var_count + anonymous_count); var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); } else { var_names = &flecs_this_name_array; } query->pub.vars = (char**)var_names; query_vars ++; var_names ++; var_count --; if (var_count) { ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); for (i = 0; i < var_count; i ++) { ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], ECS_INTERNAL_ERROR, NULL); var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); } } /* Hide anonymous table variables from application */ query->pub.var_count = flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); /* Sanity check to make sure that the public part of the variable array only * contains entity variables. */ #ifdef FLECS_DEBUG for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } #endif return 0; error: return -1; } static bool flecs_query_var_is_unknown( ecs_query_impl_t *query, ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx) { ecs_query_var_t *vars = query->vars; if (ctx->written & (1ull << var_id)) { return false; } else { ecs_var_id_t table_var = vars[var_id].table_id; if (table_var != EcsVarNone) { return flecs_query_var_is_unknown(query, table_var, ctx); } } return true; } /* Returns whether term is unknown. A term is unknown when it has variable * elements (first, second, src) that are all unknown. */ static bool flecs_query_term_is_unknown( ecs_query_impl_t *query, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t dummy = {0}; flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); bool has_vars = dummy.flags & ((EcsQueryIsVar << EcsQueryFirst) | (EcsQueryIsVar << EcsQuerySecond) | (EcsQueryIsVar << EcsQuerySrc)); if (!has_vars) { /* If term has no variables (typically terms with a static src) there * can't be anything that's unknown. */ return false; } if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { return false; } } if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { return false; } } if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { return false; } } return true; } /* Find the next known term from specified offset. This function is used to find * a term that can be evaluated before a term that is unknown. Evaluating known * before unknown terms can significantly decrease the search space. */ static int32_t flecs_query_term_next_known( ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, int32_t offset, ecs_flags64_t compiled) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = offset; i < count; i ++) { ecs_term_t *term = &terms[i]; if (compiled & (1ull << i)) { continue; } /* Only evaluate And terms */ if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ continue; } /* Don't reorder terms in scopes */ if (term->flags_ & EcsTermIsScope) { continue; } if (flecs_query_term_is_unknown(query, term, ctx)) { continue; } return i; } return -1; } /* If the first part of a query contains more than one trivial term, insert a * special instruction which batch-evaluates multiple terms. */ static void flecs_query_insert_trivial_search( ecs_query_impl_t *query, ecs_flags64_t *compiled, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; int32_t i, term_count = q->term_count; ecs_flags64_t trivial_set = 0; /* Trivial search always ignores prefabs and disabled entities */ if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { return; } /* Find trivial terms, which can be handled in single instruction */ int32_t trivial_wildcard_terms = 0; int32_t trivial_terms = 0; for (i = 0; i < term_count; i ++) { /* Term is already compiled */ if (*compiled & (1ull << i)) { continue; } ecs_term_t *term = &terms[i]; if (!(term->flags_ & EcsTermIsTrivial)) { continue; } /* We can only add trivial terms to plan if they have no up traversal */ if ((term->src.id & EcsTraverseFlags) != EcsSelf) { continue; } /* Wildcards are not supported for trivial queries */ if (ecs_id_is_wildcard(term->id)) { continue; } trivial_set |= (1llu << i); trivial_terms ++; } if (trivial_terms >= 2) { /* Mark terms as compiled & populated */ for (i = 0; i < q->term_count; i ++) { if (trivial_set & (1llu << i)) { *compiled |= (1ull << i); } } /* If there's more than 1 trivial term, batch them in trivial search */ ecs_query_op_t trivial = {0}; if (!trivial_wildcard_terms) { trivial.kind = EcsQueryTriv; } /* Store the bitset with trivial terms on the instruction */ trivial.src.entity = trivial_set; flecs_query_op_insert(&trivial, ctx); /* Mark $this as written */ ctx->written |= (1llu << 0); } } static void flecs_query_insert_cache_search( ecs_query_impl_t *query, ecs_flags64_t *compiled, ecs_query_compile_ctx_t *ctx) { if (!query->cache) { return; } ecs_query_t *q = &query->pub; int32_t childof_term = -1; bool has_childof_trav = false; if (q->cache_kind == EcsQueryCacheAll) { /* If all terms are cacheable, make sure no other terms are compiled */ *compiled = 0xFFFFFFFFFFFFFFFF; } else if (q->cache_kind == EcsQueryCacheAuto) { /* The query is partially cacheable */ ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = 0; i < count; i ++) { if ((*compiled) & (1ull << i)) { continue; } ecs_term_t *term = &terms[i]; if (!(term->flags_ & EcsTermIsCacheable)) { continue; } if (term->flags_ & EcsTermNonFragmentingChildOf) { if (!term->trav) { childof_term = i; } } if (term->trav == EcsChildOf) { has_childof_trav = true; } *compiled |= (1ull << i); } } /* Insert the operation for cache traversal */ ecs_query_op_t op = {0}; if (q->flags & EcsQueryIsCacheable) { op.kind = EcsQueryIsCache; } else { op.kind = EcsQueryCache; } flecs_query_write(0, &op.written); flecs_query_write_ctx(0, ctx, false); flecs_query_op_insert(&op, ctx); if (childof_term != -1) { flecs_query_compile_term( q->world, query, &q->terms[childof_term], ctx); } if (has_childof_trav) { ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (!((*compiled) & (1ull << i))) { continue; } if (!(term->flags_ & EcsTermIsCacheable)) { continue; } if (term->trav == EcsChildOf && (term->oper == EcsAnd || term->oper == EcsOptional)) { ecs_oper_kind_t oper = q->terms[i].oper; q->terms[i].oper = EcsAnd; flecs_query_compile_term( q->world, query, &q->terms[i], ctx); q->terms[i].oper = (int16_t)oper; } } } } static bool flecs_term_ref_match_multiple( ecs_term_ref_t *ref) { return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); } static bool flecs_term_match_multiple( ecs_term_t *term) { return flecs_term_ref_match_multiple(&term->first) || flecs_term_ref_match_multiple(&term->second); } static int flecs_query_insert_toggle( ecs_query_impl_t *impl, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &impl->pub; int32_t i, j, term_count = q->term_count; ecs_term_t *terms = q->terms; ecs_flags64_t fields_done = 0; for (i = 0; i < term_count; i ++) { if (fields_done & (1llu << i)) { continue; } ecs_term_t *term = &terms[i]; if (term->flags_ & EcsTermIsToggle) { ecs_query_op_t cur = {0}; flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); ecs_flags64_t and_toggles = 0; ecs_flags64_t not_toggles = 0; ecs_flags64_t optional_toggles = 0; for (j = i; j < term_count; j ++) { if (fields_done & (1llu << j)) { continue; } /* Also includes term[i], so flags get set correctly */ term = &terms[j]; /* If term is not for the same src, skip */ ecs_query_op_t next = {0}; flecs_query_compile_term_ref(NULL, impl, &next, &term->src, &next.src, EcsQuerySrc, EcsVarAny, ctx, false); if (next.src.entity != cur.src.entity || next.flags != cur.flags) { continue; } /* Source matches, set flag */ if (term->oper == EcsNot) { not_toggles |= (1llu << term->field_index); } else if (term->oper == EcsOptional) { optional_toggles |= (1llu << term->field_index); } else { and_toggles |= (1llu << term->field_index); } fields_done |= (1llu << j); } if (and_toggles || not_toggles) { ecs_query_op_t op = {0}; op.kind = EcsQueryToggle; op.src = cur.src; op.flags = cur.flags; if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_write(op.src.var, &op.written); } /* Encode fields: * - first.entity is the fields that match enabled bits * - second.entity is the fields that match disabled bits */ op.first.entity = and_toggles; op.second.entity = not_toggles; flecs_query_op_insert(&op, ctx); } /* Insert separate instructions for optional terms. To make sure * entities are returned in batches where fields are never partially * set or unset, the result must be split up into batches that have * the exact same toggle masks. Instead of complicating the toggle * instruction with code to scan for blocks that have the same bits * set, separate instructions let the query engine backtrack to get * the right results. */ if (optional_toggles) { ecs_flags64_t optional_done = 0; for (j = i; j < term_count; j ++) { uint64_t field_bit = 1ull << terms[j].field_index; if (!(optional_toggles & field_bit) || (optional_done & field_bit)) { continue; } optional_done |= field_bit; ecs_query_op_t op = {0}; op.kind = EcsQueryToggleOption; op.src = cur.src; op.first.entity = field_bit; op.flags = cur.flags; flecs_query_op_insert(&op, ctx); } } } } return 0; } static int flecs_query_insert_fixed_src_terms( ecs_world_t *world, ecs_query_impl_t *impl, ecs_flags64_t *compiled, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &impl->pub; int32_t i, term_count = q->term_count; ecs_term_t *terms = q->terms; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper == EcsNot) { /* If term has not operator and variables for first/second, we can't * put the term first as this could prevent us from getting back * valid results. For example: * !$var(e), Tag($var) * * Here, the first term would evaluate to false (and cause the * entire query not to match) if 'e' has any components. * * However, when reordering we get results: * Tag($var), !$var(e) * * Now the query returns all entities with Tag, that 'e' does not * have as component. For this reason, queries should never use * unwritten variables in not terms- and we should also not reorder * terms in a way that results in doing this. */ if (flecs_term_match_multiple(term)) { continue; } } /* Don't reorder terms in scopes */ if (term->flags_ & EcsTermIsScope) { continue; } if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { if (flecs_query_compile_term(world, impl, term, ctx)) { return -1; } *compiled |= (1llu << i); } } return 0; } int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query) { /* Compile query to operations. Only necessary for non-trivial queries, as * trivial queries use trivial iterators that don't use query ops. */ bool needs_plan = true; ecs_flags32_t flags = query->pub.flags; if (query->cache) { if (flags & EcsQueryIsCacheable) { if (!(flags & EcsQueryCacheWithFilter)) { needs_plan = false; } } } else { ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; if ((flags & trivial_flags) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { needs_plan = false; } } } if (!needs_plan) { /* Initialize space for $this variable */ query->pub.var_count = 1; query->var_count = 1; query->var_size = 1; query->vars = &flecs_this_array; query->pub.vars = &flecs_this_name_array; query->pub.flags |= EcsQueryHasTableThisVar; return 0; } ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; ecs_query_compile_ctx_t ctx = {0}; ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); ctx.ops = &stage->operations; ctx.cur = ctx.ctrlflow; ctx.cur->lbl_begin = -1; ecs_vec_clear(ctx.ops); /* Find all variables defined in query */ if (flecs_query_discover_vars(stage, query)) { return -1; } /* If query contains fixed source terms, insert operation to set sources */ int32_t i, term_count = q->term_count; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->src.id & EcsIsEntity) { ecs_query_op_t set_fixed = {0}; set_fixed.kind = EcsQuerySetFixed; flecs_query_op_insert(&set_fixed, &ctx); break; } } /* If the query contains terms with fixed ids (no wildcards, variables), * insert instruction that initializes ecs_iter_t::ids. This allows for the * insertion of simpler instructions later on. * If the query is entirely cacheable, ids are populated by the cache. */ if (q->cache_kind != EcsQueryCacheAll) { for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (flecs_term_is_fixed_id(q, term) || (term->src.id & EcsIsEntity && !(term->src.id & ~EcsTermRefFlags))) { ecs_query_op_t set_ids = {0}; set_ids.kind = EcsQuerySetIds; flecs_query_op_insert(&set_ids, &ctx); break; } } } ecs_flags64_t compiled = 0; /* Always evaluate terms with fixed source before other terms */ flecs_query_insert_fixed_src_terms( world, query, &compiled, &ctx); /* Compile cacheable terms */ flecs_query_insert_cache_search(query, &compiled, &ctx); /* Insert trivial term search if query allows for it */ flecs_query_insert_trivial_search(query, &compiled, &ctx); /* If a query starts with one or more optional terms, first compile the non * optional terms. This prevents having to insert an instruction that * matches the query against every entity in the storage. * Only skip optional terms at the start of the query so that any * short-circuiting behavior isn't affected (a non-optional term can become * optional if it uses a variable set in an optional term). */ int32_t start_term = 0; for (; start_term < term_count; start_term ++) { if (terms[start_term].oper != EcsOptional) { break; } } do { /* Compile remaining query terms to instructions */ for (i = start_term; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int32_t compile = i; if (compiled & (1ull << i)) { continue; /* Already compiled */ } if (term->oper == EcsOptional && start_term) { /* Don't reorder past the first optional term that's not in the * initial list of optional terms. This protects short-circuiting * branching in the query. * A future algorithm could look at which variables are * accessed by optional terms, and continue reordering terms * that don't access those variables. */ break; } bool can_reorder = true; if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ can_reorder = false; } /* If variables have been written, but this term has no known variables, * first try to resolve terms that have known variables. This can * significantly reduce the search space. * Only perform this optimization after at least one variable has been * written to, as all terms are unknown otherwise. */ if (can_reorder && ctx.written && flecs_query_term_is_unknown(query, term, &ctx)) { int32_t term_index = flecs_query_term_next_known( query, &ctx, i + 1, compiled); if (term_index != -1) { term = &q->terms[term_index]; compile = term_index; i --; /* Repeat current term */ } } if (flecs_query_compile_term(world, query, term, &ctx)) { return -1; } compiled |= (1ull << compile); } if (start_term) { start_term = 0; /* Repeat, now also insert optional terms */ } else { break; } } while (true); /* If there is only one term and it's a Tree instruction, replace it * with Children. If the queried-for parent has the OrderedChildren * trait, the Children instruction will return the array with child * entities vs. returning children one by one. */ if (term_count == 1 && ecs_vec_count(ctx.ops)) { ecs_query_op_t *op = ecs_vec_last_t(ctx.ops, ecs_query_op_t); ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); if (op->kind == EcsQueryTree) { op->kind = EcsQueryChildren; } else if (op->kind == EcsQueryTreeWildcard) { op->kind = EcsQueryChildrenWc; } } ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); if (this_id != EcsVarNone) { /* If This variable has been written as entity, insert an operation to * assign it to it.entities for consistency. */ if (ctx.written & (1ull << this_id)) { ecs_query_op_t set_this = {0}; set_this.kind = EcsQuerySetThis; set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); set_this.first.var = this_id; flecs_query_op_insert(&set_this, &ctx); } } /* Make sure non-This variables are written as entities */ if (query->vars) { for (i = 0; i < query->var_count; i ++) { ecs_query_var_t *var = &query->vars[i]; if (var->id && var->kind == EcsVarTable && var->name) { ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, EcsVarEntity); if (!flecs_query_is_written(var_id, ctx.written)) { /* Skip anonymous variables */ if (!flecs_query_var_is_anonymous(query, var_id)) { flecs_query_insert_each(var->id, var_id, &ctx, false); } } } } } /* If query contains non-This variables as term source, build lookup array */ if (query->src_vars) { ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); bool only_anonymous = true; for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; if (!var_id) { continue; } if (!flecs_query_var_is_anonymous(query, var_id)) { only_anonymous = false; break; } else { /* Don't fetch component data for anonymous variables. Because * not all metadata (such as it.sources) is initialized for * anonymous variables, and because they may only be available * as table variables (each is not guaranteed to be inserted for * anonymous variables) the iterator may not have sufficient * information to resolve component data. */ for (int32_t t = 0; t < q->term_count; t ++) { ecs_term_t *term = &q->terms[t]; if (term->field_index == i) { term->inout = EcsInOutNone; } } } } /* Don't insert setvar instruction if all vars are anonymous */ if (!only_anonymous) { ecs_query_op_t set_vars = {0}; set_vars.kind = EcsQuerySetVars; flecs_query_op_insert(&set_vars, &ctx); } for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; if (!var_id) { continue; } if (query->vars[var_id].kind == EcsVarTable) { var_id = flecs_query_find_var_id(query, query->vars[var_id].name, EcsVarEntity); /* Variables used as source that aren't This must be entities */ ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } query->src_vars[i] = var_id; } } ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); /* If query is empty, insert Nothing instruction */ if (!(term_count - ctx.skipped)) { ecs_vec_clear(ctx.ops); ecs_query_op_t nothing = {0}; nothing.kind = EcsQueryNothing; flecs_query_op_insert(¬hing, &ctx); } else { /* If query contains terms for toggleable components, insert toggle */ if (!(q->flags & EcsQueryTableOnly)) { flecs_query_insert_toggle(query, &ctx); } /* Insert yield. If program reaches this operation, a result was found */ ecs_query_op_t yield = {0}; yield.kind = EcsQueryYield; flecs_query_op_insert(&yield, &ctx); } int32_t op_count = ecs_vec_count(ctx.ops); if (op_count) { query->op_count = op_count; query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); } return 0; } #define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ ecs_var_id_t flecs_query_find_var_id( const ecs_query_impl_t *query, const char *name, ecs_var_kind_t kind) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (kind == EcsVarTable) { if (!ecs_os_strcmp(name, EcsThisName)) { if (query->pub.flags & EcsQueryHasTableThisVar) { return 0; } else { return EcsVarNone; } } if (!flecs_name_index_is_init(&query->tvar_index)) { return EcsVarNone; } uint64_t index = flecs_name_index_find( &query->tvar_index, name, 0, 0); if (index == 0) { return EcsVarNone; } return flecs_utovar(index); } if (kind == EcsVarEntity) { if (!flecs_name_index_is_init(&query->evar_index)) { return EcsVarNone; } uint64_t index = flecs_name_index_find( &query->evar_index, name, 0, 0); if (index == 0) { return EcsVarNone; } return flecs_utovar(index); } ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); /* If searching for any kind of variable, start with most specific */ ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); if (index != EcsVarNone) { return index; } return flecs_query_find_var_id(query, name, EcsVarTable); } static ecs_var_id_t flecs_query_most_specific_var( ecs_query_impl_t *query, const char *name, ecs_var_kind_t kind, ecs_query_compile_ctx_t *ctx) { if (kind == EcsVarTable || kind == EcsVarEntity) { return flecs_query_find_var_id(query, name, kind); } ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { /* If entity variable is available and written to, it contains the most * specific result and should be used. */ return evar; } ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { /* If variable of any kind is requested and variable hasn't been written * yet, write to table variable */ return tvar; } /* If table var is written, and entity var doesn't exist or is not written, * return table var */ if (tvar != EcsVarNone) { return tvar; } else { return evar; } } ecs_query_lbl_t flecs_query_op_insert( ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); int32_t count = ecs_vec_count(ctx->ops); *elem = *op; if (count > 1) { if (ctx->cur->lbl_begin == -1) { /* Variables written by previous instruction can't be written by * this instruction, except when this is part of an OR chain. */ elem->written &= ~elem[-1].written; } } elem->next = flecs_itolbl(count); elem->prev = flecs_itolbl(count - 2); return flecs_itolbl(count - 1); } static ecs_query_op_t* flecs_query_begin_block( ecs_query_op_kind_t kind, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t op = {0}; op.kind = flecs_ito(uint8_t, kind); ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); } static void flecs_query_end_block( ecs_query_compile_ctx_t *ctx, bool reset) { ecs_query_op_t new_op = {0}; new_op.kind = EcsQueryEnd; ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); ops[ctx->cur->lbl_begin].next = end; ecs_query_op_t *end_op = &ops[end]; if (reset && ctx->cur->lbl_query != -1) { ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; end_op->prev = ctx->cur->lbl_begin; end_op->src = query_op->src; end_op->first = query_op->first; end_op->second = query_op->second; end_op->flags = query_op->flags; end_op->field_index = query_op->field_index; } else { end_op->prev = ctx->cur->lbl_begin; end_op->field_index = -1; } ctx->cur->lbl_begin = -1; } static void flecs_query_begin_block_cond_eval( ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx, ecs_write_flags_t cond_write_state) { ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; ecs_write_flags_t cond_mask = 0; if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { first_var = op->first.var; ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << first_var); } if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { second_var = op->second.var; ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << second_var); } if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { src_var = op->src.var; ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << src_var); } /* Variables set in an OR chain are marked as conditional writes. However, * writes from previous terms in the current OR chain shouldn't be treated * as variables that are conditionally set, so instead use the write mask * from before the chain started. */ if (ctx->ctrlflow->in_or) { cond_write_state = ctx->ctrlflow->cond_written_or; } /* If this term uses conditionally set variables, insert instruction that * jumps over the term if the variables weren't set yet. */ if (cond_mask & cond_write_state) { ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); ecs_query_op_t jmp_op = {0}; jmp_op.kind = EcsQueryIfVar; if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); jmp_op.first.var = first_var; } if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); jmp_op.second.var = second_var; } if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); jmp_op.src.var = src_var; } flecs_query_op_insert(&jmp_op, ctx); } else { ctx->cur->lbl_cond_eval = -1; } } static void flecs_query_end_block_cond_eval( ecs_query_compile_ctx_t *ctx) { if (ctx->cur->lbl_cond_eval == -1) { return; } ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); ecs_query_op_t end_op = {0}; end_op.kind = EcsQueryEnd; ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); ops[ctx->cur->lbl_cond_eval].next = end; ecs_query_op_t *end_op_ptr = &ops[end]; ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; end_op_ptr->prev = ctx->cur->lbl_cond_eval; end_op_ptr->src = query_op->src; end_op_ptr->first = query_op->first; end_op_ptr->second = query_op->second; end_op_ptr->flags = query_op->flags; end_op_ptr->field_index = query_op->field_index; } static void flecs_query_begin_block_or( ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); or_op->kind = EcsQueryOr; or_op->field_index = term->field_index; /* Set the source of the evaluated terms as source of the Or instruction. * This lets the engine determine whether the variable has already been * written. When the source is not yet written, an OR operation needs to * take the union of all the terms in the OR chain. When the variable is * known, it will return after the first matching term. * * In case a term in the OR expression is an equality predicate which * compares the left hand side with a variable, the variable acts as an * alias, so we can always assume that it's written. */ bool add_src = true; if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { if (!(flecs_query_is_written(op->src.var, ctx->written))) { add_src = false; } } if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { if (add_src) { or_op->flags = (EcsQueryIsVar << EcsQuerySrc); or_op->src = op->src; ctx->cur->src_or = op->src; } ctx->cur->src_written_or = flecs_query_is_written( op->src.var, ctx->written); } } static void flecs_query_end_block_or( ecs_query_impl_t *impl, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t op = {0}; op.kind = EcsQueryEnd; ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); int32_t i, prev_or = ctx->cur->lbl_begin + 1; for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { if (ops[i].next == FlecsRuleOrMarker) { if (i == (end - 1)) { ops[prev_or].prev = ctx->cur->lbl_begin; } else { ops[prev_or].prev = flecs_itolbl(i + 1); } ops[i].next = flecs_itolbl(end); prev_or = i + 1; } } ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); first->next = flecs_itolbl(end); ops[end].prev = ctx->cur->lbl_begin; ops[end - 1].prev = ctx->cur->lbl_begin; ctx->ctrlflow->in_or = false; ctx->cur->lbl_begin = -1; if (src_is_var) { ecs_var_id_t src_var = first->src.var; ctx->written |= (1llu << src_var); /* If src is a table variable, it is possible that this was resolved to * an entity variable in all of the OR terms. If this is the case, mark * entity variable as written as well. */ ecs_query_var_t *var = &impl->vars[src_var]; if (var->kind == EcsVarTable) { const char *name = var->name; if (!name) { name = "this"; } ecs_var_id_t evar = flecs_query_find_var_id( impl, name, EcsVarEntity); if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { ctx->written |= (1llu << evar); ctx->cond_written &= ~(1llu << evar); } } } ctx->written |= ctx->cond_written; /* Scan which variables were conditionally written in the OR chain and * reset instructions after the OR chain. If a variable is set in part one * of a chain but not part two, there would be nothing writing to the * variable in part two, leaving it to the previous value. To address this * a reset is inserted that resets the variable value on redo. */ for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); /* Skip variable if it's the source for the OR chain */ if (src_is_var && (i == first->src.var)) { continue; } /* Skip variable if it was written before the OR chain */ if (ctx->ctrlflow->written_or & (1llu << i)) { continue; } if (!prev && cur) { ecs_query_op_t reset_op = {0}; reset_op.kind = EcsQueryReset; reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); reset_op.src.var = flecs_itovar(i); flecs_query_op_insert(&reset_op, ctx); } } } void flecs_query_insert_each( ecs_var_id_t tvar, ecs_var_id_t evar, ecs_query_compile_ctx_t *ctx, bool cond_write) { ecs_query_op_t each = {0}; each.kind = EcsQueryEach; each.src.var = evar; each.first.var = tvar; each.flags = (EcsQueryIsVar << EcsQuerySrc) | (EcsQueryIsVar << EcsQueryFirst); flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &each.written); flecs_query_op_insert(&each, ctx); } static void flecs_query_insert_lookup( ecs_var_id_t base_var, ecs_var_id_t evar, ecs_query_compile_ctx_t *ctx, bool cond_write) { ecs_query_op_t lookup = {0}; lookup.kind = EcsQueryLookup; lookup.src.var = evar; lookup.first.var = base_var; lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | (EcsQueryIsVar << EcsQueryFirst); flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &lookup.written); flecs_query_op_insert(&lookup, ctx); } static void flecs_query_insert_unconstrained_transitive( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx, bool cond_write) { /* Create anonymous variable to store the target ids. This will return the * list of targets without constraining the variable of the term, which * needs to stay variable to find all transitive relationships for a src. */ ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); /* First, find ids to start traversal from. This fixes op.second. */ ecs_query_op_t find_ids = {0}; find_ids.kind = EcsQueryIdsRight; find_ids.field_index = -1; find_ids.first = op->first; find_ids.second = op->second; find_ids.flags = op->flags; find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); find_ids.second.var = tgt; flecs_query_write_ctx(tgt, ctx, cond_write); flecs_query_write(tgt, &find_ids.written); flecs_query_op_insert(&find_ids, ctx); /* Next, iterate all tables for the ids. This fixes op.src */ ecs_query_op_t and_op = {0}; and_op.kind = EcsQueryAnd; and_op.field_index = op->field_index; and_op.first = op->first; and_op.second = op->second; and_op.src = op->src; and_op.flags = op->flags | EcsQueryIsSelf; and_op.second.var = tgt; flecs_query_write_ctx(and_op.src.var, ctx, cond_write); flecs_query_write(and_op.src.var, &and_op.written); flecs_query_op_insert(&and_op, ctx); } static void flecs_query_insert_inheritance( ecs_query_impl_t *query, ecs_term_t *term, ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx, bool cond_write) { /* Anonymous variable to store the resolved component ids */ ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, ECS_TERM_REF_ID(&term->first))); flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, ECS_TERM_REF_ID(&term->first))); ecs_query_op_t trav_op = {0}; trav_op.kind = EcsQueryTrav; trav_op.field_index = -1; trav_op.first.entity = EcsIsA; trav_op.second.entity = ECS_TERM_REF_ID(&term->first); trav_op.src.var = tvar; trav_op.flags = EcsQueryIsSelf; trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); trav_op.written |= (1ull << tvar); if (term->first.id & EcsSelf) { trav_op.match_flags |= EcsTermReflexive; } flecs_query_op_insert(&trav_op, ctx); flecs_query_insert_each(tvar, evar, ctx, cond_write); ecs_query_ref_t r = { .var = evar }; op->first = r; op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); op->flags |= (EcsQueryIsVar << EcsQueryFirst); } void flecs_query_compile_term_ref( ecs_world_t *world, ecs_query_impl_t *query, ecs_query_op_t *op, ecs_term_ref_t *term_ref, ecs_query_ref_t *ref, ecs_flags8_t ref_kind, ecs_var_kind_t kind, ecs_query_compile_ctx_t *ctx, bool create_wildcard_vars) { (void)world; if (!ecs_term_ref_is_set(term_ref)) { return; } if (term_ref->id & EcsIsVariable) { op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); const char *name = flecs_term_ref_var_name(term_ref); if (name) { ref->var = flecs_query_most_specific_var(query, name, kind, ctx); ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } else if (create_wildcard_vars) { bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); if (is_wildcard && (kind == EcsVarAny)) { ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); } else { ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); } if (is_wildcard) { flecs_set_var_label(&query->vars[ref->var], ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); } ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } } if (term_ref->id & EcsIsEntity) { op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); ref->entity = ECS_TERM_REF_ID(term_ref); } } static int flecs_query_compile_ensure_vars( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_query_ref_t *ref, ecs_flags16_t ref_kind, ecs_query_compile_ctx_t *ctx, bool cond_write, bool *written_out) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); bool written = false; if (flags & EcsQueryIsVar) { ecs_var_id_t var_id = ref->var; ecs_query_var_t *var = &query->vars[var_id]; if (var->kind == EcsVarEntity && !flecs_query_is_written(var_id, ctx->written)) { /* If entity variable is not yet written but a table variant exists * that has been written, insert each operation to translate from * entity variable to table */ ecs_var_id_t tvar = var->table_id; if ((tvar != EcsVarNone) && flecs_query_is_written(tvar, ctx->written)) { if (var->lookup) { if (!flecs_query_is_written(tvar, ctx->written)) { ecs_err("dependent variable of '$%s' is not written", var->name); return -1; } if (!flecs_query_is_written(var->base_id, ctx->written)) { flecs_query_insert_each( tvar, var->base_id, ctx, cond_write); } } else { flecs_query_insert_each(tvar, var_id, ctx, cond_write); } /* Variable was written, just not as entity */ written = true; } else if (var->lookup) { if (!flecs_query_is_written(var->base_id, ctx->written)) { ecs_err("dependent variable of '$%s' is not written", var->name); return -1; } } } written |= flecs_query_is_written(var_id, ctx->written); } else { /* If it's not a variable, it's always written */ written = true; } if (written_out) { *written_out = written; } return 0; } static bool flecs_query_compile_lookup( ecs_query_impl_t *query, ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx, bool cond_write) { ecs_query_var_t *var = &query->vars[var_id]; if (var->lookup) { flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); return true; } else { return false; } } static void flecs_query_insert_contains( ecs_query_impl_t *query, ecs_var_id_t src_var, ecs_var_id_t other_var, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t contains = {0}; if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { contains.kind = EcsQueryContain; contains.src.var = src_var; contains.first.var = other_var; contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | (EcsQueryIsVar << EcsQueryFirst); flecs_query_op_insert(&contains, ctx); } } static void flecs_query_insert_pair_eq( int32_t field_index, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t contains = {0}; contains.kind = EcsQueryPairEq; contains.field_index = flecs_ito(int8_t, field_index); flecs_query_op_insert(&contains, ctx); } static int flecs_query_compile_builtin_pred( ecs_query_t *q, ecs_term_t *term, ecs_query_op_t *op, ecs_write_flags_t write_state) { ecs_entity_t id = ECS_TERM_REF_ID(&term->first); ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); if (id == EcsPredEq) { if (term->second.id & EcsIsName) { op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); } else { op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); } } else if (id == EcsPredMatch) { op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); } if (flags_2nd & EcsQueryIsVar) { if (!(write_state & (1ull << op->second.var))) { ecs_err("uninitialized variable '%s' on right-hand side of " "equality operator", ecs_query_var_name(q, op->second.var)); return -1; } } ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); (void)flags_src; if (!(write_state & (1ull << op->src.var))) { /* If this is an == operator with a right-hand side that resolves to a * single entity, the left-hand side is allowed to be undefined, as the * instruction will be evaluated as an assignment. */ if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { ecs_err("uninitialized variable '%s' on left-hand side of " "equality operator", ecs_query_var_name(q, op->src.var)); return -1; } } return 0; } static int flecs_query_ensure_scope_var( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_query_ref_t *ref, ecs_flags16_t ref_kind, ecs_query_compile_ctx_t *ctx) { ecs_var_id_t var = ref->var; if (query->vars[var].kind == EcsVarEntity && !flecs_query_is_written(var, ctx->written)) { ecs_var_id_t table_var = query->vars[var].table_id; if (table_var != EcsVarNone && flecs_query_is_written(table_var, ctx->written)) { if (flecs_query_compile_ensure_vars( query, op, ref, ref_kind, ctx, false, NULL)) { goto error; } } } return 0; error: return -1; } static int flecs_query_ensure_scope_vars( ecs_world_t *world, ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, ecs_term_t *term) { /* If the scope uses variables as entity that have only been written as * table, resolve them as entities before entering the scope. */ ecs_term_t *cur = term; while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { /* Dummy operation to obtain variable information for term */ ecs_query_op_t op = {0}; flecs_query_compile_term_ref(world, query, &op, &cur->first, &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(world, query, &op, &cur->second, &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { if (flecs_query_ensure_scope_var( query, &op, &op.first, EcsQueryFirst, ctx)) { goto error; } } if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { if (flecs_query_ensure_scope_var( query, &op, &op.second, EcsQuerySecond, ctx)) { goto error; } } cur ++; } return 0; error: return -1; } static void flecs_query_compile_push( ecs_query_compile_ctx_t *ctx) { ctx->cur = &ctx->ctrlflow[++ ctx->scope]; ctx->cur->lbl_begin = -1; ctx->cur->lbl_begin = -1; } static void flecs_query_compile_pop( ecs_query_compile_ctx_t *ctx) { /* Should've been caught by query validator */ ecs_assert(ctx->scope > 0, ECS_INTERNAL_ERROR, NULL); ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } static int flecs_query_compile_0_src( ecs_world_t *world, ecs_query_impl_t *impl, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { /* If the term has a 0 source, check if it's a scope open/close */ if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { goto error; } if (term->oper == EcsNot) { ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); flecs_query_begin_block(EcsQueryNot, ctx); } else { ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); } flecs_query_compile_push(ctx); } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { flecs_query_compile_pop(ctx); if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { flecs_query_end_block(ctx, false); } } else { /* Noop */ } return 0; error: return -1; } static ecs_flags32_t flecs_query_to_table_flags( const ecs_query_t *q) { ecs_flags32_t query_flags = q->flags; if (!(query_flags & EcsQueryMatchDisabled) || !(query_flags & EcsQueryMatchPrefab)) { ecs_flags32_t table_flags = EcsTableNotQueryable; if (!(query_flags & EcsQueryMatchDisabled)) { table_flags |= EcsTableIsDisabled; } if (!(query_flags & EcsQueryMatchPrefab)) { table_flags |= EcsTableIsPrefab; } return table_flags; } return EcsTableNotQueryable; } static bool flecs_query_select_all( const ecs_query_t *q, ecs_term_t *term, ecs_query_op_t *op, ecs_var_id_t src_var, ecs_query_compile_ctx_t *ctx) { bool builtin_pred = flecs_term_is_builtin_pred(term); bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; if (term->oper == EcsNot || term->oper == EcsOptional || term->oper == EcsNotFrom || pred_match) { ecs_query_op_t match_any = {0}; match_any.kind = EcsQueryAll; match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); match_any.src = op->src; match_any.field_index = -1; if (!pred_match) { match_any.first.entity = EcsAny; } else { /* If matching by name, instead of finding all tables, just find * the ones with a name. */ match_any.first.entity = ecs_id(EcsIdentifier); match_any.second.entity = EcsName; match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); } match_any.written = (1ull << src_var); match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); flecs_query_op_insert(&match_any, ctx); flecs_query_write_ctx(op->src.var, ctx, false); /* Update write administration */ return true; } return false; } static int flecs_query_compile_begin_member_term( ecs_world_t *world, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_entity_t first_id) { (void)world; (void)term; (void)ctx; (void)first_id; return 0; } static int flecs_query_compile_end_member_term( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_id_t term_id, ecs_entity_t first_id, ecs_entity_t second_id, bool cond_write) { (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; (void)first_id; (void)second_id; (void)cond_write; return 0; } static void flecs_query_mark_last_or_op( ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); op_ptr->next = FlecsRuleOrMarker; } static void flecs_query_set_op_kind( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_term_t *term, bool src_is_var) { (void)query; /* Default instruction for And operators. If the source is fixed (like for * singletons or terms with an entity source), use With, which is like And but * just matches against a source (vs. finding a source). */ op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; /* Ignore cascade flag */ ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); /* Handle *From operators */ if (term->oper == EcsAndFrom) { op->kind = EcsQueryAndFrom; } else if (term->oper == EcsOrFrom) { op->kind = EcsQueryOrFrom; } else if (term->oper == EcsNotFrom) { op->kind = EcsQueryNotFrom; /* If term is transitive, use Trav(ersal) instruction */ } else if (term->flags_ & EcsTermTransitive) { ecs_assert(ecs_term_ref_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); op->kind = EcsQueryTrav; /* Handle non-fragmenting components */ } else if (term->flags_ & EcsTermDontFragment) { if (op->kind == EcsQueryAnd) { op->kind = EcsQuerySparse; if (term->oper == EcsNot) { op->kind = EcsQuerySparseNot; } } else { op->kind = EcsQuerySparseWith; } if ((term->src.id & trav_flags) == EcsUp) { op->kind = EcsQuerySparseUp; } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { op->kind = EcsQuerySparseSelfUp; } } else { if ((term->src.id & trav_flags) == EcsUp) { op->kind = EcsQueryUp; if (term->trav == EcsChildOf) { if (term->flags_ & EcsTermIsCacheable && query->cache) { op->kind = EcsQueryTreeUpPost; } else if (query->pub.flags & EcsQueryNested) { op->kind = EcsQueryTreeUpPre; } } } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { op->kind = EcsQuerySelfUp; if (term->trav == EcsChildOf) { if (term->flags_ & EcsTermIsCacheable && query->cache) { op->kind = EcsQueryTreeSelfUpPost; } else if (query->pub.flags & EcsQueryNested) { op->kind = EcsQueryTreeSelfUpPre; } } } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { op->kind = EcsQueryAndAny; } else if (ECS_IS_PAIR(term->id) && ECS_PAIR_FIRST(term->id) == EcsWildcard) { if (op->kind == EcsQueryAnd) { op->kind = EcsQueryAndWcTgt; } else { op->kind = EcsQueryWithWcTgt; } } /* ChildOf terms need to take into account both ChildOf pairs and the * Parent component for non-fragmenting hierarchies. */ if (term->flags_ & EcsTermNonFragmentingChildOf && !term->trav) { if (query->pub.flags & EcsQueryNested) { /* If this is a nested query (used to populate a cache), insert * instruction that matches tables with ChildOf pairs and Parent * component, without filtering the non-fragmenting parents as * this cannot be cached. */ op->kind = EcsQueryTreePre; } else { if (!src_is_var) { op->kind = EcsQueryTreeWith; } else { if (op->kind == EcsQueryAnd) { if (ECS_PAIR_SECOND(term->id) == EcsWildcard) { op->kind = EcsQueryTreeWildcard; } else { op->kind = EcsQueryTree; } if (term->flags_ & EcsTermIsCacheable) { if (query->cache) { op->kind = EcsQueryTreePost; } } } else if (op->kind == EcsQueryAndAny) { if (ECS_PAIR_SECOND(term->id)) { op->kind = EcsQueryTreeWildcard; } else { /* If it's a (ChildOf, 0) query then we don't need to * evaluate it as a wildcard. */ } } } } } } } int flecs_query_compile_term( ecs_world_t *world, ecs_query_impl_t *query, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_id_t term_id = term->id; ecs_entity_t first_id = term->first.id; ecs_entity_t second_id = term->second.id; bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; bool member_term = (term->flags_ & EcsTermIsMember) != 0; if (member_term) { flecs_query_compile_begin_member_term(world, term, ctx, first_id); } ecs_query_t *q = &query->pub; bool first_term = term == q->terms; bool first_is_var = term->first.id & EcsIsVariable; bool second_is_var = term->second.id & EcsIsVariable; bool src_is_var = term->src.id & EcsIsVariable; bool src_is_wildcard = src_is_var && (ECS_TERM_REF_ID(&term->src) == EcsWildcard || ECS_TERM_REF_ID(&term->src) == EcsAny); bool src_is_lookup = false; bool builtin_pred = flecs_term_is_builtin_pred(term); bool is_optional = (term->oper == EcsOptional); bool is_or = flecs_term_is_or(q, term); bool first_or = false, last_or = false; bool cond_write = term->oper == EcsOptional || is_or; ecs_query_op_t op = {0}; if (is_or) { first_or = first_term || (term[-1].oper != EcsOr); last_or = term->oper != EcsOr; } if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { const ecs_type_t *type = ecs_get_type(world, term->id); if (!type) { /* Empty type for id in *From operation is a noop */ ctx->skipped ++; return 0; } int32_t i, count = type->count; ecs_id_t *ti_ids = type->array; for (i = 0; i < count; i ++) { ecs_id_t ti_id = ti_ids[i]; ecs_component_record_t *cr = flecs_components_get(world, ti_id); if (!(cr->flags & EcsIdOnInstantiateDontInherit)) { break; } } if (i == count) { /* Type did not contain any ids to perform operation on */ ctx->skipped ++; return 0; } } /* !_ (don't match anything) terms always return nothing. */ if (term->oper == EcsNot && term->id == EcsAny) { op.kind = EcsQueryNothing; flecs_query_op_insert(&op, ctx); return 0; } if (first_or) { ctx->ctrlflow->cond_written_or = ctx->cond_written; ctx->ctrlflow->in_or = true; } else if (is_or) { ctx->written = ctx->ctrlflow->written_or; } if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { if (flecs_query_compile_0_src(world, query, term, ctx)) { goto error; } return 0; } if (builtin_pred) { ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); if (id_noflags == EcsWildcard || id_noflags == EcsAny) { if (!is_or) { /* Noop */ return 0; } op.field_index = flecs_ito(int8_t, term->field_index); op.term_index = flecs_ito(int8_t, term - q->terms); op.kind = EcsQueryNothing; if (first_or) { flecs_query_begin_block_or(&op, term, ctx); ctx->ctrlflow->written_or = ctx->written; } flecs_query_op_insert(&op, ctx); ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); flecs_query_mark_last_or_op(ctx); if (last_or) { flecs_query_end_block_or(query, ctx); } return 0; } } op.field_index = flecs_ito(int8_t, term->field_index); op.term_index = flecs_ito(int8_t, term - q->terms); flecs_query_set_op_kind(query, &op, term, src_is_var); bool is_not = (term->oper == EcsNot) && !builtin_pred; if (op.kind == EcsQuerySparseNot) { is_not = false; } /* Save write state at start of term so we can use it to reliably track * variables got written by this term. */ ecs_write_flags_t cond_write_state = ctx->cond_written; /* Resolve variables and entities for operation arguments */ flecs_query_compile_term_ref(world, query, &op, &term->first, &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); flecs_query_compile_term_ref(world, query, &op, &term->second, &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); flecs_query_compile_term_ref(world, query, &op, &term->src, &op.src, EcsQuerySrc, EcsVarAny, ctx, true); bool src_written = true; if (src_is_var) { src_is_lookup = query->vars[op.src.var].lookup != NULL; src_written = flecs_query_is_written(op.src.var, ctx->written); } /* Insert each instructions for table -> entity variable if needed */ bool first_written, second_written; if (flecs_query_compile_ensure_vars( query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) { goto error; } if (flecs_query_compile_ensure_vars( query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) { goto error; } /* Store write state of variables for first OR term in chain which will get * restored for the other terms in the chain, so that all OR terms make the * same assumptions about which variables were already written. */ if (first_or) { ctx->ctrlflow->written_or = ctx->written; } /* If an optional or not term is inserted for a source that's not been * written to yet, insert instruction that selects all entities so we have * something to match the optional/not against. */ if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { src_written = flecs_query_select_all(q, term, &op, op.src.var, ctx); } /* A bit of special logic for OR expressions and equality predicates. If the * left-hand of an equality operator is a table, and there are multiple * operators in an Or expression, the Or chain should match all entities in * the table that match the right hand sides of the operator expressions. * For this to work, the src variable needs to be resolved as entity, as an * Or chain would otherwise only yield the first match from a table. */ if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { if (query->vars[op.src.var].kind == EcsVarTable) { flecs_query_compile_term_ref(world, query, &op, &term->src, &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); ctx->ctrlflow->written_or |= (1llu << op.src.var); } } if (flecs_query_compile_ensure_vars( query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) { goto error; } /* If source is Any (_) and first and/or second are unconstrained, insert an * ids instruction instead of an And */ if (term->flags_ & EcsTermMatchAnySrc) { op.kind = EcsQueryIds; /* Use up-to-date written values after potentially inserting each */ if (!first_written || !second_written) { if (!first_written) { ecs_entity_t tgt = ECS_PAIR_SECOND(term->id); if (ECS_TERM_REF_ID(&term->first) != EcsAny) { /* First is enumerable (variable or wildcard) */ if (ECS_IS_PAIR(term->id) && tgt != EcsWildcard && tgt != EcsAny) { /* If target is known, traverse left: <- (*, t) */ op.kind = EcsQueryIdsLeft; } else { /* Find all ids in use that match the wildcard. */ op.kind = EcsQueryIdsAll; } } else if (ECS_IS_PAIR(term->id) && tgt == EcsWildcard) { /* First is Any (_) and target is enumerable: find all * targets in use that match (*, t). */ op.kind = EcsQueryIdsAll; } } else { /* If second is wildcard, traverse right: (r, *) -> */ if (ECS_TERM_REF_ID(&term->second) != EcsAny && ECS_IS_PAIR(term->id)) { op.kind = EcsQueryIdsRight; } } op.src.entity = 0; src_is_var = false; op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); } /* If source variable is not written and we're querying just for Any, insert * a dedicated instruction that uses the Any record in the id index. Any * queries that are evaluated against written sources can use Wildcard * records, which is what the AndAny instruction does. */ } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { /* Lookup variables ($var.child_name) are always written */ if (!src_is_lookup) { op.kind = EcsQueryAll; /* Uses Any (_) component record */ } } /* If this is a transitive term and both the target and source are unknown, * find the targets for the relationship first. This clusters together * tables for the same target, which allows for more efficient usage of the * traversal caches. */ if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { if (!src_written && !second_written) { flecs_query_insert_unconstrained_transitive( query, &op, ctx, cond_write); } } /* Check if this term has variables that have been conditionally written, * like variables written by an optional term. */ if (ctx->cond_written) { if (!is_or || first_or) { flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); } } /* If term can toggle and is Not, change operator to Optional as we * have to match entities that have the component but disabled. */ if (toggle_term && is_not) { is_not = false; is_optional = true; } /* Handle Not, Optional, Or operators */ if (is_not) { flecs_query_begin_block(EcsQueryNot, ctx); } else if (is_optional) { flecs_query_begin_block(EcsQueryOptional, ctx); } else if (first_or) { flecs_query_begin_block_or(&op, term, ctx); } /* If term has component inheritance enabled, insert instruction to walk * down the relationship tree of the id. */ if (term->flags_ & EcsTermIdInherited) { flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); } op.match_flags = term->flags_; ecs_write_flags_t write_state = ctx->written; if (first_is_var) { op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); op.flags |= (EcsQueryIsVar << EcsQueryFirst); } if (term->src.id & EcsSelf) { op.flags |= EcsQueryIsSelf; } /* Insert instructions for lookup variables */ if (first_is_var) { if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { write_state |= (1ull << op.first.var); // lookups are resolved inline } } if (src_is_var) { if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { write_state |= (1ull << op.src.var); // lookups are resolved inline } } if (second_is_var) { if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { write_state |= (1ull << op.second.var); // lookups are resolved inline } } if (builtin_pred) { if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { goto error; } } /* If we're writing the $this variable, filter out disabled/prefab entities * unless the query explicitly matches them. * This could've been done with regular With instructions, but since * filtering out disabled/prefab entities is the default and this check is * cheap to perform on table flags, it's worth special casing. */ if (!src_written && op.src.var == 0) { op.other = flecs_itolbl(flecs_query_to_table_flags(q)); } /* After evaluating a term, a used variable is always written */ if (src_is_var) { flecs_query_write(op.src.var, &op.written); flecs_query_write_ctx(op.src.var, ctx, cond_write); } if (first_is_var) { flecs_query_write(op.first.var, &op.written); flecs_query_write_ctx(op.first.var, ctx, cond_write); } if (second_is_var) { flecs_query_write(op.second.var, &op.written); flecs_query_write_ctx(op.second.var, ctx, cond_write); } flecs_query_op_insert(&op, ctx); ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); if (is_or && !member_term) { flecs_query_mark_last_or_op(ctx); } /* Handle self-references between src and first/second variables */ if (src_is_var) { if (first_is_var) { flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); } if (second_is_var && op.first.var != op.second.var) { flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); } } /* Handle self references between first and second variables */ if (!ecs_id_is_wildcard(first_id)) { if (first_is_var && !first_written && (op.first.var == op.second.var)) { flecs_query_insert_pair_eq(term->field_index, ctx); } } /* Handle closing of Not, Optional and Or operators */ if (is_not) { flecs_query_end_block(ctx, true); } else if (is_optional) { flecs_query_end_block(ctx, true); } /* Now that the term is resolved, evaluate member of component */ if (member_term) { flecs_query_compile_end_member_term(world, query, &op, term, ctx, term_id, first_id, second_id, cond_write); if (is_or) { flecs_query_mark_last_or_op(ctx); } } if (last_or) { flecs_query_end_block_or(query, ctx); } /* Handle closing of conditional evaluation */ if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { if (!is_or || last_or) { flecs_query_end_block_cond_eval(ctx); } } /* Ensure that term id is set after evaluating Not */ if (term->flags_ & EcsTermIdInherited) { if (is_not) { ecs_query_op_t set_id = {0}; set_id.kind = EcsQuerySetId; set_id.first.entity = term->id; set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); set_id.field_index = flecs_ito(int8_t, term->field_index); flecs_query_op_insert(&set_id, ctx); } } return 0; error: return -1; } // #define FLECS_QUERY_TRACE #ifdef FLECS_QUERY_TRACE static int flecs_query_trace_indent = 0; #endif static bool flecs_query_dispatch( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_select_w_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_id_t id, ecs_flags32_t filter_mask) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_component_record_t *cr = op_ctx->cr; const ecs_table_record_t *tr; ecs_table_t *table; if (!redo) { if (!cr || cr->id != id) { cr = op_ctx->cr = flecs_components_get(ctx->world, id); if (!cr) { return false; } } if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_all_iter(&cr->cache, &op_ctx->it)) { return false; } } else { if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it)) { return false; } } } repeat: if (!redo || (op_ctx->remaining <= 0)) { tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); if (!tr) { return false; } op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); table = tr->hdr.table; flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); } else { 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, cr->id, op_ctx->column); op_ctx->remaining --; } if (flecs_query_table_filter(table, op->other, filter_mask)) { goto repeat; } flecs_query_set_match(op, table, op_ctx->column, ctx); return true; } bool flecs_query_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_id_t id = 0; if (!redo) { id = flecs_query_op_get_id(op, ctx); } return flecs_query_select_w_id(op, redo, ctx, id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } bool flecs_query_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_component_record_t *cr = op_ctx->cr; const ecs_table_record_t *tr; ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); if (!table) { return false; } if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (!cr || cr->id != id) { cr = op_ctx->cr = flecs_components_get(ctx->world, id); if (!cr) { return false; } } tr = flecs_component_get_table(cr, table); if (!tr) { return false; } op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count); op_ctx->it.cur = &tr->hdr; } else { ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); if (--op_ctx->remaining <= 0) { return false; } op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } flecs_query_set_match(op, table, op_ctx->column, ctx); 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.cr = NULL; if (field_index != -1) { it->ids[field_index] = EcsWildcard; flecs_query_it_set_tr(it, 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; } } bool flecs_query_and( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_with(op, redo, ctx); } else { return flecs_query_select(op, redo, ctx); } } bool flecs_query_select_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_filter) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; int8_t field = op->field_index; ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); if (!redo) { ecs_id_t id = it->ids[field]; ecs_component_record_t *cr = op_ctx->cr; if (!cr || cr->id != id) { cr = op_ctx->cr = flecs_components_get(ctx->world, id); if (!cr) { return false; } } if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_all_iter(&cr->cache, &op_ctx->it)) { return false; } } else { if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it)) { return false; } } } repeat: {} const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (flecs_query_table_filter(table, op->other, table_filter)) { goto repeat; } flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); flecs_query_it_set_tr(it, field, tr); return true; } bool flecs_query_and_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t match_flags = op->match_flags; if (redo) { if (match_flags & EcsTermMatchAnySrc) { return false; } } uint64_t written = ctx->written[ctx->op_index]; int32_t remaining = 1; bool result; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { result = flecs_query_with(op, redo, ctx); } else { result = flecs_query_select(op, redo, ctx); remaining = 0; } ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); if (match_flags & EcsTermMatchAny && op_ctx->remaining) { op_ctx->remaining = flecs_ito(int16_t, remaining); } int32_t field = op->field_index; if (field != -1) { ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); } flecs_query_it_set_tr(ctx->it, field, (const ecs_table_record_t*)op_ctx->it.cur); return result; } static bool flecs_query_and_wctgt( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); if (!redo) { op_ctx->non_fragmenting = false; } bool sparse_redo = true; if (!op_ctx->non_fragmenting) { bool result = flecs_query_and(op, redo, ctx); if (result) { return true; } ecs_component_record_t *cr = op_ctx->cr; if (!cr) { return false; } if (!(cr->flags & EcsIdMatchDontFragment) && (cr->id != ecs_pair(EcsWildcard, EcsWildcard))) { return false; } op_ctx->non_fragmenting = true; sparse_redo = false; } return flecs_query_sparse(op, sparse_redo, ctx); } static bool flecs_query_with_wctgt( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); if (!redo) { op_ctx->non_fragmenting = false; } bool sparse_redo = true; if (!op_ctx->non_fragmenting) { bool result = flecs_query_with(op, redo, ctx); if (result) { return true; } if (!op_ctx->cr) { return false; } if (!(op_ctx->cr->flags & EcsIdMatchDontFragment)) { return false; } op_ctx->non_fragmenting = true; sparse_redo = false; } return flecs_query_sparse_with(op, sparse_redo, ctx, false); } static bool flecs_query_triv( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); ecs_flags64_t termset = op->src.entity; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { flecs_query_set_iter_this(ctx->it, ctx); return flecs_query_trivial_test(ctx, redo, termset); } else { return flecs_query_trivial_search(ctx, op_ctx, redo, termset); } } static bool flecs_query_cache( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { (void)op; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_query_cache_test(ctx, redo); } else { return flecs_query_cache_search(ctx, redo); } } static bool flecs_query_is_cache( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { (void)op; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_query_is_cache_test(ctx, redo); } else { return flecs_query_is_cache_search(ctx, redo); } } static int32_t flecs_query_next_inheritable_id( ecs_world_t *world, ecs_type_t *type, int32_t index) { int32_t i; for (i = index; i < type->count; i ++) { ecs_component_record_t *cr = flecs_components_get(world, type->array[i]); if (!(cr->flags & EcsIdOnInstantiateDontInherit)) { return i; } } return -1; } static bool flecs_query_x_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_oper_kind_t oper) { ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); ecs_world_t *world = ctx->world; ecs_type_t *type; ecs_entity_t type_id; int32_t i; if (!redo) { /* Find entity that acts as the template from which we match the ids */ type_id = flecs_query_op_get_id(op, ctx); 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_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; op_ctx->first_id_index = flecs_query_next_inheritable_id( world, type, 0); op_ctx->cur_id_index = op_ctx->first_id_index; if (op_ctx->cur_id_index == -1) { return false; /* No ids to filter on */ } } else { type_id = op_ctx->type_id; type = op_ctx->type; } ecs_id_t *ids = type->array; /* Check if source is variable, and if it's already written */ bool src_written = true; if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { uint64_t written = ctx->written[ctx->op_index]; src_written = written & (1ull << op->src.var); } do { int32_t id_index = op_ctx->cur_id_index; /* If source is not yet written, find tables with first id */ if (!src_written) { ecs_entity_t first_id = ids[id_index]; if (!flecs_query_select_w_id(op, redo, ctx, first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { if (oper == EcsOrFrom) { id_index = flecs_query_next_inheritable_id( world, type, id_index + 1); if (id_index != -1) { op_ctx->cur_id_index = id_index; redo = false; continue; } } return false; } id_index ++; /* First id got matched */ } else if (redo && src_written) { return false; } ecs_table_t *src_table = flecs_query_get_table( op, &op->src, EcsQuerySrc, ctx); if (!src_table) { continue; } redo = true; if (!src_written && oper == EcsOrFrom) { /* Eliminate duplicate matches from tables that have multiple * 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_component_record_t *cr = flecs_components_get(world, ids[i]); if (!cr) { continue; } if (cr->flags & EcsIdOnInstantiateDontInherit) { continue; } if (flecs_component_get_table(cr, src_table) != NULL) { /* Already matched */ break; } } if (i != op_ctx->cur_id_index) { continue; } } goto match; } if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { for (i = id_index; i < type->count; i ++) { ecs_component_record_t *cr = flecs_components_get(world, ids[i]); if (!cr) { if (oper == EcsAndFrom) { return false; } else { continue; } } if (cr->flags & EcsIdOnInstantiateDontInherit) { continue; } if (flecs_component_get_table(cr, src_table) == NULL) { if (oper == EcsAndFrom) { break; /* Must have all ids */ } } else { if (oper == EcsNotFrom) { break; /* Must have none of the ids */ } else if (oper == EcsOrFrom) { goto match; /* Single match is enough */ } } } if (i == type->count) { if (oper == EcsAndFrom || oper == EcsNotFrom) { break; /* All ids matched */ } } } } while (true); match: ctx->it->ids[op->field_index] = type_id; return true; } static bool flecs_query_and_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsAndFrom); } static bool flecs_query_not_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsNotFrom); } static bool flecs_query_or_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsOrFrom); } static bool flecs_query_ids_check( ecs_component_record_t *cur) { if (!cur->cache.tables.count) { if (!(cur->flags & EcsIdOrderedChildren)) { return false; } ecs_assert(cur->pair != NULL, ECS_INTERNAL_ERROR, NULL); if (!ecs_vec_count(&cur->pair->ordered_children)) { return false; } } return true; } static bool flecs_query_ids_in_use( ecs_component_record_t *cur) { ecs_table_cache_iter_t it; flecs_table_cache_iter(&cur->cache, &it); if (flecs_table_cache_next(&it, ecs_table_record_t)) { return true; } if (cur->sparse && flecs_sparse_count(cur->sparse)) { return true; } if (cur->flags & EcsIdOrderedChildren) { ecs_assert(cur->pair != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_vec_count(&cur->pair->ordered_children)) { return true; } } return false; } static bool flecs_query_ids( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_component_record_t *cur; ecs_id_t id = flecs_query_op_get_id(op, ctx); { cur = flecs_components_get(ctx->world, id); if (!cur) { return false; } if (!flecs_query_ids_check(cur)) { return false; } } flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; flecs_query_it_set_tr(it, op->field_index, NULL); /* Mark field as set */ } return true; } static bool flecs_query_idsright( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_component_record_t *cur; ecs_iter_t *it = ctx->it; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); cur = op_ctx->cur = flecs_components_get(ctx->world, id); if (!ecs_id_is_wildcard(id)) { /* If id is not a wildcard, we can directly return it. This can * happen if a variable was constrained by an iterator. */ op_ctx->cur = NULL; if (!cur) { return false; } flecs_query_set_vars(op, id, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); return flecs_query_ids_check(cur); } if (!cur) { return false; } } else { if (!op_ctx->cur) { return false; } } next: do { cur = op_ctx->cur = flecs_component_first_next(op_ctx->cur); } while (cur && !flecs_query_ids_in_use(cur)); /* 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) { ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } return true; } static bool flecs_query_idsleft( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_component_record_t *cur; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (!ecs_id_is_wildcard(id)) { /* If id is not a wildcard, we can directly return it. This can * happen if a variable was constrained by an iterator. */ op_ctx->cur = NULL; flecs_query_set_vars(op, id, ctx); return true; } cur = op_ctx->cur = flecs_components_get(ctx->world, id); if (!cur) { return false; } } else { if (!op_ctx->cur) { return false; } } do { cur = op_ctx->cur = flecs_component_second_next(op_ctx->cur); } while (cur && !flecs_query_ids_in_use(cur)); /* Skip empty ids */ if (!cur) { return false; } flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } return true; } static bool flecs_query_idsall_match( ecs_component_record_t *cur, bool want_pair, bool collapse_first, bool collapse_second) { ecs_id_t id = cur->id; if (!flecs_query_ids_in_use(cur)) { return false; } if (ECS_IS_PAIR(id) != want_pair) { return false; } if (!want_pair) { return !ecs_id_is_wildcard(id); } if (id == ecs_pair(EcsChildOf, 0)) { return false; } /* An Any element (_) is collapsed to the (R, *) or (*, T) wildcard record, * a variable or wildcard element is enumerated as a concrete id. */ bool first_wildcard = ECS_PAIR_FIRST(id) == EcsWildcard; bool second_wildcard = ECS_PAIR_SECOND(id) == EcsWildcard; if (collapse_first != first_wildcard) { return false; } if (collapse_second != second_wildcard) { return false; } return true; } static bool flecs_query_idsall( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_iter_t *it = ctx->it; ecs_component_record_t *cur; bool want_pair = ECS_IS_PAIR(flecs_query_op_get_id(op, ctx)); bool collapse_first = false, collapse_second = false; if (want_pair) { const ecs_term_t *term = &ctx->query->pub.terms[op->term_index]; collapse_first = ECS_TERM_REF_ID(&term->first) == EcsAny; collapse_second = ECS_TERM_REF_ID(&term->second) == EcsAny; } if (!redo) { op_ctx->components_it = (ecs_components_iter_t){0}; } do { cur = flecs_components_next(ctx->world, &op_ctx->components_it); if (!cur) { return false; } } while (!flecs_query_idsall_match( cur, want_pair, collapse_first, collapse_second)); flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { it->ids[op->field_index] = cur->id; it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } return true; } static bool flecs_query_each( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); int32_t row; ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); ecs_table_t *table = range.table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!redo) { if (!ecs_table_count(table)) { return false; } row = op_ctx->row = range.offset; } else { int32_t end = range.count; if (end) { end += range.offset; } else { end = ecs_table_count(table); } row = ++ op_ctx->row; if (op_ctx->row >= end) { return false; } } ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); const ecs_entity_t *entities = ecs_table_entities(table); flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); return true; } static bool flecs_query_store( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (!redo) { flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); return true; } else { return false; } } static bool flecs_query_reset( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (!redo) { return true; } else { flecs_query_var_reset(op->src.var, ctx); return false; } } static bool flecs_query_lookup( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } const ecs_query_impl_t *query = ctx->query; ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); ecs_query_var_t *var = &query->vars[op->src.var]; ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, NULL, NULL, false); if (!result) { flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); return false; } flecs_query_var_set_entity(op, op->src.var, result, ctx); return true; } static bool flecs_query_setvars( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; ecs_var_id_t *src_vars = query->src_vars; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; ecs_flags32_t up_fields = it->up_fields; for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = src_vars[i]; if (!var_id) { continue; } if (up_fields & (1u << i)) { continue; } it->sources[i] = flecs_query_var_get_entity(var_id, ctx); ECS_CONST_CAST(int16_t*, it->columns)[i] = -1; } return true; } static bool flecs_query_setthis( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); ecs_var_t *vars = ctx->vars; ecs_var_t *this_var = &vars[op->first.var]; if (!redo) { /* Save values so we can restore them later */ op_ctx->range = vars[0].range; /* Constrain This table variable to a single entity from the table */ vars[0].range = flecs_range_from_entity(ctx->world, this_var->entity); vars[0].entity = this_var->entity; return true; } else { /* Restore previous values, so that instructions that are operating on * the table variable use all the entities in the table. */ vars[0].range = op_ctx->range; vars[0].entity = 0; return false; } } static bool flecs_query_setfixed( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; for (i = 0; i < q->term_count; i ++) { const ecs_term_t *term = &q->terms[i]; const ecs_term_ref_t *src = &term->src; if (src->id & EcsIsEntity) { it->sources[term->field_index] = ECS_TERM_REF_ID(src); ECS_CONST_CAST(int16_t*, it->columns)[term->field_index] = -1; } } return true; } bool flecs_query_setids( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; for (i = 0; i < q->term_count; i ++) { const ecs_term_t *term = &q->terms[i]; it->ids[term->field_index] = term->id; } return true; } static bool flecs_query_setid( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); ctx->it->ids[op->field_index] = op->first.entity; return true; } /* Check if entity is stored in table */ static bool flecs_query_contain( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_var_id_t src_id = op->src.var; ecs_var_id_t first_id = op->first.var; ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); return table == ecs_get_table(ctx->world, e); } /* Check if first and second id of pair from last operation are the same */ static bool flecs_query_pair_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_iter_t *it = ctx->it; ecs_id_t id = it->ids[op->field_index]; return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); } static void flecs_query_reset_after_block( const ecs_query_op_t *start_op, ecs_query_run_ctx_t *ctx, ecs_query_ctrl_ctx_t *op_ctx, bool result) { ecs_query_lbl_t op_index = start_op->next; const ecs_query_op_t *op = &ctx->qit->ops[op_index]; int32_t field = op->field_index; if (field == -1) { goto done; } /* Set/unset field */ ecs_iter_t *it = ctx->it; if (result) { ECS_TERMSET_SET(it->set_fields, 1u << field); return; } /* Reset state after a field was not matched */ ctx->written[op_index] = ctx->written[ctx->op_index]; ctx->op_index = op_index; ECS_TERMSET_CLEAR(it->set_fields, 1u << field); /* Ignore variables written by Not operation */ uint64_t *written = ctx->written; uint64_t written_cur = written[op->prev + 1]; ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); /* Overwrite id with cleared out variables */ ecs_id_t id = flecs_query_op_get_id(op, ctx); if (id) { it->ids[field] = id; } flecs_query_it_set_tr(it, field, NULL); /* Reset variables */ if (flags_1st & EcsQueryIsVar) { if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ flecs_query_var_reset(op->first.var, ctx); } } if (flags_2nd & EcsQueryIsVar) { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ flecs_query_var_reset(op->second.var, ctx); } } /* If term has entity src, set it because no other instruction might */ if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { it->sources[field] = op->src.entity; ECS_CONST_CAST(int16_t*, it->columns)[field] = -1; } done: op_ctx->op_index = op_index; } static bool flecs_query_run_block( bool redo, ecs_query_run_ctx_t *ctx, ecs_query_ctrl_ctx_t *op_ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; if (!redo) { op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { return false; } ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; bool result = flecs_query_run_until( redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); return result; } static ecs_query_lbl_t flecs_query_last_op_for_or_cond( const ecs_query_op_t *ops, ecs_query_lbl_t cur, ecs_query_lbl_t last) { const ecs_query_op_t *cur_op, *last_op = &ops[last]; do { cur_op = &ops[cur]; cur ++; } while (cur_op->next != last && cur_op != last_op); return cur; } static bool flecs_query_run_until_for_select_or( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last) { ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( ops, cur, flecs_itolbl(last)); if (redo) { /* If redoing, start from the last instruction of the last executed * sequence */ cur = flecs_itolbl(last_for_cur - 1); } flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); #ifdef FLECS_QUERY_TRACE printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", ctx->op_index == last ? "true" : "false"); #endif return ctx->op_index == last; } static bool flecs_query_select_or( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); if (!redo) { op_ctx->op_index = first; } const ecs_query_op_t *ops = qit->ops; const ecs_query_op_t *first_op = &ops[first - 1]; ecs_query_lbl_t last = first_op->next; const ecs_query_op_t *last_op = &ops[last]; const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; bool result = false; do { ecs_query_lbl_t cur = op_ctx->op_index; ctx->op_index = cur; ctx->written[cur] = ctx->written[first - 1] | op->written; result = flecs_query_run_until_for_select_or( redo, ctx, ops, flecs_itolbl(first - 1), cur, last); if (result) { if (first == cur) { break; } /* If a previous operation in the OR chain returned a result for the * same matched source, skip it so we don't yield for each matching * element in the chain. */ /* Copy written status so that the variables we just wrote will show * up as written for the test. This ensures that the instructions * match against the result we already found, vs. starting a new * search (the difference between select & with). */ ecs_query_lbl_t prev = first; bool dup_found = false; /* While terms of an OR chain always operate on the same source, it * is possible that a table variable is resolved to an entity * variable. When checking for duplicates, copy the entity variable * to the table, to ensure we're only testing the found entity. */ const ecs_query_op_t *prev_op = &ops[cur - 1]; ecs_var_t old_table_var; ecs_os_memset_t(&old_table_var, 0, ecs_var_t); bool restore_table_var = false; if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { if (first_op->src.var != prev_op->src.var) { restore_table_var = true; old_table_var = ctx->vars[first_op->src.var]; ctx->vars[first_op->src.var] = ctx->vars[prev_op->src.var]; } } int16_t field_index = op->field_index; ecs_id_t prev_id = it->ids[field_index]; const ecs_table_record_t *prev_tr = it->trs[field_index]; int16_t prev_column = it->columns[field_index]; do { ctx->written[prev] = ctx->written[last]; ecs_query_op_ctx_t *op_ctx_ptr = &ctx->op_ctx[first]; ecs_query_op_ctx_t tmp_op_ctx = *op_ctx_ptr; ecs_os_zeromem(op_ctx_ptr); flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), prev, cur); flecs_query_op_ctx_fini(ctx->it, &ops[first], op_ctx_ptr); ecs_os_memcpy_t(op_ctx_ptr, &tmp_op_ctx, ecs_query_op_ctx_t); if (ctx->op_index == last) { /* Duplicate match was found, find next result */ redo = true; dup_found = true; break; } break; } while (true); /* Restore table variable to full range for next result */ if (restore_table_var) { ctx->vars[first_op->src.var] = old_table_var; } if (dup_found) { continue; } /* Restore id in case op set it */ it->ids[field_index] = prev_id; it->trs[field_index] = prev_tr; ECS_CONST_CAST(int16_t*, it->columns)[field_index] = prev_column; break; } /* No result was found, go to next operation in chain */ op_ctx->op_index = flecs_query_last_op_for_or_cond( ops, op_ctx->op_index, last); cur_op = &qit->ops[op_ctx->op_index]; redo = false; } while (cur_op != last_op); return result; } static bool flecs_query_with_or( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); bool result = flecs_query_run_block(redo, ctx, op_ctx); if (result) { /* If a match was found, no need to keep searching for this source */ op_ctx->op_index = op->next; } return result; } static bool flecs_query_or( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { uint64_t written = ctx->written[ctx->op_index]; if (!(written & (1ull << op->src.var))) { return flecs_query_select_or(op, redo, ctx); } } return flecs_query_with_or(op, redo, ctx); } static bool flecs_query_run_block_w_reset( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); bool result = flecs_query_run_block(redo, ctx, op_ctx); flecs_query_reset_after_block(op, ctx, op_ctx, result); return result; } static bool flecs_query_not( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } return !flecs_query_run_block_w_reset(op, redo, ctx); } static bool flecs_query_optional( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { bool result = flecs_query_run_block_w_reset(op, redo, ctx); ecs_query_optional_ctx_t *op_ctx = flecs_op_ctx(ctx, optional); if (!redo) { op_ctx->range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); return true; /* Return at least once */ } else { if (!result) { ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (range.offset != op_ctx->range.offset) { /* Different range is returned, so yield again. */ result = true; op_ctx->range = range; } } return result; } } static bool flecs_query_eval_if( const ecs_query_op_t *op, ecs_query_run_ctx_t *ctx, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind) { bool result = true; if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { result = ctx->vars[ref->var].entity != EcsWildcard; ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); flecs_query_reset_after_block(op, ctx, op_ctx, result); return result; } return true; } static bool flecs_query_if_var( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (!redo) { if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) { return true; } } ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); return flecs_query_run_block(redo, ctx, op_ctx); } static bool flecs_query_if_set( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; int8_t field_index = flecs_ito(int8_t, op->other); ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); if (!redo) { op_ctx->is_set = ecs_field_is_set(it, field_index); } if (!op_ctx->is_set) { return !redo; } return flecs_query_run_block(redo, ctx, op_ctx); } static bool flecs_query_end( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; (void)ctx; return !redo; } static bool flecs_query_dispatch( const ecs_query_op_t *op, bool redo, 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 EcsQueryAndWcTgt: return flecs_query_and_wctgt(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 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); case EcsQueryWithWcTgt: return flecs_query_with_wctgt(op, redo, ctx); case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); case EcsQueryIds: return flecs_query_ids(op, redo, ctx); case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); case EcsQueryIdsAll: return flecs_query_idsall(op, redo, ctx); case EcsQueryEach: return flecs_query_each(op, redo, ctx); case EcsQueryStore: return flecs_query_store(op, redo, ctx); case EcsQueryReset: return flecs_query_reset(op, redo, ctx); case EcsQueryOr: return flecs_query_or(op, redo, ctx); case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); case EcsQueryEnd: return flecs_query_end(op, redo, ctx); case EcsQueryNot: return flecs_query_not(op, redo, ctx); case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); case EcsQuerySparse: return flecs_query_sparse(op, redo, ctx); case EcsQuerySparseWith: return flecs_query_sparse_with(op, redo, ctx, false); case EcsQuerySparseNot: return flecs_query_sparse_with(op, redo, ctx, true); case EcsQuerySparseSelfUp: return flecs_query_sparse_self_up(op, redo, ctx); case EcsQuerySparseUp: return flecs_query_sparse_up(op, redo, ctx); case EcsQueryTree: return flecs_query_tree_and(op, redo, ctx); case EcsQueryTreeWildcard: return flecs_query_tree_and_wildcard(op, redo, ctx, false); case EcsQueryTreePre: return flecs_query_tree_pre(op, redo, ctx); case EcsQueryTreePost: return flecs_query_tree_post(op, redo, ctx); case EcsQueryTreeUpPre: return flecs_query_tree_up_pre(op, redo, ctx, false); case EcsQueryTreeSelfUpPre: return flecs_query_tree_up_pre(op, redo, ctx, true); case EcsQueryTreeUpPost: return flecs_query_tree_up_post(op, redo, ctx, false); case EcsQueryTreeSelfUpPost: return flecs_query_tree_up_post(op, redo, ctx, true); case EcsQueryChildrenWc: return flecs_query_tree_and_wildcard(op, redo, ctx, true); case EcsQueryTreeWith: return flecs_query_tree_with(op, redo, ctx); case EcsQueryChildren: return flecs_query_children(op, redo, ctx); case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); case EcsQueryContain: return flecs_query_contain(op, redo, ctx); case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); case EcsQueryYield: return false; case EcsQueryNothing: return false; } return false; } bool flecs_query_run_until( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last) { ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); ctx->op_index = cur; const ecs_query_op_t *op = &ops[ctx->op_index]; const ecs_query_op_t *last_op = &ops[last]; ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_QUERY_TRACE printf("%*sblock:\n", flecs_query_trace_indent*2, ""); flecs_query_trace_indent ++; #endif do { #ifdef FLECS_DEBUG ctx->qit->profile[ctx->op_index].count[redo] ++; #endif #ifdef FLECS_QUERY_TRACE printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", ctx->op_index, flecs_query_op_str(op->kind)); #endif bool result = flecs_query_dispatch(op, redo, ctx); cur = (&op->prev)[result]; redo = cur < ctx->op_index; if (!redo) { ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; } ctx->op_index = cur; op = &ops[ctx->op_index]; if (cur <= first) { #ifdef FLECS_QUERY_TRACE printf("%*sfalse\n", flecs_query_trace_indent*2, ""); flecs_query_trace_indent --; #endif return false; } } while (op < last_op); #ifdef FLECS_QUERY_TRACE printf("%*strue\n", flecs_query_trace_indent*2, ""); flecs_query_trace_indent --; #endif return true; } static void flecs_query_iter_run_ctx_init( ecs_iter_t *it, ecs_query_run_ctx_t *ctx) { ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); ctx->world = it->real_world; ctx->query = impl; ctx->it = it; ctx->vars = qit->vars; ctx->query_vars = qit->query_vars; ctx->written = qit->written; ctx->op_ctx = qit->op_ctx; ctx->qit = qit; } void flecs_query_iter_constrain( ecs_iter_t *it) { ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_query_impl_t *query = ctx.query; const ecs_query_t *q = &query->pub; ecs_flags64_t it_written = it->constrained_vars; ctx.written[0] = it_written; if (it_written && ctx.query->src_vars) { /* If variables were constrained, check if there are any table * variables that have a constrained entity variable. */ ecs_var_t *vars = ctx.vars; int32_t i, count = q->field_count; for (i = 0; i < count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; ecs_query_var_t *var = &query->vars[var_id]; if (!(it_written & (1ull << var_id)) || (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) { continue; } /* Initialize table variable with constrained entity variable */ ecs_var_t *tvar = &vars[var->table_id]; tvar->range = flecs_range_from_entity(ctx.world, vars[var_id].entity); ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ } } /* This function can be called multiple times when setting variables, so * reset flags before setting them. */ it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| EcsIterTrivialSearch); /* Figure out whether this query can utilize specialized iterator modes for * improved performance. */ ecs_flags32_t flags = q->flags; ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; ecs_query_cache_t *cache = query->cache; if (it_written) { /* When we're testing against an entity or table, set the $this * variable in advance since it won't change later on. This * initializes it.count, it.entities and it.table. */ flecs_query_set_iter_this(it, &ctx); if (!cache) { if ((flags & (trivial_flags)) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialTest; flecs_query_setids(NULL, false, &ctx); } } } else if (flags & EcsQueryIsCacheable) { if (!query->ops) { if (!cache->order_by_callback && (cache->query->flags & EcsQueryTrivialCache && !(query->pub.flags & EcsQueryHasChangeDetection))) { it->flags |= EcsIterTrivialTest|EcsIterTrivialCached| EcsIterTrivialChangeDetection; it->ids = cache->query->ids; it->sources = cache->sources; it->set_fields = flecs_uto(uint32_t, (1llu << it->field_count) - 1); } else { it->flags |= EcsIterTrivialTest|EcsIterCached; } } } } else { if (!cache) { if ((flags & (trivial_flags)) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialSearch| EcsIterTrivialChangeDetection; flecs_query_setids(NULL, false, &ctx); } } } else if (flags & EcsQueryIsCacheable) { if (!query->ops) { if (!cache->order_by_callback && (cache->query->flags & EcsQueryTrivialCache && !(query->pub.flags & EcsQueryHasChangeDetection))) { it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached| EcsIterTrivialChangeDetection; it->ids = cache->query->ids; it->sources = cache->sources; it->set_fields = flecs_uto(uint32_t, (1llu << it->field_count) - 1); } else { it->flags |= EcsIterTrivialSearch|EcsIterCached; } } } } } static void flecs_query_change_detection( ecs_iter_t *it, ecs_query_iter_t *qit, ecs_query_impl_t *impl) { /* Change detection */ if (!(it->flags & EcsIterSkip)) { /* Mark table columns that are written to dirty */ flecs_query_mark_fields_dirty(impl, it); if (qit->elem) { if (impl->pub.flags & EcsQueryHasChangeDetection) { /* If this query uses change detection, synchronize the * monitor for the iterated table with the query */ flecs_query_sync_match_monitor(impl, qit->elem); } } } } static void flecs_query_self_change_detection( ecs_iter_t *it, ecs_query_iter_t *qit, ecs_query_impl_t *impl) { if (!it->table->dirty_state) { return; } flecs_query_change_detection(it, qit, impl); } #ifdef FLECS_DEBUG static void flecs_iter_assert_columns( ecs_iter_t *it) { const int16_t *columns = it->columns; const ecs_table_record_t **trs = it->trs; const ecs_entity_t *sources = it->sources; ecs_termset_t up_fields = it->up_fields; int8_t i, count = it->field_count; for (i = 0; i < count; i ++) { const ecs_table_record_t *tr = trs[i]; int16_t expected = (tr && !sources[i] && !(up_fields & (1llu << i))) ? tr->column : -1; ecs_assert(columns[i] == expected, ECS_INTERNAL_ERROR, "it->columns[%d]=%d does not match expected %d " "(tr=%p src=%llu up=%d)", i, columns[i], expected, (const void*)tr, (unsigned long long)sources[i], (int)((up_fields >> i) & 1)); } } #endif bool ecs_query_next( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->next == ecs_query_next || it->next == flecs_query_trivial_cached_next || it->next == flecs_default_next_callback, ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); ecs_assert(impl != NULL, ECS_INVALID_OPERATION, "cannot call ecs_query_next on invalid iterator"); ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); bool redo = it->flags & EcsIterIsValid; if (redo) { if (it->flags & EcsIterTrivialChangeDetection) { flecs_query_self_change_detection(it, qit, impl); } else { flecs_query_change_detection(it, qit, impl); } } it->flags &= ~(EcsIterSkip); it->flags |= EcsIterIsValid; it->frame_offset += it->count; /* Specialized iterator modes. When a query doesn't use any advanced * features, it can call specialized iterator functions directly instead of * going through the dispatcher of the query engine. * The iterator mode is set during iterator initialization. Besides being * determined by the query, there are different modes for searching and * testing, where searching returns all matches for a query, whereas testing * tests a single table or table range against the query. */ if (it->flags & EcsIterTrivialCached) { /* Trivial cache iterator. Only supported for search */ ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (it->flags & EcsIterTrivialSearch) { if (flecs_query_is_trivial_cache_search(&ctx)) { return true; } } else if (it->flags & EcsIterTrivialTest) { if (flecs_query_is_trivial_cache_test(&ctx, redo)) { return true; } } } else if (it->flags & EcsIterCached) { /* Cached iterator modes */ if (it->flags & EcsIterTrivialSearch) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_is_cache_search(&ctx, redo)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialTest) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_is_cache_test(&ctx, redo)) { goto yield; } } } else { /* Uncached iterator modes */ if (it->flags & EcsIterTrivialSearch) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { goto yield; } } else if (it->flags & EcsIterTrivialTest) { ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); int32_t fields = ctx.query->pub.term_count; ecs_flags64_t mask = (2llu << (fields - 1)) - 1; if (flecs_query_trivial_test(&ctx, redo, mask)) { goto yield; } } else { const ecs_query_op_t *ops = qit->ops; /* Default iterator mode. This enters the query VM dispatch loop. */ if (flecs_query_run_until( redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) { ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, ECS_INTERNAL_ERROR, NULL); flecs_query_set_iter_this(it, &ctx); ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); qit->op = flecs_itolbl(ctx.op_index - 1); #ifdef FLECS_DEBUG flecs_iter_assert_columns(it); #endif goto yield; } } } /* Done iterating */ flecs_query_mark_fixed_fields_dirty(impl, it); if (ctx.query->monitor) { flecs_query_update_fixed_monitor( ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); } it->flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(it); ecs_os_linc(&it->real_world->info.queries_ran_total); return false; trivial_search_yield: it->table = ctx.vars[0].range.table; it->count = ecs_table_count(it->table); it->entities = ecs_table_entities(it->table); yield: return true; } bool flecs_query_trivial_cached_next( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); bool redo = it->flags & EcsIterIsValid; if (redo) { flecs_query_self_change_detection(it, qit, impl); } it->flags &= ~(EcsIterSkip); it->flags |= EcsIterIsValid; it->frame_offset += it->count; ecs_assert(it->flags & EcsIterTrivialCached, ECS_INVALID_OPERATION, "query does not have trivial cache, use ecs_query_next instead"); ecs_assert(it->flags & EcsIterTrivialSearch, ECS_INVALID_OPERATION, "iterator has constrained variables, use ecs_query_next instead"); ecs_assert(impl->ops == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_is_trivial_cache_search(&ctx)) { return true; } it->flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(it); ecs_os_linc(&it->real_world->info.queries_ran_total); return false; } void flecs_query_op_ctx_fini( ecs_iter_t *it, const ecs_query_op_t *op, ecs_query_op_ctx_t *ctx) { switch(op->kind) { case EcsQueryTrav: { ecs_allocator_t *a = flecs_query_get_allocator(it); flecs_query_trav_cache_fini(a, &ctx->is.trav.cache); break; } case EcsQueryUp: case EcsQuerySelfUp: case EcsQueryTreeUp: case EcsQueryTreeSelfUp: case EcsQueryTreeUpPre: case EcsQueryTreeSelfUpPre: case EcsQueryTreeUpPost: case EcsQueryTreeSelfUpPost: case EcsQuerySparseUp: case EcsQuerySparseSelfUp: { ecs_allocator_t *a = flecs_query_get_allocator(it); ecs_query_up_ctx_t *op_ctx = &ctx->is.up; ecs_query_up_impl_t *impl = op_ctx->impl; if (impl) { ecs_trav_up_cache_t *cache = &impl->cache; if (cache->dir == EcsTravDown) { flecs_query_down_cache_fini(a, cache); } else { flecs_query_up_cache_fini(cache); } flecs_free_t(a, ecs_query_up_impl_t, impl); } break; } default: break; } } static void flecs_query_iter_fini_ctx( ecs_iter_t *it, ecs_query_iter_t *qit) { const ecs_query_impl_t *query = flecs_query_impl(it->query); int32_t i, count = query->op_count; ecs_query_op_t *ops = query->ops; ecs_query_op_ctx_t *ctx = qit->op_ctx; for (i = 0; i < count; i ++) { ecs_query_op_t *op = &ops[i]; flecs_query_op_ctx_fini(it, op, &ctx[i]); } } static void flecs_query_iter_fini( ecs_iter_t *it) { ecs_query_iter_t *qit = &it->priv_.iter.query; const ecs_query_t *q = it->query; ecs_assert(q != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(q, ecs_query_t); int32_t op_count = flecs_query_impl(q)->op_count; int32_t var_count = flecs_query_impl(q)->var_count; if (it->flags & EcsIterIsValid) { flecs_query_change_detection(it, qit, flecs_query_impl(q)); } #ifdef FLECS_DEBUG if (it->flags & EcsIterProfile) { char *str = ecs_query_plan_w_profile(q, it); printf("%s\n", str); ecs_os_free(str); } flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); #endif flecs_query_iter_fini_ctx(it, qit); flecs_iter_free_n(qit->vars, ecs_var_t, var_count); flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); qit->vars = NULL; qit->written = NULL; qit->op_ctx = NULL; it->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->cr_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) { ecs_iter_t it = {0}; ecs_query_iter_t *qit = &it.priv_.iter.query; ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_DEBUG flecs_check_exclusive_world_access_write(q->real_world); #endif flecs_query_validate_final_fields(q); flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); int32_t i, var_count = impl->var_count; int32_t op_count = impl->op_count ? impl->op_count : 1; it.world = ECS_CONST_CAST(ecs_world_t*, world); /* If world passed to iterator is the real world, but query was created from * a stage, stage takes precedence. */ if (flecs_poly_is(it.world, ecs_world_t) && flecs_poly_is(q->world, ecs_stage_t)) { it.world = ECS_CONST_CAST(ecs_world_t*, q->world); } it.real_world = q->real_world; ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || it.world != it.real_world, ECS_INVALID_PARAMETER, "create iterator for stage when world is in multithreaded mode"); it.query = q; it.system = q->entity; it.next = ecs_query_next; it.fini = flecs_query_iter_fini; it.field_count = q->field_count; it.sizes = q->sizes; it.set_fields = q->set_fields; it.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; it.row_fields = q->row_fields; it.up_fields = 0; flecs_query_apply_iter_flags(&it, q); bool fully_cached = (q->flags & EcsQueryIsCacheable) && !(q->flags & EcsQueryCacheWithFilter); flecs_iter_init(it.world, &it, !impl->cache || !fully_cached); qit->query_vars = impl->vars; qit->ops = impl->ops; if (q->flags & EcsQueryMatchEmptyTables) { it.flags |= EcsIterMatchEmptyTables; } flecs_query_cache_iter_init(&it, qit, impl); if (var_count) { qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } if (op_count) { qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); } #ifdef FLECS_DEBUG qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); #endif for (i = 1; i < var_count; i ++) { qit->vars[i].entity = EcsWildcard; } /* Set flags for unconstrained query iteration. Can be reinitialized when * variables are constrained on the iterator. */ flecs_query_iter_constrain(&it); error: return it; } int flecs_query_trivial_has_range( const ecs_query_t *q, ecs_iter_t *it, const ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count) { ecs_query_impl_t *impl = flecs_query_impl(q); ecs_flags32_t flags = q->flags; ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; if (impl->cache || ((flags & trivial_flags) != trivial_flags) || (flags & EcsQueryMatchWildcards) || q->row_fields) { return -1; } ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; if (table && ((offset + count) > ecs_table_count(table))) { return 0; } if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { return 0; } ecs_iter_t lit = {0}; lit.world = ECS_CONST_CAST(ecs_world_t*, world); lit.real_world = q->real_world; lit.query = q; lit.system = q->entity; lit.field_count = q->field_count; lit.sizes = q->sizes; lit.set_fields = q->set_fields; lit.table = table; lit.offset = offset; lit.count = count; flecs_iter_init(lit.world, &lit, true); lit.flags |= EcsIterIsValid; ecs_os_memcpy_n(ECS_CONST_CAST(ecs_id_t*, lit.ids), q->ids, ecs_id_t, q->field_count); const ecs_term_t *terms = q->terms; int16_t *columns = ECS_CONST_CAST(int16_t*, lit.columns); int32_t t, term_count = q->term_count; for (t = 0; t < term_count; t ++) { const ecs_term_t *term = &terms[t]; ecs_component_record_t *cr = flecs_components_get( lit.real_world, term->id); if (!cr) { goto no_match; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { goto no_match; } lit.trs[term->field_index] = tr; columns[term->field_index] = tr->column; } const ecs_entity_t *entities = ecs_table_entities(table); if (entities) { lit.entities = &entities[offset]; } *it = lit; return 1; no_match: lit.flags |= EcsIterSkip; ecs_iter_fini(&lit); return 0; } ecs_iter_t ecs_query_iter( const ecs_world_t *world, const ecs_query_t *q) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); /* Ok, only for stats */ ecs_os_linc(&ECS_CONST_CAST(ecs_query_t*, q)->eval_count); ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_cache_t *cache = impl->cache; if (cache) { /* If monitors changed, do query rematching */ ecs_flags32_t flags = q->flags; if (!(ecs_world_get_flags(world) & EcsWorldReadonly) && (flags & EcsQueryHasRefs)) { flecs_eval_component_monitors(q->world); } } return flecs_query_iter(world, q); } static bool flecs_query_member_cmp( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, bool neq) { ecs_table_range_t range; if (op->other) { ecs_var_id_t table_var = flecs_itovar(op->other - 1); range = flecs_query_var_get_range(table_var, ctx); } else { range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); } ecs_table_t *table = range.table; if (!table) { return false; } ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; if (!range.count) { range.count = ecs_table_count(range.table); } int32_t row, end = range.count; if (end) { end += range.offset; } else { end = ecs_table_count(range.table); } void *data; if (!redo) { row = op_ctx->each.row = range.offset; /* Get data ptr starting from offset 0 so we can use row to index */ range.offset = 0; /* Populate data field so we have the array we can compare the member * value against. */ data = op_ctx->data = ecs_table_get_column(range.table, it->trs[field_index]->column, 0); it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; } else { row = ++ op_ctx->each.row; if (op_ctx->each.row >= end) { return false; } data = op_ctx->data; } int32_t offset = (int32_t)op->first.entity; int32_t size = (int32_t)(op->first.entity >> 32); const ecs_entity_t *entities = ecs_table_entities(table); ecs_entity_t e = 0; ecs_entity_t *val; ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); bool second_written = true; if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { uint64_t written = ctx->written[ctx->op_index]; second_written = written & (1ull << op->second.var); } if (second_written) { ecs_flags16_t second_flags = flecs_query_ref_flags( op->flags, EcsQuerySecond); ecs_entity_t second = flecs_get_ref_entity( &op->second, second_flags, ctx); do { e = entities[row]; val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); if (val[0] == second || second == EcsWildcard) { if (!neq) { goto match; } } else { if (neq) { goto match; } } row ++; } while (row < end); return false; } else { e = entities[row]; val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); flecs_query_var_set_entity(op, op->second.var, val[0], ctx); } match: if (op->other) { ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); flecs_query_var_set_entity(op, op->src.var, e, ctx); } ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); it->ids[field_index] = ecs_pair(mbr, val[0]); op_ctx->each.row = row; return true; } bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_member_cmp(op, redo, ctx, false); } bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_member_cmp(op, redo, ctx, true); } static const char* flecs_query_name_arg( const ecs_query_op_t *op, ecs_query_run_ctx_t *ctx) { int8_t term_index = op->term_index; const ecs_term_t *term = &ctx->query->pub.terms[term_index]; return term->second.name; } static bool flecs_query_match_substr_i( const char *name, const char *match) { if (!match[0]) { return true; } for (; *name; name ++) { const char *n = name, *m = match; while (*n && *m && (tolower((unsigned char)*n) == tolower((unsigned char)*m))) { n ++; m ++; } if (!*m) { return true; } } return false; } static bool flecs_query_compare_range( const ecs_table_range_t *l, const ecs_table_range_t *r) { if (l->table != r->table) { return false; } if (l->count) { int32_t l_end = l->offset + l->count; int32_t r_end = r->offset + r->count; if (r->offset < l->offset) { return false; } if (r_end > l_end) { return false; } } else { /* Entire table is matched */ } return true; } static bool flecs_query_pred_eq_w_range( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_table_range_t r) { if (redo) { return false; } uint64_t written = ctx->written[ctx->op_index]; ecs_var_id_t src_var = op->src.var; if (!(written & (1ull << src_var))) { /* left = unknown, right = known. Assign right-hand value to left */ ecs_var_id_t l = src_var; ctx->vars[l].range = r; if (r.count == 1) { ctx->vars[l].entity = ecs_table_entities(r.table)[r.offset]; } return true; } else { ecs_table_range_t l = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!flecs_query_compare_range(&l, &r)) { return false; } ctx->vars[src_var].range.offset = r.offset; ctx->vars[src_var].range.count = r.count; return true; } } bool flecs_query_pred_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized eq operand"); ecs_table_range_t r = flecs_query_get_range( op, &op->second, EcsQuerySecond, ctx); return flecs_query_pred_eq_w_range(op, redo, ctx, r); } bool flecs_query_pred_eq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { const char *name = flecs_query_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return false; } ecs_table_range_t r = flecs_range_from_entity(ctx->world, e); return flecs_query_pred_eq_w_range(op, redo, ctx, r); } static bool flecs_query_pred_neq_w_range( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_table_range_t r) { ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); ecs_var_id_t src_var = op->src.var; ecs_table_range_t l = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); /* If tables don't match, neq always returns once */ if (l.table != r.table) { return true && !redo; } int32_t l_offset; int32_t l_count; if (!redo) { /* Make sure we're working with the correct table count */ if (!l.count && l.table) { l.count = ecs_table_count(l.table); } l_offset = l.offset; l_count = l.count; /* Cache old value */ op_ctx->range = l; op_ctx->redo = false; } else { l_offset = op_ctx->range.offset; l_count = op_ctx->range.count; } /* If the table matches, a Neq returns twice: once for the slice before the * excluded slice, once for the slice after the excluded slice. If the right * hand range starts & overlaps with the left hand range, there is only * one slice. */ ecs_var_t *var = &ctx->vars[src_var]; if (!redo && r.offset > l_offset) { int32_t end = r.offset; if (end > (l_offset + l_count)) { end = l_offset + l_count; } /* Return first slice */ var->range.table = l.table; var->range.offset = l_offset; var->range.count = end - l_offset; op_ctx->redo = false; return true; } else if (!op_ctx->redo) { int32_t l_end = op_ctx->range.offset + l_count; int32_t r_end = r.offset + r.count; if (l_end <= r_end) { /* If end of existing range falls inside the excluded range, there's * nothing more to return */ var->range = l; return false; } /* Return second slice. Clamp the start to the source range in case the * excluded range starts before it, so rows outside the source range are * not returned. */ int32_t r_start = r_end; if (r_start < l_offset) { r_start = l_offset; } var->range.table = l.table; var->range.offset = r_start; var->range.count = l_end - r_start; /* Flag so we know we're done on the next redo */ op_ctx->redo = true; return true; } else { /* Restore previous value */ var->range = l; return false; } } static bool flecs_query_pred_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, bool is_neq) { ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); uint64_t written = ctx->written[ctx->op_index]; ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized match operand"); (void)written; ecs_var_id_t src_var = op->src.var; const char *match = flecs_query_name_arg(op, ctx); ecs_table_range_t l; if (!redo) { l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); if (!l.table) { return false; } if (!l.count) { l.count = ecs_table_count(l.table); } op_ctx->range = l; op_ctx->index = l.offset; op_ctx->name_col = flecs_ito(int16_t, ecs_table_get_type_index(ctx->world, l.table, ecs_pair(ecs_id(EcsIdentifier), EcsName))); if (op_ctx->name_col == -1) { return is_neq; } op_ctx->name_col = flecs_ito(int16_t, l.table->column_map[op_ctx->name_col]); ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); } else { if (op_ctx->name_col == -1) { /* Table has no name */ return false; } l = op_ctx->range; } const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data; int32_t count = l.offset + l.count, offset = -1; for (; op_ctx->index < count; op_ctx->index ++) { const char *name = names[op_ctx->index].value; bool result = flecs_query_match_substr_i(name, match); if (is_neq) { result = !result; } if (!result) { if (offset != -1) { break; } } else { if (offset == -1) { offset = op_ctx->index; } } } if (offset == -1) { ctx->vars[src_var].range = op_ctx->range; return false; } ctx->vars[src_var].range.offset = offset; ctx->vars[src_var].range.count = (op_ctx->index - offset); return true; } bool flecs_query_pred_eq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_pred_match(op, redo, ctx, false); } bool flecs_query_pred_neq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_pred_match(op, redo, ctx, true); } bool flecs_query_pred_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized neq operand"); ecs_table_range_t r = flecs_query_get_range( op, &op->second, EcsQuerySecond, ctx); return flecs_query_pred_neq_w_range(op, redo, ctx, r); } bool flecs_query_pred_neq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { const char *name = flecs_query_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return true && !redo; } ecs_table_range_t r = flecs_range_from_entity(ctx->world, e); return flecs_query_pred_neq_w_range(op, redo, ctx, r); } static bool flecs_query_sparse_init_sparse( ecs_query_sparse_ctx_t *op_ctx, ecs_component_record_t *cr) { if (!cr) { return false; } ecs_assert(cr->flags & EcsIdDontFragment, ECS_INTERNAL_ERROR, NULL); op_ctx->sparse = cr->sparse; if (!op_ctx->sparse) { return false; } int32_t count = flecs_sparse_count(op_ctx->sparse); if (!count) { return false; } op_ctx->cur = count; return true; } static void flecs_query_sparse_init_range( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_sparse_ctx_t *op_ctx) { ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!range.count) { range.count = ecs_table_count(range.table); } op_ctx->range = range; op_ctx->cur = range.offset - 1; } static bool flecs_query_sparse_next_entity( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_sparse_ctx_t *op_ctx, bool not, void **ptr_out) { int32_t end = op_ctx->range.count + op_ctx->range.offset; next: op_ctx->cur ++; if (op_ctx->cur == end) { flecs_query_src_set_range(op, &op_ctx->range, ctx); return false; } ecs_entity_t e = ecs_table_entities(op_ctx->range.table)[op_ctx->cur]; bool result; if (ptr_out) { void *ptr = flecs_sparse_get(op_ctx->sparse, 0, e); result = ptr != NULL; *ptr_out = ptr; } else { result = flecs_sparse_has(op_ctx->sparse, e); } if (not) { result = !result; } if (!result) { goto next; } flecs_query_src_set_single(op, op_ctx->cur, ctx); return true; } static bool flecs_query_sparse_select_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_mask, ecs_id_t id) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; if (!redo) { ecs_component_record_t *cr = flecs_components_get(ctx->world, id); if (!flecs_query_sparse_init_sparse(op_ctx, cr)) { return false; } } next: if (!(op_ctx->cur--)) { return false; } ecs_assert(op_ctx->cur >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(op_ctx->cur < flecs_sparse_count(op_ctx->sparse), ECS_INVALID_OPERATION, "sparse iterator invalidated while iterating"); ecs_entity_t e = flecs_sparse_ids(op_ctx->sparse)[op_ctx->cur]; ecs_table_range_t range = flecs_range_from_entity(ctx->world, e); if (flecs_query_table_filter(range.table, op->other, table_mask)) { goto next; } flecs_query_var_set_range(op, op->src.var, range.table, range.offset, range.count, ctx); it->ids[field_index] = id; flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } static bool flecs_query_sparse_select_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_mask, ecs_id_t id) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); if (!redo) { ecs_component_record_t *cr = flecs_components_get(ctx->world, id); if (!cr) { return false; } if (ECS_PAIR_FIRST(id) == EcsWildcard) { op_ctx->cr = cr->pair->second.next; } else { ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); op_ctx->cr = cr->pair->first.next; } } else { goto next_select; } next: if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { return false; } next_select: if (flecs_query_sparse_select_id(op, true, ctx, table_mask, 0)) { ecs_id_t actual_id = op_ctx->cr->id; ctx->it->ids[op->field_index] = actual_id; flecs_query_set_vars(op, actual_id, ctx); if (op->match_flags & EcsTermMatchAny) { ctx->it->ids[op->field_index] = id; } return true; } next_component: { ecs_component_record_t *cr = op_ctx->cr; if (ECS_PAIR_FIRST(id) == EcsWildcard) { cr = op_ctx->cr = cr->pair->second.next; } else { ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); cr = op_ctx->cr = cr->pair->first.next; } if (!cr) { return false; } if (!(cr->flags & EcsIdDontFragment)) { goto next_component; } } goto next; } static bool flecs_query_sparse_next_wildcard_pair( ecs_query_sparse_ctx_t *op_ctx) { ecs_component_record_t *cr = op_ctx->cr; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); do { cr = cr->non_fragmenting.next; } while (cr && (!ECS_IS_PAIR(cr->id) || ecs_id_is_wildcard(cr->id))); if (!cr) { return false; } op_ctx->cr = cr; ecs_assert(cr->flags & EcsIdDontFragment, ECS_INTERNAL_ERROR, NULL); return true; } static bool flecs_query_sparse_select_all_wildcard_pairs( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_mask) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); if (!redo) { ecs_component_record_t *cr = op_ctx->cr = ctx->world->cr_non_fragmenting_head; if (!cr) { return false; } if (!ECS_IS_PAIR(cr->id)|| ecs_id_is_wildcard(cr->id)) { goto next_component; } } else { goto next_select; } next: if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { return false; } next_select: if (flecs_query_sparse_select_id(op, true, ctx, table_mask, 0)) { ecs_id_t actual_id = op_ctx->cr->id; ctx->it->ids[op->field_index] = actual_id; flecs_query_set_vars(op, actual_id, ctx); return true; } next_component: if (!flecs_query_sparse_next_wildcard_pair(op_ctx)) { return false; } goto next; } bool flecs_query_sparse_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_mask) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (ecs_id_is_wildcard(id)) { if (id == ecs_pair(EcsWildcard, EcsWildcard)) { return flecs_query_sparse_select_all_wildcard_pairs( op, redo, ctx, table_mask); } else { return flecs_query_sparse_select_wildcard( op, redo, ctx, table_mask, id); } } else { return flecs_query_sparse_select_id(op, redo, ctx, table_mask, id); } } static bool flecs_query_sparse_with_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool not, ecs_id_t id, void **ptr_out) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); if (!redo) { ecs_component_record_t *cr = flecs_components_get(ctx->world, id); if (!cr) { goto no_sparse; } ecs_sparse_t *sparse = cr->sparse; if (!sparse || !flecs_sparse_count(sparse)) { goto no_sparse; } op_ctx->sparse = sparse; flecs_query_sparse_init_range(op, ctx, op_ctx); } else { if (!op_ctx->range.table) { return false; } } return flecs_query_sparse_next_entity(op, ctx, op_ctx, not, ptr_out); no_sparse: op_ctx->sparse = NULL; op_ctx->range.table = NULL; op_ctx->range.offset = 0; op_ctx->range.count = 0; return not; } static bool flecs_query_sparse_with_exclusive( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool not, ecs_id_t id) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); if (!redo) { if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { return false; } } ecs_id_t actual_id = op_ctx->cr->id; void *tgt_ptr = NULL; if (flecs_query_sparse_with_id(op, redo, ctx, not, actual_id, &tgt_ptr)) { if (!not) { ecs_entity_t tgt = *(ecs_entity_t*)tgt_ptr; actual_id = ctx->it->ids[op->field_index] = ecs_pair(ECS_PAIR_FIRST(actual_id), tgt); } if (not || (op->match_flags & EcsTermMatchAny)) { ctx->it->ids[op->field_index] = id; } flecs_query_set_vars(op, actual_id, ctx); return true; } return false; } static bool flecs_query_sparse_with_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool not, ecs_id_t id) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); bool with_redo = false; if (!redo) { ecs_component_record_t *cr = flecs_components_get(ctx->world, id); if (!cr) { op_ctx->cr = NULL; return not; } if (cr->flags & EcsIdExclusive) { op_ctx->cr = cr; op_ctx->exclusive = true; return flecs_query_sparse_with_exclusive(op, false, ctx, not, id); } if (!not) { if (ECS_PAIR_FIRST(id) == EcsWildcard) { op_ctx->cr = cr->pair->second.next; } else { ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); op_ctx->cr = cr->pair->first.next; } } else { op_ctx->cr = cr; } if (!op_ctx->cr) { return not; } } else { if (op_ctx->exclusive) { return flecs_query_sparse_with_exclusive(op, true, ctx, not, id); } if (!op_ctx->cr) { return false; } with_redo = true; goto next_select; } next: if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { return not; } next_select: { ecs_id_t actual_id = op_ctx->cr->id; if (flecs_query_sparse_with_id(op, with_redo, ctx, not, actual_id, NULL)) { ctx->it->ids[op->field_index] = actual_id; flecs_query_set_vars(op, actual_id, ctx); if (op->match_flags & EcsTermMatchAny) { ctx->it->ids[op->field_index] = id; } return true; } } next_component: { ecs_component_record_t *cr = op_ctx->cr; if (!not) { if (ECS_PAIR_FIRST(id) == EcsWildcard) { cr = op_ctx->cr = cr->pair->second.next; } else { ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); cr = op_ctx->cr = cr->pair->first.next; } } else { cr = NULL; } if (!cr) { return false; } if (!(cr->flags & EcsIdDontFragment)) { goto next_component; } } with_redo = false; goto next; } static bool flecs_query_sparse_with_all_wildcard_pairs( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool not) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); bool with_redo = false; if (!redo) { ecs_component_record_t *cr = op_ctx->cr = ctx->world->cr_non_fragmenting_head; if (!cr) { return false; } if (!ECS_IS_PAIR(cr->id) || ecs_id_is_wildcard(cr->id)) { goto next_component; } } else { with_redo = true; goto next_select; } next: if (!flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { return false; } next_select: { ecs_id_t actual_id = op_ctx->cr->id; if (flecs_query_sparse_with_id(op, with_redo, ctx, not, actual_id, NULL)) { ctx->it->ids[op->field_index] = actual_id; flecs_query_set_vars(op, actual_id, ctx); return true; } } next_component: if (!flecs_query_sparse_next_wildcard_pair(op_ctx)) { return false; } with_redo = false; goto next; } bool flecs_query_sparse_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool not) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (ecs_id_is_wildcard(id)) { if (id == ecs_pair(EcsWildcard, EcsWildcard)) { return flecs_query_sparse_with_all_wildcard_pairs(op, redo, ctx, not); } else { return flecs_query_sparse_with_wildcard(op, redo, ctx, not, id); } } else { return flecs_query_sparse_with_id(op, redo, ctx, not, id, NULL); } } bool flecs_query_sparse_up( 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)) { /* Can use regular up traversal since sparse components are resolved * by the traversal cache */ return flecs_query_up_with(op, redo, ctx); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectUp, FlecsQueryUpSelectSparse); } } bool flecs_query_sparse_self_up( 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)) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); ecs_query_up_ctx_t *up_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; if (!redo) { op_ctx->self = true; } if (op_ctx->self) { bool result = flecs_query_sparse_with(op, redo, ctx, false); if (result) { if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { it->sources[op->field_index] = op->src.entity; } else { it->sources[op->field_index] = 0; } flecs_reset_source_set_flag(it, op->field_index); return true; } op_ctx->self = false; redo = false; } else if (up_ctx->cur == -1) { return flecs_query_sparse_next_entity(op, ctx, op_ctx, true, NULL); } next_up: if (!flecs_query_up_with(op, redo, ctx)) { return false; } if (up_ctx->cur != -1) { ecs_table_range_t row = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); ecs_entity_t e = ecs_table_entities(row.table)[row.offset]; if (op_ctx->sparse && flecs_sparse_has(op_ctx->sparse, e)) { redo = true; goto next_up; } return true; } flecs_query_sparse_init_range(op, ctx, op_ctx); return flecs_query_sparse_next_entity(op, ctx, op_ctx, true, NULL); } else { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); if (!redo) { op_ctx->self = true; } bool up_redo = true; if (op_ctx->self) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (flecs_query_sparse_select_id(op, redo, ctx, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled), id)) { return true; } op_ctx->self = false; up_redo = false; goto next_table; } next_entity: { bool result = flecs_query_sparse_next_entity( op, ctx, op_ctx, true, NULL); if (!result) { op_ctx->cur = op_ctx->prev_cur; op_ctx->range = op_ctx->prev_range; goto next_table; } return true; } next_table: { if (!flecs_query_up_select(op, up_redo, ctx, FlecsQueryUpSelectUp, FlecsQueryUpSelectSparse)) { return false; } op_ctx->prev_cur = op_ctx->cur; op_ctx->prev_range = op_ctx->range; flecs_query_sparse_init_range(op, ctx, op_ctx); goto next_entity; } } } bool flecs_query_sparse( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_sparse_with(op, redo, ctx, false); } else { return flecs_query_sparse_select(op, redo, ctx, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } } typedef struct { ecs_flags64_t mask; bool has_bitset; } flecs_query_row_mask_t; /* Portable count-trailing-zeros for 64-bit values. Input must be nonzero. */ static inline int32_t flecs_ctz64(uint64_t v) { #if defined(__clang__) || defined(__GNUC__) return (int32_t)__builtin_ctzll(v); #elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_ARM64)) unsigned long idx; _BitScanForward64(&idx, v); return (int32_t)idx; #else int32_t count = 0; while ((v & 1u) == 0u) { v >>= 1; count ++; } return count; #endif } static flecs_query_row_mask_t flecs_query_get_row_mask( ecs_iter_t *it, ecs_table_t *table, int32_t block_index, ecs_flags64_t and_fields, ecs_flags64_t not_fields, ecs_query_toggle_ctx_t *op_ctx) { ecs_flags64_t mask = UINT64_MAX; int32_t i, field_count = it->field_count; ecs_flags64_t fields = and_fields | not_fields; bool has_bitset = false; for (i = 0; i < field_count; i ++) { uint64_t field_bit = 1llu << i; if (!(fields & field_bit)) { continue; } if (not_fields & field_bit) { it->set_fields &= (ecs_termset_t)~field_bit; } else if (and_fields & field_bit) { ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); } else { ecs_abort(ECS_INTERNAL_ERROR, NULL); } ecs_id_t id = it->ids[i]; ecs_bitset_t *bs = flecs_table_get_toggle(table, id); if (!bs) { if (not_fields & field_bit) { if (op_ctx->prev_set_fields & field_bit) { has_bitset = false; break; } } continue; } ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); ecs_flags64_t block = bs->data[block_index]; if (not_fields & field_bit) { block = ~block; } mask &= block; has_bitset = true; } return (flecs_query_row_mask_t){ mask, has_bitset }; } static bool flecs_query_toggle_for_up( ecs_iter_t *it, ecs_flags64_t and_fields, ecs_flags64_t not_fields) { int32_t i, field_count = it->field_count; ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; for (i = 0; i < field_count; i ++) { uint64_t field_bit = 1llu << i; if (!(fields & field_bit)) { continue; } bool match = false; if ((it->set_fields & field_bit)) { ecs_entity_t src = it->sources[i]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); match = ecs_is_enabled_id(it->world, src, it->ids[i]); } if (field_bit & not_fields) { match = !match; } if (!match) { return false; } } return true; } static bool flecs_query_toggle_cmp( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_flags64_t and_fields, ecs_flags64_t not_fields) { ecs_iter_t *it = ctx->it; ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); ecs_table_t *table = range.table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if ((and_fields & op_ctx->prev_set_fields) != and_fields) { /* If not all fields matching 'and' toggles are set, table can't match */ return false; } ecs_flags32_t up_fields = it->up_fields; if (!redo) { if (up_fields & (and_fields|not_fields)) { /* If there are toggle fields that were matched with query * traversal, evaluate those separately. */ if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { return false; } it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); } } /* Shared fields are evaluated, can be ignored from now on */ // and_fields &= ~up_fields; not_fields &= flecs_uto(uint64_t, ~up_fields); if (!(table->flags & EcsTableHasToggle)) { if (not_fields) { /* If any of the toggle fields with a not operator are for fields * that are set, without a bitset those fields can't match. */ return false; } else { /* If table doesn't have toggles but query matched toggleable * components, all entities match. */ if (!redo) { return true; } else { return false; } } } if (table && !range.count) { range.count = ecs_table_count(table); if (!range.count) { return false; } } int32_t last, block_index, cur; uint64_t block = 0; if (!redo) { op_ctx->range = range; cur = op_ctx->cur = range.offset; block_index = op_ctx->block_index = -1; last = range.offset + range.count; } else { if (!op_ctx->has_bitset) { goto done; } last = op_ctx->range.offset + op_ctx->range.count; cur = op_ctx->cur; ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); if (cur == last) { goto done; } block_index = op_ctx->block_index; block = op_ctx->block; } /* If end of last iteration is start of new block, compute new block */ int32_t new_block_index = cur / 64; if (new_block_index != block_index) { compute_block: block_index = op_ctx->block_index = new_block_index; flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( it, table, block_index, and_fields, not_fields, op_ctx); /* If table doesn't have bitset columns, all columns match */ if (!(op_ctx->has_bitset = row_mask.has_bitset)) { if (!not_fields) { return true; } else { goto done; } } /* No enabled bits */ block = row_mask.mask; if (!block) { next_block: new_block_index ++; cur = new_block_index * 64; if (cur >= last) { /* No more rows */ goto done; } op_ctx->cur = cur; goto compute_block; } op_ctx->block = block; } /* Find first enabled bit using bit operations */ int32_t first_bit = cur - (block_index * 64); int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); uint64_t low_mask = (first_bit == 0) ? UINT64_MAX : (~0ull << first_bit); uint64_t high_mask = (last_bit == 64) ? UINT64_MAX : ((1ull << last_bit) - 1ull); uint64_t masked = block & low_mask & high_mask; if (!masked) { goto next_block; } int32_t tz = flecs_ctz64(masked); uint64_t run = masked >> tz; /* ones from start of run */ int32_t run_len; if (~run == 0ull) { run_len = 64 - tz; } else { run_len = flecs_ctz64(~run); } int32_t max_len = last_bit - tz; if (run_len > max_len) { run_len = max_len; } int32_t row = tz + (block_index * 64); cur = tz + run_len + (block_index * 64); ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); if (!(cur - row)) { goto done; } flecs_query_src_set_range(op, &(ecs_table_range_t){ .table = table, .offset = row, .count = cur - row }, ctx); op_ctx->cur = cur; return true; done: /* Restore range & set fields */ flecs_query_src_set_range(op, &op_ctx->range, ctx); it->set_fields = op_ctx->prev_set_fields; return false; } bool flecs_query_toggle( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); if (!redo) { op_ctx->prev_set_fields = it->set_fields; } ecs_flags64_t and_fields = op->first.entity; ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; return flecs_query_toggle_cmp( op, redo, ctx, and_fields, not_fields); } bool flecs_query_toggle_option( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); if (!redo) { op_ctx->prev_set_fields = it->set_fields; op_ctx->optional_not = false; op_ctx->has_bitset = false; } repeat: {} ecs_flags64_t and_fields = 0, not_fields = 0; if (op_ctx->optional_not) { not_fields = op->first.entity & op_ctx->prev_set_fields; } else { and_fields = op->first.entity; } bool result = flecs_query_toggle_cmp( op, redo, ctx, and_fields, not_fields); if (!result) { if (!op_ctx->optional_not) { /* Run the not-branch of optional fields */ op_ctx->optional_not = true; it->set_fields = op_ctx->prev_set_fields; redo = false; goto repeat; } } return result; } static bool flecs_query_trav_fixed_src_reflexive( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_table_range_t *range, ecs_entity_t trav, ecs_entity_t second) { ecs_table_t *table = range->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_entity_t *entities = ecs_table_entities(table); int32_t count = range->count; if (!count) { count = ecs_table_count(table); } int32_t i = range->offset, end = i + count; for (; i < end; i ++) { if (entities[i] == second) { /* Even though table doesn't have the specific relationship * pair, the relationship is reflexive and the target entity * is stored in the table. */ break; } } if (i == end) { /* Table didn't contain target entity */ return false; } if (count > 1) { /* If the range contains more than one entity, set the range to * return only the entity matched by the reflexive property. */ ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[op->src.var]; ecs_table_range_t *var_range = &var->range; var_range->offset = i; var_range->count = 1; var->entity = entities[i]; } flecs_query_set_trav_match(op, NULL, trav, second, ctx); return true; } static bool flecs_query_trav_unknown_src_reflexive( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_entity_t trav, ecs_entity_t second) { ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_id_t src_var = op->src.var; flecs_query_var_set_entity(op, src_var, second, ctx); flecs_query_var_get_table(src_var, ctx); ecs_table_t *table = ctx->vars[src_var].range.table; if (table) { if (flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { return false; } } flecs_query_set_trav_match(op, NULL, trav, second, ctx); return true; } static bool flecs_query_trav_fixed_src_up_fixed_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; /* If everything's fixed, can only have a single result */ } ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; /* Check if table has transitive relationship by traversing upwards */ ecs_table_record_t *tr = NULL; ecs_search_relation(ctx->world, table, 0, ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); if (!tr) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); } else { return false; } } flecs_query_set_trav_match(op, tr, trav, second, ctx); return true; } static bool flecs_query_trav_unknown_src_up_fixed_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { ecs_record_t *r_second = flecs_entities_get(ctx->world, second); bool traversable = r_second && r_second->row & EcsEntityIsTraversable; bool reflexive = op->match_flags & EcsTermReflexive; if (!traversable && !reflexive) { trav_ctx->cache.id = 0; /* If there's no record for the entity, it can't have a subtree so * forward operation to a regular select. */ return flecs_query_select(op, redo, ctx); } /* Entity is traversable, which means it could have a subtree */ flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->index = -1; if(flecs_query_trav_unknown_src_reflexive( op, ctx, trav, second)) { /* It's possible that we couldn't return the entity required for * reflexive matching, like when it's a prefab or disabled. */ return true; } } } else { if (!trav_ctx->cache.id) { /* No traversal cache, which means this is a regular select */ return flecs_query_select(op, redo, ctx); } } if (trav_ctx->index == -1) { redo = false; /* First result after handling reflexive relationship */ trav_ctx->index = 0; } /* Forward to select */ int32_t count = ecs_vec_count(&trav_ctx->cache.entities); ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); for (; trav_ctx->index < count; trav_ctx->index ++) { ecs_trav_elem_t *el = &elems[trav_ctx->index]; trav_ctx->and.cr = el->cr; /* prevents lookup by select */ if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { return true; } redo = false; } return false; } static bool flecs_query_trav_yield_reflexive_src( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_table_range_t *range, ecs_entity_t trav) { ecs_var_t *vars = ctx->vars; ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); int32_t offset = trav_ctx->offset, count = trav_ctx->count; bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); if (trav_ctx->index >= (offset + count)) { /* Restore previous offset, count */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; vars[src_var].range.offset = offset; vars[src_var].range.count = count; vars[src_var].entity = 0; } return false; } ecs_entity_t entity = ecs_table_entities(range->table)[trav_ctx->index]; flecs_query_set_trav_match(op, NULL, trav, entity, ctx); /* Hijack existing variable to return one result at a time */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; ecs_table_t *table = vars[src_var].range.table; ecs_assert(!table || table == ecs_get_table(ctx->world, entity), ECS_INTERNAL_ERROR, NULL); (void)table; vars[src_var].entity = entity; vars[src_var].range = flecs_range_from_entity(ctx->world, entity); } return true; } static bool flecs_query_trav_fixed_src_up_unknown_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->yield_reflexive = true; trav_ctx->index = range.offset; trav_ctx->offset = range.offset; trav_ctx->count = range.count ? range.count : ecs_table_count(table); } } else { trav_ctx->index ++; } if (trav_ctx->yield_reflexive) { if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { return true; } trav_ctx->yield_reflexive = false; trav_ctx->index = 0; } if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { return false; } ecs_trav_elem_t *el = ecs_vec_get_t( &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); flecs_query_set_trav_match(op, el->tr, trav, el->entity, ctx); return true; } bool flecs_query_trav( 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)) { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { /* This can't happen, src or second should have been resolved */ ecs_abort(ECS_INTERNAL_ERROR, "invalid instruction sequence: unconstrained traversal"); } else { return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); } } else { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); } else { return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); } } } const EcsParent* flecs_query_tree_get_parents( ecs_table_range_t range) { int32_t parent_column = range.table->component_map[ecs_id(EcsParent)]; ecs_assert(parent_column != -1, ECS_INTERNAL_ERROR, NULL); return ecs_table_get_column(range.table, parent_column - 1, range.offset); } static bool flecs_query_tree_select_tgt( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); if (redo) { op_ctx->cur ++; if (op_ctx->cur >= op_ctx->range.count) { return false; } } ecs_entity_t child = op_ctx->entities[op_ctx->cur]; ecs_assert(child != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_range_t range = flecs_range_from_entity(ctx->world, child); flecs_query_var_set_range(op, op->src.var, range.table, range.offset, range.count, ctx); return true; } static bool flecs_query_tree_with_parent( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); ecs_table_range_t range = op_ctx->range; repeat: if (redo) { op_ctx->cur ++; } if ((op_ctx->cur - range.offset) >= range.count) { flecs_query_src_set_range(op, &op_ctx->range, ctx); return false; } ecs_assert(op_ctx->cur >= range.offset, ECS_INTERNAL_ERROR, NULL); ecs_assert(op_ctx->cur < (range.offset + range.count), ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = op_ctx->tgt; ecs_entity_t parent = op_ctx->parents[op_ctx->cur - range.offset].value; ecs_iter_t *it = ctx->it; flecs_query_iter_set_id(it, op->field_index, ecs_childof(parent)); if (tgt != EcsWildcard) { if (parent != op_ctx->tgt) { redo = true; goto repeat; } } flecs_query_src_set_single(op, op_ctx->cur, ctx); return true; } static bool flecs_query_tree_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); op_ctx->tgt = ECS_PAIR_SECOND(id); op_ctx->cur = 0; ecs_component_record_t *cr = flecs_components_get(ctx->world, id); if (!cr) { return false; } if (!(cr->flags & EcsIdOrderedChildren)) { op_ctx->tgt = 0; return flecs_query_and(op, redo, ctx); } ecs_assert(op_ctx->tgt != EcsWildcard, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *v_children = &cr->pair->ordered_children; op_ctx->entities = ecs_vec_first_t(v_children, ecs_entity_t); op_ctx->range.count = ecs_vec_count(v_children); if (!op_ctx->range.count) { return false; } } else { if (!op_ctx->tgt) { return flecs_query_and(op, redo, ctx); } } return flecs_query_tree_select_tgt(op, redo, ctx); } static bool flecs_query_tree_select_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_id_t pair) { ecs_query_tree_wildcard_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_wildcard); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); if (!redo) { op_ctx->cr = ctx->world->cr_childof_wildcard; ecs_assert(op_ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); op_ctx->state = EcsQueryTreeIterEntities; } next: switch(op_ctx->state) { case EcsQueryTreeIterEntities: { bool result = flecs_query_select_w_id( op, redo, ctx, ecs_id(EcsParent), (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); if (!result) { op_ctx->state = EcsQueryTreeIterTables; redo = false; goto next; } it->ids[field_index] = ecs_pair(EcsChildOf, EcsWildcard); return true; } case EcsQueryTreeIterTables: { bool result = flecs_query_select_w_id( op, redo, ctx, pair, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); if (!result) { return false; } it->ids[field_index] = pair; return true; } case EcsQueryTreeIterNext: default: return false; } } static bool flecs_query_tree_select_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool bulk_return) { ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = ECS_PAIR_SECOND(id); if (tgt != EcsWildcard) { it->ids[field_index] = id; /* Happens when variable has been constrained */ if (bulk_return) { return flecs_query_children(op, redo, ctx); } else { return flecs_query_tree_select(op, redo, ctx); } } ecs_query_tree_wildcard_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_wildcard); if (!redo) { op_ctx->cr = ctx->world->cr_childof_wildcard; ecs_assert(op_ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); op_ctx->state = EcsQueryTreeIterNext; } next: switch(op_ctx->state) { /* Select next (ChildOf, parent) pair */ case EcsQueryTreeIterNext: { ecs_component_record_t *cr = op_ctx->cr = flecs_component_first_next(op_ctx->cr); if (!cr) { return false; } if (cr == ctx->world->cr_childof_0) { goto next; } flecs_query_var_reset(0, ctx); if (op->match_flags & EcsTermMatchAny) { it->ids[field_index] = ecs_childof(EcsWildcard); } else { it->ids[field_index] = op_ctx->cr->id; } if (cr->flags & EcsIdOrderedChildren) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *v_children = &cr->pair->ordered_children; if (bulk_return) { op_ctx->state = EcsQueryTreeIterNext; it->table = NULL; it->offset = 0; it->entities = ecs_vec_first_t(v_children, ecs_entity_t); it->count = ecs_vec_count(v_children); goto done; } else { op_ctx->entities = ecs_vec_first_t(v_children, ecs_entity_t); op_ctx->count = ecs_vec_count(v_children); op_ctx->cur = -1; op_ctx->state = EcsQueryTreeIterEntities; goto next; } } else { if (!flecs_component_iter(cr, &op_ctx->it)) { op_ctx->state = EcsQueryTreeIterNext; } else { op_ctx->state = EcsQueryTreeIterTables; } } goto next; } /* Iterate tables for (ChildOf, parent) */ case EcsQueryTreeIterTables: { const ecs_table_record_t *tr = flecs_component_next(&op_ctx->it); if (!tr) { op_ctx->state = EcsQueryTreeIterNext; goto next; } else { ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table) || (table->flags & EcsTableNotQueryable)) { goto next; } flecs_query_it_set_tr(it, field_index, tr); flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); goto done; } } /* Return ordered entities for (ChildOf, parent) one by one */ case EcsQueryTreeIterEntities: { op_ctx->cur ++; if (op_ctx->cur >= op_ctx->count) { op_ctx->state = EcsQueryTreeIterNext; goto next; } else { ecs_entity_t child = op_ctx->entities[op_ctx->cur]; ecs_assert(child != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_range_t range = flecs_range_from_entity(ctx->world, child); flecs_query_var_set_range(op, op->src.var, range.table, range.offset, range.count, ctx); goto done; } } } done: flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } bool flecs_query_tree_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); if (redo) { if (op_ctx->tgt) { return flecs_query_tree_with_parent(op, redo, ctx); } flecs_query_src_set_range(op, &op_ctx->range, ctx); return false; } ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!range.count) { range.count = ecs_table_count(range.table); } op_ctx->range = range; ecs_table_t *table = range.table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); /* Fragmenting childof */ if (table->flags & EcsTableHasChildOf) { if (op->match_flags & EcsTermMatchAny) { return flecs_query_and_any(op, redo, ctx); } else { return flecs_query_with(op, redo, ctx); } } ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_assert(ECS_IS_PAIR(id), ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = ECS_PAIR_SECOND(id); /* Root */ if (!(table->flags & EcsTableHasParent)) { if (!tgt) { int8_t field_index = op->field_index; it->set_fields &= ~(1u << field_index); flecs_query_iter_set_id(it, op->field_index, ecs_childof(EcsWildcard)); return true; } return false; } /* Non-fragmenting childof */ const EcsParent *parents = flecs_query_tree_get_parents(range); /* Evaluating a single entity */ if (range.count == 1) { ecs_entity_t parent = parents[0].value; /* Wildcard query */ if (tgt == EcsWildcard) { if (op->match_flags & EcsTermMatchAny) { flecs_query_iter_set_id(it, op->field_index, ecs_childof(EcsWildcard)); } else { ecs_id_t actual_id = flecs_query_iter_set_id(it, op->field_index, ecs_childof(parent)); flecs_query_set_vars(op, actual_id, ctx); } /* Parent query */ } else { if ((uint32_t)parent != tgt) { return false; } ecs_id_t actual_id = flecs_query_iter_set_id(it, op->field_index, ecs_childof(parent)); flecs_query_set_vars(op, actual_id, ctx); } /* Evaluating an entity range */ } else { /* Wildcard query */ if (tgt == EcsWildcard) { if (op->match_flags & EcsTermMatchAny) { flecs_query_iter_set_id(it, op->field_index, ecs_childof(EcsWildcard)); } else { op_ctx->parents = parents; op_ctx->tgt = tgt; op_ctx->cur = range.offset; return flecs_query_tree_with_parent(op, redo, ctx); } /* Parent query */ } else { ecs_component_record_t *cr = flecs_components_get( ctx->world, ecs_pair(EcsChildOf, tgt)); if (!cr) { return false; } ecs_parent_record_t *pr = flecs_component_get_parent_record( cr, range.table); if (!pr) { /* Table doesn't have entities with parent */ return false; } ecs_entity_t child = pr->entity; if (child) { ecs_assert(pr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get_any(ctx->world, child); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == range.table, ECS_INTERNAL_ERROR, NULL); int32_t cur = ECS_RECORD_TO_ROW(r->row); /* Table contains a single entity for parent */ if ((range.offset > cur) || ((range.offset + range.count) <= cur)) { /* Entity does not fall within range */ return false; } flecs_query_iter_set_id(it, op->field_index, ecs_childof(tgt)); flecs_query_src_set_single(op, cur, ctx); return true; } /* Table contains multiple entities for same parent, scan */ op_ctx->parents = parents; op_ctx->tgt = tgt; op_ctx->cur = range.offset; return flecs_query_tree_with_parent(op, redo, ctx); } } return true; } static bool flecs_query_tree_with_pre( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (range.table->flags & EcsTableHasChildOf) { bool result = flecs_query_with(op, redo, ctx); if (op->match_flags & EcsTermMatchAny) { flecs_query_iter_set_id( ctx->it, op->field_index, ecs_childof(EcsWildcard)); } return result; } if (range.table->flags & EcsTableHasParent) { flecs_query_iter_set_id( ctx->it, op->field_index, ecs_pair(EcsChildOf, EcsWildcard)); return true; } return false; } static bool flecs_query_children_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); ecs_assert(op_ctx->tgt == 0, ECS_INTERNAL_ERROR, NULL); ecs_iter_t *it = ctx->it; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = ECS_PAIR_SECOND(id); ecs_assert(tgt != EcsWildcard, ECS_INTERNAL_ERROR, NULL); ecs_assert(tgt != EcsAny, ECS_INTERNAL_ERROR, NULL); (void)tgt; ecs_component_record_t *cr = flecs_components_get(ctx->world, id); if (!cr) { return false; } op_ctx->tgt = 0; if (!(cr->flags & EcsIdOrderedChildren)) { /* No vector with ordered children, forward to regular search. */ op_ctx->state = EcsQueryTreeIterTables; goto next; } ecs_pair_record_t *pr = cr->pair; ecs_assert(pr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *v_children = &pr->ordered_children; uint32_t filter = flecs_ito(uint32_t, op->other); if ((!pr->disabled_tables || !(filter & EcsTableIsDisabled)) && (!pr->prefab_tables || !(filter & EcsTableIsPrefab))) { it->table = NULL; it->offset = 0; it->entities = ecs_vec_first_t(v_children, ecs_entity_t); it->count = ecs_vec_count(v_children); return true; } else { /* Flag that we're going to iterate each entity separately because we * need to filter out disabled entities. */ op_ctx->state = EcsQueryTreeIterEntities; op_ctx->entities = ecs_vec_first_t(v_children, ecs_entity_t); op_ctx->cur = -1; op_ctx->range.count = ecs_vec_count(v_children); op_ctx->cr = cr; } } else { if (!op_ctx->state) { return false; } } next: if (op_ctx->state == EcsQueryTreeIterEntities) { int32_t cur = ++ op_ctx->cur; if (cur >= op_ctx->range.count) { return false; } ecs_entity_t *e = &op_ctx->entities[cur]; ecs_record_t *r = flecs_entities_get(ctx->world, *e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_table_filter(r->table, op->other, (EcsTableIsDisabled|EcsTableIsPrefab))) { /* Skip disabled/prefab entities */ goto next; } it->entities = e; it->count = 1; return true; } else { ecs_assert(op_ctx->state == EcsQueryTreeIterTables, ECS_INTERNAL_ERROR, NULL); return flecs_query_and(op, redo, ctx); } } static bool flecs_query_children_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (range.table->flags & EcsTableHasChildOf) { return flecs_query_and(op, redo, ctx); } if (!(range.table->flags & EcsTableHasParent)) { /* If table doesn't have ChildOf or Parent, its entities don't have * parents. */ return false; } ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_assert(ECS_PAIR_FIRST(id) == EcsChildOf, ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = ECS_PAIR_SECOND(id); if (tgt == EcsWildcard || tgt == EcsAny) { /* Entities in table have parents, so wildcard will always match. */ return true; } /* TODO: if a ChildOf query is constrained to a table with Parent component, * only some entities in the table may match the query. TBD on what the * behavior should be in this case. For now the query engine only supports * constraining the query to a single entity or an entire table. */ ecs_assert(range.count < 2, ECS_UNSUPPORTED, "can only use set_var() to a single entity for ChildOf($this, parent) terms"); if (range.count == 0) { /* If matching the entire table, return true. Even though not all * entities in the table may match, this lets us add tables with the * Parent component to query caches. */ return true; } if (flecs_query_table_filter(range.table, op->other, EcsTableIsDisabled|EcsTableIsPrefab)) { return false; } const EcsParent *parents = flecs_query_tree_get_parents(range); if ((uint32_t)parents->value == tgt) { return true; } return false; } bool flecs_query_children( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_children_with(op, redo, ctx); } else { return flecs_query_children_select(op, redo, ctx); } } bool flecs_query_tree_and( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_tree_with(op, redo, ctx); } else { return flecs_query_tree_select(op, redo, ctx); } } bool flecs_query_tree_and_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool bulk_return) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_tree_with(op, redo, ctx); } else { if (op->match_flags & EcsTermMatchAny) { return flecs_query_tree_select_any( op, redo, ctx, ecs_pair(EcsChildOf, EcsWildcard)); } else { return flecs_query_tree_select_wildcard(op, redo, ctx, bulk_return); } } } bool flecs_query_tree_pre( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_tree_with_pre(op, redo, ctx); } else { ecs_id_t id = flecs_query_op_get_id(op, ctx); return flecs_query_tree_select_any(op, redo, ctx, id); } } bool flecs_query_tree_post( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { /* Source should have been written as this instruction can only be inserted * after a cache instruction has been evaluated. */ uint64_t written = ctx->written[ctx->op_index]; ecs_assert(written & (1ull << op->src.var), ECS_INTERNAL_ERROR, NULL); (void)written; ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (range.table->flags & (EcsTableHasChildOf)) { return !redo; } /* Shouldn't have gotten here if the table has neither ChildOf nor Parent */ ecs_assert(range.table->flags & EcsTableHasParent, ECS_INTERNAL_ERROR, NULL); return flecs_query_tree_with(op, redo, ctx); } bool flecs_query_tree_up_pre( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool self) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { if (self) { return flecs_query_self_up(op, redo, ctx); } else { return flecs_query_up(op, redo, ctx); } } else { ecs_query_tree_pre_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_pre); if (!redo) { op_ctx->state = EcsQueryTreeIterTables; } if (op_ctx->state == EcsQueryTreeIterTables) { bool result; retry: if (self) { result = flecs_query_self_up(op, redo, ctx); } else { result = flecs_query_up(op, redo, ctx); } if (!result) { op_ctx->state = EcsQueryTreeIterEntities; redo = false; } else { ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); if (range.table->flags & EcsTableHasParent) { /* Skip tables with Parent, since we'll yield all tables * with Parent in the second phase. */ redo = true; goto retry; } return true; } } bool result = flecs_query_select_w_id(op, redo, ctx, ecs_id(EcsParent), (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); if (result) { /* Signal this table needs post processing */ ctx->it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, ctx->it->columns)[op->field_index] = -1; } return result; } } bool flecs_query_tree_up_post( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool self) { ecs_query_tree_ctx_t *op_ctx = flecs_op_ctx(ctx, tree); /* Source should have been written as this instruction can only be inserted * after a cache instruction has been evaluated. */ uint64_t written = ctx->written[ctx->op_index]; ecs_assert(written & (1ull << op->src.var), ECS_INTERNAL_ERROR, NULL); (void)written; ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); /* Passthrough tables with ChildOf pair */ if (!(range.table->flags & EcsTableHasParent)) { return !redo; } if (!redo) { op_ctx->tgt = ctx->it->sources[op->field_index]; } /* Passthrough tables that own the component */ if (op_ctx->tgt != EcsWildcard) { return !redo; } /* Shouldn't have gotten here if the table has neither ChildOf nor Parent */ ecs_assert(range.table->flags & EcsTableHasParent, ECS_INTERNAL_ERROR, NULL); const ecs_term_t *term = &ctx->query->pub.terms[op->term_index]; if (term->oper == EcsOptional) { if (!redo) { if (!range.count) { range.count = ecs_table_count(range.table); } op_ctx->range = range; op_ctx->cur = range.offset - 1; } op_ctx->cur ++; if (op_ctx->cur >= (op_ctx->range.offset + op_ctx->range.count)) { flecs_query_src_set_range(op, &op_ctx->range, ctx); return false; } bool result = false; flecs_query_src_set_single(op, op_ctx->cur, ctx); if (self) { result = flecs_query_self_up_with(op, false, ctx); } else { result = flecs_query_up_with(op, false, ctx); } uint64_t field_bit = 1llu << op->field_index; if (!result) { ctx->it->set_fields &= (ecs_termset_t)~field_bit; } else { ctx->it->set_fields |= (ecs_termset_t)field_bit; } return true; } else { if (self) { return flecs_query_self_up_with(op, redo, ctx); } else { return flecs_query_up_with(op, redo, ctx); } } } /* Find tables with requested component that have traversable entities. */ static bool flecs_query_up_select_table( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_query_up_impl_t *impl = op_ctx->impl; ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_t *it = ctx->it; bool self = trav_kind == FlecsQueryUpSelectSelfUp; ecs_table_range_t range; do { bool result; if (ECS_PAIR_FIRST(impl->with) == EcsChildOf) { if (impl->with == ecs_childof(EcsWildcard)) { result = flecs_query_tree_and_wildcard(op, redo, ctx, false); } else { result = flecs_query_tree_and(op, redo, ctx); } } else if (kind == FlecsQueryUpSelectId) { result = flecs_query_select_id(op, redo, ctx, 0); } else if (kind == FlecsQueryUpSelectDefault) { result = flecs_query_select_w_id(op, redo, ctx, impl->with, 0); } else if (kind == FlecsQueryUpSelectSparse) { result = flecs_query_sparse_select(op, redo, ctx, 0); } else { ecs_abort(ECS_INTERNAL_ERROR, NULL); } if (!result) { /* No remaining tables with component found. */ return false; } redo = true; range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep searching until we find a table that has the requested component, * with traversable entities */ } while (!self && range.table->_->traversable_count == 0); if (!range.count) { range.count = ecs_table_count(range.table); } impl->table = range.table; impl->row = range.offset; impl->end = range.offset + range.count; impl->matched = it->ids[op->field_index]; return true; } /* Find next traversable entity in table. */ static ecs_trav_down_t* flecs_query_up_find_next_traversable( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_query_up_impl_t *impl = op_ctx->impl; ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ctx->world; ecs_iter_t *it = ctx->it; const ecs_query_t *q = &ctx->query->pub; ecs_table_t *table = impl->table; bool self = trav_kind == FlecsQueryUpSelectSelfUp; if (table->_->traversable_count == 0) { /* No traversable entities in table */ impl->table = NULL; return NULL; } else { int32_t row; ecs_entity_t entity = 0; const ecs_entity_t *entities = ecs_table_entities(table); for (row = impl->row; row < impl->end; row ++) { entity = entities[row]; ecs_record_t *record = flecs_entities_get(world, entity); if (record->row & EcsEntityIsTraversable) { /* Found traversable entity */ it->sources[op->field_index] = entity; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; break; } } if (row == impl->end) { /* No traversable entities remaining in table */ impl->table = NULL; return NULL; } impl->row = row; /* Get down cache entry for traversable entity */ bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; impl->down = flecs_query_get_down_cache(ctx, &impl->cache, impl->trav, entity, impl->cr_with, self, match_empty); impl->cache_elem = -1; } return impl->down; } /* Select all tables that can reach the target component through the traversal * relationship. */ bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_query_up_impl_t *impl = op_ctx->impl; if (!impl) { ecs_iter_t *it = ctx->it; ecs_allocator_t *a = flecs_query_get_allocator(it); impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); } ecs_iter_t *it = ctx->it; bool redo_select = redo; const ecs_query_t *q = &ctx->query->pub; bool self = trav_kind == FlecsQueryUpSelectSelfUp; impl->trav = q->terms[op->term_index].trav; /* Reuse component record from previous iteration if possible */ if (!impl->cr_trav) { impl->cr_trav = flecs_components_get(ctx->world, ecs_pair(impl->trav, EcsWildcard)); } /* If component record is not found, or if it doesn't have any tables, revert to * iterating owned components (no traversal) */ if (!impl->cr_trav || !flecs_table_cache_count(&impl->cr_trav->cache)) { if (!self) { /* If operation does not match owned components, return false */ return false; } else if (kind == FlecsQueryUpSelectId) { return flecs_query_select_id(op, redo, ctx, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } else if (kind == FlecsQueryUpSelectDefault) { return flecs_query_select(op, redo, ctx); } else if (kind == FlecsQueryUpSelectSparse) { return flecs_query_sparse_select(op, redo, ctx, 0); } else { /* Invalid select kind */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } } if (!redo) { /* Get component id to match */ impl->with = flecs_query_op_get_id(op, ctx); /* Get component record for component to match */ impl->cr_with = flecs_components_get(ctx->world, impl->with); if (!impl->cr_with) { /* If component record does not exist, there can't be any results */ return false; } impl->down = NULL; impl->cache_elem = 0; } /* Get last used entry from down traversal cache. Cache entries in the down * traversal cache contain a list of tables that can reach the requested * component through the traversal relationship, for a traversable entity * which acts as the key for the cache. */ ecs_trav_down_t *down = impl->down; next_down_entry: /* Get (next) entry in down traversal cache */ while (!down) { ecs_table_t *table = impl->table; /* Get (next) table with traversable entities that have the * requested component. We'll traverse downwards from the * traversable entities in the table to find all entities that can * reach the component through the traversal relationship. */ if (!table) { /* Reset source, in case we have to return a component matched * by the entity in the found table. */ it->sources[op->field_index] = 0; if (!flecs_query_up_select_table( op, redo_select, ctx, trav_kind, kind)) { return false; } table = impl->table; /* If 'self' is true, we're evaluating a term with self|up. This * means that before traversing downwards, we should also return * the current table as result. */ if (self) { if (!flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { flecs_reset_source_set_flag(it, op->field_index); const ecs_table_record_t *tr = it->trs[op->field_index]; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = tr ? tr->column : -1; impl->row --; return true; } } redo_select = true; } else { /* Evaluate next entity in table */ impl->row ++; } /* Get down cache entry for next traversable entity in table */ down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); if (!down) { goto next_down_entry; } } next_down_elem: /* Get next element (table) in cache entry */ if ((++ impl->cache_elem) >= ecs_vec_count(&down->elems)) { /* No more elements in cache entry, find next. */ down = NULL; goto next_down_entry; } ecs_trav_down_elem_t *elem = ecs_vec_get_t( &down->elems, ecs_trav_down_elem_t, impl->cache_elem); flecs_query_var_set_range(op, op->src.var, elem->range.table, elem->range.offset, elem->range.count, ctx); flecs_query_set_vars(op, impl->matched, ctx); if (flecs_query_table_filter(elem->range.table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { /* Go to next table if table contains prefabs, disabled entities or * entities that are not queryable. */ goto next_down_elem; } flecs_set_source_set_flag(it, op->field_index); return true; } /* Check if a table can reach the target component through the traversal * relationship. */ bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { const ecs_query_t *q = &ctx->query->pub; ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_query_up_impl_t *impl = op_ctx->impl; ecs_iter_t *it = ctx->it; if (!impl) { ecs_allocator_t *a = flecs_query_get_allocator(it); impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); } impl->trav = q->terms[op->term_index].trav; if (!impl->cr_trav) { impl->cr_trav = flecs_components_get(ctx->world, ecs_pair(impl->trav, EcsWildcard)); } if (!impl->cr_trav || !flecs_table_cache_count(&impl->cr_trav->cache)) { /* If there are no tables with traversable relationship, there are no * matches. */ return false; } if (!redo) { impl->trav = q->terms[op->term_index].trav; impl->with = flecs_query_op_get_id(op, ctx); impl->cr_with = flecs_components_get(ctx->world, impl->with); /* If component record for component doesn't exist, there are no matches */ if (!impl->cr_with) { return false; } /* Get the range (table) that is currently being evaluated. In most * cases the range will cover the entire table, but in some cases it * can only cover a subset of the entities in the table. */ ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!range.table) { return false; } op_ctx->range = range; if (!op_ctx->range.count) { op_ctx->range.count = ecs_table_count(op_ctx->range.table); } op_ctx->cur = -1; /* Handle tables with non-fragmenting ChildOf */ if (impl->trav == EcsChildOf) { if (range.table->flags & EcsTableHasParent) { if (q->flags & EcsQueryNested) { /* If this is a nested query (used to populate a cache), * don't store entries for individual entities in the cache. * Instead, match the entire table, and figure out from * which parent the entity gets the component in an uncached * operation. */ /* Signal that the uncached instruction needs to search. * This helps distinguish between tables with a Parent * component that own the component vs. those that don't. */ it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; return true; } /* Signals that we need to evaluate individual rows. */ op_ctx->cur = 0; flecs_query_src_set_single(op, range.offset, ctx); } } } else { next_row: if (op_ctx->cur == -1) { /* The table either can or can't reach the component, nothing to do * for a second evaluation of this operation. */ return false; } /* Evaluate next row. Only necessary for non-fragmenting ChildOf since * in that case different table rows can have different parents. */ op_ctx->cur ++; if (op_ctx->cur >= op_ctx->range.count) { return false; } flecs_query_src_set_single(op, op_ctx->range.offset + op_ctx->cur, ctx); } /* Get entry from up traversal cache. The up traversal cache contains * the entity on which the component was found, with additional metadata * on where it is stored. */ ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &impl->cache, op_ctx->range.table, op_ctx->range.offset + op_ctx->cur, impl->with, impl->trav, impl->cr_with, impl->cr_trav); if (!up) { /* Component is not reachable from table */ goto next_row; } it->sources[op->field_index] = flecs_entities_get_alive( ctx->world, up->src); flecs_query_it_set_tr(it, op->field_index, up->tr); it->ids[op->field_index] = up->id; if (op->match_flags & EcsTermMatchAny) { it->ids[op->field_index] = ecs_pair(impl->trav, EcsWildcard); } flecs_query_set_vars(op, up->id, ctx); flecs_set_source_set_flag(it, op->field_index); return true; } /* Check if a table can reach the target component through the traversal * relationship, or if the table has the target component itself. */ bool flecs_query_self_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_query_up_impl_t *impl = op_ctx->impl; ecs_iter_t *it = ctx->it; if (!redo) { if (!op_ctx->impl) { ecs_allocator_t *a = flecs_query_get_allocator(it); impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); } flecs_reset_source_set_flag(ctx->it, op->field_index); op_ctx->cur = -1; impl->trav = 0; impl->with = flecs_query_op_get_id(op, ctx); } /* First check if table has the component */ if (op_ctx->impl->trav == 0) { bool result; ecs_id_t with = impl->with; if (ECS_PAIR_FIRST(with) == EcsChildOf) { if (with == ecs_childof(EcsWildcard)) { result = flecs_query_tree_and_wildcard(op, redo, ctx, false); } else { result = flecs_query_tree_and(op, redo, ctx); } } else { result = flecs_query_with(op, redo, ctx); } if (result) { /* Table has component, no need to traverse */ if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { /* Matching self, so set sources to 0 */ it->sources[op->field_index] = 0; flecs_reset_source_set_flag(it, op->field_index); const ecs_table_record_t *tr = it->trs[op->field_index]; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = tr ? tr->column : -1; } return true; } } /* Table doesn't have component, traverse relationship */ return flecs_query_up_with(op, redo, ctx); } bool flecs_query_up( 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_up_with(op, redo, ctx); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); } } bool flecs_query_self_up( 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_self_up_with(op, redo, ctx); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); } } void flecs_query_set_iter_this( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx) { const ecs_var_t *var = &ctx->vars[0]; const ecs_table_range_t *range = &var->range; ecs_table_t *table = range->table; int32_t count = range->count; if (table) { if (!count) { count = ecs_table_count(table); } it->table = table; it->offset = range->offset; it->count = count; it->entities = ecs_table_entities(table); if (it->entities) { it->entities += it->offset; } } else if (count == 1) { it->count = 1; it->entities = &ctx->vars[0].entity; } } ecs_query_op_ctx_t* flecs_op_ctx_( const ecs_query_run_ctx_t *ctx) { return &ctx->op_ctx[ctx->op_index]; } #define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) void flecs_reset_source_set_flag( ecs_iter_t *it, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); } void flecs_set_source_set_flag( ecs_iter_t *it, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); ECS_TERMSET_SET(it->up_fields, 1u << field_index); ECS_CONST_CAST(int16_t*, it->columns)[field_index] = -1; } ecs_table_range_t flecs_query_var_get_range( int32_t var_id, const ecs_query_run_ctx_t *ctx) { ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; ecs_table_t *table = var->range.table; if (table) { return var->range; } ecs_entity_t entity = var->entity; if (entity && entity != EcsWildcard) { var->range = flecs_range_from_entity(ctx->world, entity); return var->range; } return (ecs_table_range_t){ 0 }; } ecs_table_t* flecs_query_var_get_table( int32_t var_id, const ecs_query_run_ctx_t *ctx) { ecs_var_t *var = &ctx->vars[var_id]; ecs_table_t *table = var->range.table; if (table) { return table; } ecs_entity_t entity = var->entity; if (entity && entity != EcsWildcard) { var->range = flecs_range_from_entity(ctx->world, entity); return var->range.table; } return NULL; } ecs_table_t* flecs_query_get_table( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); if (flags & EcsQueryIsEntity) { return ecs_get_table(ctx->world, ref->entity); } else { return flecs_query_var_get_table(ref->var, ctx); } } ecs_table_range_t flecs_query_get_range( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); if (flags & EcsQueryIsEntity) { ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); return flecs_range_from_entity(ctx->world, ref->entity); } else { ecs_var_t *var = &ctx->vars[ref->var]; if (var->range.table) { return ctx->vars[ref->var].range; } else if (var->entity) { return flecs_range_from_entity(ctx->world, var->entity); } } return (ecs_table_range_t){0}; } ecs_entity_t flecs_query_var_get_entity( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; ecs_entity_t entity = var->entity; if (entity) { return entity; } ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = var->range.table; const ecs_entity_t *entities = ecs_table_entities(table); var->entity = entities[var->range.offset]; return var->entity; } void flecs_query_var_reset( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { ctx->vars[var_id].entity = EcsWildcard; ctx->vars[var_id].range.table = NULL; } void flecs_query_var_set_range( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx) { (void)op; ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_query_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->entity = 0; var->range = (ecs_table_range_t){ .table = table, .offset = offset, .count = count }; } void flecs_query_src_set_single( const ecs_query_op_t *op, int32_t row, const ecs_query_run_ctx_t *ctx) { if (!(op->flags & (EcsQueryIsVar << EcsQuerySrc))) { return; } ecs_var_id_t src = op->src.var; ecs_var_t *var = &ctx->vars[src]; ecs_assert(var->range.table != NULL, ECS_INTERNAL_ERROR, NULL); var->range.offset = row; var->range.count = 1; var->entity = ecs_table_entities(var->range.table)[row]; } void flecs_query_src_set_range( const ecs_query_op_t *op, const ecs_table_range_t *range, const ecs_query_run_ctx_t *ctx) { if (!(op->flags & (EcsQueryIsVar << EcsQuerySrc))) { return; } ecs_var_id_t src = op->src.var; ecs_var_t *var = &ctx->vars[src]; var->entity = 0; ecs_assert(var->range.table == range->table, ECS_INTERNAL_ERROR, NULL); var->range = *range; if (ctx->query_vars[src].kind != EcsVarTable) { ecs_assert(range->count == 1, ECS_INTERNAL_ERROR, NULL); var->entity = ecs_table_entities(range->table)[range->offset]; } } void flecs_query_var_set_entity( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_entity_t entity, const ecs_query_run_ctx_t *ctx) { (void)op; ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_query_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->range.table = NULL; var->entity = entity; } void flecs_query_set_vars( const ecs_query_op_t *op, ecs_id_t id, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); if (flags_1st & EcsQueryIsVar) { ecs_var_id_t var = op->first.var; if (op->written & (1ull << var)) { if (ECS_IS_PAIR(id)) { flecs_query_var_set_entity( op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); } else { flecs_query_var_set_entity(op, var, id, ctx); } } } if (flags_2nd & EcsQueryIsVar) { ecs_var_id_t var = op->second.var; if (op->written & (1ull << var)) { flecs_query_var_set_entity( op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); } } } ecs_table_range_t flecs_get_ref_range( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx) { if (flag & EcsQueryIsEntity) { return flecs_range_from_entity(ctx->world, ref->entity); } else if (flag & EcsQueryIsVar) { return flecs_query_var_get_range(ref->var, ctx); } return (ecs_table_range_t){0}; } ecs_entity_t flecs_get_ref_entity( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx) { if (flag & EcsQueryIsEntity) { return ref->entity; } else if (flag & EcsQueryIsVar) { return flecs_query_var_get_entity(ref->var, ctx); } return 0; } ecs_id_t flecs_query_op_get_id_w_written( const ecs_query_op_t *op, uint64_t written, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_entity_t first = 0, second = 0; if (flags_1st) { if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { first = flecs_get_ref_entity(&op->first, flags_1st, ctx); } else if (flags_1st & EcsQueryIsVar) { first = EcsWildcard; } } if (flags_2nd) { if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); } else if (flags_2nd & EcsQueryIsVar) { second = EcsWildcard; } } if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { return ecs_pair(first, second); } else { return flecs_entities_get_alive(ctx->world, first); } } ecs_id_t flecs_query_op_get_id( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; return flecs_query_op_get_id_w_written(op, written, ctx); } int16_t flecs_query_next_column( ecs_table_t *table, ecs_id_t id, int32_t column) { if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { column = column + 1; } else { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); column = ecs_search_offset(NULL, table, column + 1, id, NULL); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); } return flecs_ito(int16_t, column); } void flecs_query_it_set_tr( ecs_iter_t *it, int32_t field_index, const ecs_table_record_t *tr) { ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); it->trs[field_index] = tr; ECS_CONST_CAST(int16_t*, it->columns)[field_index] = (tr && !it->sources[field_index] && !(it->up_fields & (1llu << field_index))) ? tr->column : -1; } static ecs_id_t flecs_query_it_set_id( ecs_iter_t *it, ecs_table_t *table, int32_t field_index, int32_t column) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); return it->ids[field_index] = table->type.array[column]; } void flecs_query_set_match( const ecs_query_op_t *op, ecs_table_t *table, int32_t column, const ecs_query_run_ctx_t *ctx) { int32_t field_index = op->field_index; if (field_index == -1 || column == -1) { return; } ecs_iter_t *it = ctx->it; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = &table->_->records[column]; flecs_query_it_set_tr(it, field_index, tr); ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); flecs_query_set_vars(op, matched, ctx); } void flecs_query_set_trav_match( const ecs_query_op_t *op, const ecs_table_record_t *tr, ecs_entity_t trav, ecs_entity_t second, const ecs_query_run_ctx_t *ctx) { int32_t field_index = op->field_index; if (field_index == -1) { return; } ecs_iter_t *it = ctx->it; ecs_id_t matched = ecs_pair(trav, second); it->ids[op->field_index] = matched; flecs_query_it_set_tr(it, op->field_index, tr); flecs_query_set_vars(op, matched, ctx); } bool flecs_query_table_filter( ecs_table_t *table, ecs_query_lbl_t other, ecs_flags32_t filter_mask) { uint32_t filter = flecs_ito(uint32_t, other); return (table->flags & filter_mask & filter) != 0; } static void flecs_query_build_down_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity) { ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(trav, entity)); if (!cr) { return; } ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); elem->entity = entity; elem->cr = cr; ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&cr->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; if (!table->_->traversable_count) { continue; } int32_t i, count = ecs_table_count(table); const ecs_entity_t *entities = ecs_table_entities(table); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->row & EcsEntityIsTraversable) { flecs_query_build_down_cache( world, a, ctx, cache, trav, entities[i]); } } } } } static void flecs_query_build_up_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table, const ecs_table_record_t *tr, int32_t root_column) { ecs_id_t *ids = table->type.array; int32_t i = tr->index, end = i + tr->count; bool is_root = root_column == -1; for (; i < end; i ++) { ecs_entity_t second = ecs_pair_second(world, ids[i]); if (is_root) { root_column = i; } ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); el->entity = second; el->tr = &table->_->records[i]; el->cr = NULL; ecs_record_t *r = flecs_entities_get_any(world, second); if (r->table) { const ecs_table_record_t *r_tr = flecs_component_get_table( cache->cr, r->table); if (!r_tr) { continue; } flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, r_tr, root_column); } } } void flecs_query_trav_cache_fini( ecs_allocator_t *a, ecs_trav_cache_t *cache) { ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); } void flecs_query_get_trav_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity) { if (cache->id != ecs_pair(trav, entity) || cache->up) { ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); cache->id = ecs_pair(trav, entity); cache->up = false; } } void flecs_query_get_trav_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_component_record_t *cr = cache->cr; if (!cr || cr->id != ecs_pair(trav, EcsWildcard)) { cr = cache->cr = flecs_components_get(world, ecs_pair(trav, EcsWildcard)); if (!cr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } ecs_id_t id = table->type.array[tr->index]; if (cache->id != id || !cache->up) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); cache->id = id; cache->up = true; } } static void flecs_trav_entity_down_isa( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_component_record_t *cr_with, bool self, bool empty); static void flecs_trav_entity_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_component_record_t *cr_trav, ecs_component_record_t *cr_with, bool self, bool empty); static ecs_trav_down_t* flecs_trav_table_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, const ecs_table_range_t *range, ecs_component_record_t *cr_with, bool self, bool empty) { ecs_table_t *table = range->table; ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); if (!table->_->traversable_count) { return dst; } ecs_assert(cr_with != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_entity_t *entities = ecs_table_entities(table); int32_t i = range->offset, end = i + range->count; for (; i < end; i ++) { ecs_entity_t entity = entities[i]; ecs_record_t *record = flecs_entities_get(world, entity); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_component_record_t *cr_trav = flecs_components_get(world, ecs_pair(trav, entity)); if (!cr_trav) { continue; } flecs_trav_entity_down(world, a, cache, dst, trav, cr_trav, cr_with, self, empty); } } return dst; } static void flecs_trav_entity_down_isa( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_component_record_t *cr_with, bool self, bool empty) { if (trav == EcsIsA || !world->cr_isa_wildcard) { return; } ecs_component_record_t *cr_isa = flecs_components_get( world, ecs_pair(EcsIsA, entity)); if (!cr_isa) { return; } ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&cr_isa->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->_->traversable_count) { continue; } if (ecs_table_has_id(world, table, cr_with->id)) { /* Table owns component */ continue; } const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *record = flecs_entities_get(world, e); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_component_record_t *cr_trav = flecs_components_get(world, ecs_pair(trav, e)); if (cr_trav) { flecs_trav_entity_down(world, a, cache, dst, trav, cr_trav, cr_with, self, empty); } flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, cr_with, self, empty); } } } } } static void flecs_trav_entity_down_iter_children( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_component_record_t *cr_trav, ecs_component_record_t *cr_with, bool self, bool empty) { (void)cache; (void)trav; (void)empty; ecs_vec_t *children = &cr_trav->pair->ordered_children; int32_t i, count = ecs_vec_count(children); ecs_entity_t *elems = ecs_vec_first(children); for (i = 0; i < count; i ++) { ecs_entity_t e = elems[i]; ecs_record_t *r = flecs_entities_get(world, e); bool leaf = false; /* Check if table has the component */ if (self || r->table->_->traversable_count) { if (flecs_component_get_table(cr_with, r->table) != NULL) { if (self) { continue; } leaf = true; } } /* Add element to the cache for a single child */ ecs_trav_down_elem_t *elem = ecs_vec_append_t( a, &dst->elems, ecs_trav_down_elem_t); elem->range.table = r->table; elem->range.offset = ECS_RECORD_TO_ROW(r->row); elem->range.count = 1; elem->leaf = leaf; } } static void flecs_trav_entity_down_iter_tables( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_component_record_t *cr_trav, ecs_component_record_t *cr_with, bool self, bool empty) { (void)cache; ecs_table_cache_iter_t it; bool result; if (empty) { result = flecs_table_cache_all_iter(&cr_trav->cache, &it); } else { result = flecs_table_cache_iter(&cr_trav->cache, &it); } if (result) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = tr->hdr.table; bool leaf = false; if (self || table->_->traversable_count) { if (flecs_component_get_table(cr_with, table) != NULL) { if (self) { continue; } leaf = true; } } /* If record is not the first instance of (trav, *), don't add it * to the cache. */ int32_t index = tr->index; if (index) { ecs_id_t id = table->type.array[index - 1]; if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { int32_t col = ecs_search_relation(world, table, 0, cr_with->id, trav, EcsUp, NULL, NULL, &tr); ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); if (col != index) { /* First relationship through which the id is * reachable is not the current one, so skip. */ continue; } } } ecs_trav_down_elem_t *elem = ecs_vec_append_t( a, &dst->elems, ecs_trav_down_elem_t); elem->range.table = table; elem->range.offset = 0; elem->range.count = ecs_table_count(table); elem->leaf = leaf; } } } static void flecs_trav_entity_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_component_record_t *cr_trav, ecs_component_record_t *cr_with, bool self, bool empty) { ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr_trav != NULL, ECS_INTERNAL_ERROR, NULL); int32_t first = ecs_vec_count(&dst->elems); if (cr_trav->flags & EcsIdOrderedChildren) { flecs_trav_entity_down_iter_children( world, a, cache, dst, trav, cr_trav, cr_with, self, empty); } else { flecs_trav_entity_down_iter_tables( world, a, cache, dst, trav, cr_trav, cr_with, self, empty); } /* Breadth first walk */ int32_t t, last = ecs_vec_count(&dst->elems); for (t = first; t < last; t ++) { ecs_trav_down_elem_t *elem = ecs_vec_get_t( &dst->elems, ecs_trav_down_elem_t, t); if (!elem->leaf) { flecs_trav_table_down(world, a, cache, dst, trav, &elem->range, cr_with, self, empty); } } } ecs_trav_down_t* flecs_query_get_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t e, ecs_component_record_t *cr_with, bool self, bool empty) { ecs_world_t *world = ctx->it->real_world; ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); cache->dir = EcsTravDown; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_trav_down_t *result = &cache->down; ecs_vec_init_if_t(&result->elems, ecs_trav_down_elem_t); ecs_vec_clear(&result->elems); ecs_component_record_t *cr_trav = flecs_components_get(world, ecs_pair(trav, e)); if (!cr_trav) { if (trav != EcsIsA) { if (cr_with->flags & EcsIdOnInstantiateInherit) { flecs_trav_entity_down_isa( world, a, cache, result, trav, e, cr_with, self, empty); } } return result; } /* Cover IsA -> trav paths. If a parent inherits a component, then children * of that parent should find the component through up traversal. */ if (cr_with->flags & EcsIdOnInstantiateInherit) { flecs_trav_entity_down_isa( world, a, cache, result, trav, e, cr_with, self, empty); } flecs_trav_entity_down( world, a, cache, result, trav, cr_trav, cr_with, self, empty); return result; } void flecs_query_down_cache_fini( ecs_allocator_t *a, ecs_trav_up_cache_t *cache) { ecs_vec_fini_t(a, &cache->down.elems, ecs_trav_down_elem_t); } static ecs_trav_up_t* flecs_trav_up_ensure( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t tgt) { ecs_trav_up_t **trav = ecs_map_ensure_ref( &cache->src, ecs_trav_up_t, tgt); if (!trav[0]) { trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); } return trav[0]; } static int32_t flecs_trav_type_search( ecs_trav_up_t *up, const ecs_table_t *table, ecs_component_record_t *cr_with, ecs_type_t *type) { ecs_table_record_t *tr = ecs_table_cache_get(&cr_with->cache, table); if (tr) { up->id = type->array[tr->index]; up->tr = tr; return tr->index; } return -1; } static int32_t flecs_trav_type_offset_search( ecs_trav_up_t *up, const ecs_table_t *table, int32_t offset, ecs_id_t with, ecs_type_t *type) { ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); while (offset < type->count) { ecs_id_t type_id = type->array[offset ++]; if (ecs_id_match(type_id, with)) { up->id = type_id; up->tr = &table->_->records[offset - 1]; return offset - 1; } } return -1; } static ecs_trav_up_t* flecs_trav_table_up( const ecs_query_run_ctx_t *ctx, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, const ecs_world_t *world, ecs_entity_t src, ecs_id_t with, ecs_id_t rel, ecs_component_record_t *cr_with, ecs_component_record_t *cr_trav); static void flecs_trav_table_up_w( const ecs_query_run_ctx_t *ctx, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, const ecs_world_t *world, ecs_entity_t src, ecs_id_t with, ecs_id_t rel, ecs_component_record_t *cr_with, ecs_component_record_t *cr_trav, ecs_trav_up_t *up) { ecs_record_t *src_record = flecs_entities_get_any(world, src); ecs_table_t *table = src_record->table; if (!table) { goto not_found; } ecs_type_t type = table->type; if (cr_with->flags & EcsIdDontFragment) { if (flecs_sparse_has(cr_with->sparse, src)) { up->src = src; up->tr = NULL; up->id = cr_with->id; goto found; } } else { if (flecs_trav_type_search(up, table, cr_with, &type) >= 0) { up->src = src; goto found; } else if (ECS_PAIR_FIRST(with) == EcsChildOf) { if (table->flags & EcsTableHasParent) { const EcsParent *p = ecs_table_get_id( world, table, ecs_id(EcsParent), ECS_RECORD_TO_ROW(src_record->row)); ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); if ((uint32_t)p->value == ECS_PAIR_SECOND(with)) { up->src = src; up->tr = NULL; up->id = cr_with->id; goto found; } } } } ecs_flags32_t flags = table->flags; if ((flags & EcsTableHasPairs) && rel) { bool is_a = cr_trav == world->cr_isa_wildcard; if (is_a) { if (!(flags & EcsTableHasIsA)) { goto not_found; } if (!flecs_type_can_inherit_id(world, table, cr_with, with)) { goto not_found; } } if ((rel == ecs_pair(EcsChildOf, EcsWildcard) && (flags & EcsTableHasParent))) { const EcsParent *p = ecs_table_get_id( world, table, ecs_id(EcsParent), ECS_RECORD_TO_ROW(src_record->row)); ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, p->value, with, rel, cr_with, cr_trav); if (up_parent->src) { up->src = up_parent->src; up->tr = up_parent->tr; up->id = up_parent->id; goto found; } } ecs_trav_up_t up_pair = {0}; int32_t r_column = flecs_trav_type_search( &up_pair, table, cr_trav, &type); while (r_column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, tgt, with, rel, cr_with, cr_trav); if (up_parent->src) { up->src = up_parent->src; up->tr = up_parent->tr; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, table, r_column + 1, rel, &type); } if (!is_a && (cr_with->flags & EcsIdOnInstantiateInherit)) { cr_trav = world->cr_isa_wildcard; r_column = flecs_trav_type_search( &up_pair, table, cr_trav, &type); while (r_column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, tgt, with, rel, cr_with, cr_trav); if (up_parent->src) { up->src = up_parent->src; up->tr = up_parent->tr; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, table, r_column + 1, rel, &type); } } } not_found: up->tr = NULL; found: return; } static ecs_trav_up_t* flecs_trav_table_up( const ecs_query_run_ctx_t *ctx, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, const ecs_world_t *world, ecs_entity_t src, ecs_id_t with, ecs_id_t rel, ecs_component_record_t *cr_with, ecs_component_record_t *cr_trav) { ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); if (up->ready) { return up; } flecs_trav_table_up_w(ctx, a, cache, world, src, with, rel, cr_with, cr_trav, up); up->ready = true; return up; } ecs_trav_up_t* flecs_query_get_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_table_t *table, int32_t row, ecs_id_t with, ecs_entity_t trav, ecs_component_record_t *cr_with, ecs_component_record_t *cr_trav) { if (cache->with && cache->with != with) { flecs_query_up_cache_fini(cache); } ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_map_init_if(&cache->src, a); ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); cache->dir = EcsTravUp; cache->with = with; ecs_assert(cr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr_trav != NULL, ECS_INTERNAL_ERROR, NULL); if (trav == EcsChildOf) { if (table->flags & EcsTableHasParent) { ecs_assert(row != -1, ECS_INTERNAL_ERROR, NULL); const EcsParent *p = flecs_query_tree_get_parents((ecs_table_range_t){ .table = table, .offset = row, .count = 1 }); ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t tgt = (uint32_t)p->value; ecs_trav_up_t *result = &cache->up; *result = (ecs_trav_up_t){0}; flecs_trav_table_up_w(ctx, a, cache, world, tgt, with, ecs_pair(trav, EcsWildcard), cr_with, cr_trav, result); if (result->src != 0) { return result; } return NULL; } } ecs_table_record_t *tr = ecs_table_cache_get(&cr_trav->cache, table); if (!tr) { return NULL; /* Table doesn't have the relationship */ } int32_t i = tr->index, end = i + tr->count; for (; i < end; i ++) { ecs_id_t id = table->type.array[i]; ecs_entity_t tgt = ECS_PAIR_SECOND(id); ecs_trav_up_t *result = &cache->up; *result = (ecs_trav_up_t){0}; flecs_trav_table_up_w(ctx, a, cache, world, tgt, with, ecs_pair(trav, EcsWildcard), cr_with, cr_trav, result); if (result->src != 0) { return result; } } return NULL; } void flecs_query_up_cache_fini( ecs_trav_up_cache_t *cache) { ecs_map_fini(&cache->src); } static bool flecs_query_trivial_search_init( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, const ecs_query_t *query, bool redo, ecs_flags64_t term_set) { if (!redo) { /* Find first trivial term */ int32_t t = 0; if (term_set) { for (; t < query->term_count; t ++) { if (term_set & (1llu << t)) { break; } } } ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); op_ctx->start_from = t; ecs_component_record_t *cr = flecs_components_get(ctx->world, query->ids[t]); if (!cr) { return false; } if (query->flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_all_iter(&cr->cache, &op_ctx->it)){ return false; } } else { if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it)) { return false; } } /* Find next term to evaluate once */ for (t = t + 1; t < query->term_count; t ++) { if (term_set & (1llu << t)) { break; } } op_ctx->first_to_eval = t; } return true; } bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t term_set) { const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; int32_t t, term_count = query->pub.term_count; if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { return false; } uint64_t q_filter = q->bloom_filter; do { const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { continue; } if (!flecs_table_bloom_filter_test(table, q_filter)) { continue; } int16_t *columns = ECS_CONST_CAST(int16_t*, it->columns); for (t = op_ctx->first_to_eval; t < term_count; t ++) { if (!(term_set & (1llu << t))) { continue; } const ecs_term_t *term = &terms[t]; ecs_component_record_t *cr = flecs_components_get(ctx->world, term->id); if (!cr) { break; } const ecs_table_record_t *tr_with = flecs_component_get_table( cr, table); if (!tr_with) { break; } it->trs[term->field_index] = tr_with; columns[term->field_index] = tr_with->column; } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; it->trs[op_ctx->start_from] = tr; columns[op_ctx->start_from] = tr->column; break; } } while (true); return true; } bool flecs_query_is_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo) { const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; const ecs_id_t *ids = q->ids; ecs_iter_t *it = ctx->it; int32_t t, term_count = query->pub.term_count; if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { return false; } uint64_t q_filter = q->bloom_filter; next: { const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { goto next; } if (!flecs_table_bloom_filter_test(table, q_filter)) { goto next; } int16_t *columns = ECS_CONST_CAST(int16_t*, it->columns); for (t = 1; t < term_count; t ++) { ecs_component_record_t *cr = flecs_components_get(ctx->world, ids[t]); if (!cr) { return false; } const ecs_table_record_t *tr_with = flecs_component_get_table( cr, table); if (!tr_with) { goto next; } it->trs[t] = tr_with; columns[t] = tr_with->column; } it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); it->trs[0] = tr; columns[0] = tr->column; } return true; } bool flecs_query_trivial_test( const ecs_query_run_ctx_t *ctx, bool redo, ecs_flags64_t term_set) { if (redo) { return false; } else { const ecs_query_impl_t *impl = ctx->query; const ecs_query_t *q = &impl->pub; const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; int32_t t, term_count = impl->pub.term_count; ecs_table_t *table = it->table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the variable set on the iterator is missing a table"); if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { return false; } int16_t *columns = ECS_CONST_CAST(int16_t*, it->columns); for (t = 0; t < term_count; t ++) { if (!(term_set & (1llu << t))) { continue; } const ecs_term_t *term = &terms[t]; ecs_component_record_t *cr = flecs_components_get(q->world, term->id); if (!cr) { return false; } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return false; } it->trs[term->field_index] = tr; columns[term->field_index] = tr->column; } it->entities = ecs_table_entities(table); if (it->entities) { it->entities = &it->entities[it->offset]; } return true; } }