#include "flecs.h" #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H #if !defined(__MACH__) && !defined(_POSIX_C_SOURCE) #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #ifdef ECS_TARGET_WINDOWS #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #endif #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; #ifdef FLECS_ENTITY_RANGES ecs_vec_t ranges; /* vec - sorted by min */ ecs_entity_range_t *active_range; /* Currently active range (NULL = off) */ #endif 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); #ifdef FLECS_ENTITY_RANGES /* Initialize entity range state. */ void flecs_entity_ranges_init( ecs_allocator_t *allocator, ecs_entity_index_t *index); /* Deinitialize entity range state. */ void flecs_entity_ranges_fini( ecs_entity_index_t *index); void flecs_entity_ranges_on_ensure( ecs_entity_index_t *index, uint32_t id); /* Move a recycled entity to the range it belongs to. */ void flecs_entity_ranges_on_delete( ecs_entity_index_t *index, uint64_t entity, ecs_record_t *r); /* 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); #endif /* 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_ /** Table cache. * Stores table records in a dense array for cache-friendly iteration. The map * tracks the index of each table record into the array so that insertion and * removal remain O(1) amortized. */ typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_vec_t records; /* vec */ int32_t queryable_count; /* Number of queryable tables, stored at front */ } 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 flecs_table_cache_set_column( ecs_table_cache_t *cache, const ecs_table_t *table, int16_t column); 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); const ecs_table_cache_elem_t* flecs_table_cache_get_elem( const ecs_table_cache_t *cache, const ecs_table_t *table); #define flecs_table_cache_count(cache) ecs_vec_count(&(cache)->records) bool flecs_table_cache_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out, ecs_flags32_t empty_flags); bool flecs_table_cache_queryable_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out, ecs_flags32_t empty_flags); const ecs_table_cache_elem_t* flecs_table_cache_next( ecs_table_cache_iter_t *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; /* A child that switches between the ChildOf pair and Parent component * representations is removed from ordered_children by the table move and * added back by the hooks of the new representation. When the parent didn't * change the child must keep its old position, so remember where it was. */ ecs_entity_t ordered_pending_entity; int32_t ordered_pending_index; /* Lists for all id records that match a pair wildcard. The wildcard id * record is at the head of the list. */ ecs_id_record_elem_t first; /* (R, *) */ ecs_id_record_elem_t second; /* (*, T) */ ecs_id_record_elem_t trav; /* (*, T) with only traversable relationships */ /* Parent component record. For pair records the parent is the (R, *) record. */ ecs_component_record_t *parent; /* Cache for finding components that are reachable through a relationship */ ecs_reachable_cache_t reachable; } ecs_pair_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); /* Update the EcsIdHasBases flag for entities that got an IsA pair added or * removed. Used to track which components have base components, so that tables * can register records for the base ids of the components they store. */ void flecs_components_on_isa_change( ecs_world_t *world, const ecs_table_t *table, const ecs_entity_t *entities, int32_t count, bool has_bases); /* 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; typedef struct ecs_graph_edges_t ecs_graph_edges_t; typedef struct ecs_graph_edge_t { struct ecs_graph_edge_t *next; struct ecs_graph_edge_t **prev; ecs_graph_edges_t *from; ecs_table_t *to; ecs_table_diff_t *diff; ecs_id_t id; } ecs_graph_edge_t; struct ecs_graph_edges_t { ecs_graph_edge_t *lo; ecs_map_t *hi; }; typedef struct ecs_graph_node_t { ecs_graph_edges_t add; ecs_graph_edges_t remove; ecs_graph_edge_t *incoming[2]; } 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_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_entities(table) {\ .array = table->data.entities,\ .count = table->data.count,\ .size = table->data.size,\ } #endif typedef struct ecs_table_override_base_t { const ecs_pair_record_t *pair; int32_t generation; } ecs_table_override_base_t; typedef struct ecs_table_overrides_t { ecs_ref_t *refs; int32_t count; ecs_size_t size; ecs_table_override_base_t bases[1]; } 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 inherited_start; /* First record registered for a base id */ 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 */ }; extern const int16_t flecs_table_empty_component_map[]; /* Test whether a table record was registered for a base id (meaning the table * stores a component that inherits from it) instead of for an id the table * actually stores. */ #define flecs_table_record_is_inherited(table, tr)\ (((tr) - (table)->_->records) >= (table)->_->inherited_start) /* 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); /* 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, const ecs_table_t *dst, 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. */ /* Keep the position of a child that is about to be re-added to the same parent * with a different ChildOf representation. Pass -1 to clear. */ void flecs_ordered_entities_keep_position( ecs_component_record_t *cr, ecs_entity_t e, int32_t index); int32_t 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; #define flecs_query_impl(query) (ECS_CONST_CAST(ecs_query_impl_t*, query)) #ifdef FLECS_QUERY_PLANS typedef uint8_t ecs_var_id_t; typedef int16_t ecs_query_lbl_t; typedef ecs_flags64_t ecs_write_flags_t; #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 */ 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 */ 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. */ EcsQueryTreeUpNot, /* Up traversal for ChildOf Not terms that evaluates tables w/Parent component per entity. */ EcsQueryTreeSelfUpNot, /* Up traversal for ChildOf Not terms that evaluates tables w/Parent component per entity. */ 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) #define EcsQueryIsValuePair (1 << 7) /* 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; typedef struct { ecs_component_record_t *cr; ecs_table_cache_iter_t it; int16_t column; int16_t remaining; bool non_fragmenting; ecs_table_t *prev_table; const ecs_table_record_t *prev_tr; } ecs_query_table_iter_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_table_t *prev_table; const ecs_table_record_t *prev_tr; ecs_component_record_t *df_cr; int32_t cur; int32_t row; int32_t count; } ecs_query_and_ctx_t; /* Sparse context */ typedef struct { ecs_query_table_iter_ctx_t and_; /* For mixed results */ ecs_sparse_t *sparse; ecs_table_range_t range; ecs_id_t id; int32_t cur; bool self; 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_table_iter_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_id_t matched; /* 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_table_t *last_down_table; bool start_down_walk; } ecs_query_up_impl_t; typedef struct { union { ecs_query_table_iter_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; ecs_entity_t trav; ecs_id_t with; ecs_component_record_t *cr_with; ecs_component_record_t *cr_trav; /* Data for returning tables with non-fragmenting ChildOf */ const EcsParent *parents; ecs_table_range_t range; int32_t cur; int32_t cur_count; bool match_inherited; } ecs_query_up_ctx_t; typedef struct { union { ecs_query_table_iter_ctx_t and_; ecs_query_up_ctx_t up_; } is; ecs_query_tree_iter_state_t state; } ecs_query_tree_pre_ctx_t; typedef struct { union { ecs_query_table_iter_ctx_t and_; ecs_query_up_ctx_t up_; ecs_query_tree_ctx_t tree_; } is; ecs_entity_t tgt; ecs_table_range_t range; int32_t cur; } ecs_query_tree_up_ctx_t; /* Cache for storing results of upward/downward "all" traversal. This type of * traversal iterates and caches the entire tree. */ typedef struct { ecs_entity_t entity; ecs_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_table_iter_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 sparse iterator context */ #define FLECS_QUERY_SPARSE_BATCH_SIZE (1024) typedef struct { ecs_sparse_t **sparse; ecs_sparse_t *lead_sparse; ecs_entity_t *entities; int32_t cur; } ecs_query_sparse_trivial_ctx_t; /* *From operator iterator context */ typedef struct { ecs_query_table_iter_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_sparse_trivial_ctx_t sparse_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_up_ctx_t tree_up; 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; #endif // FLECS_QUERY_PLANS /* Query run state */ typedef struct { #ifdef FLECS_QUERY_PLANS 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_query_op_ctx_t *op_ctx; /* Operation context (stack) */ const ecs_query_var_t *query_vars; /* Reference to query variable array */ #endif ecs_iter_t *it; /* Iterator */ ecs_world_t *world; /* Reference to world */ const ecs_query_impl_t *query; /* Reference to query */ 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 */ #ifdef FLECS_QUERY_PLANS /* 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 */ int16_t tree_cache_op; int16_t op_ctx_zero_first; int16_t op_ctx_zero_count; int16_t op_ctx_fini_first; int16_t op_ctx_fini_count; #endif /* 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 */ ecs_termset_t dont_fragment_fields; /* Fields with only DontFragment terms */ #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 */ /* Component record cache */ ecs_component_record_t **cr_cache; /* 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); #ifdef FLECS_QUERY_PLANS /* 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); /* 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); #endif #endif #ifndef FLECS_QUERY_CACHE_H #define FLECS_QUERY_CACHE_H typedef struct ecs_query_cache_t ecs_query_cache_t; #ifdef FLECS_CACHED_QUERIES void flecs_query_cache_bootstrap( ecs_world_t *world); void flecs_query_revalidate_table( ecs_world_t *world, ecs_query_impl_t *impl, uint64_t table_id); /** 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. */ 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 */ 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; ecs_observer_t *rematch_observer; ecs_observer_t *ancestry_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; int32_t cascade_by; /* Identify cascade term */ int32_t parent_table_count; int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ 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* 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 where entire query is cached */ bool flecs_query_is_cache_search( 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); #ifdef FLECS_QUERY_PLANS /* Cache search used by query plan operations. */ bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test used by query plan operations. */ bool flecs_query_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); bool flecs_query_tree_cache_search( ecs_query_run_ctx_t *ctx, bool redo); #endif // FLECS_QUERY_PLANS #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 #ifdef FLECS_CACHED_QUERIES void flecs_query_mark_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it); void flecs_query_sync_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_match_t *match); 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 #endif // FLECS_CACHED_QUERIES #endif #ifndef FLECS_QUERY_ENGINE_H #define FLECS_QUERY_ENGINE_H #ifndef FLECS_QUERY_TRIVIAL_ITER_H #define FLECS_QUERY_TRIVIAL_ITER_H void flecs_query_trivial_set_ids( ecs_query_run_ctx_t *ctx); /* 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); #ifdef FLECS_QUERY_PLANS void flecs_query_trivial_set_iter_this( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx); /* Iterator for query plan operations 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); #endif // FLECS_QUERY_PLANS #endif #ifdef FLECS_QUERY_PLANS #ifndef FLECS_QUERY_TRAV_CACHE_H #define FLECS_QUERY_TRAV_CACHE_H #ifdef FLECS_QUERY_PLANS /* 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); int flecs_query_up_reachable( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t with, ecs_entity_t trav, ecs_component_record_t *cr_with, bool match_inherited, ecs_trav_up_t *out); 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 // FLECS_QUERY_PLANS #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_w_var_refs( const ecs_query_op_t *op, ecs_id_t id, const ecs_query_run_ctx_t *ctx); static inline void flecs_query_set_vars( const ecs_query_op_t *op, ecs_id_t id, const ecs_query_run_ctx_t *ctx) { if (op->flags & ((EcsQueryIsVar << EcsQueryFirst)| (EcsQueryIsVar << EcsQuerySecond))) { flecs_query_set_vars_w_var_refs(op, 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); 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_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); bool flecs_query_trivial_sparse_search( const ecs_query_run_ctx_t *ctx, bool redo); /* 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_up_not( 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, 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_init( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx); void flecs_query_up_parent_begin( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx, ecs_table_range_t range); bool flecs_query_up_parent_next( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx, ecs_trav_up_t *up, bool *found); void flecs_query_up_set_fields( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, const ecs_query_up_ctx_t *op_ctx, const ecs_trav_up_t *up); 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 // FLECS_QUERY_PLANS #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); #ifdef FLECS_QUERY_PLANS /* Convert unsigned integer to variable id */ ecs_var_id_t flecs_utovar( uint64_t val); /* 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); /* 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); ecs_id_t flecs_query_iter_set_id( ecs_iter_t *it, int8_t field, ecs_id_t id); /* Convert instruction kind to string */ const char* flecs_query_op_str( uint16_t kind); #endif // FLECS_QUERY_PLANS /* 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); #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); #ifdef FLECS_CACHED_QUERIES /* Reclaim memory from queries */ void flecs_query_reclaim( ecs_query_t *query); #endif #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, int16_t column, 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, void *ptr); /* 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, void *ptr); /* Same as flecs_notify_on_set, but with a known component record. */ void flecs_notify_on_set_w_cr( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id, ecs_component_record_t *cr, bool invoke_hook, void *ptr); /* 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. */ /* 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; 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) */ bool force_delete; /* Delete prefab tables (used for delete_with) */ } 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, bool deferred); void flecs_commands_grow( ecs_stage_t *stage); bool flecs_commands_flush( 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, bool deferred, ecs_entity_t entity, ecs_entity_t component); /* Insert clone command. */ bool flecs_defer_clone( ecs_stage_t *stage, bool deferred, 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, bool deferred, 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, bool deferred, ecs_entity_t entity); /* Insert clear command. */ bool flecs_defer_clear( ecs_stage_t *stage, bool deferred, ecs_entity_t entity); /* Insert delete_with/remove_all command. */ bool flecs_defer_on_delete_action( ecs_stage_t *stage, bool deferred, ecs_id_t id, ecs_entity_t action, bool force_delete); /* Insert enable command (component toggling). */ bool flecs_defer_enable( ecs_stage_t *stage, bool deferred, ecs_entity_t entity, ecs_entity_t component, bool enable); /* Insert add component command. */ bool flecs_defer_add( ecs_stage_t *stage, bool deferred, ecs_entity_t entity, ecs_id_t id); /* Insert remove component command. */ bool flecs_defer_remove( ecs_stage_t *stage, bool deferred, 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));\ } #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)) typedef struct { const ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; flecs_component_ptr_t flecs_table_get_component( ecs_table_t *table, int32_t column_index, int32_t row); 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); /* 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); /* 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_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_now( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component); void flecs_remove_id_now( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component); 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); /* Delete all entities with id, including prefabs when force_delete is true. */ void flecs_delete_with( ecs_world_t *world, ecs_id_t 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); ecs_entity_t flecs_get_prefab_instance_child( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t prefab_child); #ifdef FLECS_DEBUG void flecs_tree_spawner_assert_not_instantiated( ecs_world_t *world, ecs_entity_t parent); void flecs_prefab_assert_not_instantiated( ecs_world_t *world, ecs_entity_t prefab, const ecs_table_diff_t *diff); #else #define flecs_tree_spawner_assert_not_instantiated(world, parent) #define flecs_prefab_assert_not_instantiated(world, prefab, diff) #endif /* Called during bootstrap to register spawner entities with the world. */ void flecs_bootstrap_prefab( ecs_world_t *world); /* Called during world fini to delete all spawners in the world. */ void flecs_fini_prefab( ecs_world_t *world); #ifdef FLECS_PREFAB 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); EcsTreeSpawner* flecs_prefab_spawner_build( ecs_world_t *world, ecs_entity_t base); 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_tree_spawner_fini( ecs_world_t *world, ecs_entity_t parent); void EcsTreeSpawner_free( EcsTreeSpawner *ptr); #else #define flecs_tree_spawner_fini(world, parent) #endif #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; int32_t up_notify_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_termset_t last_event_field; /**< Field that last handled event triggered */ 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); bool flecs_up_notify_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); void flecs_observers_invoke_up_notify( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav); void flecs_observers_invoke_skip_up_notify( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav); /* Invalidate reachable cache. */ const ecs_vec_t* flecs_reachable_cache_get( ecs_world_t *world, ecs_component_record_t *cr); 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 memory from iterator allocator. */ void* flecs_iter_alloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align); /* 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); void flecs_poly_update_ctx( void **ctx, ecs_ctx_free_t *ctx_free, void *value, ecs_ctx_free_t free_value, bool preserve_null); #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; #ifdef FLECS_CACHED_QUERIES ecs_block_allocator_t query_cache; #endif } ecs_stage_allocators_t; typedef struct ecs_stage_ensure_t { ecs_entity_t entity; ecs_type_t ids; } ecs_stage_ensure_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, 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; ecs_commands_t *cmd; ecs_commands_t cmd_root; const ecs_stage_ensure_t *ensure_add; /* 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 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; #ifdef FLECS_SCRIPT /* Thread-specific runtime for script execution */ ecs_script_runtime_t *runtime; #endif }; static inline bool flecs_commands_begin( ecs_world_t *world, ecs_stage_t *stage) { (void)world; if (!stage->cmd->next) { flecs_commands_grow(stage); } stage->cmd = stage->cmd->next; return true; } /* 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); /* Test if component is added by an operation that is about to assign a value. */ bool flecs_stage_is_ensure_add( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component); #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 sparse_chunk; /* Temporary vector for tree spawner */ ecs_vec_t tree_spawner; } ecs_world_allocators_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; int32_t cr_flag_count; int32_t non_fragmenting_child_count; /* Set once any table has dirty state allocated for change detection */ bool has_dirty_state; /* -- 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; /* -- 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 -- */ #ifdef FLECS_MULTI_WORLD ecs_vec_t component_ids; /* World local component ids */ #endif /* 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; /* -- 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); /* Increase type info refcount. */ void flecs_type_info_claim( const ecs_type_info_t *ti); /* Decrease type info refcount, free when it reaches 0. */ void flecs_type_info_release( const ecs_type_info_t *ti); /* 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__\ }\ }\ } #ifdef FLECS_MULTI_WORLD void flecs_multi_world_init( ecs_world_t *world); void flecs_multi_world_fini( ecs_world_t *world); #else #define flecs_multi_world_init(world) (void)world #define flecs_multi_world_fini(world) (void)world #endif #endif #ifdef FLECS_JOURNAL #ifndef FLECS_LOG #define FLECS_LOG #endif #ifndef FLECS_JOURNAL_H #define FLECS_JOURNAL_H /** * @defgroup c_addons_journal Journal * @ingroup c_addons * Journaling addon (disabled by default). * * * @{ */ /* Trace when log level is at or higher than level */ #define FLECS_JOURNAL_LOG_LEVEL (0) #ifdef __cplusplus extern "C" { #endif /* Journaling API, meant to be used by internals. */ typedef enum ecs_journal_kind_t { EcsJournalNew, EcsJournalMove, EcsJournalClear, EcsJournalDelete, EcsJournalSetParent, EcsJournalDeleteWith, EcsJournalRemoveAll, EcsJournalTableEvents } ecs_journal_kind_t; FLECS_DBG_API void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove); FLECS_DBG_API void flecs_journal_end(void); #define flecs_journal(...)\ flecs_journal_begin(__VA_ARGS__);\ flecs_journal_end(); #ifdef __cplusplus } #endif // __cplusplus /** @} */ #endif // FLECS_JOURNAL_H #else #define flecs_journal_begin(...) #define flecs_journal_end(...) #define flecs_journal(...) #endif // FLECS_JOURNAL #ifdef FLECS_PIPELINE #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 */ /* 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 #endif /* 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); /* Assert that a relationship isn't in use, so that its traits can be changed. */ void flecs_assert_relation_unused( ecs_world_t *world, ecs_entity_t rel, ecs_entity_t trait); #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)]); void flecs_bootstrap_make_alive( ecs_world_t *world, ecs_entity_t e); #ifndef FLECS_CONSTRAINT_TRAITS_PRIVATE_H #define FLECS_CONSTRAINT_TRAITS_PRIVATE_H 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); #ifdef FLECS_CONSTRAINT_TRAITS /* 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); void flecs_bootstrap_constraint_traits( ecs_world_t *world); #ifdef FLECS_DEBUG int flecs_check_constraint_traits( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t rel, ecs_entity_t tgt); #else #define flecs_check_constraint_traits(world, cr, rel, tgt) (0) #endif #else #define flecs_get_oneof(world, e) ((void)(world), (void)(e), 0) #define flecs_bootstrap_constraint_traits(world) (void)world #define flecs_check_constraint_traits(world, cr, rel, tgt) (0) #endif #ifdef FLECS_CONSTRAINT_TRAITS #define flecs_bootstrap_trait(world, name)\ flecs_bootstrap_tag(world, name)\ ecs_add_id(world, name, EcsTrait) #else #define flecs_bootstrap_trait(world, name)\ flecs_bootstrap_tag(world, name) #endif #endif //////////////////////////////////////////////////////////////////////////////// //// 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); bool flecs_log_capture_set_append( bool append); void flecs_log_capture_push( bool try); char* flecs_log_capture_pop(void); /* 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); /* Search table type from offset for an id. */ int32_t flecs_table_offset_search( const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out); /* Search table type from offset, also matching derived components. */ int32_t flecs_table_offset_search_w_inherited( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out); /* 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 -- */ 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; } 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_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->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_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_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 == 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_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_observer( ecs_iter_t *it) { ecs_world_t *world = it->world; bool should_disable = it->event == EcsOnAdd; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; flecs_observer_set_disable_bit( world, e, EcsObserverIsDisabled, should_disable); if (ecs_owns_id(world, e, EcsModule)) { flecs_disable_module_observers(world, e, should_disable); } } } 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)) { /* Also flags existing child tables so children added after the * trait are tracked in the ordered list. */ flecs_component_ordered_children_init(it->world, cr); flecs_ordered_children_populate(it->world, cr); } } } else if (!(it->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 = symbol_col[row].hash; symbol_col[row].index = &world->symbols; flecs_name_index_ensure(&world->symbols, entity, symbol_col[row].value, symbol_length, symbol_col[row].hash); } /** 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); for (int32_t i = 0; i < result->column_count; i ++) { ecs_column_t *column = &result->data.columns[i]; column->data = ecs_os_malloc(column->ti->size * v_entities.size); } 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 */ 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, 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, 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 }); /* 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); /* 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, 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, EcsInheritable); flecs_bootstrap_trait(world, EcsPairIsTag); flecs_bootstrap_trait(world, EcsExclusive); flecs_bootstrap_trait(world, EcsTraversable); flecs_bootstrap_trait(world, EcsWith); flecs_bootstrap_trait(world, EcsCanToggle); flecs_bootstrap_trait(world, EcsOnDelete); flecs_bootstrap_trait(world, EcsOnDeleteTarget); 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, 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, EcsTraversable); /* 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. */ 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, EcsAny) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete, .global_observer = true }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsAny) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete_object, .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 }); /* 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 = EcsDisabled }}, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_observer, .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, 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, EcsDependsOn, EcsPairIsTag); ecs_add_id(world, EcsFlag, EcsPairIsTag); ecs_add_id(world, EcsWith, EcsPairIsTag); flecs_bootstrap_constraint_traits(world); /* Transitive relationships are always Traversable */ ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); /* DontFragment components are always sparse */ ecs_add_pair(world, EcsDontFragment, EcsWith, EcsSparse); /* Acyclic/Traversable components */ ecs_add_id(world, EcsIsA, EcsTraversable); ecs_add_id(world, EcsDependsOn, EcsTraversable); /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_parent_component(world); flecs_bootstrap_prefab(world); #ifdef FLECS_CACHED_QUERIES flecs_query_cache_bootstrap(world); #endif 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_commands_t *commands = stage->cmd; ecs_vec_t *cmds = &commands->queue; int32_t cur = ecs_vec_count(cmds); if (!cur) { commands->entity = e; commands->entry.first = -1; commands->second_entity = 0; } ecs_cmd_entry_t *entry; if (e == commands->entity) { entry = &commands->entry; } else if (e == commands->second_entity) { entry = &commands->second_entry; } else if (cur == 1) { commands->second_entity = e; commands->second_entry.first = -1; entry = &commands->second_entry; } else { entry = flecs_sparse_get_t(&commands->entries, ecs_cmd_entry_t, e); } 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; } void flecs_commands_grow( ecs_stage_t *stage) { ecs_commands_t *cmd = stage->cmd; ecs_assert(cmd->next == NULL, ECS_INTERNAL_ERROR, NULL); cmd->next = ecs_os_calloc_t(ecs_commands_t); flecs_commands_init(stage, cmd->next); cmd->next->prev = cmd; } bool flecs_defer_cmd( ecs_stage_t *stage, bool deferred) { if (deferred) { return true; } flecs_check_exclusive_world_access_write(stage->world); flecs_commands_begin(stage->world, stage); return false; } bool flecs_defer_modified( ecs_stage_t *stage, bool deferred, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage, deferred)) { 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, bool deferred, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage, deferred)) { 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) { 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; } bool flecs_defer_delete( ecs_stage_t *stage, bool deferred, ecs_entity_t entity) { if (flecs_defer_cmd(stage, deferred)) { 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, bool deferred, ecs_entity_t entity) { if (flecs_defer_cmd(stage, deferred)) { 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, bool deferred, ecs_id_t id, ecs_entity_t action, bool force_delete) { if (flecs_defer_cmd(stage, deferred)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdOnDeleteAction; cmd->id = id; cmd->entity = action; cmd->is._1.force_delete = force_delete; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, bool deferred, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage, deferred)) { 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, bool deferred, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage, deferred)) { 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, bool deferred, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage, deferred)) { 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, bool deferred, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage, deferred)) { 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; #ifdef FLECS_PREFAB /* 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); ECS_TABLE_LOCK(world, table); flecs_type_info_copy(dst, src, 1, ti); ECS_TABLE_UNLOCK(world, table); } } } } } } #endif 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; } static void* flecs_defer_find_cmd_value( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { ecs_commands_t *commands = stage->cmd; ecs_cmd_entry_t *entry = entity == commands->entity ? &commands->entry : entity == commands->second_entity ? &commands->second_entry : flecs_sparse_get_t(&commands->entries, ecs_cmd_entry_t, entity); if (!entry || entry->first == -1) { return NULL; } ecs_cmd_t *cmds = ecs_vec_first_t(&stage->cmd->queue, ecs_cmd_t); if (cmds[entry->last].entity != entity) { return NULL; } void *result = NULL; int32_t cur = entry->first; do { ecs_cmd_t *cmd = &cmds[cur]; switch(cmd->kind) { case EcsCmdSet: case EcsCmdSetDontFragment: case EcsCmdEmplace: case EcsCmdEnsure: case EcsCmdEnsureDontFragment: if (cmd->id == id) { result = cmd->is._1.value; } break; case EcsCmdRemove: if (cmd->id == id) { result = NULL; } break; case EcsCmdClone: case EcsCmdClear: case EcsCmdDelete: result = NULL; break; case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdPath: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: break; } int32_t next = cmd->next_for_entity; cur = next < 0 ? -next : next; } while (cur); return result; } 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_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"); bool use_cmd_storage = !ptr.ptr || ti->hooks.on_replace != NULL; if (use_cmd_storage) { void *existing = flecs_defer_find_cmd_value(stage, entity, id); if (existing) { return existing; } } ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->entity = entity; cmd->id = id; ecs_table_t *table = r->table; if (use_cmd_storage) { ecs_stack_t *stack = &stage->cmd->stack; void *value = flecs_stack_alloc(stack, size, ti->alignment); cmd->kind = EcsCmdEnsure; cmd->is._1.size = size; cmd->is._1.value = value; if (ptr.ptr) { flecs_type_info_copy_ctor(value, ptr.ptr, 1, ti); } else { /* 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(value, 1, ti); } else { /* Override */ flecs_type_info_copy_ctor(value, base, 1, ti); } } ptr.ptr = value; } else { cmd->kind = EcsCmdAdd; } return ptr.ptr; error: return NULL; } static FLECS_ALWAYS_INLINE void* flecs_defer_set_impl( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, const void *value, bool copy) { 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 (copy && world->stage_count != 1) { 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); 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); cmd->kind = EcsCmdEnsure; cmd->is._1.size = size; cmd->is._1.value = ptr.ptr; } else { 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); if (copy) { flecs_type_info_copy_ctor(ptr.ptr, value, 1, ti); } else { flecs_type_info_ctor(ptr.ptr, 1, ti); } } else { cmd->kind = EcsCmdAddModified; if (ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, id, ptr.ptr, value, ti); } if (copy) { ECS_TABLE_LOCK(world, r->table); flecs_type_info_copy(ptr.ptr, value, 1, ti); ECS_TABLE_UNLOCK(world, r->table); } } 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) { return flecs_defer_set_impl(world, stage, entity, id, size, value, true); } 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) { return flecs_defer_set_impl(world, stage, entity, id, size, value, false); } 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) { const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); /* Safe const cast, command makes copy of value */ void *param = ECS_CONST_CAST(void*, desc->param); flecs_type_info_dtor(param, 1, ti); flecs_stack_free(param, ti->size); } } 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_vec_t *set_ids, 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 ++; ecs_vec_clear(set_ids); 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 ++; ecs_vec_append_t(&world->allocator, set_ids, ecs_id_t)[0] = id; 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); ecs_stage_t *stage = world->stages[0]; const ecs_stage_ensure_t *prev_ensure_add = stage->ensure_add; ecs_stage_ensure_t ensure_add_type = { entity, { ecs_vec_first_t(set_ids, ecs_id_t), ecs_vec_count(set_ids) }}; if (ensure_add_type.ids.count) { stage->ensure_add = &ensure_add_type; } flecs_commands_begin(world, stage); flecs_commit(world, entity, r, table, &table_diff, 0, 0); flecs_commands_end(world, stage); stage->ensure_add = prev_ensure_add; /* 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_commands_begin(world, world->stages[0]); flecs_invoke_replace_hook(world, prev_table, entity, cmd->id, dst.ptr, ptr, ti); flecs_commands_end(world, world->stages[0]); 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; ECS_TABLE_LOCK(world, r->table); flecs_type_info_move(dst.ptr, ptr, 1, ti); if (move_hook) { flecs_type_info_dtor(ptr, 1, ti); } ECS_TABLE_UNLOCK(world, r->table); 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; bool update_parent_records = !table_diff.removed.count || !(start_table->flags & EcsTableHasParent); flecs_commands_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_commands_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_commands_flush( ecs_world_t *world, ecs_stage_t *stage) { flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); ecs_commands_t *commands = stage->cmd; if (!ecs_vec_count(&commands->queue)) { return false; } flecs_check_exclusive_world_access_write(world); ecs_stage_t *dst_stage = world->stages[0]; ecs_commands_t *root = commands; ecs_vec_t *queue; ecs_cmd_t *cmds; int32_t i, count; ecs_table_diff_builder_t diff = {0}; ecs_vec_t set_ids = {0}; bool diff_builder_used = false; enter: queue = &commands->queue; ecs_os_perf_trace_push("flecs.commands.merge"); flecs_commands_begin(world, stage); if (dst_stage != stage) { flecs_commands_begin(world, dst_stage); } if (world->on_commands_active) { world->on_commands_active(stage, queue, world->on_commands_ctx_active); } cmds = ecs_vec_first(queue); count = ecs_vec_count(queue); i = 0; resume: for (; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; if (cmd->kind == EcsCmdSkip && !cmd->is._1.value) { if (cmd->entry) { cmd->entry->first = -1; } world->info.cmd.discard_count ++; continue; } ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_alive(world, e); if (cmd->next_for_entity < 0) { diff.added_flags = 0; diff.removed_flags = 0; if (is_alive) { if (!diff_builder_used) { flecs_table_diff_builder_init(world, &diff); ecs_vec_init_t(&world->allocator, &set_ids, ecs_id_t, 0); diff_builder_used = true; } flecs_cmd_batch_for_entity( world, &diff, &set_ids, e, cmds, i); is_alive = flecs_entities_is_alive(world, e); } else { world->info.cmd.discard_count ++; } } if (cmd->entry) { cmd->entry->first = -1; } 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); goto next; } 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_now(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_now(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, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsCmdEmplace: { 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, 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, 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: flecs_commands_begin(world, flecs_stage_from_readonly_world(world)); flecs_on_delete(world, id, e, false, cmd->is._1.force_delete); flecs_commands_end(world, flecs_stage_from_readonly_world(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 ++; goto next; case EcsCmdPath: { bool keep_alive = true; ecs_make_alive(world, e); if (cmd->id) { if (ecs_is_alive(world, cmd->id)) { if (ecs_get_parent(world, e) != 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); } next: if (dst_stage != stage && ecs_vec_count(&dst_stage->cmd->queue)) { flecs_commands_flush(world, dst_stage); } if (ecs_vec_count(&stage->cmd->queue)) { commands->cursor = i + 1; commands = stage->cmd; goto enter; } } if (dst_stage != stage) { flecs_commands_end(world, dst_stage); } stage->cmd = commands; flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); if (world->on_commands_active) { world->on_commands_active(stage, NULL, world->on_commands_ctx_active); } ecs_os_perf_trace_pop("flecs.commands.merge"); if (commands != root) { commands = commands->prev; queue = &commands->queue; cmds = ecs_vec_first(queue); count = ecs_vec_count(queue); i = commands->cursor; goto resume; } if (diff_builder_used) { flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &set_ids, ecs_id_t); } return true; } bool flecs_commands_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_commands_t *cmd = stage->cmd; ecs_assert(cmd->prev != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_vec_count(&cmd->queue)) { flecs_commands_flush(world, stage); } stage->cmd = cmd->prev; return true; } /* 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); 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); stage->cmd->entry.first = -1; stage->cmd->second_entry.first = -1; } return true; error: return false; } void flecs_commands_init( ecs_stage_t *stage, ecs_commands_t *cmd) { cmd->entity = 0; cmd->entry.first = -1; cmd->second_entity = 0; cmd->second_entry.first = -1; 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); } void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, const ecs_component_record_t *cr, int16_t column, 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, void *ptr) { ecs_iter_t it = { .field_count = 1 }; it.entities = entities; if (ptr) { it.ptrs = &ptr; } const ecs_table_record_t *tr = NULL; ecs_entity_t dummy_src = 0; it.stage = ecs_get_stage(world, 0); it.world = world; it.table = table; it.trs = &tr; it.columns = &column; it.row_fields = !!(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; ECS_TABLE_LOCK(world, table); hook(&it); ECS_TABLE_UNLOCK(world, table); } 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) { 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.stage = ecs_get_stage(world, 0); it.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; ECS_TABLE_LOCK(world, table); ti->hooks.on_replace(&it); ECS_TABLE_UNLOCK(world, table); } 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 (table->flags & EcsTableHasOrderedChildren) { flecs_ordered_children_unparent( world, table, other_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 bool flecs_on_set_emit_up_notify( const ecs_table_t *table, ecs_id_t id) { return (id == ecs_id(EcsParent)) && (table->flags & EcsTableHasUpNotify) && (table->flags & EcsTableHasTraversable); } static bool flecs_actions_emit_for_diff( const ecs_table_t *table, ecs_flags32_t diff_flags, ecs_flags32_t event_flag) { if (diff_flags & event_flag) { return true; } return (diff_flags & EcsTableHasUpNotify) && (table->flags & EcsTableHasTraversable); } 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 (flecs_actions_emit_for_diff(table, diff_flags, EcsTableHasOnAdd)) { 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 (flecs_actions_emit_for_diff(table, 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 & EcsWorldQuit)) { ecs_check(!(table->flags & EcsTableHasBuiltins), ECS_INVALID_OPERATION, "removing components from builtin entities is not allowed"); } ecs_flags32_t diff_flags = diff->removed_flags; /* The OrderedChildren flag can be set on a table after the edges for the * table were created, which means the cached edge diff can be out of date. * The table flag is kept up to date, so use that instead. */ ecs_flags32_t ordered_children = table->flags & EcsTableHasOrderedChildren; if (!(diff_flags|ordered_children)) { return; } if ((diff_flags & EcsTableEdgeReparent) || ordered_children) { if (table != other_table && (!other_table || !(other_table->flags & EcsTableHasChildOf))) { flecs_on_unparent( world, table, other_table, row, count, diff_flags); } } if (!diff_flags) { return; } 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) { if (table->flags & EcsTableHasTraversable) { flecs_emit_propagate_invalidate(world, table, row, count); } ecs_flags32_t diff_flags = diff->removed_flags; if (!diff_flags) { return; } 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 emit_on_set = false; bool any_validate = 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); emit_on_set |= (cr->flags & EcsIdDontFragment) != 0; emit_on_set |= flecs_on_set_emit_up_notify(table, id); 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; ecs_on_validate_t on_validate = ti->hooks.on_validate; if (on_validate) { any_validate = true; } if (!on_set && !on_validate) { continue; } int16_t column = -1; if (!(cr->flags & EcsIdSparse)) { const ecs_table_cache_elem_t *elem = flecs_table_cache_get_elem( &cr->cache, table); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->tr->count == 1, ECS_INTERNAL_ERROR, NULL); column = elem->column; } if (on_validate) { bool emit = ((cr->flags & EcsIdDontFragment) != 0) || (table->flags & EcsTableHasOnSet); int32_t j; for (j = 0; j < count; j ++) { ecs_entity_t e = entities[j]; void *ptr = flecs_get_component(world, table, row + j, cr); if (!on_validate(world, e, ptr)) { continue; } if (on_set) { int32_t hook_row = (cr->flags & EcsIdSparse) ? row : row + j; flecs_invoke_hook(world, table, cr, column, 1, hook_row, &entities[j], id, ti, EcsOnSet, on_set, ptr); } if (emit) { ecs_type_t single = { .array = &id, .count = 1 }; flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = &single, .table = table, .offset = row + j, .count = 1, .observable = world, .set_ptr = ptr }); } } continue; } if (cr->flags & EcsIdSparse) { int32_t j; for (j = 0; j < count; j ++) { flecs_invoke_hook(world, table, cr, -1, 1, row, &entities[j], id, ti, EcsOnSet, on_set, NULL); } } else { flecs_invoke_hook(world, table, cr, column, count, row, entities, id, ti, EcsOnSet, on_set, NULL); } } /* Run OnSet notifications */ if (!any_validate) { if ((emit_on_set || 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 }); } } else if (emit_on_set || table->flags & EcsTableHasOnSet) { for (i = 0; i < ids->count; i ++) { ecs_id_t id = ids->array[i]; ecs_component_record_t *cr = flecs_components_get(world, id); if (cr->type_info->hooks.on_validate) { continue; } ecs_type_t single = { .array = &id, .count = 1 }; flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = &single, .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, void *ptr) { flecs_notify_on_set_w_cr(world, table, row, id, flecs_components_get(world, id), invoke_hook, ptr); } void flecs_notify_on_set_w_cr( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id, ecs_component_record_t *cr, bool invoke_hook, void *ptr) { 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_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); bool emit_on_set = (cr->flags & EcsIdDontFragment) != 0; emit_on_set |= flecs_on_set_emit_up_notify(table, id); const ecs_type_info_t *ti = cr->type_info; ecs_on_validate_t on_validate = ti->hooks.on_validate; if (on_validate) { if (!ptr) { ptr = flecs_get_component(world, table, row, cr); } if (!on_validate(world, entities[0], ptr)) { return; } } if (invoke_hook) { ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { int16_t column = -1; if (!(cr->flags & EcsIdSparse)) { const ecs_table_cache_elem_t *elem = flecs_table_cache_get_elem(&cr->cache, table); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->tr->count == 1, ECS_INTERNAL_ERROR, NULL); column = elem->column; } flecs_invoke_hook(world, table, cr, column, 1, row, entities, id, ti, EcsOnSet, on_set, ptr); } } if ((emit_on_set || 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, .set_ptr = ptr }); } } 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, EcsTableNotEmpty); 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 = { .world = ECS_CONST_CAST(ecs_world_t*, world), .stage = ECS_CONST_CAST(ecs_world_t*, stage), .field_count = 1, .next = ecs_each_next }; ecs_table_cache_iter_t *cache_it = &it.priv_.iter.each.it; cache_it->elems = NULL; cache_it->remaining = 0; 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_cache_elem_t *elem = flecs_table_cache_next( &each_iter->it); it->flags |= EcsIterIsValid; if (elem) { each_iter->trs = elem->tr; each_iter->columns = elem->column; ecs_table_t *table = elem->table; it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); if (elem->index != -1) { it->ids = &table->type.array[elem->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 = { .world = ECS_CONST_CAST(ecs_world_t*, world), .stage = 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; } #ifdef FLECS_QUERY_DSL #ifndef FLECS_QUERY_DSL_H #define FLECS_QUERY_DSL_H #ifdef FLECS_QUERY_DSL 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 #endif #endif 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 const ecs_table_record_t* flecs_get_derived_record( const ecs_world_t *world, ecs_table_t *table, const ecs_table_record_t *tr, ecs_component_record_t **cr_out) { if (!(table->flags & EcsTableHasDerived) || !flecs_table_record_is_inherited(table, tr)) { return NULL; } ecs_component_record_t *cr = flecs_components_get( world, table->type.array[tr->index]); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); *cr_out = cr; return flecs_component_get_table(cr, table); } 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]; void *ptr = flecs_component_sparse_get(world, cr, table, entity); if (ptr || !(table->flags & EcsTableHasDerived)) { return (flecs_component_ptr_t){ .ti = cr->type_info, .ptr = ptr }; } } const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (!tr) { return (flecs_component_ptr_t){0}; } if ((tr->column == -1) || (cr->flags & EcsIdSparse)) { tr = flecs_get_derived_record(world, table, tr, &cr); if (!tr) { return (flecs_component_ptr_t){0}; } if (cr->flags & EcsIdSparse) { 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) }; } if (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; } 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); #ifdef FLECS_ENTITY_RANGES 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); #endif 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); /* Invoke remove actions for removed components */ flecs_actions_move_remove(world, src_table, dst_table, src_row, 1, diff); /* Append new row to destination table. This has to happen after the remove * actions, as the components of the new row are only initialized by the * move below. Appending before the remove actions would expose a row with * uninitialized component values to observers that evaluate a query * against the destination table, such as observers with a Not term. */ int32_t dst_row = ecs_table_count(dst_table); flecs_table_append(world, dst_table, entity, false, false); /* 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); if (!diff->added.count && (record->row & EcsEntityIsTraversable)) { flecs_emit_propagate_invalidate(world, dst_table, dst_row, 1); } 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); if (src_table->flags & EcsTableIsPrefab) { flecs_prefab_assert_not_instantiated(world, entity, diff); } flecs_move_entity(world, entity, record, dst_table, diff, emplace_id, evt_flags); flecs_table_traversable_add(src_table, -is_trav); 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_commands_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_commands_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]); ECS_TABLE_LOCK(world, table); if (is_move) { flecs_type_info_move(ptr, src_ptr, 1, ti); } else { flecs_type_info_copy(ptr, src_ptr, 1, ti); } ECS_TABLE_UNLOCK(world, table); flecs_notify_on_set(world, table, row + e, id, true, ptr); 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); ECS_TABLE_LOCK(world, table); if (is_move) { flecs_type_info_move(ptr, src_ptr, count, ti); } else { flecs_type_info_copy(ptr, src_ptr, count, ti); } ECS_TABLE_UNLOCK(world, table); } }; 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_commands_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); ecs_stage_t *stage = world->stages[0]; const ecs_stage_ensure_t *ensure_add = stage->ensure_add; ecs_stage_ensure_t ensure_add_type = { entity, { &component, 1 } }; stage->ensure_add = &ensure_add_type; 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. */ stage->ensure_add = ensure_add; } void flecs_add_id_now( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { 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); } void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, deferred, entity, component)) { return; } flecs_add_id_now(world, entity, component); flecs_commands_end(world, stage); } void flecs_remove_id_now( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { 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); } void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, deferred, entity, component)) { return; } flecs_remove_id_now(world, entity, component); flecs_commands_end(world, stage); } void flecs_add_ids( ecs_world_t *world, ecs_entity_t entity, const 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_commands_begin(world, flecs_stage_from_readonly_world(world)); flecs_commit(world, entity, r, table, &table_diff, 0, 0); flecs_commands_end(world, flecs_stage_from_readonly_world(world)); 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 && (table->flags & EcsTableHasDerived)) { /* If the table only stores a component that inherits from the * requested component, add the requested component instead. */ const ecs_table_record_t *tr = flecs_component_get_table(cr, table); if (tr && flecs_table_record_is_inherited(table, tr)) { dst.ptr = NULL; } } 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_commands_end(world, world->stages[0]); flecs_commands_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; } /* -- Public functions -- */ 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); ecs_stage_t *stage = 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_commands_begin(world, stage); flecs_new_entity(world, entity, r, table, &table_diff, true, 0); flecs_commands_end(world, stage); 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)) bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage, deferred)) { ecs_entity_t e = ecs_new(world); ecs_add_id((ecs_world_t*)stage, 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_commands_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); } ECS_TABLE_LOCK(world, r->table); flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); ECS_TABLE_UNLOCK(world, r->table); 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, dst_ptr); } static int flecs_entity_init_name( ecs_world_t *world, ecs_entity_t entity, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, bool new_entity, bool name_assigned) { bool deferred = ecs_is_stage(world); const char *symbol = desc->symbol; if (symbol && (deferred || symbol[0])) { const char *existing = ecs_get_symbol(world, entity); if (!deferred && existing) { ecs_assert(!ecs_os_strcmp(symbol, existing), ECS_INCONSISTENT_NAME, "entity symbol inconsistent: %s (provided) vs. %s (existing)", symbol, existing); } else if (!existing || ecs_os_strcmp(existing, symbol)) { if (deferred && ecs_get_stage_count(world) <= 1) { ecs_suspend_readonly_state_t state; ecs_world_t *real_world = flecs_suspend_readonly(world, &state); ecs_set_symbol(world, entity, symbol); flecs_resume_readonly(real_world, &state); } else { ecs_set_symbol(world, entity, symbol); } } } if (name && !name_assigned) { if (!ecs_add_path_w_sep(world, entity, scope, name, desc->sep, desc->root_sep) && !deferred && name[0] == '#') { return -1; } } else if (new_entity && scope) { ecs_add_pair(world, entity, EcsChildOf, scope); } return 0; } 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"); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t scope = stage->scope; ecs_entity_t result = desc->id; const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name) { if (!name[0]) { name = NULL; } else if (flecs_name_is_id(name)){ ecs_entity_t id = flecs_name_to_id(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 (flecs_entity_init_name( deferred ? (ecs_world_t*)stage : world, result, name, desc, scope, new_entity, name_assigned)) { return 0; } return result; error: return 0; } ecs_entity_t ecs_insert_w_values( ecs_world_t *world, const ecs_value_t *values) { ecs_entity_t result = ecs_entity_init(world, &(ecs_entity_desc_t){0}); if (!result) { goto error; } if (values) { int32_t i; for (i = 0; values[i].type; i ++) { if (values[i].ptr) { const ecs_type_info_t *ti = ecs_get_type_info( world, values[i].type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "component '%s' passed to ecs_insert is a tag/zero sized", flecs_errstr(ecs_id_str(world, values[i].type))); ecs_set_id(world, result, values[i].type, flecs_ito(size_t, ti->size), values[i].ptr); } else { ecs_add_id(world, result, values[i].type); } } } 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); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, deferred, 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_commands_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); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, deferred, 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_commands_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); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_delete(stage, deferred, 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) { if (world->cr_flag_count) { 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_commands_end(world, stage); flecs_commands_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_commands_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"); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!dst) { dst = ecs_new(world); } if (flecs_defer_clone(stage, deferred, 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; ECS_TABLE_LOCK(world, dst_table); flecs_type_info_copy(dst_ptr, src_ptr, 1, ti); ECS_TABLE_UNLOCK(world, dst_table); flecs_notify_on_set(world, dst_table, row, component, true, dst_ptr); } 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, cur->type_info->size, 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_commands_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 ((tr->column == -1) || (cr->flags & EcsIdSparse)) { const ecs_table_record_t *derived_tr = flecs_get_derived_record( world, table, tr, &cr); if (derived_tr) { tr = derived_tr; } } 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_get_sparse_id( const 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, "get_sparse"); ecs_check(!ecs_id_is_wildcard(component), ECS_INVALID_PARAMETER, "cannot call get_sparse() with wildcard component '%s'", flecs_errstr(ecs_id_str(world, component))); ecs_check(ecs_id_is_valid(world, component), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_component_record_t *cr = flecs_components_get(world, component); if (!cr) { return NULL; } ecs_check(cr->flags & EcsIdSparse, ECS_INVALID_PARAMETER, "cannot call get_sparse() for non-sparse component '%s' " "(use get()/get_mut())", flecs_errstr(ecs_id_str(world, component))); ecs_check(!(cr->flags & EcsIdOnInstantiateInherit), ECS_INVALID_PARAMETER, "cannot call get_sparse() for component '%s' with the " "(OnInstantiate, Inherit) trait (use get())", flecs_errstr(ecs_id_str(world, component))); ecs_assert(cr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_sparse_get(cr->sparse, flecs_utosize(size), entity); 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"); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage, deferred)) { return flecs_defer_ensure( world, stage, entity, component, flecs_uto(int32_t, size)); } 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_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_commands_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))); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage, deferred)) { 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_commands_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_commands_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; } 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"); flecs_poly_assert(world, ecs_world_t); flecs_check_exclusive_world_access_write(world); 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); void *ptr = NULL; if (cr) { ptr = flecs_get_component(world, table, row, cr); } if (!ptr) { return; } flecs_notify_on_set(world, table, row, component, invoke_hook, ptr); flecs_table_mark_dirty(world, table, component); 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"); bool deferred = ecs_is_stage(world); 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, deferred, 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, NULL); flecs_table_mark_dirty(world, table, component); flecs_commands_end(world, stage); error: return; } void flecs_set_id_move( ecs_world_t *world, ecs_entity_t entity, ecs_id_t component, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { flecs_check_exclusive_world_access_write(world); 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); } ECS_TABLE_LOCK(world, r->table); 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); } ECS_TABLE_UNLOCK(world, r->table); 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) { flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), component, true, dst.ptr); } } else if (cmd_kind == EcsCmdSetDontFragment) { flecs_notify_on_set( world, r->table, ECS_RECORD_TO_ROW(r->row), component, true, dst.ptr); } 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"); } bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage, deferred)) { 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)); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); 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_commands_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); bool deferred = ecs_is_stage(world); 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(deferred ? (ecs_world_t*)stage : world, entity, bs_id); if (flecs_defer_enable(stage, deferred, 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_commands_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); bool trivial_lookup = component < FLECS_HI_COMPONENT_ID && !world->non_trivial_lookup[component]; 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 (trivial_lookup) { 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; } #ifdef FLECS_PREFAB 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; } #endif 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); bool trivial_lookup = component < FLECS_HI_COMPONENT_ID && !world->non_trivial_lookup[component]; 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) { int16_t column = table->component_map[component]; if (trivial_lookup) { return column != 0; } if (column != 0) { return true; } } 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; } 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_t( cr->sparse, ecs_entity_t, entity); if (tgt) { return *tgt; } } else { ecs_type_t *type = flecs_sparse_get_t( cr->sparse, ecs_type_t, 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) { bool deferred = ecs_is_stage(world); 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; } } } if (deferred) { if (name) { for (ecs_commands_t *cmd = stage->cmd; cmd; cmd = cmd->prev) { ecs_cmd_t *commands = ecs_vec_first(&cmd->queue); int32_t i, count = ecs_vec_count(&cmd->queue); for (i = count - 1; i >= 0; i --) { ecs_cmd_t *c = &commands[i]; if (c->kind == EcsCmdPath && c->id == parent && c->is._1.value && !ecs_os_strcmp(c->is._1.value, name)) { return c->entity; } } } } ecs_world_t *stage_world = (ecs_world_t*)stage; entity = ecs_new(stage_world); ecs_set(stage_world, entity, EcsParent, { parent }); if (name) { flecs_defer_path(stage, parent, entity, name); } return entity; } ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot create entity while world is readonly"); 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); if (name) { ecs_table_diff_t name_diff = ECS_TABLE_DIFF_INIT; ecs_id_t name_id = ecs_pair_t(EcsIdentifier, EcsName); table = flecs_table_traverse_add(world, table, &name_id, &name_diff); 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, true, false); r->table = table; r->row = (uint32_t)row; 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_commands_begin(world, stage); flecs_actions_new(world, table, row, 1, &table_diff, 0, true, EcsWildcard); if (name) { flecs_set_identifier(world, NULL, entity, EcsName, name); } flecs_add_non_fragmenting_child_w_records(world, parent, entity, cr, r); /* Notify OnSet for the Parent component, which is forwarded as an OnAdd * event for the (ChildOf, parent) pair. Without this, observers for the * pair don't run for children created with this operation. */ flecs_notify_on_set( world, table, row, ecs_id(EcsParent), true, parent_ptr); flecs_commands_end(world, stage); 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"); #ifdef FLECS_CONSTRAINT_TRAITS 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))); #endif 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_stage(world)), ECS_INVALID_OPERATION, "cannot change generation for entity %u through a stage", (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 (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); #ifndef FLECS_PARSER if (escape) { ecs_abort(ECS_UNSUPPORTED, "escaped paths require the FLECS_PARSER addon"); } #endif 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 ++) { #ifdef FLECS_PARSER char esc[3]; #endif if (ch != sep[0]) { #ifdef FLECS_PARSER 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 ecs_strbuf_appendch(buf, ch); #endif } else { if (!escape) { ecs_strbuf_appendch(buf, '\\'); ecs_strbuf_appendch(buf, sep[0]); } #ifdef FLECS_PARSER else { ecs_strbuf_appendlit(buf, "\\\\"); flecs_chresc(esc, ch, '\"'); ecs_strbuf_appendch(buf, esc[0]); if (esc[1]) { ecs_strbuf_appendch(buf, esc[1]); } } #endif } } } 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); int64_t value = atoll(name + 1); if (value < 0) { return 0; /* Invalid id */ } ecs_entity_t res = flecs_ito(uint64_t, value); 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 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; char sep0 = sep[0]; ecs_size_t sep_len = ecs_os_strlen(sep); 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 == '>') { if (template_nesting > 0) { template_nesting --; } } else if (ch == '\\') { ptr ++; ch = ptr[0]; if (!ch) { break; } escaped = true; } if (!escaped && !template_nesting && ch == sep0) { if (sep_len == 1 || !ecs_os_strncmp(ptr, sep, sep_len)) { ptr += sep_len; 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; } } static const char* flecs_path_elem_n( const char *path, const char *sep, ecs_size_t sep_len, ecs_size_t *len_out) { const char *ptr; char ch, sep0 = sep[0]; int32_t template_nesting = 0; for (ptr = path; (ch = *ptr); ptr ++) { if (ch == '<') { template_nesting ++; } else if (ch == '>') { if (template_nesting > 0) { template_nesting --; } } else if (!template_nesting && ch == sep0) { if (sep_len == 1 || !ecs_os_strncmp(ptr, sep, sep_len)) { *len_out = flecs_ito(ecs_size_t, ptr - path); return ptr + sep_len; } } } *len_out = flecs_ito(ecs_size_t, ptr - path); return NULL; } static ecs_entity_t flecs_lookup_child_n( const ecs_world_t *world, ecs_entity_t parent, const char *name, ecs_size_t length, uint64_t hash) { if (name[0] == '#') { uint64_t value = 0; ecs_size_t i; for (i = 1; i < length; i ++) { char ch = name[i]; if (ch < '0' || ch > '9') { break; } if (value < UINT32_MAX) { value = value * 10 + flecs_ito(uint64_t, ch - '0'); } } if (i == length && value && value < UINT32_MAX) { /* Resolve current generation for recycled ids */ ecs_entity_t e = flecs_entities_get_alive(world, value); if (e) { if (parent && !ecs_has_pair(world, e, EcsChildOf, parent)) { return 0; } return e; } } } ecs_component_record_t *cr = flecs_components_get( world, ecs_childof(parent)); if (cr) { ecs_hashmap_t *index = cr->pair->name_index; if (index) { return flecs_name_index_find(index, name, length, hash); } } return 0; } 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; } void ecs_on_set(EcsIdentifier)( ecs_iter_t *it) { ecs_world_t *world = it->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); if (src_index == dst_index) { /* Both tables share a name index, so the entry doesn't need to move. * Also covers the case where neither table has a name 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); return flecs_lookup_child_n(world, parent, name, ecs_os_strlen(name), 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 = NULL; const ecs_entity_t *lookup_path_cur = NULL; 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); } ecs_size_t sep_len = ecs_os_strlen(sep); char sep0 = sep[0]; bool has_escape = strchr(path, '\\') != NULL; ecs_size_t path_len = 0; uint64_t path_hash = 0; if (!has_escape && !strchr(path, sep0)) { path_len = ecs_os_strlen(path); path_hash = flecs_hash(path, path_len); } retry: cur = parent; ptr = path; if (path_len) { cur = flecs_lookup_child_n(world, cur, path, path_len, path_hash); if (!cur) { goto tail; } } else if (!has_escape) { while (ptr) { ecs_size_t len; const char *next = flecs_path_elem_n(ptr, sep, sep_len, &len); if (!len && !next) { break; } cur = flecs_lookup_child_n(world, cur, ptr, len, 0); if (!cur) { goto tail; } ptr = next; } } else { 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; lookup_path = ecs_get_lookup_path(stage); lookup_path_cur = lookup_path; while (lookup_path_cur && *lookup_path_cur) { lookup_path_cur ++; } } } 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); } ecs_world_t *stage_world = world; if (defer_suspend) { world = real_world; } 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(&stage_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_stage(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 && stage) { /* 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 }); } bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (deferred && name) { flecs_defer_path(stage, 0, entity, name); } else { flecs_set_identifier(deferred ? (ecs_world_t*)stage : world, NULL, 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); } #ifdef FLECS_QUERY_DSL #endif 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) { #ifdef FLECS_QUERY_DSL ecs_id_t result; /* Temporarily disable parser logging */ int prev_level = ecs_log_set_level(-3); if (!flecs_id_parse(world, NULL, expr, &result)) { /* Invalid expression */ ecs_log_set_level(prev_level); return 0; } ecs_log_set_level(prev_level); return result; #else (void)world; (void)expr; ecs_abort(ECS_UNSUPPORTED, "ecs_id_from_str requires FLECS_QUERY_DSL addon"); #endif } 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); } /* 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_alloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align) { ecs_world_t *world = it->stage; ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); ecs_stack_t *stack = &stage->allocators.iter_stack; return flecs_stack_alloc(stack, size, align); } void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align) { ecs_world_t *world = it->stage; 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->stage; 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 --- */ /* Get field data. When base is true the field is matched through component * inheritance, which means the stored (derived) component can be larger than * the requested (base) type, so the column stride is used instead of size. */ static FLECS_ALWAYS_INLINE void* flecs_field_ptr( const ecs_iter_t *it, size_t size, int8_t index, bool base) { 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->stage, it->ids[index]))); (void)size; if (it->ptrs) { return it->ptrs[index]; } int16_t column = it->columns[index]; if (column >= 0) { ecs_column_t *col = &it->table->data.columns[column]; ecs_assert(base || ((ecs_size_t)size == col->ti->size), ECS_INVALID_PARAMETER, "field %d is matched on a derived component, use ecs_base_field()", index); return ECS_ELEM(col->data, base ? col->ti->size : (ecs_size_t)size, it->offset); } return flecs_field_shared(it, size, index); error: return NULL; } void* ecs_field_w_size( const ecs_iter_t *it, size_t size, int8_t index) { return flecs_field_ptr(it, size, index, false); } void* ecs_base_field_w_size( const ecs_iter_t *it, size_t size, int8_t index) { return flecs_field_ptr(it, size, index, true); } 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->world, src); ecs_table_t *table = r->table; int32_t row = ECS_RECORD_TO_ROW(r->row); (void)size; const ecs_table_record_t *tr = it->trs[index]; if (tr && tr->hdr.table == table) { ecs_column_t *col = &table->data.columns[tr->column]; return ECS_ELEM(col->data, col->ti->size, row); } ecs_id_t id = it->ids[index]; if (id < FLECS_HI_COMPONENT_ID) { int16_t column_index = table->component_map[id]; if (column_index > 0) { ecs_column_t *col = &table->data.columns[column_index - 1]; return ECS_ELEM(col->data, col->ti->size, row); } } ecs_component_record_t *cr = flecs_components_get(it->world, id); tr = flecs_component_get_table(cr, table); ecs_column_t *col = &table->data.columns[tr->column]; return ECS_ELEM(col->data, col->ti->size, row); } static ecs_component_record_t* flecs_field_cr( const ecs_iter_t *it, int8_t index) { ecs_component_record_t *cr = NULL; const ecs_table_record_t *tr = it->trs[index]; if (!tr) { const ecs_query_t *q = it->query; if (q) { ecs_component_record_t **cr_cache = flecs_query_impl(q)->cr_cache; cr = cr_cache[index]; if (!cr || cr->id != it->ids[index]) { cr = cr_cache[index] = flecs_components_get( it->world, it->ids[index]); } } else { cr = flecs_components_get(it->world, it->ids[index]); } ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); } else { cr = tr->hdr.cr; } return cr; } 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 = flecs_field_cr(it, index); 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 = it->entities[row]; } 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->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->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; } size_t ecs_field_stride( 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); int16_t column = it->columns[index]; if (column >= 0 && it->table) { return (size_t)it->table->data.columns[column].ti->size; } return (size_t)it->sizes[index]; error: return 0; } 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; } #ifdef FLECS_QUERY_PLANS 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->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->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->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; } 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; } #endif // FLECS_QUERY_PLANS 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(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, "cannot constrain variable while iterating"); #ifdef FLECS_QUERY_PLANS 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(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->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; #else ecs_check(var_id == 0, ECS_UNSUPPORTED, "setting variables other than $this requires the FLECS_QUERY_PLANS addon"); ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, "can only constrain query iterators"); ecs_query_iter_t *qit = &it->priv_.iter.query; qit->constrained_this = true; qit->entity = entity; ecs_record_t *r = flecs_entities_get(it->world, entity); if (r) { it->table = r->table; it->offset = ECS_RECORD_TO_ROW(r->row); it->count = 1; it->entities = &ecs_table_entities(it->table)[it->offset]; } else { it->table = NULL; it->offset = 0; it->count = 1; it->entities = &qit->entity; } #endif /* 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); #ifdef FLECS_QUERY_PLANS ecs_check(var_id < ecs_iter_get_var_count(it), ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); #else ecs_check(var_id == 0, ECS_INVALID_PARAMETER, "only the $this variable is available without query plan support"); ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, "can only constrain query iterators"); #endif 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"); #ifdef FLECS_QUERY_PLANS 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; #else ecs_query_iter_t *qit = &it->priv_.iter.query; qit->constrained_this = true; qit->entity = 0; it->table = range->table; it->offset = range->offset; it->count = range->count; if (!it->count) { it->count = ecs_table_count(it->table); } it->entities = ecs_table_entities(it->table); if (it->entities) { it->entities += it->offset; } #endif /* Update iterator for constrained iterator */ flecs_query_iter_constrain(it); error: return; } #ifdef FLECS_CACHED_QUERIES 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; } #endif // FLECS_CACHED_QUERIES 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 && !chain_it->count) { 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; if (it->table) { it->entities = &(ecs_table_entities(it->table)[it->offset]); } else { it->entities = &it->entities[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; if (it->table) { it->entities = &(ecs_table_entities(it->table)[it->offset]); } else { it->entities = &it->entities[first]; } 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 = ecs_os_fopen(filename, "r"); if (!file) { ecs_err("%s (%s)", ecs_os_strerror(errno), filename); return NULL; } int32_t size = 0; int32_t cap = 4096; char *content = ecs_os_malloc(cap + 1); size_t read; while ((read = ecs_os_fread( &content[size], 1, flecs_itosize(cap - size), file))) { size += flecs_uto(int32_t, read); if (size == cap) { cap *= 2; content = ecs_os_realloc(content, cap + 1); } } content[size] = '\0'; ecs_os_fclose(file); return content; } #define FLECS_ERRSTR_MAX (256) static char flecs_errstr_bufs[6][FLECS_ERRSTR_MAX]; static const char* flecs_errstr_intern( int32_t buf, char *str) { ecs_os_strncpy(flecs_errstr_bufs[buf], str, FLECS_ERRSTR_MAX - 1); ecs_os_free(str); return flecs_errstr_bufs[buf]; } const char* flecs_errstr( char *str) { return flecs_errstr_intern(0, str); } const char* flecs_errstr_1( char *str) { return flecs_errstr_intern(1, str); } const char* flecs_errstr_2( char *str) { return flecs_errstr_intern(2, str); } const char* flecs_errstr_3( char *str) { return flecs_errstr_intern(3, str); } const char* flecs_errstr_4( char *str) { return flecs_errstr_intern(4, str); } const char* flecs_errstr_5( char *str) { return flecs_errstr_intern(5, str); } 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; observable->on_table_create.event = EcsOnTableCreate; observable->on_table_delete.event = EcsOnTableDelete; } 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); else if (event == EcsOnTableCreate) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_table_create); else if (event == EcsOnTableDelete) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_table_delete); /* 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 (er->event_ids_filter & (1llu << (id % 64))) { 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); er->event_ids_filter |= 1llu << (id % 64); 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); er->event_ids_filter = 0; } else { uint64_t filter = 0; ecs_map_iter_t it = ecs_map_iter(&er->event_ids); while (ecs_map_next(&it)) { filter |= 1llu << (ecs_map_key(&it) % 64); } er->event_ids_filter = filter; } } } 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; } static ecs_event_id_record_t* flecs_event_id_record_get_for( 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 NULL; } return flecs_event_id_record_get(er, id); } bool flecs_observers_exist( const ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event) { ecs_event_id_record_t *ider = flecs_event_id_record_get_for( observable, id, event); if (!ider) { return false; } return (ider->observer_count - ider->up_notify_count) > 0; } bool flecs_up_notify_observers_exist( const ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event) { ecs_event_id_record_t *ider = flecs_event_id_record_get_for( observable, id, event); if (!ider) { return false; } return ider->up_notify_count > 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 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 = offset; 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]; if (count) { flecs_observers_invoke(world, &ider->up, it, table, trav); if (!owned) { /* Owned takes precedence */ flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } else if (ider->up_notify_count) { flecs_observers_invoke_up_notify(world, &ider->up, it, table, trav); if (!owned) { flecs_observers_invoke_up_notify( world, &ider->self_up, it, table, trav); } } } if (!table->_->traversable_count || owned) { 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; bool invoke = false; for (i = 0; i < ider_count; i ++) { ecs_event_id_record_t *ider = iders[i]; if ((ider->observer_count - ider->up_notify_count) > 0) { invoke = true; break; } } for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, children[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!invoke && (table->flags & EcsTableHasParent)) { if (!(r->row & EcsEntityIsTraversable)) { continue; } if (flecs_component_get_table(cr, table)) { continue; } ecs_component_record_t *cr_t = flecs_components_get( world, ecs_pair(EcsWildcard, children[i])); if (cr_t) { flecs_emit_propagate(world, it, cr, cr_t, trav, iders, ider_count); } continue; } flecs_emit_propagate_id_for_range( world, it, cr, trav, iders, ider_count, &(ecs_table_range_t){ .table = table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1 }); } it->event_cur = event_cur; } return; } if (!flecs_table_cache_iter(&cur->cache, &idt, EcsTableEmpty|EcsTableNotEmpty)) { return; } const ecs_table_cache_elem_t *elem; int32_t event_cur = it->event_cur; while ((elem = flecs_table_cache_next(&idt))) { const ecs_table_record_t *tr = elem->tr; ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table)) { flecs_emit_propagate_id_for_range( world, it, cr, trav, iders, ider_count, &(ecs_table_range_t){ .table = 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 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))) { /* 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(); } static ecs_component_record_t* flecs_traversable_target_cr( ecs_world_t *world, ecs_entity_t entity) { ecs_record_t *r = flecs_entities_get(world, entity); if (!r || !(r->row & EcsEntityIsTraversable)) { return NULL; } return flecs_components_get(world, ecs_pair(EcsWildcard, entity)); } 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_traversable_target_cr( world, children[i]); if (cr_t) { flecs_emit_propagate_invalidate_tables(world, cr_t); } } } ecs_table_cache_iter_t idt; if (!flecs_table_cache_iter(&cur->cache, &idt, EcsTableEmpty|EcsTableNotEmpty)) { continue; } const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&idt))) { const ecs_table_record_t *tr = elem->tr; 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_traversable_target_cr( world, 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_traversable_target_cr( world, 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_traversable_target_cr( world, 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_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]; /* This path skips up notify observers, so when the id has nothing but * up notify observers there is nothing to walk the maps for. */ if (ider->observer_count == ider->up_notify_count) { continue; } flecs_observers_invoke_skip_up_notify( world, &ider->up, it, table, trav); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke_skip_up_notify( 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_reachable_cache_ensure( ecs_world_t *world, ecs_component_record_t *cr, int32_t depth, bool validate) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_reachable_cache_t *rc = &cr->pair->reachable; if (rc->current == rc->generation) { return; } if (depth >= FLECS_DAG_DEPTH_MAX) { ecs_abort(ECS_CYCLE_DETECTED, "cycle in traversable relationship"); } ecs_allocator_t *a = &world->allocator; ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); ecs_entity_t tgt = flecs_entities_get_alive(world, ECS_PAIR_SECOND(cr->id)); ecs_record_t *r = tgt ? flecs_entities_try(world, tgt) : NULL; ecs_table_t *table = r ? r->table : NULL; ecs_entity_t trav = ECS_PAIR_FIRST(cr->id); if (table) { for (int32_t i = 0; i < table->type.count; i ++) { const ecs_table_record_t *tr = &table->_->records[i]; ecs_component_record_t *cur = tr->hdr.cr; ecs_id_t id = cur->id; if (trav == EcsIsA && !(cur->flags & EcsIdOnInstantiateInherit)) { continue; } if ((ECS_IS_PAIR(id) && (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA)) || (trav == EcsChildOf && id == ecs_id(EcsParent))) { if (id == ecs_id(EcsParent)) { const EcsParent *parent = ecs_get(world, tgt, EcsParent); ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); cur = flecs_components_get(world, ecs_childof(parent->value)); ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); } flecs_reachable_cache_ensure(world, cur, depth + 1, true); const ecs_vec_t *ids = &cur->pair->reachable.ids; const ecs_reachable_elem_t *elems = ecs_vec_first(ids); for (int32_t j = 0; j < ecs_vec_count(ids); j ++) { if (!flecs_component_get_table(elems[j].tr->hdr.cr, table)) { ecs_reachable_elem_t *elem = ecs_vec_append_t( a, &rc->ids, ecs_reachable_elem_t); *elem = elems[j]; } } } else { ecs_reachable_elem_t *elem = ecs_vec_append_t( a, &rc->ids, ecs_reachable_elem_t); *elem = (ecs_reachable_elem_t){ .tr = tr, .record = r, .src = tgt, .id = id }; #ifndef FLECS_NDEBUG elem->table = table; #endif } } } if (validate) { rc->current = rc->generation; } } const ecs_vec_t* flecs_reachable_cache_get( ecs_world_t *world, 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) { if (world->flags & EcsWorldMultiThreaded) { return NULL; } flecs_reachable_cache_ensure(world, cr, 0, true); } return &rc->ids; } static void flecs_emit_forward( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_component_record_t *cr) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); ecs_reachable_cache_t *rc = &cr->pair->reachable; flecs_reachable_cache_ensure(world, cr, 0, it->event == EcsOnAdd || it->event == EcsOnRemove); 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) { 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) { elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_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 */ /* 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 = { .stage = (ecs_world_t*)world->stages[0], .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; pdesc.set_ptr = NULL; 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) || (event == EcsOnRemove)) { flecs_components_on_isa_change(world, table, &ecs_table_entities(table)[offset], count, (event == EcsOnAdd) || (it.other_table && (it.other_table->flags & EcsTableHasIsA))); } 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) { /* 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; } if (desc->set_ptr && id_count == 1) { it.ptrs = &desc->set_ptr; } /* 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); } it.ptrs = 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: 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_stage_t *main_stage = world->stages[0]; 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; } flecs_commands_begin(world, main_stage); flecs_emit(world, stage, desc); flecs_commands_end(world, main_stage); 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_stage(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_observer_set_table_flags( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_flags32_t flags) { table->flags |= flags; if (id && (flags == EcsTableHasOnAdd || flags == EcsTableHasOnRemove || flags == EcsTableHasUpNotify)) { flecs_table_edges_add_flags(world, table, id, flags); } } static void flecs_observer_set_tables_flags( ecs_world_t *world, ecs_id_t id, ecs_flags32_t flags) { if (!flags || (world->flags & EcsWorldFini)) { return; } if (!id || id == EcsAny) { ecs_sparse_t *tables = &world->store.tables; int32_t count = flecs_sparse_count(tables); for (int32_t i = 0; i < count; i ++) { flecs_observer_set_table_flags(world, flecs_sparse_get_dense_t(tables, ecs_table_t, i), id, flags); } } else { ecs_component_record_t *cr = flecs_components_get(world, id); if (cr) { ecs_table_cache_iter_t it; flecs_table_cache_iter(&cr->cache, &it, EcsTableEmpty|EcsTableNotEmpty); const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { flecs_observer_set_table_flags(world, elem->table, id, flags); } } } } 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, bool up_notify) { 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; int32_t category_result; if (up_notify) { category_result = idt->up_notify_count += value; } else { category_result = result - idt->up_notify_count; } ecs_flags32_t flags = up_notify ? EcsIdHasUpNotify : flecs_id_flag_for_event(event); if ((category_result == value && value > 0) || (category_result == 0 && value < 0)) { ecs_component_record_t *cr = flecs_components_get(world, id); if (value > 0) { flecs_observer_set_tables_flags(world, id, flags); if (cr) { cr->flags |= flags; } if (!up_notify && (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 (cr) { cr->flags &= ~flags; } } if (result == 0) { 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, up_notify); } else { flecs_inc_observer_count( world, event, evt, ecs_id(EcsParent), value, up_notify); } } } 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_observer_update_id( ecs_world_t *world, ecs_observer_t *o, size_t offset, ecs_id_t id, int32_t delta) { 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; bool up_notify = (impl->flags & EcsObserverIsUpNotify) != 0; for (int32_t i = 0; i < o->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event(term, o->events[i]); int32_t j; for (j = 0; j < i; j ++) { if (event == flecs_get_observer_event(term, o->events[j])) { break; } } if (i != j) { continue; } ecs_event_record_t *er = flecs_event_record_ensure(o->observable, event); ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, er, id); ecs_map_t *observers = ECS_OFFSET(idt, offset); if (delta > 0) { ecs_map_init_if(observers, &world->allocator); ecs_map_insert_ptr(observers, impl->id, o); } else { ecs_map_remove(observers, impl->id); if (!ecs_map_count(observers)) { ecs_map_fini(observers); } } flecs_inc_observer_count(world, event, er, id, delta, up_notify); if (trav && id != ecs_id(EcsParent)) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), delta, up_notify); } } } static void flecs_observer_update_registration( ecs_world_t *world, ecs_observer_t *o, int32_t delta) { ecs_query_t *q = o->query; if (q && !q->term_count) { return; } ecs_term_t *term = q ? &q->terms[0] : NULL; ecs_flags64_t flags = term ? ECS_TERM_REF_FLAGS(&term->src) : EcsSelf; ecs_id_t id = flecs_observer_id(flecs_observer_impl(o)->register_id); if (flags & (EcsSelf|EcsUp)) { size_t offset = flags & EcsSelf ? (flags & EcsUp ? offsetof(ecs_event_id_record_t, self_up) : offsetof(ecs_event_id_record_t, self)) : offsetof(ecs_event_id_record_t, up); flecs_observer_update_id(world, o, offset, id, delta); } if (term && term->trav == EcsChildOf && (q->flags & EcsQueryTableOnly)) { flecs_observer_update_id(world, o, offsetof(ecs_event_id_record_t, self), ecs_id(EcsParent), delta); flecs_observer_update_id(world, o, offsetof(ecs_event_id_record_t, self), ecs_pair(EcsChildOf, EcsWildcard), delta); } } 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_term_ref_is_named_var( const ecs_term_ref_t *ref) { return (ref->id & EcsIsVariable) && ref->name != NULL; } 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) { #ifdef FLECS_QUERY_PLANS bool first_var = flecs_term_ref_is_named_var(&term->first); bool second_var = flecs_term_ref_is_named_var(&term->second); if (!first_var && !second_var) { return ecs_query_has_range(query, range, it); } ecs_world_t *world = query->world; ecs_entity_t first = 0, second = 0; if (first_var) { first = flecs_entities_get_alive(world, ECS_IS_PAIR(event_id) ? ECS_PAIR_FIRST(event_id) : event_id); if (!first) { return false; } } if (second_var) { if (!ECS_IS_PAIR(event_id)) { return false; } second = flecs_entities_get_alive(world, ECS_PAIR_SECOND(event_id)); if (!second) { return false; } } *it = ecs_query_iter(query->stage, 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), first); } if (second_var) { ecs_iter_set_var(it, ecs_query_find_var(query, term->second.name), second); } return ecs_query_next(it); #else (void)term; (void)event_id; return ecs_query_has_range(query, range, it); #endif } 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 ++; } typedef enum flecs_observers_invoke_mode_t { FlecsObserversInvokeAll, FlecsObserversInvokeUpNotifyOnly, FlecsObserversInvokeSkipUpNotify } flecs_observers_invoke_mode_t; static void flecs_observers_invoke_intern( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, flecs_observers_invoke_mode_t mode) { if (ecs_map_is_init(observers)) { ECS_TABLE_LOCK(it->stage, table); ecs_map_iter_t oit = ecs_map_iter(observers); while (ecs_map_next(&oit)) { ecs_observer_t *o = ecs_map_ptr(&oit); bool up_notify = (flecs_observer_impl(o)->flags & EcsObserverIsUpNotify) != 0; if (mode == FlecsObserversInvokeUpNotifyOnly && !up_notify) { continue; } if (mode == FlecsObserversInvokeSkipUpNotify && up_notify) { continue; } 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->stage, table); } } void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { flecs_observers_invoke_intern(world, observers, it, table, trav, FlecsObserversInvokeAll); } void flecs_observers_invoke_up_notify( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { flecs_observers_invoke_intern(world, observers, it, table, trav, FlecsObserversInvokeUpNotifyOnly); } void flecs_observers_invoke_skip_up_notify( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { flecs_observers_invoke_intern(world, observers, it, table, trav, FlecsObserversInvokeSkipUpNotify); } static void flecs_multi_observer_invoke_user_it( ecs_observer_t *o, ecs_iter_t *it, ecs_iter_t *user_it, ecs_table_t *lock_table) { ecs_world_t *world = it->world; int8_t pivot_term = it->term_index; int8_t pivot_field = o->query->terms[pivot_term].field_index; ecs_termset_t pivot_field_bit = ((ecs_termset_t)1 << pivot_field); (void)lock_table; /* 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. */ 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] || !(user_it->set_fields & pivot_field_bit)) ? -1 : it->columns[0]; user_it->term_index = pivot_term; user_it->stage = it->stage; 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->stage, lock_table); if (o->run) { ecs_iter_next_action_t next = user_it->next; user_it->next = flecs_default_next_callback; o->run(user_it); user_it->next = next; user_it->interrupted_by = 0; } else { user_it->callback(user_it); } ECS_TABLE_UNLOCK(it->stage, lock_table); flecs_stage_set_system(world->stages[0], old_system); } static void flecs_multi_observer_invoke_fixed_src( ecs_observer_t *o, ecs_iter_t *it) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_termset_t pivot_field_bit = (ecs_termset_t)1 << o->query->terms[it->term_index].field_index; bool match = false; ecs_iter_t user_it = ecs_query_iter(it->stage, o->query); while (ecs_query_next(&user_it)) { match = true; impl->last_event_id[0] = it->event_cur; impl->last_event_field = pivot_field_bit; flecs_multi_observer_invoke_user_it(o, it, &user_it, user_it.table); user_it.flags |= EcsIterSkip; } if (!match) { o->query->eval_count --; } } 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->world; int8_t pivot_term = it->term_index; ecs_term_t *term = &o->query->terms[pivot_term]; int8_t pivot_field = term->field_index; ecs_termset_t pivot_field_bit = ((ecs_termset_t)1 << pivot_field); if (impl->last_event_id[0] == it->event_cur && !(impl->last_event_field & pivot_field_bit)) { /* Already handled this event for a different field */ return; } bool is_not = term->oper == EcsNot; if (!is_not && !ecs_term_match_this(term) && (o->query->flags & EcsQueryMatchThis)) { flecs_multi_observer_invoke_fixed_src(o, it); return; } ecs_table_t *table = it->table; ecs_table_t *prev_table = it->other_table; 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, .offset = it->offset, .count = it->count }; 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->stage, 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; impl->last_event_field = pivot_field_bit; flecs_multi_observer_invoke_user_it(o, it, &user_it, lock_table); user_it.flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(&user_it); } 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->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->stage, 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->stage, 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; } flecs_commands_begin(world, flecs_stage_from_readonly_world(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(ecs_get_stage(world, 0), o->query); } else { it = ecs_each_id(ecs_get_stage(world, 0), 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; } } flecs_commands_end(world, flecs_stage_from_readonly_world(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_observer_update_registration(world, o, 1); 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); } static bool flecs_observer_init_trivial( ecs_world_t *world, const ecs_query_desc_t *desc, ecs_term_t *term, ecs_flags32_t *flags) { if (desc->expr || desc->order_by_callback || desc->group_by_callback || !ecs_term_is_initialized(&desc->terms[0]) || ecs_term_is_initialized(&desc->terms[1])) { return false; } *term = desc->terms[0]; ecs_query_validator_ctx_t ctx = { .world = world, .desc = desc, .term = term }; if (flecs_term_finalize(world, term, &ctx)) { return false; } bool wildcard = ecs_id_is_wildcard(term->id); bool disabled = term->id == EcsDisabled; #ifdef FLECS_PREFAB bool prefab = term->id == EcsPrefab; #else bool prefab = false; #endif bool special = wildcard || disabled || prefab; #ifdef FLECS_SANITIZE if (!special) { return false; } #endif if (term->oper != EcsAnd || !ecs_term_match_this(term) || ((term->src.id & EcsTraverseFlags) != EcsSelf) || term->trav || ECS_PAIR_FIRST(term->id) == EcsChildOf || (term->flags_ & (EcsTermIsToggle|EcsTermDontFragment|EcsTermIsSparse| EcsTermTransitive|EcsTermReflexive|EcsTermIdInherited|EcsTermIsMember)) || (!special && (!(term->flags_ & EcsTermIsTrivial) || term->inout)) || (wildcard && term->id != EcsAny && !(term->flags_ & EcsTermIsCacheable))) { return false; } ecs_component_record_t *cr = flecs_components_get(world, term->id); ecs_flags32_t cr_flags = cr ? cr->flags : flecs_component_get_flags(world, term->id); if (cr_flags & (EcsIdSparse|EcsIdDontFragment)) { return false; } #ifdef FLECS_CONSTRAINT_TRAITS if (!desc->terms[0].src.id && !desc->terms[0].src.name && (cr_flags & EcsIdSingleton)) { return false; } #endif *flags = desc->flags; if (disabled) { *flags |= EcsQueryMatchDisabled; } if (prefab) { *flags |= EcsQueryMatchPrefab; } flecs_component_lock(world, term->id); return true; } 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_term_t trivial_term; ecs_term_t *terms = &trivial_term; int32_t term_count = 1; ecs_flags32_t query_flags = 0; bool trivial = desc->events[0] != EcsMonitor && flecs_observer_init_trivial(world, &query_desc, &trivial_term, &query_flags); if (!trivial) { ecs_query_t *query = o->query = ecs_query_init(world, &query_desc); if (!query) { flecs_observer_fini(o); return NULL; } terms = query->terms; term_count = query->term_count; query_flags = query->flags; } ecs_check(term_count > 0, ECS_INVALID_PARAMETER, "observer must have at least one term"); int i; #ifdef FLECS_QUERY_PLANS int var_count = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (!ecs_term_match_this(term)) { if (term->src.id & EcsIsVariable) { /* Term has a non-$this variable source */ var_count ++; } } } ecs_check(term_count > var_count, ECS_UNSUPPORTED, "observers with only non-$this variable sources are not yet supported"); (void)var_count; #endif 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 (term_count == 1 && !desc->last_event_id) { ecs_term_t *term = &terms[0]; /* If the query has a single term but it is a *From operator, we * need to create a multi observer */ multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); /* An observer with only optional terms is a special case that is * only handled by multi observers */ multi |= term->oper == EcsOptional; } if (term_count == 1 && !(desc->query.flags & EcsQueryNested)) { ecs_term_t *term = &terms[0]; multi |= flecs_term_ref_is_named_var(&term->first) || flecs_term_ref_is_named_var(&term->second); } bool is_monitor = impl->flags & EcsObserverIsMonitor; if (term_count == 1 && !is_monitor && !multi) { ecs_term_t *term = &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; /* EcsObserver is added before the poly object is installed. If the * entity was already disabled, initialize the observer flag now. */ if (ecs_has_id(world, entity, EcsDisabled)) { flecs_observer_set_disable_bit( world, entity, EcsObserverIsDisabled, true); } 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))); flecs_poly_update_ctx(&o->ctx, &o->ctx_free, desc->ctx, desc->ctx_free, true); flecs_poly_update_ctx(&o->callback_ctx, &o->callback_ctx_free, desc->callback_ctx, desc->callback_ctx_free, false); flecs_poly_update_ctx(&o->run_ctx, &o->run_ctx_free, desc->run_ctx, desc->run_ctx_free, false); 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; } } 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_observer_update_registration(world, o, -1); } 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 (world->cr_flag_count) { 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; } if (flecs_table_cache_count(&cr->cache)) { return false; } return true; } static void flecs_component_delete_non_fragmenting_childof( ecs_world_t *world, ecs_component_record_t *cr, bool force_delete) { if (force_delete) { flecs_tree_spawner_fini(world, ECS_PAIR_SECOND(cr->id)); } 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); bool is_target = (r->row & EcsEntityIsTarget) != 0; if (is_target) { 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); if (is_target) { 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_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) { if (!world->non_fragmenting_child_count) { return false; } 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 (flecs_is_childof_tgt_only(childof_cr)) { /* Entity is only used as ChildOf target */ flecs_component_delete_non_fragmenting_childof( world, childof_cr, force_delete); return true; } if (force_delete) { flecs_tree_spawner_fini(world, tgt); } 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, EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { const ecs_table_record_t *tr = elem->tr; 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_iter(&cr->cache, &it, EcsTableEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { elem->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; } } } } 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_type_t *type = &table->type; int32_t i, t, type_count = type->count; if (!type_count) { return; } bool *remove = flecs_wcalloc_n(world, bool, type_count); int32_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; int32_t remove_count = 0; for (i = 0; i < count; i ++) { ecs_component_record_t *cr = ids[i].cr; if (!(tr = flecs_component_get_table(cr, table))) { continue; } t = tr->index; do { if (!remove[t]) { remove[t] = true; remove_count ++; } } while ((t = ecs_search_offset(world, table, t + 1, cr->id, NULL)) != -1); } if (remove_count) { int32_t dst_count = type_count - remove_count; ecs_id_t *dst_array = dst_count ? flecs_walloc_n(world, ecs_id_t, dst_count) : NULL; ecs_id_t *removed = flecs_walloc_n(world, ecs_id_t, remove_count); ecs_flags32_t removed_flags = table->flags & EcsTableRemoveEdgeFlags; int32_t d = 0, r = 0; bool removed_isa = false; for (t = 0; t < type_count; t ++) { ecs_id_t id = type->array[t]; if (remove[t]) { removed[r ++] = id; if (ECS_IS_PAIR(id)) { ecs_entity_t first = ECS_PAIR_FIRST(id); if (first == EcsChildOf) { removed_flags |= EcsTableEdgeReparent; } else if (first == EcsIsA) { removed_isa = true; } } } else { dst_array[d ++] = id; } } ecs_table_t *dst_table = flecs_table_find_or_create(world, &(ecs_type_t){ .array = dst_array, .count = dst_count }); ecs_assert(dst_table != table, ECS_INTERNAL_ERROR, NULL); int32_t table_count = ecs_table_count(table); if (removed_isa && table_count) { /* Ids aren't removed from tables with an OnRemove event here, so * notify the component index of the removed IsA pairs directly. */ flecs_components_on_isa_change(world, table, ecs_table_entities(table), table_count, (dst_table->flags & EcsTableHasIsA) != 0); } if (table_count) { ecs_log_push_3(); ecs_table_diff_t diff = { .removed = { .array = removed, .count = remove_count }, .removed_flags = removed_flags }; flecs_actions_move_remove(world, table, dst_table, 0, table_count, &diff); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table); flecs_wfree_n(world, ecs_id_t, dst_count, dst_array); flecs_wfree_n(world, ecs_id_t, remove_count, removed); } flecs_wfree_n(world, bool, type_count, remove); } 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, EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_table_t *table = elem->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); for (c = count - 1; c >= 0; c --) { ecs_delete(world, children[c]); } } /* 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 || (world->flags & (EcsWorldFini|EcsWorldQuit)), 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 flecs_delete_with( ecs_world_t *world, ecs_id_t id, bool force_delete) { flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, deferred, id, EcsDelete, force_delete)) { return; } flecs_on_delete(world, id, EcsDelete, false, force_delete); flecs_commands_end(world, stage); flecs_journal_end(); } void ecs_delete_with( ecs_world_t *world, ecs_id_t id) { flecs_delete_with(world, id, false); } void ecs_remove_all( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, deferred, id, EcsRemove, false)) { return; } flecs_on_delete(world, id, EcsRemove, false, false); flecs_commands_end(world, stage); flecs_journal_end(); } 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 #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); } static size_t ecs_os_api_fread(void *ptr, size_t size, size_t count, FILE *file) { return fread(ptr, size, count, file); } char* ecs_os_strdup_(const char *str) { if (!str) { return NULL; } return ecs_os_api.strdup_(str); } 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; ecs_os_api.fread_ = ecs_os_api_fread; /* 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 */ world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(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); 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); } void flecs_poly_update_ctx( void **ctx, ecs_ctx_free_t *ctx_free, void *value, ecs_ctx_free_t free_value, bool preserve_null) { if (*ctx != value && (!preserve_null || value) && *ctx && *ctx_free) { (*ctx_free)(*ctx); if (!preserve_null) { *ctx = NULL; *ctx_free = NULL; } } if (value) { *ctx = value; } if (free_value) { *ctx_free = free_value; } } 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; } 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; } /* Test whether a component (transitively) inherits from a base component. */ static bool flecs_component_inherits_from( const ecs_world_t *world, ecs_entity_t component, ecs_entity_t base, int32_t depth) { if (depth >= FLECS_DAG_DEPTH_MAX) { return false; } ecs_record_t *r = flecs_entities_get_any(world, component); ecs_table_t *table = r ? r->table : NULL; if (!table || !(table->flags & EcsTableHasIsA)) { return false; } 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_entity_t b = ECS_PAIR_SECOND(ids[i]); if (b == (uint32_t)base) { return true; } if (flecs_component_inherits_from(world, b, base, depth + 1)) { return true; } } return false; } /* Test whether an id in a table type is derived from the searched for id. */ static bool flecs_id_match_inherited( const ecs_world_t *world, ecs_id_t type_id, ecs_id_t id) { if (ECS_IS_PAIR(id)) { if (!ECS_IS_PAIR(type_id)) { return false; } ecs_entity_t tgt = ECS_PAIR_SECOND(id); if ((tgt != EcsWildcard) && (tgt != EcsAny)) { if (ECS_PAIR_SECOND(type_id) != tgt) { return false; } } return flecs_component_inherits_from(world, ECS_PAIR_FIRST(type_id), ECS_PAIR_FIRST(id), 0); } if (type_id & ECS_ID_FLAGS_MASK) { return false; } return flecs_component_inherits_from(world, type_id, id, 0); } int32_t flecs_table_offset_search_w_inherited( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out) { if (!(table->flags & EcsTableHasDerived)) { return flecs_table_offset_search(table, offset, id, id_out); } ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t *ids = table->type.array; const ecs_table_record_t *trs = table->_->records; int32_t count = table->type.count; while (offset < count) { ecs_id_t type_id = ids[offset ++]; if (ecs_id_match(type_id, id) || ((trs[offset - 1].hdr.cr->flags & EcsIdHasBases) && flecs_id_match_inherited(world, 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) { const ecs_table_record_t *tr = flecs_component_get_table(cr_wc, table); if (tr) { if (ECS_PAIR_SECOND(id) == EcsWildcard) { return false; } if (table->type.array[tr->index] != id) { 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); int32_t result = -1; if (cr->flags & EcsIdDontFragment) { ecs_sparse_t *sparse = cr->sparse; if (flecs_sparse_get(sparse, sparse->size, tgt) != NULL) { result = -2; goto found; } } else { ecs_table_t *tgt_table = r->table; if (!tgt_table) { return -1; } 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) { flecs_poly_assert(world, ecs_world_t); if (!offset) { return ecs_search(world, table, id, id_out); } return flecs_table_offset_search_w_inherited( world, 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, remaining = tr->count; for (; remaining; i ++) { i = flecs_table_offset_search_w_inherited(world, table, i, cr->id, NULL); if (i == -1) { break; } remaining --; 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); flecs_check_exclusive_world_access_write(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) { flecs_commands_flush(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_commands_flush(world, s); } } if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; 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); #ifdef FLECS_CACHED_QUERIES flecs_ballocator_init_t(&stage->allocators.query_cache, ecs_query_cache_t); #endif ecs_allocator_t *a = &stage->allocator; ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); flecs_commands_init(stage, &stage->cmd_root); stage->cmd = &stage->cmd_root; 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); ecs_commands_t *cmd = &stage->cmd_root; while (cmd) { ecs_commands_t *next = cmd->next; flecs_commands_fini(stage, cmd); if (cmd != &stage->cmd_root) { ecs_os_free(cmd); } cmd = next; } #ifdef FLECS_SCRIPT if (stage->runtime) { ecs_script_runtime_free(stage->runtime); } #endif flecs_stack_fini(&stage->allocators.iter_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); flecs_ballocator_fini(&stage->allocators.query_impl); #ifdef FLECS_CACHED_QUERIES flecs_ballocator_fini(&stage->allocators.query_cache); #endif 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; 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; } 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_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); state->is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); state->scope = world->stages[0]->scope; ecs_assert(!state->is_readonly || !(world->flags & EcsWorldMultiThreaded), ECS_INVALID_WHILE_READONLY, NULL); ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); return world; } void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state) { flecs_poly_assert(world, ecs_world_t); if (state->is_readonly) { ecs_run_aperiodic(world, 0); } ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); world->stages[0]->scope = state->scope; } 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); return flecs_poly_is(stage, ecs_world_t) && ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); } void ecs_stage_shrink( ecs_stage_t *stage) { for (ecs_commands_t *cmd = &stage->cmd_root; cmd; cmd = cmd->next) { flecs_sparse_shrink(&cmd->entries); ecs_vec_reclaim_t(&stage->allocator, &cmd->queue, ecs_cmd_t); } } bool ecs_is_stage( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_poly_is(world, ecs_stage_t); error: return false; } bool flecs_stage_is_ensure_add( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t component) { const ecs_stage_ensure_t *ensure_add = world->stages[0]->ensure_add; if (!ensure_add || ensure_add->entity != entity) { return false; } int32_t i, count = ensure_add->ids.count; for (i = 0; i < count; i ++) { if (ensure_add->ids.array[i] == component) { return true; } } return false; } #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) & ~(ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS| ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_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; } if ((a->on_validate != NULL) != (b->on_validate != NULL)) { return false; } return true; } static ecs_flags32_t flecs_type_hooks_specified( const ecs_type_hooks_t *hooks, bool exclude_illegal) { ecs_flags32_t result = 0; if (hooks->ctor && (!exclude_illegal || hooks->ctor != flecs_ctor_illegal)) { result |= ECS_TYPE_HOOK_CTOR_ILLEGAL; } if (hooks->dtor && (!exclude_illegal || hooks->dtor != flecs_dtor_illegal)) { result |= ECS_TYPE_HOOK_DTOR_ILLEGAL; } if (hooks->copy && (!exclude_illegal || hooks->copy != flecs_copy_illegal)) { result |= ECS_TYPE_HOOK_COPY_ILLEGAL; } if (hooks->move && (!exclude_illegal || hooks->move != flecs_move_illegal)) { result |= ECS_TYPE_HOOK_MOVE_ILLEGAL; } if (hooks->copy_ctor && (!exclude_illegal || hooks->copy_ctor != flecs_copy_ctor_illegal)) { result |= ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL; } if (hooks->move_ctor && (!exclude_illegal || hooks->move_ctor != flecs_move_ctor_illegal)) { result |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; } if (hooks->ctor_move_dtor && (!exclude_illegal || hooks->ctor_move_dtor != flecs_move_ctor_illegal)) { result |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL; } if (hooks->move_dtor && (!exclude_illegal || hooks->move_dtor != flecs_move_ctor_illegal)) { result |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; } if (hooks->cmp && (!exclude_illegal || hooks->cmp != flecs_comp_illegal)) { result |= ECS_TYPE_HOOK_CMP_ILLEGAL; } if (hooks->equals && (!exclude_illegal || hooks->equals != flecs_equals_illegal)) { result |= ECS_TYPE_HOOK_EQUALS_ILLEGAL; } return result; } 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); ecs_flags32_t flags = h->flags & ~(ecs_flags32_t)ECS_TYPE_HOOKS; ecs_flags32_t conflicts = flags & flecs_type_hooks_specified(h, true); (void)conflicts; ecs_check(!conflicts, ECS_INVALID_PARAMETER, "illegal call to set_hooks() for component '%s': " "cannot specify callbacks with illegal flags (0x%x)", flecs_errstr(ecs_get_path(world, component)), conflicts); 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; ecs_type_hooks_t hooks = prev_hooks; flags |= (prev_hooks.flags & ECS_TYPE_HOOKS_ILLEGAL) & ~flecs_type_hooks_specified(h, false); 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) hooks.ctor = h->ctor; if (h->dtor) hooks.dtor = h->dtor; if (h->copy) hooks.copy = h->copy; if (h->move) hooks.move = h->move; if (h->copy_ctor) hooks.copy_ctor = h->copy_ctor; if (h->move_ctor) hooks.move_ctor = h->move_ctor; if (h->ctor_move_dtor) hooks.ctor_move_dtor = h->ctor_move_dtor; if (h->move_dtor) hooks.move_dtor = h->move_dtor; if (h->cmp) hooks.cmp = h->cmp; if (h->equals) hooks.equals = h->equals; if (h->on_add) hooks.on_add = h->on_add; if (h->on_remove) hooks.on_remove = h->on_remove; if (h->on_set) hooks.on_set = h->on_set; if (h->on_replace) hooks.on_replace = h->on_replace; if (h->on_validate) hooks.on_validate = h->on_validate; if (h->ctx) hooks.ctx = h->ctx; if (h->binding_ctx) hooks.binding_ctx = h->binding_ctx; if (h->lifecycle_ctx) hooks.lifecycle_ctx = h->lifecycle_ctx; if (h->ctx_free) hooks.ctx_free = h->ctx_free; if (h->binding_ctx_free) hooks.binding_ctx_free = h->binding_ctx_free; if (h->lifecycle_ctx_free) 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)) { 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) { hooks.copy_ctor = flecs_default_copy_ctor; } } if (!h->move_ctor && !(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL)) { if (h->move) { 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 */ 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 */ 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 */ hooks.ctor_move_dtor = 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; hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { hooks.ctor_move_dtor = 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; hooks.move_dtor = flecs_default_move_w_dtor; } else { hooks.move_dtor = flecs_default_move; } } else { if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; hooks.move_dtor = flecs_default_dtor; } } if(flags & illegal_check) { flags |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; } } if (!hooks.cmp || hooks.cmp == flecs_comp_illegal) { flags |= ECS_TYPE_HOOK_CMP_ILLEGAL; } if (!hooks.equals || hooks.equals == flecs_equals_illegal) { if(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) { flags |= ECS_TYPE_HOOK_EQUALS_ILLEGAL; } else { hooks.equals = flecs_default_equals; flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; } } if(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) hooks.ctor = flecs_ctor_illegal; if(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) hooks.dtor = flecs_dtor_illegal; if(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) hooks.copy = flecs_copy_illegal; if(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) hooks.move = flecs_move_illegal; if(flags & ECS_TYPE_HOOK_CMP_ILLEGAL) hooks.cmp = flecs_comp_illegal; if(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) hooks.equals = flecs_equals_illegal; if(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) { hooks.copy_ctor = flecs_copy_ctor_illegal; } if(flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) { hooks.move_ctor = flecs_move_ctor_illegal; } if(flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) { hooks.ctor_move_dtor = flecs_move_ctor_illegal; } if(flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) { hooks.move_dtor = flecs_move_ctor_illegal; } hooks.flags = flags; if (hooks.ctor) hooks.flags |= ECS_TYPE_HOOK_CTOR; if (hooks.dtor) hooks.flags |= ECS_TYPE_HOOK_DTOR; if (hooks.move) hooks.flags |= ECS_TYPE_HOOK_MOVE; if (hooks.move_ctor) hooks.flags |= ECS_TYPE_HOOK_MOVE_CTOR; if (hooks.ctor_move_dtor) hooks.flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR; if (hooks.move_dtor) hooks.flags |= ECS_TYPE_HOOK_MOVE_DTOR; if (hooks.copy) hooks.flags |= ECS_TYPE_HOOK_COPY; if (hooks.copy_ctor) hooks.flags |= ECS_TYPE_HOOK_COPY_CTOR; if (hooks.cmp) hooks.flags |= ECS_TYPE_HOOK_CMP; if (hooks.equals) hooks.flags |= ECS_TYPE_HOOK_EQUALS; hooks.flags |= prev_hooks.flags & ECS_TYPE_HOOK_IN_USE; if (in_use && !flecs_type_hooks_storage_equal(&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))); } ti->hooks = hooks; if (component < FLECS_HI_COMPONENT_ID) { if (hooks.on_set || hooks.copy || hooks.move || hooks.on_replace || hooks.on_validate) { 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); ecs_assert(ti->refcount == 1, ECS_INTERNAL_ERROR, "type info for component '%s' has outstanding claims", ti->name); flecs_type_info_release(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; ti_mut->refcount = 1; } 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) { ecs_map_remove(&world->type_info, component); flecs_type_info_release(ti); } } void flecs_type_info_claim( const ecs_type_info_t *ti) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti->refcount > 0, ECS_INTERNAL_ERROR, NULL); ECS_CONST_CAST(ecs_type_info_t*, ti)->refcount ++; } void flecs_type_info_release( const ecs_type_info_t *ti) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti->refcount > 0, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); if (!(-- ti_mut->refcount)) { flecs_type_info_fini(ti_mut); ecs_os_free(ti_mut); } } 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; } #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; #ifdef FLECS_PREFAB const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 9; #endif const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 10; const ecs_entity_t EcsNotQueryable = FLECS_HI_COMPONENT_ID + 11; 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 EcsInheritable = FLECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 21; #ifdef FLECS_PREFAB const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 23; #endif const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 24; const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 26; const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 29; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 32; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 35; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 38; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 42; #ifdef FLECS_CONSTRAINT_TRAITS /* Constraint traits */ const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 46; const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 47; const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 48; const ecs_entity_t EcsSingleton = FLECS_HI_COMPONENT_ID + 49; const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 50; #endif /* 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 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; #ifdef FLECS_PIPELINE 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; #endif #ifdef FLECS_META /* Meta primitive components (don't use low ids to save id space) */ const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 81; const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 82; const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 83; const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 84; const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 85; const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 86; const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 87; const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 88; const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 89; const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 90; const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 91; const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 92; const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 93; const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 94; const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 95; const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 96; const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 97; const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 98; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsConstants) = FLECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsTypeSerializer) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t ecs_id(EcsType) = FLECS_HI_COMPONENT_ID + 110; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 111; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 112; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 113; const ecs_entity_t ecs_id(EcsMap) = FLECS_HI_COMPONENT_ID + 122; const ecs_entity_t ecs_id(ecs_value_t) = FLECS_HI_COMPONENT_ID + 123; #endif const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 114; /* Doc module components */ #ifdef FLECS_DOC const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 116; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 117; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 118; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 119; const ecs_entity_t EcsDocUuid = FLECS_HI_COMPONENT_ID + 120; #endif /* REST module components */ #ifdef FLECS_REST const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 121; #endif /* 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 */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(EcsAlert); ECS_COMPONENT_DECLARE(EcsAlertInstance); ECS_COMPONENT_DECLARE(EcsAlertsActive); ECS_TAG_DECLARE(EcsAlertInfo); ECS_TAG_DECLARE(EcsAlertWarning); ECS_TAG_DECLARE(EcsAlertError); ECS_TAG_DECLARE(EcsAlertCritical); #endif #ifdef FLECS_UNITS ecs_entity_t EcsUnitPrefixes; ecs_entity_t EcsYocto; ecs_entity_t EcsZepto; ecs_entity_t EcsAtto; ecs_entity_t EcsFemto; ecs_entity_t EcsPico; ecs_entity_t EcsNano; ecs_entity_t EcsMicro; ecs_entity_t EcsMilli; ecs_entity_t EcsCenti; ecs_entity_t EcsDeci; ecs_entity_t EcsDeca; ecs_entity_t EcsHecto; ecs_entity_t EcsKilo; ecs_entity_t EcsMega; ecs_entity_t EcsGiga; ecs_entity_t EcsTera; ecs_entity_t EcsPeta; ecs_entity_t EcsExa; ecs_entity_t EcsZetta; ecs_entity_t EcsYotta; ecs_entity_t EcsKibi; ecs_entity_t EcsMebi; ecs_entity_t EcsGibi; ecs_entity_t EcsTebi; ecs_entity_t EcsPebi; ecs_entity_t EcsExbi; ecs_entity_t EcsZebi; ecs_entity_t EcsYobi; ecs_entity_t EcsDuration; ecs_entity_t EcsPicoSeconds; ecs_entity_t EcsNanoSeconds; ecs_entity_t EcsMicroSeconds; ecs_entity_t EcsMilliSeconds; ecs_entity_t EcsSeconds; ecs_entity_t EcsMinutes; ecs_entity_t EcsHours; ecs_entity_t EcsDays; ecs_entity_t EcsTime; ecs_entity_t EcsDate; ecs_entity_t EcsMass; ecs_entity_t EcsGrams; ecs_entity_t EcsKiloGrams; ecs_entity_t EcsElectricCurrent; ecs_entity_t EcsAmpere; ecs_entity_t EcsAmount; ecs_entity_t EcsMole; ecs_entity_t EcsLuminousIntensity; ecs_entity_t EcsCandela; ecs_entity_t EcsForce; ecs_entity_t EcsNewton; ecs_entity_t EcsLength; ecs_entity_t EcsMeters; ecs_entity_t EcsPicoMeters; ecs_entity_t EcsNanoMeters; ecs_entity_t EcsMicroMeters; ecs_entity_t EcsMilliMeters; ecs_entity_t EcsCentiMeters; ecs_entity_t EcsKiloMeters; ecs_entity_t EcsMiles; ecs_entity_t EcsPixels; ecs_entity_t EcsPressure; ecs_entity_t EcsPascal; ecs_entity_t EcsBar; ecs_entity_t EcsSpeed; ecs_entity_t EcsMetersPerSecond; ecs_entity_t EcsKiloMetersPerSecond; ecs_entity_t EcsKiloMetersPerHour; ecs_entity_t EcsMilesPerHour; ecs_entity_t EcsAcceleration; ecs_entity_t EcsTemperature; ecs_entity_t EcsKelvin; ecs_entity_t EcsCelsius; ecs_entity_t EcsFahrenheit; ecs_entity_t EcsData; ecs_entity_t EcsBits; ecs_entity_t EcsKiloBits; ecs_entity_t EcsMegaBits; ecs_entity_t EcsGigaBits; ecs_entity_t EcsBytes; ecs_entity_t EcsKiloBytes; ecs_entity_t EcsMegaBytes; ecs_entity_t EcsGigaBytes; ecs_entity_t EcsKibiBytes; ecs_entity_t EcsGibiBytes; ecs_entity_t EcsMebiBytes; ecs_entity_t EcsDataRate; ecs_entity_t EcsBitsPerSecond; ecs_entity_t EcsKiloBitsPerSecond; ecs_entity_t EcsMegaBitsPerSecond; ecs_entity_t EcsGigaBitsPerSecond; ecs_entity_t EcsBytesPerSecond; ecs_entity_t EcsKiloBytesPerSecond; ecs_entity_t EcsMegaBytesPerSecond; ecs_entity_t EcsGigaBytesPerSecond; ecs_entity_t EcsPercentage; ecs_entity_t EcsAngle; ecs_entity_t EcsRadians; ecs_entity_t EcsDegrees; ecs_entity_t EcsColor; ecs_entity_t EcsColorRgb; ecs_entity_t EcsColorHsl; ecs_entity_t EcsColorCss; ecs_entity_t EcsBel; ecs_entity_t EcsDeciBel; ecs_entity_t EcsFrequency; ecs_entity_t EcsHertz; ecs_entity_t EcsKiloHertz; ecs_entity_t EcsMegaHertz; ecs_entity_t EcsGigaHertz; ecs_entity_t EcsUri; ecs_entity_t EcsUriHyperlink; ecs_entity_t EcsUriImage; ecs_entity_t EcsUriFile; #endif 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); } 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, EcsTableNotEmpty); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_table_t *table = elem->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((ecs_world_t*)stage0, 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((ecs_world_t*)stage0, 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_commands_end(world, stage0); flecs_commands_begin(world, stage0); break; } } } } static void flecs_fini_non_this_on_remove_observers( ecs_world_t *world) { ecs_stage_t *stage = world->stages[0]; flecs_commands_begin(world, stage); ecs_iter_t it = ecs_each_id(world, EcsObserver); while (ecs_each_next(&it)) { int32_t i; for (i = 0; i < it.count; i ++) { ecs_entity_t e = it.entities[i]; const ecs_observer_t *o = ecs_observer_get(world, e); if (!o) { continue; } const ecs_query_t *q = o->query; if (!q) { continue; } if (q->flags & EcsQueryMatchOnlyThis) { continue; } ecs_delete((ecs_world_t*)stage, it.entities[i]); } } flecs_commands_end(world, stage); } 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_commands_begin(world, world->stages[0]); flecs_fini_root_tables(world, cr, true); flecs_commands_end(world, world->stages[0]); flecs_commands_begin(world, world->stages[0]); flecs_fini_root_tables(world, cr, false); flecs_commands_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_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); 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->sparse_chunk); 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_CPP "FLECS_CPP", #endif #ifdef FLECS_CONSTRAINT_TRAITS "FLECS_CONSTRAINT_TRAITS", #endif #ifdef FLECS_ENTITY_RANGES "FLECS_ENTITY_RANGES", #endif #ifdef FLECS_MODULE "FLECS_MODULE", #endif #ifdef FLECS_MULTI_WORLD "FLECS_MULTI_WORLD", #endif #ifdef FLECS_FRAME "FLECS_FRAME", #endif #ifdef FLECS_STATS "FLECS_STATS", #endif #ifdef FLECS_METRICS "FLECS_METRICS", #endif #ifdef FLECS_ALERTS "FLECS_ALERTS", #endif #ifdef FLECS_SYSTEM "FLECS_SYSTEM", #endif #ifdef FLECS_PIPELINE "FLECS_PIPELINE", #endif #ifdef FLECS_PREFAB "FLECS_PREFAB", #endif #ifdef FLECS_TIMER "FLECS_TIMER", #endif #ifdef FLECS_META "FLECS_META", #endif #ifdef FLECS_UNITS "FLECS_UNITS", #endif #ifdef FLECS_JSON "FLECS_JSON", #endif #ifdef FLECS_DOC "FLECS_DOC", #endif #ifdef FLECS_LOG "FLECS_LOG", #endif #ifdef FLECS_JOURNAL "FLECS_JOURNAL", #endif #ifdef FLECS_APP "FLECS_APP", #endif #ifdef FLECS_CACHED_QUERIES "FLECS_CACHED_QUERIES", #endif #ifdef FLECS_QUERY_PLANS "FLECS_QUERY_PLANS", #endif #ifdef FLECS_OS_API_IMPL "FLECS_OS_API_IMPL", #endif #ifdef FLECS_PARSER "FLECS_PARSER", #endif #ifdef FLECS_QUERY_DSL "FLECS_QUERY_DSL", #endif #ifdef FLECS_SCRIPT "FLECS_SCRIPT", #endif #ifdef FLECS_SCRIPT_ASYNC "FLECS_SCRIPT_ASYNC", #endif #ifdef FLECS_HTTP "FLECS_HTTP", #endif #ifdef FLECS_REST "FLECS_REST", #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_PERF_TRACE "FLECS_PERF_TRACE", #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_PLANS #ifdef FLECS_QUERY_VARIABLE_COUNT_MAX "FLECS_QUERY_VARIABLE_COUNT_MAX=" ECS_STRINGIFY(FLECS_QUERY_VARIABLE_COUNT_MAX), #endif #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 #ifdef FLECS_PERF_TRACE .perf_trace = 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) { if (!ecs_should_log(0)) { return; } 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; while (addon[0]) { ecs_trace("%s", addon[0]); addon ++; } 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(); #ifdef FLECS_CACHED_QUERIES ecs_assert(ECS_ALIGNOF(ecs_query_triv_cache_match_t) == ECS_ALIGNOF(ecs_query_cache_match_t), ECS_INTERNAL_ERROR, NULL); #endif #ifdef FLECS_QUERY_PLANS ecs_assert(EcsQueryNothing < 256, ECS_INTERNAL_ERROR, NULL); #endif 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); flecs_multi_world_init(world); world->info.time_scale = (ecs_ftime_t)1; 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"); #ifdef FLECS_SYSTEM ECS_IMPORT(world, FlecsSystem); #endif #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); #endif #ifdef FLECS_TIMER ECS_IMPORT(world, FlecsTimer); #endif #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif #ifdef FLECS_SCRIPT ECS_IMPORT(world, FlecsScript); #endif #ifdef FLECS_REST ECS_IMPORT(world, FlecsRest); #endif #ifdef FLECS_UNITS ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); #endif ecs_trace("addons imported!"); ecs_log_pop(); #endif #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; #ifdef FLECS_DOC if (argc) { char *app = argv[0]; char *last_elem = strrchr(app, '/'); if (!last_elem) { last_elem = strrchr(app, '\\'); } if (last_elem) { app = last_elem + 1; } ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } #endif return world; } void ecs_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; } /* 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); } /* 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_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_prefab(world); /* yield_existing, OnRemove observers will fire for all matching results * when the observer is deleted, but there is no guarantee that components * and/or sources used by the observer aren't already deleted by the time * the observer entity will be deleted. Therefore delete those observers * before regular entity removal. */ flecs_fini_non_this_on_remove_observers(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_commands_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(); /* 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); flecs_multi_world_fini(world); 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* 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; } 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]; } #ifdef FLECS_EXCLUSIVE_ACCESS void flecs_check_exclusive_world_access_write( const ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); if (!world->exclusive_access) { return; /* Exclusive access is not enabled */ } ecs_os_thread_id_t thr_self = ecs_os_thread_self(); (void)thr_self; if (world->exclusive_access == UINT64_MAX) { ecs_abort(ECS_ACCESS_VIOLATION, "invalid access: world is locked for write operations " "(call exclusive_access_begin() first)"); } else if (world->exclusive_thread_name) { ecs_always_assert(world->exclusive_access == ecs_os_thread_self(), ECS_ACCESS_VIOLATION, "invalid access to world by thread %" PRIu64 ": " "thread %" PRIu64 " (%s) has exclusive access", thr_self, world->exclusive_access, world->exclusive_thread_name); } else { ecs_always_assert(world->exclusive_access == ecs_os_thread_self(), ECS_ACCESS_VIOLATION, "invalid access to world by thread %" PRIu64 ": " "thread %" PRIu64 " has exclusive access", thr_self, world->exclusive_access); } } void flecs_check_exclusive_world_access_read( const ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); if (!world->exclusive_access) { return; /* Exclusive access is not enabled */ } if (world->exclusive_access == UINT64_MAX) { return; /* World is locked, so read access is allowed */ } ecs_os_thread_id_t thr_self = ecs_os_thread_self(); (void)thr_self; if (world->exclusive_thread_name) { ecs_always_assert(world->exclusive_access == ecs_os_thread_self(), ECS_ACCESS_VIOLATION, "invalid access to world by thread %" PRIu64 ": " "(thread %" PRIu64 " (%s) has exclusive access)", thr_self, world->exclusive_access, world->exclusive_thread_name); } else { ecs_always_assert(world->exclusive_access == ecs_os_thread_self(), ECS_ACCESS_VIOLATION, "invalid access to world by thread %" PRIu64 ": " "thread %" PRIu64 " has exclusive access", thr_self, world->exclusive_access); } } #endif 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_commands_begin(world, world->stages[0]); FLECS_EACH_QUERY(query, { if (!ecs_query_is_true(query)) { ecs_add_id(world, query->entity, EcsEmpty); } }) flecs_commands_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); } } 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 flecs_table_cache_count(&cr->cache) != 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*); #ifdef FLECS_CACHED_QUERIES FLECS_EACH_QUERY(query, { flecs_query_reclaim(query); }) #endif 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"; } } #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 #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(FlecsAlerts); typedef struct EcsAlert { char *message; ecs_map_t instances; /* Active instances for alert */ ecs_ftime_t retain_period; /* How long to retain the alert */ ecs_vec_t severity_filters; /* Severity filters */ /* Member range monitoring */ ecs_id_t id; /* (Component) id that contains member to monitor */ ecs_entity_t member; /* Member to monitor */ int32_t offset; /* Offset of member in component */ int32_t size; /* Size of component */ ecs_primitive_kind_t kind; /* Primitive type kind */ ecs_ref_t ranges; /* Reference to ranges component */ int32_t var_id; /* Variable from which to obtain data (0 = $this) */ } EcsAlert; typedef struct EcsAlertTimeout { ecs_ftime_t inactive_time; /* Time the alert has been inactive */ ecs_ftime_t expire_time; /* Expiration duration */ } EcsAlertTimeout; ECS_COMPONENT_DECLARE(EcsAlertTimeout); static ECS_CTOR(EcsAlert, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->instances, NULL); ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); }) static ECS_DTOR(EcsAlert, ptr, { ecs_os_free(ptr->message); ecs_map_fini(&ptr->instances); ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); }) static ECS_MOVE(EcsAlert, dst, src, { ecs_os_free(dst->message); dst->message = src->message; src->message = NULL; ecs_map_fini(&dst->instances); dst->instances = src->instances; src->instances = (ecs_map_t){0}; ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); dst->severity_filters = src->severity_filters; src->severity_filters = (ecs_vec_t){0}; dst->retain_period = src->retain_period; dst->id = src->id; dst->member = src->member; dst->offset = src->offset; dst->size = src->size; dst->kind = src->kind; dst->ranges = src->ranges; dst->var_id = src->var_id; }) static ECS_CTOR(EcsAlertsActive, ptr, { ecs_map_init(&ptr->alerts, NULL); ptr->info_count = 0; ptr->warning_count = 0; ptr->error_count = 0; }) static ECS_DTOR(EcsAlertsActive, ptr, { ecs_map_fini(&ptr->alerts); }) static ECS_MOVE(EcsAlertsActive, dst, src, { ecs_map_fini(&dst->alerts); dst->alerts = src->alerts; dst->info_count = src->info_count; dst->warning_count = src->warning_count; dst->error_count = src->error_count; src->alerts = (ecs_map_t){0}; }) static ECS_DTOR(EcsAlertInstance, ptr, { ecs_os_free(ptr->message); }) static ECS_MOVE(EcsAlertInstance, dst, src, { ecs_os_free(dst->message); dst->message = src->message; src->message = NULL; }) static ECS_COPY(EcsAlertInstance, dst, src, { ecs_os_free(dst->message); dst->message = ecs_os_strdup(src->message); }) static void flecs_alerts_add_alert_to_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert, ecs_entity_t alert_instance) { EcsAlertsActive *active = ecs_ensure( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count ++; } else if (severity == EcsAlertWarning) { active->warning_count ++; } else if (severity == EcsAlertError) { active->error_count ++; } ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr[0] = alert_instance; ecs_modified(world, source, EcsAlertsActive); } static void flecs_alerts_remove_alert_from_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert) { EcsAlertsActive *active = ecs_ensure( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_remove(&active->alerts, alert); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count --; } else if (severity == EcsAlertWarning) { active->warning_count --; } else if (severity == EcsAlertError) { active->error_count --; } if (!ecs_map_count(&active->alerts)) { ecs_remove(world, source, EcsAlertsActive); } else { ecs_modified(world, source, EcsAlertsActive); } } static ecs_entity_t flecs_alert_get_severity( ecs_world_t *world, ecs_iter_t *it, EcsAlert *alert) { int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); ecs_alert_severity_filter_t *filters = ecs_vec_first(&alert->severity_filters); for (i = 0; i < filter_count; i ++) { ecs_alert_severity_filter_t *filter = &filters[i]; if (!filter->var) { if (ecs_table_has_id(world, it->table, filters[i].with)) { return filters[i].severity; } } else { ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); if (src && src != EcsWildcard) { if (ecs_has_id(world, src, filters[i].with)) { return filters[i].severity; } } } } return 0; } static ecs_entity_t flecs_alert_out_of_range_kind( EcsAlert *alert, const EcsMemberRanges *ranges, const void *value_ptr) { double value = 0; switch(alert->kind) { case EcsU8: value = *(const uint8_t*)value_ptr; break; case EcsU16: value = *(const uint16_t*)value_ptr; break; case EcsU32: value = *(const uint32_t*)value_ptr; break; case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; case EcsI8: value = *(const int8_t*)value_ptr; break; case EcsI16: value = *(const int16_t*)value_ptr; break; case EcsI32: value = *(const int32_t*)value_ptr; break; case EcsI64: value = (double)*(const int64_t*)value_ptr; break; case EcsF32: value = (double)*(const float*)value_ptr; break; case EcsF64: value = *(const double*)value_ptr; break; case EcsBool: case EcsChar: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: default: return 0; } bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); if (has_error && (value < ranges->error.min || value > ranges->error.max)) { return EcsAlertError; } else if (has_warning && (value < ranges->warning.min || value > ranges->warning.max)) { return EcsAlertWarning; } else { return 0; } } static void MonitorAlerts(ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsAlert *alert = ecs_field(it, EcsAlert, 0); EcsPoly *poly = ecs_field(it, EcsPoly, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t a = it->entities[i]; /* Alert entity */ ecs_entity_t default_severity = ecs_get_target( world, a, ecs_id(EcsAlert), 0); ecs_query_t *q = poly[i].poly; if (!q) { continue; } flecs_poly_assert(q, ecs_query_t); ecs_id_t member_id = alert[i].id; const EcsMemberRanges *ranges = NULL; if (member_id) { ranges = ecs_ref_get(it->world, &alert[i].ranges, EcsMemberRanges); } ecs_iter_t rit = ecs_query_iter(world, q); rit.flags |= EcsIterNoData; while (ecs_query_next(&rit)) { ecs_entity_t severity = flecs_alert_get_severity( it->world, &rit, &alert[i]); if (!severity) { severity = default_severity; } const void *member_data = NULL; ecs_entity_t member_src = 0; if (ranges) { if (alert[i].var_id) { member_src = ecs_iter_get_var(&rit, alert[i].var_id); if (!member_src || member_src == EcsWildcard) { continue; } } if (!member_src) { member_data = ecs_table_get_id( world, rit.table, member_id, rit.offset); } else { member_data = ecs_get_id(world, member_src, member_id); } if (!member_data) { continue; } member_data = ECS_OFFSET(member_data, alert[i].offset); } int32_t j, alert_src_count = rit.count; for (j = 0; j < alert_src_count; j ++) { ecs_entity_t src_severity = severity; ecs_entity_t e = rit.entities[j]; if (member_data) { ecs_entity_t range_severity = flecs_alert_out_of_range_kind( &alert[i], ranges, member_data); if (!member_src) { member_data = ECS_OFFSET(member_data, alert[i].size); } if (!range_severity) { continue; } if (range_severity < src_severity) { /* Actual severity should not exceed range severity */ src_severity = range_severity; } } ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); if (!aptr[0]) { /* Alert does not yet exist for entity */ ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); ecs_set(world, ai, EcsMetricSource, { .entity = e }); ecs_set(world, ai, EcsMetricValue, { .value = 0 }); ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); if (ECS_NEQZERO(alert[i].retain_period)) { ecs_set(world, ai, EcsAlertTimeout, { .inactive_time = 0, .expire_time = alert[i].retain_period }); } flecs_alerts_add_alert_to_src(it->world, e, a, ai); aptr[0] = ai; } else { /* Make sure alert severity is up to date */ if (ecs_vec_count(&alert[i].severity_filters) || member_data) { ecs_entity_t cur_severity = ecs_get_target( world, aptr[0], ecs_id(EcsAlert), 0); if (cur_severity != src_severity) { ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), src_severity); } } } } } } } static void MonitorAlertInstances(ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 0); EcsMetricSource *source = ecs_field(it, EcsMetricSource, 1); EcsMetricValue *value = ecs_field(it, EcsMetricValue, 2); EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 3); /* Get alert component from alert instance parent (the alert) */ ecs_id_t childof_pair; if (ecs_search(it->world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { ecs_err("alert instances must be a child of an alert"); return; } ecs_entity_t parent = ecs_pair_second(world, childof_pair); ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, "alert entity does not have Alert component"); EcsAlert *alert = ecs_ensure(world, parent, EcsAlert); const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); ecs_assert(poly != NULL, ECS_INVALID_OPERATION, "alert entity does not have (Poly, Query) component"); ecs_query_t *query = poly->poly; if (!query) { return; } flecs_poly_assert(query, ecs_query_t); ecs_id_t member_id = alert->id; const EcsMemberRanges *ranges = NULL; if (member_id) { ranges = ecs_ref_get(it->world, &alert->ranges, EcsMemberRanges); } ecs_script_vars_t *vars = ecs_script_vars_init(it->stage); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t ai = it->entities[i]; ecs_entity_t e = source[i].entity; /* If source of alert is no longer alive, delete alert instance even if * the alert has a retain period. */ if (!ecs_is_alive(world, e)) { ecs_delete(world, ai); continue; } /* Check if alert instance still matches query */ ecs_iter_t rit = ecs_query_iter(world, query); rit.flags |= EcsIterNoData; ecs_iter_set_var(&rit, 0, e); if (ecs_query_next(&rit)) { bool match = true; /* If alert is monitoring member range, test value against range */ if (ranges) { ecs_entity_t member_src = e; if (alert->var_id) { member_src = ecs_iter_get_var(&rit, alert->var_id); } const void *member_data = ecs_get_id( world, member_src, member_id); if (!member_data) { match = false; } else { member_data = ECS_OFFSET(member_data, alert->offset); if (flecs_alert_out_of_range_kind( alert, ranges, member_data) == 0) { match = false; } } } if (match) { /* Only increase alert duration if the alert was active */ value[i].value += (double)it->delta_system_time; bool generate_message = alert->message; if (generate_message) { if (alert_instance[i].message) { /* If a message was already generated, only regenerate if * query has multiple variables. Variable values could have * changed, this ensures the message remains up to date. */ generate_message = ecs_iter_get_var_count(&rit) > 1; } } if (generate_message) { if (alert_instance[i].message) { ecs_os_free(alert_instance[i].message); } ecs_script_vars_from_iter(&rit, vars, 0); alert_instance[i].message = ecs_script_string_interpolate( world, alert->message, vars); } if (timeout) { if (ECS_NEQZERO(timeout[i].inactive_time)) { /* The alert just became active. Remove Disabled tag */ flecs_alerts_add_alert_to_src(world, e, parent, ai); ecs_remove_id(world, ai, EcsDisabled); } timeout[i].inactive_time = 0; } /* Alert instance still matches query, keep it alive */ ecs_iter_fini(&rit); continue; } ecs_iter_fini(&rit); } /* Alert instance is no longer active */ if (timeout) { if (ECS_EQZERO(timeout[i].inactive_time)) { /* The alert just became inactive. Add Disabled tag */ flecs_alerts_remove_alert_from_src(world, e, parent); ecs_add_id(world, ai, EcsDisabled); } ecs_ftime_t t = timeout[i].inactive_time; timeout[i].inactive_time += it->delta_system_time; if (t < timeout[i].expire_time) { /* Alert instance no longer matches query, but is still * within the timeout period. Keep it alive. */ continue; } } /* Alert instance no longer matches query, remove it */ flecs_alerts_remove_alert_from_src(world, e, parent); ecs_map_remove(&alert->instances, e); ecs_delete(world, ai); } ecs_script_vars_fini(vars); } ecs_entity_t ecs_alert_init( ecs_world_t *world, const ecs_alert_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_alert_desc_t was not initialized to zero"); ecs_check(!desc->query.entity || desc->entity == desc->query.entity, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); } ecs_query_desc_t private_desc = desc->query; private_desc.entity = result; ecs_query_t *q = ecs_query_init(world, &private_desc); if (!q) { ecs_err("failed to create alert query"); return 0; } if (!(q->flags & EcsQueryMatchThis)) { ecs_err("alert query must have at least one '$this' term"); ecs_query_fini(q); return 0; } /* Initialize Alert component which identifies entity as alert */ EcsAlert *alert = ecs_ensure(world, result, EcsAlert); ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); alert->message = ecs_os_strdup(desc->message); alert->retain_period = desc->retain_period; /* Initialize severity filters */ int32_t i; for (i = 0; i < 4; i ++) { if (desc->severity_filters[i].with) { if (!desc->severity_filters[i].severity) { ecs_err("severity filter must have severity"); goto error; } ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, &alert->severity_filters, ecs_alert_severity_filter_t); *sf = desc->severity_filters[i]; if (sf->var) { sf->_var_index = ecs_query_find_var(q, sf->var); if (sf->_var_index == -1) { ecs_err("unresolved variable '%s' in alert severity filter", sf->var); goto error; } } } } /* Fetch data for member monitoring */ if (desc->member) { alert->member = desc->member; if (!desc->id) { alert->id = ecs_get_parent(world, desc->member); if (!alert->id) { ecs_err("ecs_alert_desc_t::member is not a member"); goto error; } ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); } else { alert->id = desc->id; } ecs_component_record_t *cr = flecs_components_ensure(world, alert->id); ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); if (!cr->type_info) { ecs_err("ecs_alert_desc_t::id must be a component"); goto error; } ecs_entity_t type = cr->type_info->component; if (type != ecs_get_parent(world, desc->member)) { char *type_name = ecs_get_path(world, type); ecs_err("member '%s' is not a member of '%s'", ecs_get_name(world, desc->member), type_name); ecs_os_free(type_name); goto error; } const EcsMember *member = ecs_get(world, alert->member, EcsMember); if (!member) { ecs_err("ecs_alert_desc_t::member is not a member"); goto error; } if (!member->type) { ecs_err("ecs_alert_desc_t::member must have a type"); goto error; } const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); if (!pr) { ecs_err("ecs_alert_desc_t::member must be of a primitive type"); goto error; } if (!ecs_has(world, desc->member, EcsMemberRanges)) { ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); goto error; } int32_t var_id = 0; if (desc->var) { var_id = ecs_query_find_var(q, desc->var); if (var_id == -1) { ecs_err("unresolved variable '%s' in alert member", desc->var); goto error; } } alert->offset = member->offset; alert->size = cr->type_info->size; alert->kind = pr->kind; alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); alert->var_id = var_id; } ecs_modified(world, result, EcsAlert); /* Register alert as metric */ ecs_add(world, result, EcsMetric); ecs_add_pair(world, result, EcsMetric, EcsCounter); /* Add severity to alert */ ecs_entity_t severity = desc->severity; if (!severity) { severity = EcsAlertError; } ecs_add_pair(world, result, ecs_id(EcsAlert), severity); if (desc->doc_name) { #ifdef FLECS_DOC ecs_doc_set_name(world, result, desc->doc_name); #else ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); goto error; #endif } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); goto error; #endif } return result; error: if (result) { ecs_delete(world, result); } return 0; } int32_t ecs_get_alert_count( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t alert) { flecs_poly_assert(world, ecs_world_t); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!alert || ecs_has(world, alert, EcsAlert), ECS_INVALID_PARAMETER, NULL); const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); if (!active) { return 0; } if (alert) { return ecs_map_get(&active->alerts, alert) != NULL; } return ecs_map_count(&active->alerts); error: return 0; } ecs_entity_t ecs_get_alert( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t alert) { flecs_poly_assert(world, ecs_world_t); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); if (!active) { return 0; } ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); if (ptr) { return ptr[0]; } error: return 0; } void FlecsAlertsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsAlerts); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); ECS_IMPORT(world, FlecsMetrics); ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsAlert); ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); ECS_COMPONENT_DEFINE(world, EcsAlertsActive); ecs_set_name_prefix(world, "EcsAlert"); ECS_COMPONENT_DEFINE(world, EcsAlertInstance); ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); ECS_TAG_DEFINE(world, EcsAlertInfo); ECS_TAG_DEFINE(world, EcsAlertWarning); ECS_TAG_DEFINE(world, EcsAlertError); ECS_TAG_DEFINE(world, EcsAlertCritical); ecs_add_id(world, ecs_id(EcsAlert), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); ecs_struct(world, { .entity = ecs_id(EcsAlertInstance), .members = { { .name = "message", .type = ecs_id(ecs_string_t) } } }); ecs_set_hooks(world, EcsAlert, { .ctor = ecs_ctor(EcsAlert), .dtor = ecs_dtor(EcsAlert), .move = ecs_move(EcsAlert) }); ecs_set_hooks(world, EcsAlertsActive, { .ctor = ecs_ctor(EcsAlertsActive), .dtor = ecs_dtor(EcsAlertsActive), .move = ecs_move(EcsAlertsActive) }); ecs_set_hooks(world, EcsAlertInstance, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsAlertInstance), .move = ecs_move(EcsAlertInstance), .copy = ecs_copy(EcsAlertInstance) }); ecs_struct(world, { .entity = ecs_id(EcsAlertsActive), .members = { { .name = "info_count", .type = ecs_id(ecs_i32_t) }, { .name = "warning_count", .type = ecs_id(ecs_i32_t) }, { .name = "error_count", .type = ecs_id(ecs_i32_t) } } }); ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, flecs.metrics.Source, flecs.metrics.Value, ?Timeout, ?Disabled); ecs_system_update(world, ecs_id(MonitorAlerts), &(ecs_system_desc_t){ .immediate = true, .interval = (ecs_ftime_t)0.5 }); ecs_system_update(world, ecs_id(MonitorAlertInstances), &(ecs_system_desc_t){ .interval = (ecs_ftime_t)0.5 }); } #endif #ifdef FLECS_APP static int flecs_default_run_action( ecs_world_t *world, ecs_app_desc_t *desc) { if (desc->init) { desc->init(world); } int result = 0; if (desc->frames) { int32_t i; for (i = 0; i < desc->frames; i ++) { if ((result = ecs_app_run_frame(world, desc)) != 0) { break; } } } else { while ((result = ecs_app_run_frame(world, desc)) == 0) { } } /* Ensure quit flag is set on world, which can be used to determine if * world needs to be cleaned up. */ #ifndef __EMSCRIPTEN__ ecs_quit(world); #endif if (result == 1) { return 0; /* Normal exit */ } else { return result; /* Error code */ } } static int flecs_default_frame_action( ecs_world_t *world, const ecs_app_desc_t *desc) { return !ecs_progress(world, desc->delta_time); } static ecs_app_run_action_t run_action = flecs_default_run_action; static ecs_app_frame_action_t frame_action = flecs_default_frame_action; static ecs_app_desc_t ecs_app_desc; /* Serve REST API from wasm image when running in Emscripten */ #if defined(ECS_TARGET_EM) && defined(FLECS_REST) #include ecs_http_server_t *flecs_wasm_rest_server = NULL; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request, char *body) { ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, "wasm REST server is not initialized yet"); static char *reply_body; ecs_os_free(reply_body); ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request( flecs_wasm_rest_server, method, request, body, &reply); reply_body = ecs_strbuf_get(&reply.body); ecs_strbuf_reset(&reply.headers); if (!reply_body && reply.code != 200) { reply_body = flecs_asprintf( "{\"error\": \"bad request\", \"status\": %d}", reply.code); } return reply_body; } #endif int ecs_app_run( ecs_world_t *world, ecs_app_desc_t *desc) { ecs_app_desc = *desc; #ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } #endif /* REST server enables connecting to app with explorer */ if (desc->enable_rest) { #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); ecs_assert(flecs_wasm_rest_server != NULL, ECS_INTERNAL_ERROR, "failed to create wasm REST server (unexpected error)"); #else ECS_IMPORT(world, FlecsRest); ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); #endif #else ecs_warn("cannot enable remote API, REST addon not available"); #endif } /* Monitoring periodically collects statistics */ if (desc->enable_stats) { #ifdef FLECS_STATS ECS_IMPORT(world, FlecsStats); #else ecs_warn("cannot enable monitoring, MONITOR addon not available"); #endif } return run_action(world, &ecs_app_desc); } int ecs_app_run_frame( ecs_world_t *world, const ecs_app_desc_t *desc) { return frame_action(world, desc); } int ecs_app_set_run_action( ecs_app_run_action_t callback) { if (run_action != flecs_default_run_action && run_action != callback) { ecs_err("run action already set"); return -1; } run_action = callback; return 0; } int ecs_app_set_frame_action( ecs_app_frame_action_t callback) { if (frame_action != flecs_default_frame_action && frame_action != callback) { ecs_err("frame action already set"); return -1; } frame_action = callback; return 0; } #endif #ifdef FLECS_DOC static ECS_COPY(EcsDocDescription, dst, src, { ecs_os_strset((char**)&dst->value, src->value); }) static ECS_MOVE(EcsDocDescription, dst, src, { ecs_os_free((char*)dst->value); dst->value = src->value; src->value = NULL; }) static ECS_DTOR(EcsDocDescription, ptr, { ecs_os_free((char*)ptr->value); }) static void flecs_doc_set( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t kind, const char *value) { if (value) { ecs_set_pair(world, entity, EcsDocDescription, kind, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, value) }); } else { ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); } } void ecs_doc_set_uuid( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_doc_set(world, entity, EcsDocUuid, name); } void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_doc_set(world, entity, EcsName, name); } void ecs_doc_set_brief( ecs_world_t *world, ecs_entity_t entity, const char *brief) { flecs_doc_set(world, entity, EcsDocBrief, brief); } void ecs_doc_set_detail( ecs_world_t *world, ecs_entity_t entity, const char *detail) { flecs_doc_set(world, entity, EcsDocDetail, detail); } void ecs_doc_set_link( ecs_world_t *world, ecs_entity_t entity, const char *link) { flecs_doc_set(world, entity, EcsDocLink, link); } void ecs_doc_set_color( ecs_world_t *world, ecs_entity_t entity, const char *color) { flecs_doc_set(world, entity, EcsDocColor, color); } const char* ecs_doc_get_uuid( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocUuid); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_name( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsName); if (ptr) { return ptr->value; } else { return ecs_get_name(world, entity); } } const char* ecs_doc_get_brief( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocBrief); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_detail( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocDetail); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_link( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocLink); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_color( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocColor); if (ptr) { return ptr->value; } else { return NULL; } } void FlecsDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsDoc); ecs_set_name_prefix(world, "EcsDoc"); flecs_bootstrap_component(world, EcsDocDescription); flecs_bootstrap_tag(world, EcsDocUuid); flecs_bootstrap_tag(world, EcsDocBrief); flecs_bootstrap_tag(world, EcsDocDetail); flecs_bootstrap_tag(world, EcsDocLink); flecs_bootstrap_tag(world, EcsDocColor); ecs_set_hooks(world, EcsDocDescription, { .ctor = flecs_default_ctor, .move = ecs_move(EcsDocDescription), .copy = ecs_copy(EcsDocDescription), .dtor = ecs_dtor(EcsDocDescription) }); #ifdef FLECS_META ecs_struct(world, { .entity = ecs_id(EcsDocDescription), .members = { { .name = "value", .type = ecs_id(ecs_string_t) } } }); #endif ecs_add_pair(world, ecs_id(EcsDocDescription), EcsOnInstantiate, EcsDontInherit); #ifdef FLECS_CONSTRAINT_TRAITS ecs_add_id(world, ecs_id(EcsDocDescription), EcsRelationship); #endif } #endif #ifdef FLECS_ENTITY_RANGES void flecs_entity_ranges_init( ecs_allocator_t *allocator, ecs_entity_index_t *index) { index->active_range = NULL; ecs_vec_init_t(allocator, &index->ranges, ecs_entity_range_t*, 0); } void flecs_entity_ranges_fini( ecs_entity_index_t *index) { 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*); } 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; } void flecs_entity_ranges_on_ensure( ecs_entity_index_t *index, uint32_t id) { ecs_entity_range_t *active = index->active_range; if (!active || (id >= active->min && (!active->max || id <= active->max))) { index->max_id = id > index->max_id ? id : index->max_id; return; } ecs_entity_range_t *range = flecs_entity_index_find_range(index, id); if (range) { range->cur = id > range->cur ? id : range->cur; } } void flecs_entity_ranges_on_delete( ecs_entity_index_t *index, uint64_t entity, ecs_record_t *r) { 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. 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; } 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; } #endif /* Utilities for C++ API */ #ifdef FLECS_CPP /* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to * a uniform identifier */ #define ECS_CONST_PREFIX "const " #define ECS_STRUCT_PREFIX "struct " #define ECS_CLASS_PREFIX "class " #define ECS_ENUM_PREFIX "enum " #define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) #define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) #define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) #define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) static ecs_size_t ecs_cpp_strip_prefix( char *typeName, ecs_size_t len, const char *prefix, ecs_size_t prefix_len) { if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); typeName[len - prefix_len] = '\0'; len -= prefix_len; } return len; } static void ecs_cpp_trim_type_name( char *typeName) { ecs_size_t len = ecs_os_strlen(typeName); len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); while (typeName[len - 1] == ' ' || typeName[len - 1] == '&' || typeName[len - 1] == '*') { len --; typeName[len] = '\0'; } /* Remove const at end of string */ if (len > ECS_CONST_LEN) { if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { typeName[len - ECS_CONST_LEN] = '\0'; } len -= ECS_CONST_LEN; } /* Check if there are any remaining "struct " strings, which can happen * if this is a template type on msvc. */ if (len > ECS_STRUCT_LEN) { char *ptr = typeName; while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { /* Make sure we're not matched with part of a longer identifier * that contains 'struct' */ if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); len -= ECS_STRUCT_LEN; } } } } char* ecs_cpp_get_type_name( char *type_name, const char *func_name, size_t len, size_t front_len) { memcpy(type_name, func_name + front_len, len); type_name[len] = '\0'; ecs_cpp_trim_type_name(type_name); return type_name; } char* ecs_cpp_get_symbol_name( char *symbol_name, const char *type_name, size_t len) { ecs_assert(type_name != NULL, ECS_INVALID_PARAMETER, NULL); if (!len) { len = flecs_itosize(ecs_os_strlen(type_name)); } if (!symbol_name) { symbol_name = ecs_os_malloc_n(char, flecs_uto(int32_t, len + 1)); ecs_assert(symbol_name != NULL, ECS_OUT_OF_MEMORY, NULL); } const char *ptr; size_t i; for (i = 0, ptr = type_name; i < len && *ptr; i ++, ptr ++) { if (*ptr == ':' && ptr[1] == ':') { symbol_name[i] = '.'; ptr ++; } else { symbol_name[i] = *ptr; } } symbol_name[i] = '\0'; return symbol_name; } static const char* flecs_cpp_func_rchr( const char *func_name, ecs_size_t func_name_len, ecs_size_t func_back_len, char ch) { const char *r = strrchr(func_name, ch); if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { return NULL; } return r; } static const char* flecs_cpp_func_max( const char *a, const char *b) { if (a > b) return a; return b; } char* ecs_cpp_get_constant_name( char *constant_name, const char *func_name, size_t func_name_len, size_t func_back_len) { ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len); const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' '); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ')')); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ':')); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ',')); ecs_assert(start != NULL, ECS_INVALID_PARAMETER, "%s", func_name); start ++; ecs_size_t len = flecs_uto(ecs_size_t, (f_len - (start - func_name) - fb_len)); ecs_os_memcpy_n(constant_name, start, char, len); constant_name[len] = '\0'; return constant_name; } // Names returned from the name_helper class do not start with :: // but are relative to the root. If the namespace of the type // overlaps with the namespace of the current module, strip it from // the implicit identifier. // This allows for registration of component types that are not in the // module namespace to still be registered under the module scope. const char* ecs_cpp_trim_module( ecs_world_t *world, const char *type_name) { ecs_entity_t scope = ecs_get_scope(world); if (!scope) { return type_name; } char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); if (path) { ecs_size_t len = ecs_os_strlen(path); if (!ecs_os_strncmp(path, type_name, len) && type_name[len] == ':') { // Type is a child of current parent, trim name of parent type_name += len; ecs_assert(type_name[0], ECS_INVALID_PARAMETER, "invalid C++ type name"); ecs_assert(type_name[0] == ':', ECS_INVALID_PARAMETER, "invalid C++ type name"); ecs_assert(type_name[1] == ':', ECS_INVALID_PARAMETER, "invalid C++ type name"); type_name += 2; } else { // Type is not a child of current parent, trim entire path char *ptr = strrchr(type_name, ':'); if (ptr) { type_name = ptr + 1; } } } ecs_os_free(path); return type_name; } ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, const ecs_cpp_component_desc_t *desc) { ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); const char *name = desc->name; const char *cpp_symbol = desc->cpp_symbol; bool existing = false; #ifdef FLECS_MULTI_WORLD ecs_entity_t c = flecs_component_ids_get(world, desc->ids_index); #else ecs_entity_t c = *desc->id_storage; #endif if (!c || !ecs_is_alive(world, c)) { } else { return c; } ecs_assert(desc->cpp_name != NULL, ECS_INTERNAL_ERROR, NULL); if (!desc->cpp_symbol) { ecs_size_t len = ecs_os_strlen(desc->cpp_name); char *symbol_name = ecs_os_alloca_n(char, len + 1); cpp_symbol = ecs_cpp_get_symbol_name( symbol_name, desc->cpp_name, flecs_itosize(len)); } const char *user_name = NULL; bool implicit_name = true; ecs_entity_t module = 0; if (desc->explicit_registration) { user_name = desc->name; implicit_name = false; if (!user_name) { user_name = desc->cpp_name; /* Keep track of whether the name was explicitly set. If not, and * the component was already registered, just use the registered * name. The registered name may differ from the typename as the * registered name includes the flecs scope. This can in theory * be different from the C++ namespace, though it is good * practice to keep them the same */ implicit_name = true; } /* If component is registered by module, ensure it's registered in * the scope of the module. */ module = ecs_get_scope(world); /* Strip off the namespace part of the component name, unless a name * was explicitly provided by the application. */ if (module && implicit_name) { /* If the type is a template type, make sure to ignore :: * inside the template parameter list. */ const char *start = strchr(user_name, '<'), *last_elem = NULL; if (start) { const char *ptr = start; while (ptr[0] && (ptr[0] != ':') && (ptr > user_name)) { ptr --; } if (ptr[0] == ':') { last_elem = ptr; } } if (last_elem) { name = last_elem + 1; } } } /* At this point it is possible that the type was already registered * with the world, just not for this binary. The registration code * uses the type symbol to check if it was already registered. Note * that the symbol is separate from the typename, as an application * can override a component name when registering a type. */ /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ ecs_entity_t prev_scope = ecs_set_scope(world, 0); if (desc->id) { c = desc->id; } else { c = ecs_lookup_path_w_sep(world, 0, user_name, "::", "::", false); existing = c != 0 && ecs_has(world, c, EcsComponent); } ecs_set_scope(world, prev_scope); /* If entity exists, compare symbol name to ensure that the component * we are trying to register under this name is the same */ if (c) { const EcsComponent *component = ecs_get(world, c, EcsComponent); if (component != NULL) { const char *sym = ecs_get_symbol(world, c); if (sym && ecs_os_strcmp(sym, cpp_symbol)) { /* Application is trying to register a type with an entity * that was already associated with another type. In most * cases this is an error, with the exception of a scenario * where the application is wrapping a C type with a C++ * type. * * In this case the C++ type typically inherits from the C * type, and adds convenience methods to the derived class * without changing anything that would change the size or * layout. * * To meet this condition, the new type must have the same * size and alignment as the existing type, and the name of * the type must be equal to the registered name. * * The latter ensures that it was the intent of the * application to alias the type, vs. accidentally * registering an unrelated type with the same * size/alignment. */ char *type_path = ecs_get_path(world, c); if (ecs_os_strcmp(type_path, cpp_symbol)) { ecs_err( "component with name '%s' is already registered for"\ " type '%s' (trying to register for type '%s')", name, sym, cpp_symbol); ecs_abort(ECS_NAME_IN_USE, NULL); } if (flecs_itosize(component->size) != desc->size || flecs_itosize(component->alignment) != desc->alignment) { ecs_err( "component with name '%s' is already registered with"\ " mismatching size/alignment)", name); ecs_abort(ECS_INVALID_COMPONENT_SIZE, NULL); } ecs_os_free(type_path); } else if (!sym) { ecs_set_symbol(world, c, cpp_symbol); } } /* If no entity is found, lookup symbol to check if the component was * registered under a different name. */ } else if (!implicit_name) { c = ecs_lookup_symbol(world, cpp_symbol, false, false); ecs_assert(c == 0 || (c == desc->id), ECS_INCONSISTENT_COMPONENT_ID, "%s", cpp_symbol); } const char *symbol = NULL; if (c) { symbol = ecs_get_symbol(world, c); } if (!symbol) { symbol = cpp_symbol; } /* When a component is implicitly registered, ensure that it is not * registered in the current scope of the application. */ prev_scope = ecs_set_scope(world, module); char *existing_name = NULL; /* If an explicit id is provided, it is possible that the symbol and * name differ from the actual type, as the application may alias * one type to another. */ if (!c) { if (!name) { /* If no name was provided first check if a type with the * provided symbol was already registered. */ ecs_id_t e = ecs_lookup_symbol(world, symbol, false, false); if (e) { existing_name = ecs_get_path_w_sep(world, 0, e, "::", "::"); name = existing_name; existing = true; } else { /* If type is not yet known, derive from type name */ name = ecs_cpp_trim_module(world, desc->cpp_name); } } } else { /* If an explicit id is provided but it has no name, inherit * the name from the type. */ if (!ecs_is_valid(world, c) || !ecs_get_name(world, c)) { name = ecs_cpp_trim_module(world, desc->cpp_name); } } if (desc->is_component || desc->size != 0) { c = ecs_entity(world, { .id = c, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); ecs_assert(c != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); c = ecs_component_init(world, &(ecs_component_desc_t){ .entity = c, .type.size = flecs_uto(int32_t, desc->size), .type.alignment = flecs_uto(int32_t, desc->alignment) }); ecs_assert(c != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); } else { c = ecs_entity(world, { .id = c, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); } ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); ecs_set_scope(world, prev_scope); /* Set component id before invoking callbacks that can request it. */ #ifdef FLECS_MULTI_WORLD flecs_component_ids_set(world, desc->ids_index, c); #else *desc->id_storage = c; #endif if (desc->lifecycle_action && desc->size && !existing) { desc->lifecycle_action(world, c); } if (desc->enum_action) { desc->enum_action(world, c); } return c; } void ecs_cpp_enum_init( ecs_world_t *world, ecs_entity_t id, ecs_entity_t underlying_type) { (void)world; (void)id; (void)underlying_type; #ifdef FLECS_META ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); EcsEnum *ptr = ecs_ensure(world, id, EcsEnum); ptr->underlying_type = underlying_type; ecs_modified(world, id, EcsEnum); flecs_resume_readonly(world, &readonly_state); #else /* Make sure that enums still behave the same even without meta */ ecs_add_id(world, id, EcsExclusive); #ifdef FLECS_CONSTRAINT_TRAITS ecs_add_id(world, id, EcsOneOf); #endif #endif } ecs_entity_t ecs_cpp_enum_constant_register( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t id, const char *name, void *value, ecs_entity_t value_type, size_t value_size) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); const char *parent_name = ecs_get_name(world, parent); ecs_size_t parent_name_len = ecs_os_strlen(parent_name); if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { name += parent_name_len; if (name[0] == '_') { name ++; } } ecs_entity_t prev = ecs_set_scope(world, parent); id = ecs_entity(world, { .id = id, .name = name }); ecs_assert(id != 0, ECS_INVALID_OPERATION, "%s", name); ecs_set_scope(world, prev); #ifdef FLECS_DEBUG const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); #endif ecs_set_id(world, id, ecs_pair(EcsConstant, value_type), value_size, value); flecs_resume_readonly(world, &readonly_state); #ifdef FLECS_META if (ecs_should_log(0)) { ecs_value_t v = { .type = value_type, .ptr = value }; char *str = NULL; ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &str); ecs_meta_set_value(&cur, &v); ecs_trace("#[green]constant#[reset] %s.%s created with value %s", ecs_get_name(world, parent), name, str); ecs_os_free(str); } #endif return id; } #ifdef FLECS_META ecs_member_t* ecs_cpp_last_member( const ecs_world_t *world, ecs_entity_t type) { const EcsStruct *st = ecs_get(world, type, EcsStruct); if (!st) { char *type_str = ecs_get_path(world, type); ecs_err("entity '%s' is not a struct", type_str); ecs_os_free(type_str); return 0; } ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, ecs_vec_count(&st->members) - 1); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); return m; } #endif ecs_cpp_get_mut_t ecs_cpp_set( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, const void *new_ptr, size_t size) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_cpp_get_mut_t result; if (flecs_defer_cmd(stage, deferred)) { result.ptr = flecs_defer_cpp_set(world, stage, entity, id, flecs_utosize(size), new_ptr); /* Modified command is already inserted */ result.call_modified = false; result.stage = NULL; return result; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r, flecs_uto(int32_t, size)); result.ptr = dst.ptr; result.world = world; result.stage = stage; if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[id]) { result.call_modified = false; goto done; } } /* Not deferring, so need to call modified after setting the component */ result.call_modified = true; if (dst.ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); } done: return result; error: return (ecs_cpp_get_mut_t){0}; } ecs_cpp_get_mut_t ecs_cpp_assign( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, const void *new_ptr, size_t size) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_cpp_get_mut_t result; if (flecs_defer_cmd(stage, deferred)) { result.ptr = flecs_defer_cpp_assign( world, stage, entity, id, flecs_uto(int32_t, size), new_ptr); /* Modified command is already inserted */ result.call_modified = false; result.stage = NULL; return result; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r, flecs_uto(int32_t, size)); ecs_assert(dst.ptr != NULL, ECS_INVALID_OPERATION, "entity does not have component, use set() instead"); result.ptr = dst.ptr; result.world = world; result.stage = stage; if (id < FLECS_HI_COMPONENT_ID) { if (!world->non_trivial_set[id]) { result.call_modified = false; goto done; } } /* Not deferring, so need to call modified after setting the component */ result.call_modified = true; if (dst.ti->hooks.on_replace) { flecs_invoke_replace_hook( world, r->table, entity, id, dst.ptr, new_ptr, dst.ti); } done: return result; error: return (ecs_cpp_get_mut_t){0}; } ecs_entity_t ecs_cpp_new( ecs_world_t *world, ecs_entity_t parent, const char *name, const char *sep, const char *root_sep) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (!parent && !name) { if (!stage->scope) { ecs_entity_t result = flecs_new_id(world); flecs_add_to_root_table(world, result); return result; } } ecs_entity_desc_t desc = {0}; desc.parent = parent; desc.name = name; desc.sep = sep; desc.root_sep = root_sep; return ecs_entity_init(world, &desc); } #endif #ifdef FLECS_FRAME static ecs_ftime_t flecs_insert_sleep( ecs_world_t *world, ecs_time_t *stop) { flecs_poly_assert(world, ecs_world_t); ecs_time_t start = *stop, now = start; ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); if (ECS_EQZERO(world->info.target_fps)) { return delta_time; } ecs_os_perf_trace_push("flecs.insert_sleep"); ecs_ftime_t target_delta_time = ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); /* Calculate the time we need to sleep by taking the measured delta from the * previous frame, and subtracting it from target_delta_time. */ ecs_ftime_t sleep = target_delta_time - delta_time; /* Pick a sleep interval that is smaller than the time one frame should take, * which increases the sleep precision. */ ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)8.0; if (sleep_time < 0) { sleep_time = 0; delta_time = (ecs_ftime_t)ecs_time_measure(&now); } else { do { /* Only call sleep when sleep_time is not 0. On some platforms, even * a sleep with a timeout of 0 can cause stutter. */ if (ECS_NEQZERO(sleep_time)) { ecs_sleepf((double)sleep_time); } now = start; delta_time = (ecs_ftime_t)ecs_time_measure(&now); } while ((target_delta_time - delta_time) > (sleep_time / (ecs_ftime_t)2.0)); } ecs_os_perf_trace_pop("flecs.insert_sleep"); *stop = now; return delta_time; } static ecs_ftime_t flecs_start_measure_frame( ecs_world_t *world, ecs_ftime_t user_delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_ftime_t delta_time = 0; if ((world->flags & EcsWorldMeasureFrameTime) || (ECS_EQZERO(user_delta_time))) { ecs_time_t t = world->frame_start_time; do { if (world->frame_start_time.nanosec || world->frame_start_time.sec){ delta_time = flecs_insert_sleep(world, &t); } else { ecs_time_measure(&t); if (ECS_NEQZERO(world->info.target_fps)) { delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; } else { /* Best guess */ delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; if (ECS_EQZERO(delta_time)) { delta_time = user_delta_time; break; } } } /* Keep trying while delta_time is zero */ } while (ECS_EQZERO(delta_time)); world->frame_start_time = t; /* Keep track of total time passed in world */ world->info.world_time_total_raw += (double)delta_time; } return (ecs_ftime_t)delta_time; } static void flecs_stop_measure_frame( ecs_world_t* world) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_t t = world->frame_start_time; world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } ecs_ftime_t ecs_frame_begin( ecs_world_t *world, ecs_ftime_t user_delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot begin frame while world is in readonly mode"); ecs_check(!(world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, "cannot begin frame while frame is already in progress"); ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); /* Start measuring total frame time */ ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); if (ECS_EQZERO(user_delta_time)) { user_delta_time = delta_time; } world->info.delta_time_raw = user_delta_time; world->info.delta_time = user_delta_time * world->info.time_scale; /* Keep track of total scaled time passed in world */ world->info.world_time_total += (double)world->info.delta_time; /* Command buffer capturing */ world->on_commands_active = world->on_commands; world->on_commands = NULL; world->on_commands_ctx_active = world->on_commands_ctx; world->on_commands_ctx = NULL; ecs_run_aperiodic(world, 0); world->flags |= EcsWorldFrameInProgress; return world->info.delta_time; error: return (ecs_ftime_t)0; } void ecs_frame_end( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot end frame while world is in readonly mode"); ecs_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, "cannot end frame while frame is not in progress"); world->info.frame_count_total ++; int32_t i, count = world->stage_count; for (i = 0; i < count; i ++) { flecs_stage_merge_post_frame(world, world->stages[i]); } flecs_stop_measure_frame(world); /* Reset command handler each frame */ world->on_commands_active = NULL; world->on_commands_ctx_active = NULL; world->flags &= ~EcsWorldFrameInProgress; 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; } 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 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_time_scale( ecs_world_t *world, ecs_ftime_t scale) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot set time scale while world is in readonly mode"); world->info.time_scale = scale; } void ecs_reset_clock( ecs_world_t *world) { world->info.world_time_total = 0; world->info.world_time_total_raw = 0; } #endif #ifdef FLECS_JOURNAL static char* flecs_journal_entitystr( ecs_world_t *world, ecs_entity_t entity) { char *path; const char *_path = ecs_get_symbol(world, entity); if (_path && !strchr(_path, '.')) { path = flecs_asprintf("#[blue]%s", _path); } else { uint32_t gen = (uint32_t)(entity >> 32); if (gen) { path = flecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); } else { path = flecs_asprintf("#[normal]_%u", (uint32_t)entity); } } return path; } static char* flecs_journal_idstr( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_VALUE_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *result = flecs_asprintf("#[cyan]ecs_value_pair#[normal](%s, %u)", first_path, ECS_PAIR_SECOND(id)); ecs_os_free(first_path); return result; } else if (ECS_IS_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *second_path = flecs_journal_entitystr(world, ecs_pair_second(world, id)); char *result = flecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", first_path, second_path); ecs_os_free(first_path); ecs_os_free(second_path); return result; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_journal_entitystr(world, id); } else { return ecs_id_str(world, id); } } static int flecs_journal_counter = 0; static int flecs_journal_sp = 0; void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove) { flecs_journal_sp ++; flecs_journal_counter ++; if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { return; } char *path = NULL; char *var_id = NULL; if (entity) { if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { path = ecs_get_path(world, entity); var_id = flecs_journal_entitystr(world, entity); } else { path = ecs_id_str(world, entity); var_id = flecs_journal_idstr(world, entity); } } if (kind == EcsJournalNew) { ecs_print(4, "%d: #[magenta]#ifndef #[normal]_var_%s", flecs_journal_counter, var_id); ecs_print(4, "%d: #[magenta]#define #[normal]_var_%s", flecs_journal_counter, var_id); ecs_print(4, "%d: #[green]ecs_entity_t %s;", flecs_journal_counter, var_id); ecs_print(4, "%d: #[magenta]#endif", flecs_journal_counter); ecs_print(4, "%d: %s = #[cyan]ecs_new#[reset](world); " "#[grey] // %s = new()", flecs_journal_counter, var_id, path); } if (kind == EcsJournalSetParent) { ecs_entity_t parent = add->array[0]; char *parentstr = flecs_journal_entitystr(world, parent); ecs_print(4, "%d: #[cyan]ecs_set#[reset](world, %s, EcsParent, {%s}); " "#[grey] // set(%s, EcsParent, %s)", flecs_journal_counter, var_id, parentstr, path, parentstr); ecs_os_free(parentstr); } else { if (add) { for (int i = 0; i < add->count; i ++) { char *jidstr = flecs_journal_idstr(world, add->array[i]); char *idstr = ecs_id_str(world, add->array[i]); ecs_print(4, "%d: #[cyan]ecs_add_id#[reset](world, %s, %s); " "#[grey] // add(%s, %s)", flecs_journal_counter, var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (remove) { for (int i = 0; i < remove->count; i ++) { char *jidstr = flecs_journal_idstr(world, remove->array[i]); char *idstr = ecs_id_str(world, remove->array[i]); ecs_print(4, "%d: #[cyan]ecs_remove_id#[reset](world, %s, %s); " "#[grey] // remove(%s, %s)", flecs_journal_counter, var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (kind == EcsJournalClear) { ecs_print(4, "%d: #[cyan]ecs_clear#[reset](world, %s); " "#[grey] // clear(%s)", flecs_journal_counter, var_id, path); } else if (kind == EcsJournalDelete) { ecs_print(4, "%d: #[cyan]ecs_delete#[reset](world, %s); " "#[grey] // delete(%s)", flecs_journal_counter, var_id, path); } else if (kind == EcsJournalDeleteWith) { ecs_print(4, "%d: #[cyan]ecs_delete_with#[reset](world, %s); " "#[grey] // delete_with(%s)", flecs_journal_counter, var_id, path); } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "%d: #[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", flecs_journal_counter, var_id, path); } } ecs_os_free(var_id); ecs_os_free(path); ecs_log_push(); } void flecs_journal_end(void) { flecs_journal_sp --; ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } int flecs_journal_get_counter(void) { return flecs_journal_counter; } #endif #ifdef FLECS_LOG static ecs_strbuf_t *flecs_log_last_err = NULL; static bool flecs_log_append_errors = false; static int32_t flecs_log_last_err_line = 0; static int32_t flecs_log_last_err_column = 0; static int32_t flecs_parser_err_line = 0; static int32_t flecs_parser_err_column = 0; static ecs_os_api_log_t flecs_log_prev_log = NULL; static ecs_os_api_log_t flecs_log_prev_fatal_log = NULL; static bool flecs_log_prev_color = false; static int flecs_log_prev_level = 0; static int flecs_log_capture_depth = 0; static int32_t flecs_log_stopped_err_line = 0; static int32_t flecs_log_stopped_err_column = 0; typedef struct flecs_log_capture_frame_t { ecs_strbuf_t *last_err; bool append_errors; int32_t last_err_line; int32_t last_err_column; int32_t parser_err_line; int32_t parser_err_column; int32_t depth; struct flecs_log_capture_frame_t *prev; } flecs_log_capture_frame_t; static flecs_log_capture_frame_t *flecs_log_capture_frames = NULL; static void flecs_set_prev_log( ecs_os_api_log_t prev_log, bool try) { flecs_log_prev_log = try ? NULL : prev_log; flecs_log_prev_fatal_log = prev_log; } static void flecs_log_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; if (level <= -4) { /* Make sure to always log fatal errors */ if (flecs_log_prev_fatal_log) { ecs_log_enable_colors(true); flecs_log_prev_fatal_log(level, file, line, msg); ecs_log_enable_colors(false); return; } else { fprintf(stderr, "%s:%d: %s", file, line, msg); } } #ifdef FLECS_DEBUG /* In debug mode, log unexpected errors to the console */ if (level < 0) { /* Also log to previous log function in debug mode */ if (flecs_log_prev_log) { ecs_log_enable_colors(true); flecs_log_prev_log(level, file, line, msg); ecs_log_enable_colors(false); } } #endif if (level <= -3 && (!flecs_log_last_err || flecs_log_append_errors)) { if (!flecs_log_last_err) { flecs_log_last_err = ecs_os_calloc_t(ecs_strbuf_t); } else { ecs_strbuf_appendch(flecs_log_last_err, '\n'); } ecs_strbuf_appendstr(flecs_log_last_err, msg); } if (!flecs_log_last_err_line && level <= -3) { flecs_log_last_err_line = flecs_parser_err_line; flecs_log_last_err_column = flecs_parser_err_column; } } static char* flecs_log_get_captured_log(void) { if (!flecs_log_last_err) { return NULL; } char *result = ecs_strbuf_get(flecs_log_last_err); ecs_os_free(flecs_log_last_err); flecs_log_last_err = NULL; return result; } bool flecs_log_capture_set_append(bool append) { bool prev = flecs_log_append_errors; flecs_log_append_errors = append; return prev; } void ecs_log_start_capture(bool try) { if (flecs_log_capture_depth ++) { return; } flecs_log_last_err_line = 0; flecs_log_last_err_column = 0; flecs_parser_err_line = 0; flecs_parser_err_column = 0; flecs_log_prev_color = ecs_log_enable_colors(false); flecs_log_prev_log = ecs_os_api.log_; flecs_log_prev_level = ecs_os_api.log_level_; flecs_set_prev_log(ecs_os_api.log_, try); ecs_os_api.log_ = flecs_log_capture_log; ecs_os_api.log_level_ = -1; /* Ignore debug tracing, log warnings/errors */ } char* ecs_log_stop_capture(void) { ecs_assert(flecs_log_capture_depth > 0, ECS_INVALID_OPERATION, "log capture not active"); if (-- flecs_log_capture_depth) { return NULL; } flecs_log_stopped_err_line = flecs_log_last_err_line; flecs_log_stopped_err_column = flecs_log_last_err_column; ecs_os_api.log_ = flecs_log_prev_fatal_log; ecs_os_api.log_level_ = flecs_log_prev_level; ecs_log_enable_colors(flecs_log_prev_color); return flecs_log_get_captured_log(); } void flecs_log_capture_push(bool try) { ecs_log_start_capture(try); if (flecs_log_capture_depth == 1) { return; } flecs_log_capture_frame_t *frame = ecs_os_malloc_t(flecs_log_capture_frame_t); frame->last_err = flecs_log_last_err; frame->append_errors = flecs_log_append_errors; flecs_log_append_errors = false; frame->last_err_line = flecs_log_last_err_line; frame->last_err_column = flecs_log_last_err_column; frame->parser_err_line = flecs_parser_err_line; frame->parser_err_column = flecs_parser_err_column; frame->depth = flecs_log_capture_depth; frame->prev = flecs_log_capture_frames; flecs_log_capture_frames = frame; flecs_log_last_err = NULL; flecs_log_last_err_line = 0; flecs_log_last_err_column = 0; flecs_parser_err_line = 0; flecs_parser_err_column = 0; } char* flecs_log_capture_pop(void) { flecs_log_capture_frame_t *frame = flecs_log_capture_frames; if (!frame || frame->depth != flecs_log_capture_depth) { return ecs_log_stop_capture(); } char *result = flecs_log_get_captured_log(); flecs_log_stopped_err_line = flecs_log_last_err_line; flecs_log_stopped_err_column = flecs_log_last_err_column; flecs_log_last_err = frame->last_err; flecs_log_append_errors = frame->append_errors; flecs_log_last_err_line = frame->last_err_line; flecs_log_last_err_column = frame->last_err_column; flecs_parser_err_line = frame->parser_err_line; flecs_parser_err_column = frame->parser_err_column; flecs_log_capture_frames = frame->prev; ecs_os_free(frame); flecs_log_capture_depth --; return result; } void flecs_log_get_captured_error_pos( int32_t *line, int32_t *column) { if (line) { *line = flecs_log_stopped_err_line; } if (column) { *column = flecs_log_stopped_err_column; } } void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf) { ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); char *ptr, ch, prev = '\0'; bool isNum = false; char isStr = '\0'; bool isVar = false; bool overrideColor = false; bool autoColor = true; bool dontAppend = false; for (ptr = msg; (ch = *ptr); ptr++) { dontAppend = false; if (!overrideColor) { if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isNum = false; } if (isStr && (isStr == ch) && prev != '\\') { isStr = '\0'; } else if (((ch == '\'') || (ch == '"')) && !isStr && !isalpha(prev) && (prev != '\\')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isStr = ch; } if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && !isalpha(prev) && !isdigit(prev) && (prev != '_') && (prev != '.')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); isNum = true; } if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isVar = false; } if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isVar = true; } } if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { bool isColor = true; overrideColor = true; /* Custom colors */ if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { autoColor = false; } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("blue]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { overrideColor = false; if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else { isColor = false; overrideColor = false; } if (isColor) { ptr += 2; while ((ch = *ptr) != ']') ptr ++; dontAppend = true; } if (!autoColor) { overrideColor = true; } } if (ch == '\n') { if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); overrideColor = false; isNum = false; isStr = false; isVar = false; } } if (!dontAppend) { ecs_strbuf_appendstrn(buf, ptr, 1); } if (!overrideColor) { if (((ch == '\'') || (ch == '"')) && !isStr) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } prev = ch; } if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } void ecs_printv_( int32_t level, const char *file, int32_t line, const char *fmt, va_list args) { (void)level; (void)line; ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; /* Apply color. Even if we don't want color, we still need to call the * colorize function to get rid of the color tags (e.g. #[green]) */ char *msg_nocolor = flecs_vasprintf(fmt, args); flecs_colorize_buf(msg_nocolor, ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); ecs_os_free(msg_nocolor); char *msg = ecs_strbuf_get(&msg_buf); if (msg) { ecs_os_api.log_(level, file, line, msg); ecs_os_free(msg); } else { ecs_os_api.log_(level, file, line, ""); } } void ecs_print_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { va_list args; va_start(args, fmt); ecs_printv_(level, file, line, fmt, args); va_end(args); } void ecs_logv_( int32_t level, const char *file, int32_t line, const char *fmt, va_list args) { if (level > ecs_os_api.log_level_) { return; } ecs_printv_(level, file, line, fmt, args); } void ecs_log_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { if (level > ecs_os_api.log_level_) { return; } va_list args; va_start(args, fmt); ecs_printv_(level, file, line, fmt, args); va_end(args); } void ecs_log_push_( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ ++; } } void ecs_log_pop_( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ --; ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); } } static void flecs_parser_errorv( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args, bool is_warning) { if (expr) { if (column_arg > ecs_os_strlen(expr)) { column_arg = 0; } } else if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the * function is called with (expr - ptr), and expr is NULL. */ column_arg = 0; } if (column_arg < -1) { column_arg = -1; } int32_t column = flecs_itoi32(column_arg); if (ecs_os_api.log_level_ >= -2) { ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; /* Count number of newlines up until column_arg */ int32_t i, line = 1, line_start = 0; if (expr) { for (i = 0; i < column; i ++) { if (expr[i] == '\n') { line ++; line_start = i + 1; } } ecs_strbuf_append(&msg_buf, "%d: ", line); } if (!is_warning) { if (expr && column != -1) { flecs_parser_err_line = line; flecs_parser_err_column = column - line_start + 1; } else { flecs_parser_err_line = 0; flecs_parser_err_column = 0; } } ecs_strbuf_vappend(&msg_buf, fmt, args); if (expr) { ecs_strbuf_appendch(&msg_buf, '\n'); /* Find start of line by taking column and looking for the * last occurring newline */ if (column != -1) { const char *ptr = &expr[column]; if (ptr[0] == '\n') { ptr --; } while (ptr[0] != '\n' && ptr > expr) { ptr --; } if (ptr[0] == '\n') { ptr ++; } if (ptr == expr) { /* ptr is already at start of line */ } else { column -= (int32_t)(ptr - expr); expr = ptr; } } /* Strip newlines from current statement, if any */ char *newline_ptr = strchr(expr, '\n'); if (newline_ptr) { /* Strip newline from expr */ ecs_strbuf_appendstrn(&msg_buf, expr, (int32_t)(newline_ptr - expr)); } else { ecs_strbuf_appendstr(&msg_buf, expr); } ecs_strbuf_appendch(&msg_buf, '\n'); if (column != -1) { int32_t c; for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } ecs_strbuf_appendch(&msg_buf, '^'); } } char *msg = ecs_strbuf_get(&msg_buf); if (is_warning) { ecs_os_warn(name, 0, msg); } else { ecs_os_err(name, 0, msg); } ecs_os_free(msg); } } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { flecs_parser_errorv(name, expr, column_arg, fmt, args, false); } void ecs_parser_warningv_( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { flecs_parser_errorv(name, expr, column_arg, fmt, args, true); } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); ecs_parser_errorv_(name, expr, column, fmt, args); va_end(args); } } void ecs_parser_warning_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); ecs_parser_warningv_(name, expr, column, fmt, args); va_end(args); } } void ecs_abort_( int32_t err, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "#[red]abort()#[reset]: %s (#[blue]%s#[reset])", msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "#[red]abort()#[reset]: #[blue]%s#[reset]", ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } void ecs_assert_log_( int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "#[red]assert(%s)#[reset]: %s (#[blue]%s#[reset])", cond_str, msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "#[red]assert(%s)#[reset] (#[blue]%s#[reset])", cond_str, ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } void ecs_deprecated_( const char *file, int32_t line, const char *msg) { ecs_err_(file, line, "%s", msg); } bool ecs_should_log(int32_t level) { # if !defined(FLECS_LOG_3) if (level == 3) { return false; } # endif # if !defined(FLECS_LOG_2) if (level == 2) { return false; } # endif # if !defined(FLECS_LOG_1) if (level == 1) { return false; } # endif return level <= ecs_os_api.log_level_; } #define ECS_ERR_STR(code) case code: return &(#code[4]) const char* ecs_strerror( int32_t error_code) { switch (error_code) { ECS_ERR_STR(ECS_INVALID_PARAMETER); ECS_ERR_STR(ECS_INTERNAL_ERROR); ECS_ERR_STR(ECS_ALREADY_DEFINED); ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); ECS_ERR_STR(ECS_NAME_IN_USE); ECS_ERR_STR(ECS_OUT_OF_MEMORY); ECS_ERR_STR(ECS_DOUBLE_FREE); ECS_ERR_STR(ECS_OPERATION_FAILED); ECS_ERR_STR(ECS_INVALID_CONVERSION); ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_LEAK_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); ECS_ERR_STR(ECS_INVALID_FROM_WORKER); ECS_ERR_STR(ECS_OUT_OF_RANGE); ECS_ERR_STR(ECS_MISSING_OS_API); ECS_ERR_STR(ECS_UNSUPPORTED); ECS_ERR_STR(ECS_ACCESS_VIOLATION); ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); ECS_ERR_STR(ECS_INCONSISTENT_NAME); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); ECS_ERR_STR(ECS_INVALID_OPERATION); ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); ECS_ERR_STR(ECS_LOCKED_STORAGE); } return "unknown error code"; } #else /* Empty bodies for when logging is disabled */ void ecs_log_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_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; } bool flecs_log_capture_set_append(bool append) { (void)append; return false; } void ecs_log_start_capture(bool try) { (void)try; } char* ecs_log_stop_capture(void) { return NULL; } void flecs_log_capture_push(bool try) { (void)try; } char* flecs_log_capture_pop(void) { return NULL; } void flecs_log_get_captured_error_pos( int32_t *line, int32_t *column) { if (line) { *line = 0; } if (column) { *column = 0; } } #endif 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; } #ifdef FLECS_METRICS /* Public components */ ECS_COMPONENT_DECLARE(FlecsMetrics); ECS_TAG_DECLARE(EcsMetricInstance); ECS_COMPONENT_DECLARE(EcsMetricValue); ECS_COMPONENT_DECLARE(EcsMetricSource); ECS_TAG_DECLARE(EcsMetric); ECS_TAG_DECLARE(EcsCounter); ECS_TAG_DECLARE(EcsCounterIncrement); ECS_TAG_DECLARE(EcsCounterId); ECS_TAG_DECLARE(EcsGauge); /* Internal components */ static ECS_COMPONENT_DECLARE(EcsMetricMember); static ECS_COMPONENT_DECLARE(EcsMetricId); static ECS_COMPONENT_DECLARE(EcsMetricOneOf); static ECS_COMPONENT_DECLARE(EcsMetricCountIds); static ECS_COMPONENT_DECLARE(EcsMetricCountTargets); static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); static ECS_COMPONENT_DECLARE(EcsMetricIdInstance); static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); /** Context for metric */ typedef struct { ecs_entity_t metric; /**< Metric entity */ ecs_entity_t kind; /**< Metric kind (gauge, counter) */ } ecs_metric_ctx_t; /** Context for metric that monitors member */ typedef struct { ecs_metric_ctx_t metric; ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */ uint16_t offset; /**< Offset of member in component */ } ecs_member_metric_ctx_t; /** Context for metric that monitors whether entity has id */ typedef struct { ecs_metric_ctx_t metric; ecs_component_record_t *cr; /**< component record for monitored component */ } ecs_id_metric_ctx_t; /** Context for metric that monitors whether entity has pair target */ typedef struct { ecs_metric_ctx_t metric; ecs_component_record_t *cr; /**< component record for monitored component */ ecs_size_t size; /**< Size of metric type */ ecs_map_t target_offset; /**< Pair target to metric type offset */ } ecs_oneof_metric_ctx_t; /** Context for metric that monitors how many entities have a pair target */ typedef struct { ecs_metric_ctx_t metric; ecs_component_record_t *cr; /**< component record for monitored component */ ecs_map_t targets; /**< Map of counters for each target */ } ecs_count_targets_metric_ctx_t; /** Stores context shared for all instances of member metric */ typedef struct { ecs_member_metric_ctx_t *ctx; } EcsMetricMember; /** Stores context shared for all instances of id metric */ typedef struct { ecs_id_metric_ctx_t *ctx; } EcsMetricId; /** Stores context shared for all instances of oneof metric */ typedef struct { ecs_oneof_metric_ctx_t *ctx; } EcsMetricOneOf; /** Stores context shared for all instances of id counter metric */ typedef struct { ecs_id_t id; } EcsMetricCountIds; /** Stores context shared for all instances of target counter metric */ typedef struct { ecs_count_targets_metric_ctx_t *ctx; } EcsMetricCountTargets; /** Instance of member metric */ typedef struct { ecs_ref_t ref; ecs_id_t id; ecs_member_metric_ctx_t *ctx; } EcsMetricMemberInstance; /** Instance of id metric */ typedef struct { ecs_record_t *r; ecs_id_metric_ctx_t *ctx; } EcsMetricIdInstance; /** Instance of oneof metric */ typedef struct { ecs_record_t *r; ecs_oneof_metric_ctx_t *ctx; } EcsMetricOneOfInstance; /** Component lifecycle */ static ECS_DTOR(EcsMetricMember, ptr, { ecs_os_free(ptr->ctx); }) static ECS_MOVE(EcsMetricMember, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricId, ptr, { ecs_os_free(ptr->ctx); }) static ECS_MOVE(EcsMetricId, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricOneOf, ptr, { if (ptr->ctx) { ecs_map_fini(&ptr->ctx->target_offset); ecs_os_free(ptr->ctx); } }) static ECS_MOVE(EcsMetricOneOf, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricCountTargets, ptr, { if (ptr->ctx) { ecs_map_fini(&ptr->ctx->targets); ecs_os_free(ptr->ctx); } }) static ECS_MOVE(EcsMetricCountTargets, dst, src, { *dst = *src; src->ctx = NULL; }) /** Observer used for creating new instances of member metric */ static void flecs_metrics_on_member_metric(ecs_iter_t *it) { ecs_world_t *world = it->stage; ecs_member_metric_ctx_t *ctx = it->ctx; ecs_id_t id = ecs_field_id(it, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricMemberInstance *src = ecs_emplace( world, m, EcsMetricMemberInstance, NULL); src->ref = ecs_ref_init_id(world, e, id); src->id = id; src->ctx = ctx; ecs_modified(world, m, EcsMetricMemberInstance); ecs_set(world, m, EcsMetricValue, { 0 }); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Observer used for creating new instances of id metric */ static void flecs_metrics_on_id_metric(ecs_iter_t *it) { ecs_world_t *world = it->stage; ecs_id_metric_ctx_t *ctx = it->ctx; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricIdInstance *src = ecs_emplace( world, m, EcsMetricIdInstance, NULL); src->r = ecs_record_find(world, e); src->ctx = ctx; ecs_modified(world, m, EcsMetricIdInstance); ecs_set(world, m, EcsMetricValue, { 0 }); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Observer used for creating new instances of oneof metric */ static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { if (it->event == EcsOnRemove) { return; } ecs_world_t *world = it->stage; ecs_oneof_metric_ctx_t *ctx = it->ctx; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricOneOfInstance *src = ecs_emplace( world, m, EcsMetricOneOfInstance, NULL); src->r = ecs_record_find(world, e); src->ctx = ctx; ecs_modified(world, m, EcsMetricOneOfInstance); ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue)); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Set doc name of metric instance to name of source entity */ #ifdef FLECS_DOC static void SetMetricDocName(ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsMetricSource *src = ecs_field(it, EcsMetricSource, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t src_e = src[i].entity; const char *name = ecs_get_name(world, src_e); if (name) { ecs_doc_set_name(world, it->entities[i], name); } } } #endif /** Delete metric instances for entities that are no longer alive */ static void ClearMetricInstance(ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsMetricSource *src = ecs_field(it, EcsMetricSource, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t src_e = src[i].entity; if (!ecs_is_alive(world, src_e)) { ecs_delete(world, it->entities[i]); } } } /** Update member metric */ static void UpdateMemberInstance(ecs_iter_t *it, bool counter) { EcsMetricValue *m = ecs_field(it, EcsMetricValue, 0); EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_member_metric_ctx_t *ctx = mi[i].ctx; ecs_ref_t *ref = &mi[i].ref; if (!ref->entity) { continue; } const void *ptr = ecs_ref_get_id(it->world, ref, mi[i].id); if (ptr) { ptr = ECS_OFFSET(ptr, ctx->offset); if (!counter) { m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr); } else { m[i].value += ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; } } else { ecs_delete(it->stage, it->entities[i]); } } } static void UpdateGaugeMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, false); } static void UpdateCounterMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, false); } static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, true); } /** Update id metric */ static void UpdateIdInstance(ecs_iter_t *it, bool counter) { EcsMetricValue *m = ecs_field(it, EcsMetricValue, 0); EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *table = r->table; if (!table) { ecs_delete(it->stage, it->entities[i]); continue; } ecs_id_metric_ctx_t *ctx = mi[i].ctx; ecs_component_record_t *cr = ctx->cr; if (ecs_search(it->world, table, cr->id, NULL) != -1) { if (!counter) { m[i].value = 1.0; } else { m[i].value += 1.0 * (double)dt; } } else { ecs_delete(it->stage, it->entities[i]); } } } static void UpdateGaugeIdInstance(ecs_iter_t *it) { UpdateIdInstance(it, false); } static void UpdateCounterIdInstance(ecs_iter_t *it) { UpdateIdInstance(it, true); } /** Update oneof metric */ static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { ecs_table_t *table = it->table; void *m = ecs_table_get_column(table, it->trs[0]->column, it->offset); EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; ecs_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *mtable = r->table; double *value = ECS_ELEM(m, ctx->size, i); if (!counter) { ecs_os_memset(value, 0, ctx->size); } if (!mtable) { ecs_delete(it->stage, it->entities[i]); continue; } ecs_component_record_t *cr = ctx->cr; ecs_id_t id; if (ecs_search(it->world, mtable, cr->id, &id) == -1) { ecs_delete(it->stage, it->entities[i]); continue; } ecs_entity_t tgt = ECS_PAIR_SECOND(id); uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt); if (!offset) { ecs_err("unexpected relationship target for metric"); continue; } value = ECS_OFFSET(value, *offset); if (!counter) { *value = 1.0; } else { *value += 1.0 * (double)dt; } } } static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { UpdateOneOfInstance(it, false); } static void UpdateCounterOneOfInstance(ecs_iter_t *it) { UpdateOneOfInstance(it, true); } static void UpdateCountTargets(ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_count_targets_metric_ctx_t *ctx = m[i].ctx; ecs_component_record_t *cur = ctx->cr; while ((cur = flecs_component_first_next(cur))) { ecs_id_t id = cur->id; ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); if (!mi[0]) { mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); ecs_entity_t tgt = ecs_pair_second(world, cur->id); const char *name = ecs_get_name(world, tgt); if (name) { ecs_set_name(world, mi[0], name); } EcsMetricSource *source = ecs_ensure( world, mi[0], EcsMetricSource); source->entity = tgt; } EcsMetricValue *value = ecs_ensure(world, mi[0], EcsMetricValue); value->value += (double)ecs_count_id(world, cur->id) * (double)it->delta_system_time; } } } static void UpdateCountIds(ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 0); EcsMetricValue *v = ecs_field(it, EcsMetricValue, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { v[i].value += (double)ecs_count_id(world, m[i].id) * (double)it->delta_system_time; } } /** Initialize member metric */ static int flecs_member_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_entity_t type = 0, member_type = 0, member = 0, id = 0; uintptr_t offset = 0; if (desc->dotmember) { if (!desc->id) { char *metric_name = ecs_get_path(world, metric); ecs_err("missing id for metric '%s' with member '%s", metric_name, desc->dotmember); ecs_os_free(metric_name); goto error; } if (desc->member) { char *metric_name = ecs_get_path(world, metric); ecs_err("cannot set both member and dotmember for metric '%s'", metric_name); ecs_os_free(metric_name); goto error; } type = ecs_get_typeid(world, desc->id); ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); if (ecs_meta_push(&cur)) { char *metric_name = ecs_get_path(world, metric); ecs_err("invalid type for metric '%s'", metric_name); ecs_os_free(metric_name); goto error; } if (ecs_meta_dotmember(&cur, desc->dotmember)) { char *metric_name = ecs_get_path(world, metric); ecs_err("invalid dotmember '%s' for metric '%s'", desc->dotmember, metric_name); ecs_os_free(metric_name); goto error; } id = desc->id; member_type = ecs_meta_get_type(&cur); offset = (uintptr_t)ecs_meta_get_ptr(&cur); member = ecs_meta_get_member_id(&cur); } else { const EcsMember *m = ecs_get(world, desc->member, EcsMember); if (!m) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("entity '%s' provided for metric '%s' is not a member", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } type = ecs_get_parent(world, desc->member); if (!type) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("member '%s' provided for metric '%s' is not part of a type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } id = type; if (desc->id) { if (type != ecs_get_typeid(world, desc->id)) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); char *id_name = ecs_get_path(world, desc->id); ecs_err("member '%s' for metric '%s' is not of type '%s'", member_name, metric_name, id_name); ecs_os_free(id_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } id = desc->id; } member = desc->member; member_type = m->type; offset = flecs_ito(uintptr_t, m->offset); } const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive); if (!p) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("member '%s' provided for metric '%s' must have primitive type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } const EcsType *mt = ecs_get(world, type, EcsType); if (!mt) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("parent of member '%s' for metric '%s' is not a type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } if (mt->kind != EcsStructType) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("parent of member '%s' for metric '%s' is not a struct", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->type_kind = p->kind; ctx->offset = flecs_uto(uint16_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .query.terms[0] = { .id = id }, .callback = flecs_metrics_on_member_metric, .yield_existing = true, .ctx = ctx }); ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** Initialize id metric */ static int flecs_id_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->cr = flecs_components_ensure(world, desc->id); ecs_check(ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .query.terms[0] = { .id = desc->id }, .callback = flecs_metrics_on_id_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** Initialize oneof metric */ static int flecs_oneof_metric_init( ecs_world_t *world, ecs_entity_t metric, ecs_entity_t scope, const ecs_metric_desc_t *desc) { ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->cr = flecs_components_ensure(world, desc->id); ecs_check(ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->target_offset, NULL); /* Add member for each child of oneof to metric, so it can be used as metric * instance type that holds values for all targets */ ecs_iter_t it = ecs_children(world, scope); uint64_t offset = 0; while (ecs_children_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_entity_t tgt = it.entities[i]; const char *name = ecs_get_name(world, tgt); if (!name) { /* Member must have name */ continue; } char *to_snake_case = flecs_to_snake_case(name); ecs_entity_t mbr = ecs_entity(world, { .name = to_snake_case, .parent = metric }); ecs_os_free(to_snake_case); ecs_set(world, mbr, EcsMember, { .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }); /* Truncate upper 32 bits of target so we can lookup the offset * with the id we get from the pair */ ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; offset += sizeof(double); } } ctx->size = flecs_uto(ecs_size_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsMonitor }, .query.terms[0] = { .id = desc->id }, .callback = flecs_metrics_on_oneof_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_id_targets_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->cr = flecs_components_ensure(world, desc->id); ecs_check(ctx->cr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->targets, NULL); ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_ids_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); ecs_set(world, metric, EcsMetricValue, { .value = 0 }); return 0; } ecs_entity_t ecs_metric_init( ecs_world_t *world, const ecs_metric_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_metric_desc_t was not initialized to zero"); flecs_poly_assert(world, ecs_world_t); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); } ecs_entity_t kind = desc->kind; if (!kind) { ecs_err("missing metric kind"); goto error; } if (kind != EcsGauge && kind != EcsCounter && kind != EcsCounterId && kind != EcsCounterIncrement) { ecs_err("invalid metric kind %s", ecs_get_path(world, kind)); goto error; } if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { ecs_err("CounterIncrement can only be used in combination with member"); goto error; } if (kind == EcsCounterId && (desc->member || desc->dotmember)) { ecs_err("CounterId cannot be used in combination with member"); goto error; } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); #endif } if (desc->member || desc->dotmember) { if (flecs_member_metric_init(world, result, desc)) { goto error; } } else if (desc->id) { if (desc->targets) { if (!ecs_id_is_pair(desc->id)) { ecs_err("cannot specify targets for id that is not a pair"); goto error; } if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { ecs_err("first element of pair cannot be wildcard with " " targets enabled"); goto error; } if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { ecs_err("second element of pair must be wildcard with " " targets enabled"); goto error; } if (kind == EcsCounterId) { if (flecs_count_id_targets_metric_init(world, result, desc)) { goto error; } } else { ecs_entity_t first = ecs_pair_first(world, desc->id); ecs_entity_t scope = flecs_get_oneof(world, first); if (!scope) { ecs_err("first element of pair must have OneOf with " " targets enabled"); goto error; } if (flecs_oneof_metric_init(world, result, scope, desc)) { goto error; } } } else { if (kind == EcsCounterId) { if (flecs_count_ids_metric_init(world, result, desc)) { goto error; } } else { if (flecs_id_metric_init(world, result, desc)) { goto error; } } } } else { ecs_err("missing source specified for metric"); goto error; } return result; error: if (result && result != desc->entity) { ecs_delete(world, result); } return 0; } void FlecsMetricsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMetrics); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); ECS_TAG_DEFINE(world, EcsMetric); ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); ECS_TAG_DEFINE(world, EcsCounter); ECS_TAG_DEFINE(world, EcsCounterIncrement); ECS_TAG_DEFINE(world, EcsCounterId); ECS_TAG_DEFINE(world, EcsGauge); ecs_set_scope(world, old_scope); ecs_set_name_prefix(world, "EcsMetric"); ECS_TAG_DEFINE(world, EcsMetricInstance); ECS_COMPONENT_DEFINE(world, EcsMetricValue); ECS_COMPONENT_DEFINE(world, EcsMetricSource); ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); ECS_COMPONENT_DEFINE(world, EcsMetricMember); ECS_COMPONENT_DEFINE(world, EcsMetricId); ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); ecs_struct(world, { .entity = ecs_id(EcsMetricValue), .members = { { .name = "value", .type = ecs_id(ecs_f64_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsMetricSource), .members = { { .name = "entity", .type = ecs_id(ecs_entity_t) } } }); ecs_set_hooks(world, EcsMetricMember, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricMember), .move = ecs_move(EcsMetricMember) }); ecs_set_hooks(world, EcsMetricId, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricId), .move = ecs_move(EcsMetricId) }); ecs_set_hooks(world, EcsMetricOneOf, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricOneOf), .move = ecs_move(EcsMetricOneOf) }); ecs_set_hooks(world, EcsMetricCountTargets, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricCountTargets), .move = ecs_move(EcsMetricCountTargets) }); #ifdef FLECS_CONSTRAINT_TRAITS ecs_add_id(world, EcsMetric, EcsOneOf); #endif #ifdef FLECS_DOC ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, Source); #endif ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, [in] Source); ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Metric.Gauge)); ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Metric.Counter)); ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Metric.CounterIncrement)); ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, [out] Value, [in] IdInstance, [none] (Metric, Metric.Gauge)); ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, [inout] Value, [in] IdInstance, [none] (Metric, Metric.Counter)); ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Metric.Gauge)); ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Metric.Counter)); ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, [inout] CountIds, Value); ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, [inout] CountTargets); } #endif #ifdef FLECS_MODULE char* flecs_module_path_from_c( const char *c_name) { ecs_strbuf_t str = ECS_STRBUF_INIT; const char *ptr; char ch; for (ptr = c_name; (ch = *ptr); ptr++) { if (isupper(ch)) { ch = flecs_ito(char, tolower(ch)); if (ptr != c_name) { ecs_strbuf_appendstrn(&str, ".", 1); } } ecs_strbuf_appendstrn(&str, &ch, 1); } return ecs_strbuf_get(&str); } ecs_entity_t ecs_import( ecs_world_t *world, ecs_module_action_t module, const char *module_name) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = flecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup(world, path); if (!e) { ecs_trace("#[magenta]import#[reset] %s", module_name); ecs_log_push(); module(world); e = ecs_lookup(world, path); ecs_log_pop(); } ecs_os_free(path); ecs_check(e != 0, ECS_MODULE_UNDEFINED, "%s", module_name); ecs_set_scope(world, old_scope); world->info.name_prefix = old_name_prefix; return e; error: return 0; } ecs_entity_t ecs_import_c( ecs_world_t *world, ecs_module_action_t module, const char *c_name) { char *name = flecs_module_path_from_c(c_name); ecs_entity_t e = ecs_import(world, module, name); ecs_os_free(name); return e; } ecs_entity_t ecs_import_from_library( ecs_world_t *world, const char *library_name, const char *module_name) { ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); char *import_func = ECS_CONST_CAST(char*, module_name); char *module = ECS_CONST_CAST(char*, module_name); if (!ecs_os_has_modules() || !ecs_os_has_dl()) { ecs_err( "library loading not supported, set module_to_dl, dlopen, dlclose " "and dlproc os API callbacks first"); return 0; } /* If no module name is specified, try default naming convention for loading * the main module from the library */ if (!import_func) { import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); const char *ptr; char ch, *bptr = import_func; bool capitalize = true; for (ptr = library_name; (ch = *ptr); ptr ++) { if (ch == '.') { capitalize = true; } else { if (capitalize) { *bptr = flecs_ito(char, toupper(ch)); bptr ++; capitalize = false; } else { *bptr = flecs_ito(char, tolower(ch)); bptr ++; } } } *bptr = '\0'; module = ecs_os_strdup(import_func); ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcat(bptr, "Import"); } char *library_filename = ecs_os_module_to_dl(library_name); if (!library_filename) { ecs_err("failed to find library file for '%s'", library_name); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("found file '%s' for library '%s'", library_filename, library_name); } ecs_os_dl_t dl = ecs_os_dlopen(library_filename); if (!dl) { ecs_err("failed to load library '%s' ('%s')", library_name, library_filename); ecs_os_free(library_filename); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("library '%s' ('%s') loaded", library_name, library_filename); } ecs_module_action_t action = (ecs_module_action_t) ecs_os_dlproc(dl, import_func); if (!action) { ecs_err("failed to load import function %s from library %s", import_func, library_name); ecs_os_free(library_filename); ecs_os_dlclose(dl); return 0; } else { ecs_trace("found import function '%s' in library '%s' for module '%s'", import_func, library_name, module); } ecs_entity_t result = ecs_import(world, action, module); if (import_func != module_name) { ecs_os_free(import_func); } if (module != module_name) { ecs_os_free(module); } ecs_os_free(library_filename); return result; error: return 0; } ecs_entity_t ecs_module_init( ecs_world_t *world, const char *c_name, const ecs_component_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); flecs_poly_assert(world, ecs_world_t); ecs_entity_t old_scope = ecs_set_scope(world, 0); ecs_entity_t e = desc->entity; if (!e) { char *module_path = flecs_module_path_from_c(c_name); e = ecs_entity(world, { .name = module_path }); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } else if (!ecs_exists(world, e)) { char *module_path = flecs_module_path_from_c(c_name); ecs_make_alive(world, e); ecs_add_fullpath(world, e, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } ecs_add_id(world, e, EcsModule); ecs_component_desc_t private_desc = *desc; private_desc.entity = e; if (desc->type.size) { ecs_entity_t result = ecs_component_init(world, &private_desc); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); (void)result; } ecs_set_scope(world, old_scope); return e; error: return 0; } #endif #ifdef FLECS_MULTI_WORLD static int32_t flecs_component_ids_last_index = 0; void flecs_multi_world_init( ecs_world_t *world) { ecs_vec_init_t( &world->allocator, &world->component_ids, ecs_entity_t, 0); } void flecs_multi_world_fini( ecs_world_t *world) { ecs_vec_fini_t( &world->allocator, &world->component_ids, ecs_entity_t); } 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; } #endif #ifndef FLECS_JSON_PRIVATE_H #define FLECS_JSON_PRIVATE_H #ifdef FLECS_JSON /* Deserialize from JSON */ typedef enum ecs_json_token_t { JsonObjectOpen, JsonObjectClose, JsonArrayOpen, JsonArrayClose, JsonColon, JsonComma, JsonNumber, JsonString, JsonBoolean, JsonTrue, JsonFalse, JsonNull, JsonLargeInt, JsonLargeString, JsonInvalid } ecs_json_token_t; typedef struct ecs_json_value_ser_ctx_t { ecs_entity_t type; const EcsTypeSerializer *ser; char *id_label; ecs_id_t id; } ecs_json_value_ser_ctx_t; /* Cached data for serializer */ typedef struct ecs_json_ser_ctx_t { ecs_component_record_t *cr_doc_name; ecs_component_record_t *cr_doc_color; ecs_json_value_ser_ctx_t value_ctx[64]; ecs_map_t serialized; ecs_strbuf_t *type_info_buf; ecs_map_t type_info_seen; } ecs_json_ser_ctx_t; typedef struct ecs_json_this_data_t { const ecs_table_t *table; const ecs_entity_t *ids; const EcsIdentifier *names; const EcsParent *parents; const EcsDocDescription *label; const EcsDocDescription *brief; const EcsDocDescription *detail; const EcsDocDescription *color; const EcsDocDescription *link; bool has_alerts; } ecs_json_this_data_t; const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token); const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf); const char* flecs_json_parse_next_member( const char *json, char *token, ecs_json_token_t *token_kind, const ecs_from_json_desc_t *desc); const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_string( const char *json, char *token, char **out, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc); /* Serialize to JSON */ void flecs_json_next( ecs_strbuf_t *buf); void flecs_json_number( ecs_strbuf_t *buf, double value); void flecs_json_u32( ecs_strbuf_t *buf, uint32_t value); void flecs_json_true( ecs_strbuf_t *buf); void flecs_json_false( ecs_strbuf_t *buf); void flecs_json_bool( ecs_strbuf_t *buf, bool value); void flecs_json_null( ecs_strbuf_t *buf); void flecs_json_array_push( ecs_strbuf_t *buf); void flecs_json_array_pop( ecs_strbuf_t *buf); void flecs_json_object_push( ecs_strbuf_t *buf); void flecs_json_object_pop( ecs_strbuf_t *buf); void flecs_json_string( ecs_strbuf_t *buf, const char *value); void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value); void flecs_json_string_escape_ctrl( ecs_strbuf_t *buf, const char *value); void flecs_json_member( ecs_strbuf_t *buf, const char *name); void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len); #define flecs_json_memberl(buf, name)\ flecs_json_membern(buf, name, sizeof(name) - 1) void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_path_or_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e, bool path); void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id); void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id, bool fullpath); ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_op_kind_t kind); int flecs_json_serialize_iter_result( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx); void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, const ecs_query_t *q, int field, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx); void flecs_json_serialize_query( const ecs_world_t *world, const ecs_query_t *q, ecs_strbuf_t *buf); int flecs_json_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str); bool flecs_json_serialize_get_value_ctx( const ecs_world_t *world, ecs_id_t id, ecs_json_value_ser_ctx_t *ctx, const ecs_iter_to_json_desc_t *desc); int flecs_json_serialize_iter_result_table( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data); int flecs_json_serialize_iter_result_query( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data); bool flecs_json_serialize_iter_this( const ecs_iter_t *it, const char *parent_path, const ecs_json_this_data_t *this_data, int32_t row, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx); bool flecs_json_serialize_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc); int flecs_json_serialize_matches( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity); int flecs_json_serialize_refs( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, ecs_entity_t relationship); int flecs_json_serialize_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity); int flecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx); bool flecs_json_should_serialize( ecs_entity_t entity, const ecs_iter_t *it, const ecs_json_ser_ctx_t *ser_ctx); void flecs_json_mark_serialized( ecs_entity_t entity, ecs_json_ser_ctx_t *ser_ctx); void flecs_json_accum_type_info( const ecs_world_t *world, ecs_entity_t typeid, ecs_json_ser_ctx_t *ser_ctx); void flecs_json_type_info_accum_init( ecs_json_ser_ctx_t *ser_ctx, ecs_strbuf_t *type_info_buf, const ecs_world_t *world); void flecs_json_type_info_accum_fini( ecs_json_ser_ctx_t *ser_ctx, ecs_strbuf_t *type_info_buf); void flecs_json_assemble_output( ecs_strbuf_t *out, ecs_strbuf_t *type_info_buf, ecs_strbuf_t *body_buf); #endif #endif /* FLECS_JSON_PRIVATE_H */ #ifdef FLECS_REST /* Retain captured commands for one minute at 60 FPS */ #define FLECS_REST_COMMAND_RETAIN_COUNT (60 * 60) typedef struct { char *cmds; ecs_time_t start_time; ecs_strbuf_t buf; } ecs_rest_cmd_sync_capture_t; typedef struct { ecs_vec_t syncs; } ecs_rest_cmd_capture_t; static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_rest_server_fini(impl->srv); } } ecs_os_free(ptr->ipaddr); }) static void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) { ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); ecs_strbuf_vappend(&reply->body, fmt, args); ecs_strbuf_appendlit(&reply->body, "\"}"); } static void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); flecs_reply_verror(reply, fmt, args); va_end(args); } static void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { if (!ecs_os_strcmp(value, "true")) { value_out[0] = true; } else { value_out[0] = false; } } } static void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = atoi(value); } } static void flecs_rest_string_param( const ecs_http_request_t *req, const char *name, char **value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = ECS_CONST_CAST(char*, value); } } static void flecs_rest_parse_json_ser_entity_params( ecs_world_t *world, ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "entity_id", &desc->serialize_entity_id); flecs_rest_bool_param(req, "doc", &desc->serialize_doc); flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "matches", &desc->serialize_matches); flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); char *rel = NULL; flecs_rest_string_param(req, "refs", &rel); if (rel) { desc->serialize_refs = ecs_lookup(world, rel); } } static void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); flecs_rest_bool_param(req, "doc", &desc->serialize_doc); flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); flecs_rest_bool_param(req, "query_profile", &desc->serialize_query_profile); flecs_rest_bool_param(req, "table", &desc->serialize_table); flecs_rest_bool_param(req, "fields", &desc->serialize_fields); bool results = true; flecs_rest_bool_param(req, "results", &results); desc->dont_serialize_results = !results; } static bool flecs_rest_get_entity( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *path = &req->path[7]; ecs_dbg_2("rest: request entity '%s'", path); ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { ecs_dbg_2("rest: entity '%s' not found", path); if (strchr(path, '.') && ecs_lookup_path_w_sep( world, 0, path, ".", NULL, false)) { char *suggestion = ecs_os_strdup(path); char *sep; for (sep = suggestion; (sep = strchr(sep, '.')); sep ++) { *sep = '/'; } flecs_reply_error(reply, "entity '%s' not found, did you mean '/entity/%s'?", path, suggestion); ecs_os_free(suggestion); } else { flecs_reply_error(reply, "entity '%s' not found", path); } reply->code = 404; return true; } ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; flecs_rest_parse_json_ser_entity_params(world, &desc, req); if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static bool flecs_rest_put_entity( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_dbg_2("rest: create entity '%s'", path); ecs_entity_t result = ecs_entity(world, { .name = path, .sep = "/" }); if (!result) { ecs_dbg_2("rest: failed to create entity '%s'", path); flecs_reply_error(reply, "failed to create entity '%s'", path); reply->code = 500; return true; } ecs_strbuf_appendlit(&reply->body, "{\"id\":\""); ecs_strbuf_appendint(&reply->body, (uint32_t)result); ecs_strbuf_appendlit(&reply->body, "\"}"); return true; } static void flecs_rest_parse_json_ser_world_params( ecs_world_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "modules", &desc->serialize_modules); } static bool flecs_rest_get_world( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { ecs_world_to_json_desc_t desc = {0}; flecs_rest_parse_json_ser_world_params(&desc, req); if (ecs_world_to_json_buf(world, &reply->body, &desc) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static ecs_entity_t flecs_rest_entity_from_path( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; } return e; } static bool flecs_rest_get_type_info( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_dbg_2("rest: request type info '%s'", path); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } char *json = ecs_type_info_to_json(world, e); if (!json) { flecs_reply_error(reply, "failed to serialize type info for '%s'", path); reply->code = 500; return true; } ecs_strbuf_appendstr(&reply->body, json); ecs_os_free(json); return true; } static bool flecs_rest_get_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { flecs_reply_error(reply, "component '%s' is not a type", component); reply->code = 400; return true; } const void *ptr = ecs_get_id(world, e, id); if (!ptr) { flecs_reply_error(reply, "failed to get component '%s'", component); reply->code = 500; return true; } ecs_ptr_to_json_buf(world, type, ptr, &reply->body); return true; } static bool flecs_rest_put_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } const char *data = ecs_http_get_param(req, "value"); if (!data) { ecs_add_id(world, e, id); return true; } const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (!ti) { flecs_reply_error(reply, "component '%s' is not a type", component); reply->code = 400; return true; } void *ptr = ecs_os_malloc(ti->size); if (!ptr) { flecs_reply_error(reply, "failed to create component '%s'", component); reply->code = 500; return true; } const void *cur = ecs_get_id(world, e, id); if (cur) { flecs_type_info_copy_ctor(ptr, cur, 1, ti); } else { ecs_os_memset(ptr, 0, ti->size); flecs_type_info_ctor(ptr, 1, ti); } ecs_entity_t type = ti->component; bool valid = ecs_ptr_from_json(world, type, ptr, data, NULL) != NULL; if (valid) { ecs_set_id(world, e, id, flecs_ito(size_t, ti->size), ptr); } flecs_type_info_dtor(ptr, 1, ti); ecs_os_free(ptr); if (!valid) { flecs_reply_error(reply, "invalid value for component '%s'", component); reply->code = 400; } return true; } static bool flecs_rest_delete_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_remove_id(world, e, id); return true; } static bool flecs_rest_delete_entity( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } ecs_delete(world, e); return true; } static bool flecs_rest_toggle( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } bool enable = true; flecs_rest_bool_param(req, "enable", &enable); const char *component = ecs_http_get_param(req, "component"); if (!component) { ecs_enable(world, e, enable); return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_entity_t rel = 0; if (ECS_IS_PAIR(id)) { rel = ecs_pair_first(world, id); } else { rel = id & ECS_COMPONENT_MASK; } if (!ecs_has_id(world, rel, EcsCanToggle)) { flecs_reply_error(reply, "cannot toggle component '%s'", component); reply->code = 400; return true; } ecs_enable_id(world, e, id, enable); return true; } static bool flecs_rest_script( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { (void)world; (void)req; (void)reply; (void)path; #ifdef FLECS_SCRIPT ecs_entity_t script = flecs_rest_entity_from_path(world, reply, path); if (!script) { /* Entity does not exist yet, create it. Reset the reply that was * populated by the failed lookup, as the request itself is valid. */ ecs_strbuf_reset(&reply->body); reply->code = 200; script = ecs_entity(world, { .name = path, .sep = "/" }); if (!script) { flecs_reply_error(reply, "invalid script name '%s'", path); reply->code = 400; return true; } } /* If true, check if file changed */ bool check_file = false; flecs_rest_bool_param(req, "check_file", &check_file); /* If true, save code to file */ bool save_file = false; flecs_rest_bool_param(req, "save_file", &save_file); const char *code = ecs_http_get_param(req, "code"); if (!code) { code = req->body; } bool try = false; flecs_rest_bool_param(req, "try", &try); if (!code) { code = ""; } ecs_strbuf_appendlit(&reply->body, "{"); const EcsScript *s = ecs_get(world, script, EcsScript); if (s && s->filename && save_file) { FILE *f = ecs_os_fopen(s->filename, "w"); fwrite(code, strlen(code), 1, f); ecs_os_fclose(f); } if (s && check_file) { ecs_strbuf_appendlit(&reply->body, "\"changed\": "); if (s->filename) { bool file_is_same; char *file_code = flecs_load_from_file(s->filename); if (!file_code) { file_is_same = code[0] == '\0'; } else { file_is_same = !ecs_os_strcmp(code, file_code); ecs_os_free(file_code); } ecs_strbuf_appendstr(&reply->body, file_is_same ? "false" : "true"); } else { ecs_strbuf_appendstr(&reply->body, "false"); } } /* Update script code */ ecs_script(world, { .entity = script, .code = code }); /* Refetch in case it moved around */ s = ecs_get(world, script, EcsScript); if (!s || s->error) { if (check_file) { ecs_strbuf_appendlit(&reply->body, ", "); } char *escaped_err = flecs_astresc('"', s && s->error ? s->error : "failed to update script"); ecs_strbuf_append(&reply->body, "\"error\": \"%s\"", escaped_err); ecs_os_free(escaped_err); if (!try) { reply->code = 400; } } ecs_strbuf_appendlit(&reply->body, "}"); return true; #else return false; #endif } static void flecs_rest_reply_capture( ecs_http_reply_t *reply, bool ok); static bool flecs_rest_call( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { (void)world; (void)req; (void)reply; (void)path; #ifdef FLECS_SCRIPT ecs_entity_t function = flecs_rest_entity_from_path(world, reply, path); if (!function) { return true; } const EcsScriptFunction *func = ecs_get( world, function, EcsScriptFunction); if (!func) { flecs_reply_error(reply, "entity '%s' is not a script function", path); reply->code = 400; return true; } if (!func->callback || !ecs_get_type_info(world, func->return_type)) { flecs_reply_error(reply, "function '%s' cannot be called synchronously", path); reply->code = 400; return true; } int32_t i, param_count = ecs_vec_count(&func->params); ecs_script_parameter_t *params = ecs_vec_first(&func->params); ecs_value_t *args = param_count ? ecs_os_calloc_n(ecs_value_t, param_count) : NULL; for (i = 0; i < param_count; i ++) { const char *value = ecs_http_get_param(req, params[i].name); if (!value) { flecs_reply_error(reply, "missing argument '%s'", params[i].name); reply->code = 400; goto done; } if (!ecs_get_type_info(world, params[i].type)) { flecs_reply_error(reply, "invalid type for argument '%s'", params[i].name); reply->code = 500; goto done; } args[i] = ecs_value_new(world, params[i].type); const EcsPrimitive *primitive = ecs_get( world, params[i].type, EcsPrimitive); if (primitive && primitive->kind == EcsString) { *(ecs_string_t*)args[i].ptr = ecs_os_strdup(value); } else if (primitive && primitive->kind == EcsChar) { if (value[0] == '\0' || value[1] != '\0') { flecs_reply_error(reply, "invalid value for argument '%s'", params[i].name); reply->code = 400; goto done; } *(char*)args[i].ptr = value[0]; } else { flecs_log_capture_push(true); ecs_expr_eval_desc_t desc = { .type = params[i].type }; const char *ptr = ecs_expr_run( world, value, &args[i], &desc); ecs_os_free(flecs_log_capture_pop()); if (!ptr || ptr[0] != '\0') { flecs_reply_error(reply, "invalid value for argument '%s'", params[i].name); reply->code = 400; goto done; } } } ecs_value_t result = {0}; flecs_log_capture_push(true); bool ok = ecs_function_call(world, function, param_count, args, &result) == 0; flecs_rest_reply_capture(reply, ok); if (ok) { if (ecs_ptr_to_json_buf( world, result.type, result.ptr, &reply->body)) { flecs_reply_error(reply, "failed to serialize result of function '%s'", path); reply->code = 500; } } ecs_value_fini(world, &result); done: for (i = 0; i < param_count; i ++) { ecs_value_fini(world, &args[i]); } ecs_os_free(args); return true; #else return false; #endif } static void flecs_rest_shrink_memory( ecs_world_t *world, void *ctx) { (void)ctx; ecs_shrink(world); } static bool flecs_rest_action( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { (void)path; char *action = &req->path[7]; ecs_dbg_2("rest: run action '%s'", action); if (ecs_os_strcmp(action, "shrink_memory") == 0) { if (ecs_world_get_flags(world) & EcsWorldFrameInProgress) { ecs_run_post_frame(world, flecs_rest_shrink_memory, NULL); } else { flecs_rest_shrink_memory(world, NULL); } } else { flecs_reply_error(reply, "unknown action '%s'", action); reply->code = 400; } return true; } static void flecs_rest_reply_capture( ecs_http_reply_t *reply, bool ok) { char *err = flecs_log_capture_pop(); if (!ok) { if (err) { char *escaped_err = flecs_astresc('"', err); flecs_reply_error(reply, "%s", escaped_err); ecs_os_free(escaped_err); } reply->code = 400; } ecs_os_free(err); } static bool flecs_rest_iter_to_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_poly_t *query, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; flecs_rest_parse_json_ser_iter_params(&desc, req); desc.query = query; int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); if (offset < 0 || limit < 0) { flecs_reply_error(reply, "invalid offset/limit parameter"); return true; } ecs_iter_t pit = ecs_page_iter(it, offset, limit); return ecs_iter_to_json_buf(&pit, &reply->body, &desc) == 0; } static bool flecs_rest_reply_existing_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *name) { ecs_entity_t qe = ecs_lookup(world, name); if (!qe) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); ecs_query_t *q = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); if (!poly_comp) { poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsObserver); if (poly_comp) { q = ((ecs_observer_t*)poly_comp->poly)->query; } else { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } } else { q = poly_comp->poly; } if (!q) { flecs_reply_error(reply, "query '%s' is not initialized", name); reply->code = 400; return true; } ecs_iter_t it = ecs_query_iter(world, q); ecs_dbg_2("rest: request query '%s'", name); flecs_log_capture_push(try); bool ok = false; const char *vars = ecs_http_get_param(req, "vars"); if (vars) { #ifdef FLECS_QUERY_DSL if (ecs_query_args_parse(q, &it, vars) == NULL) { ecs_iter_fini(&it); goto done; } #else flecs_reply_error(reply, "cannot parse query arg expression: script addon required"); reply->code = 400; ecs_iter_fini(&it); goto done; #endif } ok = flecs_rest_iter_to_reply(req, reply, q, &it); done: flecs_rest_reply_capture(reply, ok); return true; } static bool flecs_rest_get_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { const char *q_name = ecs_http_get_param(req, "name"); if (q_name) { return flecs_rest_reply_existing_query(world, req, reply, q_name); } const char *expr = ecs_http_get_param(req, "expr"); if (!expr) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'expr'"); reply->code = 400; /* bad request */ return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); ecs_dbg_2("rest: request query '%s'", expr); flecs_log_capture_push(try); ecs_query_t *q = ecs_query(world, { .expr = expr }); bool ok = q != NULL; if (q) { ecs_iter_t it = ecs_query_iter(world, q); ok = flecs_rest_iter_to_reply(req, reply, q, &it); ecs_query_fini(q); } flecs_rest_reply_capture(reply, ok); if (!q && try) { reply->code = 200; } return true; } #ifdef FLECS_STATS static void flecs_rest_array_append_( ecs_strbuf_t *reply, const char *field, int32_t field_len, const ecs_float_t *values, int32_t t) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "[", ","); int32_t i; for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { int32_t index = i % ECS_STAT_WINDOW; ecs_strbuf_list_next(reply); ecs_strbuf_appendflt(reply, (double)values[index], '"'); } ecs_strbuf_list_pop(reply, "]"); } #define flecs_rest_array_append(reply, field, values, t)\ flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) static void flecs_rest_gauge_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "{", ","); flecs_rest_array_append(reply, "avg", m->gauge.avg, t); flecs_rest_array_append(reply, "min", m->gauge.min, t); flecs_rest_array_append(reply, "max", m->gauge.max, t); if (brief) { ecs_strbuf_list_appendlit(reply, "\"brief\":\""); ecs_strbuf_appendstrn(reply, brief, brief_len); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_counter_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); } #define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_GAUGE_APPEND(reply, s, field, brief)\ ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) #define ECS_COUNTER_APPEND(reply, s, field, brief)\ ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) static void flecs_world_stats_to_json( ecs_strbuf_t *reply, const EcsWorldStats *monitor_stats) { const ecs_world_stats_t *stats = monitor_stats->stats; ecs_strbuf_list_push(reply, "{", ","); ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.ensure_count, "Ensure commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply, true); ecs_strbuf_appendch(reply, '"'); bool disabled = ecs_has_id(world, system, EcsDisabled); ecs_strbuf_list_appendlit(reply, "\"disabled\":"); ecs_strbuf_appendstr(reply, disabled ? "true" : "false"); if (!stats->task) { ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); } ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); ecs_strbuf_list_pop(reply, "}"); } static void flecs_sync_stats_to_json( ecs_http_reply_t *reply, const ecs_pipeline_stats_t *pstats, const ecs_sync_stats_t *stats) { ecs_strbuf_list_push(&reply->body, "{", ","); ecs_strbuf_list_appendlit(&reply->body, "\"multi_threaded\":"); ecs_strbuf_appendbool(&reply->body, stats->multi_threaded); ecs_strbuf_list_appendlit(&reply->body, "\"immediate\":"); ecs_strbuf_appendbool(&reply->body, stats->immediate); ECS_GAUGE_APPEND_T(&reply->body, stats, time_spent, pstats->t, ""); ECS_GAUGE_APPEND_T(&reply->body, stats, commands_enqueued, pstats->t, ""); ecs_strbuf_list_pop(&reply->body, "}"); } static void flecs_all_systems_stats_to_json( ecs_world_t *world, ecs_http_reply_t *reply, ecs_entity_t period) { const EcsSystemStats *stats = ecs_get_pair(world, EcsWorld, EcsSystemStats, period); ecs_strbuf_list_push(&reply->body, "[", ","); if (stats) { ecs_map_iter_t it = ecs_map_iter(&stats->stats); while (ecs_map_next(&it)) { ecs_entity_t id = ecs_map_key(&it); ecs_system_stats_t *sys_stats = ecs_map_ptr(&it); if (!ecs_is_alive(world, id)) { continue; } ecs_strbuf_list_next(&reply->body); flecs_system_stats_to_json(world, &reply->body, id, sys_stats); } } ecs_strbuf_list_pop(&reply->body, "]"); } static void flecs_pipeline_stats_to_json( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_entity_t period) { char *pipeline_name = NULL; flecs_rest_string_param(req, "name", &pipeline_name); if (!pipeline_name || !ecs_os_strcmp(pipeline_name, "all")) { flecs_all_systems_stats_to_json(world, reply, period); return; } ecs_entity_t e = ecs_lookup(world, pipeline_name); if (!e) { flecs_reply_error(reply, "pipeline '%s' not found", pipeline_name); reply->code = 404; return; } const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); const EcsSystemStats *system_stats = ecs_get_pair(world, EcsWorld, EcsSystemStats, period); if (!stats || !system_stats) { goto noresults; } ecs_pipeline_stats_t *pstats = ecs_map_get_deref( &stats->stats, ecs_pipeline_stats_t, e); if (!pstats) { goto noresults; } const EcsPipeline *p = ecs_get(world, e, EcsPipeline); ecs_strbuf_list_push(&reply->body, "[", ","); ecs_pipeline_op_t *ops = ecs_vec_first_t(&p->state->ops, ecs_pipeline_op_t); ecs_system_t **systems = ecs_vec_first_t(&p->state->systems, ecs_system_t*); ecs_sync_stats_t *syncs = ecs_vec_first_t( &pstats->sync_points, ecs_sync_stats_t); int32_t s, o, op_count = ecs_vec_count(&p->state->ops); for (o = 0; o < op_count; o ++) { ecs_pipeline_op_t *op = &ops[o]; for (s = op->offset; s < (op->offset + op->count); s ++) { ecs_system_t *system_data = systems[s]; ecs_entity_t system = system_data->query->entity; if (!ecs_is_alive(world, system)) { continue; } ecs_system_stats_t *sys_stats = ecs_map_get_deref( &system_stats->stats, ecs_system_stats_t, system); ecs_strbuf_list_next(&reply->body); flecs_system_stats_to_json(world, &reply->body, system, sys_stats); } ecs_strbuf_list_next(&reply->body); flecs_sync_stats_to_json(reply, pstats, &syncs[o]); } ecs_strbuf_list_pop(&reply->body, "]"); return; noresults: ecs_strbuf_appendlit(&reply->body, "[]"); } static bool flecs_rest_get_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *period_str = NULL; flecs_rest_string_param(req, "period", &period_str); char *category = &req->path[6]; ecs_entity_t period = EcsPeriod1s; if (period_str) { char *period_name = flecs_asprintf("Period%s", period_str); period = ecs_lookup_child(world, ecs_id(FlecsStats), period_name); ecs_os_free(period_name); if (!period) { flecs_reply_error(reply, "bad request (invalid period string)"); reply->code = 400; return false; } } if (!ecs_os_strcmp(category, "world")) { const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, EcsWorldStats, period); flecs_world_stats_to_json(&reply->body, stats); return true; } else if (!ecs_os_strcmp(category, "pipeline")) { flecs_pipeline_stats_to_json(world, req, reply, period); return true; } else { flecs_reply_error(reply, "bad request (unsupported category)"); reply->code = 400; return false; } } #else static bool flecs_rest_get_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; return false; } #endif static void flecs_rest_append_type_hook( ecs_strbuf_t *reply, const char *name, uint64_t flags, uint64_t illegal_flag, bool has_hook) { ecs_strbuf_list_appendlit(reply, "\""); ecs_strbuf_appendstr(reply, name); ecs_strbuf_appendlit(reply, "\":"); if (flags & illegal_flag) { ecs_strbuf_appendlit(reply, "null"); } else { ecs_strbuf_appendbool(reply, has_hook); } } static void flecs_rest_append_component_memory( ecs_world_t *world, ecs_component_record_t *cr, ecs_strbuf_t *reply, int32_t storage_bytes) { (void)world; (void)cr; (void)reply; (void)storage_bytes; #ifdef FLECS_STATS if (!ecs_id(ecs_component_index_memory_t)) { return; } ecs_component_index_memory_t component_index_memory = {0}; ecs_component_record_memory_get(cr, &component_index_memory); ecs_strbuf_list_appendlit(reply, "\"memory\":"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"component_index\":"); ecs_ptr_to_json_buf( world, ecs_id(ecs_component_index_memory_t), &component_index_memory, reply); /* Only count storage for actual components, not wildcards */ if (ecs_id_is_wildcard(cr->id)) { storage_bytes = 0; } ecs_strbuf_list_appendlit(reply, "\"storage\":"); ecs_strbuf_append(reply, "%d", storage_bytes); ecs_strbuf_list_pop(reply, "}"); #endif } static void flecs_rest_append_component_traits( ecs_component_record_t *cr, ecs_strbuf_t *reply) { ecs_flags32_t flags = cr->flags; ecs_strbuf_list_appendlit(reply, "\"traits\":"); ecs_strbuf_list_push(reply, "[", ","); if (flags & EcsIdOnDeleteRemove) { ecs_strbuf_list_appendlit(reply, "\"(OnDelete,Remove)\""); } if (flags & EcsIdOnDeleteDelete) { ecs_strbuf_list_appendlit(reply, "\"(OnDelete,Delete)\""); } if (flags & EcsIdOnDeletePanic) { ecs_strbuf_list_appendlit(reply, "\"(OnDelete,Panic)\""); } if (flags & EcsIdOnDeleteTargetRemove) { ecs_strbuf_list_appendlit(reply, "\"(OnDeleteTarget,Remove)\""); } if (flags & EcsIdOnDeleteTargetDelete) { ecs_strbuf_list_appendlit(reply, "\"(OnDeleteTarget,Delete)\""); } if (flags & EcsIdOnDeleteTargetPanic) { ecs_strbuf_list_appendlit(reply, "\"(OnDeleteTarget,Panic)\""); } if (flags & EcsIdOnInstantiateOverride) { ecs_strbuf_list_appendlit(reply, "\"(OnInstantiate,Override)\""); } if (flags & EcsIdOnInstantiateInherit) { ecs_strbuf_list_appendlit(reply, "\"(OnInstantiate,Inherit)\""); } if (flags & EcsIdOnInstantiateDontInherit) { ecs_strbuf_list_appendlit(reply, "\"(OnInstantiate,DontInherit)\""); } if (flags & EcsIdExclusive) { ecs_strbuf_list_appendlit(reply, "\"Exclusive\""); } if (flags & EcsIdTraversable) { ecs_strbuf_list_appendlit(reply, "\"Traversable\""); } if (flags & EcsPairIsTag) { ecs_strbuf_list_appendlit(reply, "\"PairIsTag\""); } if (flags & EcsIdWith) { ecs_strbuf_list_appendlit(reply, "\"With\""); } if (flags & EcsIdCanToggle) { ecs_strbuf_list_appendlit(reply, "\"CanToggle\""); } if (flags & EcsIdIsTransitive) { ecs_strbuf_list_appendlit(reply, "\"IsTransitive\""); } if (flags & EcsIdInheritable) { ecs_strbuf_list_appendlit(reply, "\"Inheritable\""); } if (flags & EcsIdSparse) { ecs_strbuf_list_appendlit(reply, "\"Sparse\""); } if (flags & EcsIdDontFragment) { ecs_strbuf_list_appendlit(reply, "\"DontFragment\""); } if (flags & EcsIdOrderedChildren) { ecs_strbuf_list_appendlit(reply, "\"OrderedChildren\""); } if (flags & EcsIdSingleton) { ecs_strbuf_list_appendlit(reply, "\"Singleton\""); } ecs_strbuf_list_pop(reply, "]"); } static void flecs_rest_append_component( ecs_world_t *world, ecs_component_record_t *cr, ecs_strbuf_t *reply) { ecs_strbuf_list_next(reply); ecs_strbuf_appendlit(reply, "\n"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":"); char *str = ecs_id_str(world, cr->id); flecs_json_string_escape(reply, str); ecs_os_free(str); ecs_strbuf_list_appendlit(reply, "\"tables\":"); ecs_strbuf_list_push(reply, "[", ","); ecs_table_cache_iter_t it; int32_t entity_count = 0, entity_size = 0, storage_bytes = 0; flecs_table_cache_iter(&cr->cache, &it, EcsTableNotEmpty); const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_strbuf_list_next(reply); ecs_strbuf_appendint(reply, (int64_t)elem->table->id); entity_count += ecs_table_count(elem->table); entity_size += ecs_table_size(elem->table); } ecs_strbuf_list_pop(reply, "]"); ecs_strbuf_list_appendlit(reply, "\"entity_count\":"); ecs_strbuf_appendint(reply, entity_count); ecs_strbuf_list_appendlit(reply, "\"entity_size\":"); ecs_strbuf_appendint(reply, entity_size); if (cr->type_info) { ecs_strbuf_list_appendlit(reply, "\"type\":"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"size\":"); ecs_strbuf_appendint(reply, cr->type_info->size); ecs_strbuf_list_appendlit(reply, "\"alignment\":"); ecs_strbuf_appendint(reply, cr->type_info->alignment); ecs_type_hooks_t hooks = cr->type_info->hooks; uint64_t flags = hooks.flags; flecs_rest_append_type_hook( reply, "ctor", flags, ECS_TYPE_HOOK_CTOR_ILLEGAL, hooks.ctor != NULL); flecs_rest_append_type_hook( reply, "dtor", flags, ECS_TYPE_HOOK_DTOR_ILLEGAL, hooks.dtor != NULL); flecs_rest_append_type_hook( reply, "copy", flags, ECS_TYPE_HOOK_COPY_ILLEGAL, hooks.copy != NULL); flecs_rest_append_type_hook( reply, "move", flags, ECS_TYPE_HOOK_MOVE_ILLEGAL, hooks.move != NULL); flecs_rest_append_type_hook( reply, "move_ctor", flags, ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL, hooks.move_ctor != NULL); flecs_rest_append_type_hook( reply, "copy_ctor", flags, ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL, hooks.copy_ctor != NULL); ecs_strbuf_list_appendlit(reply, "\"on_add\":"); ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_add != NULL); ecs_strbuf_list_appendlit(reply, "\"on_set\":"); ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_set != NULL); ecs_strbuf_list_appendlit(reply, "\"on_remove\":"); ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_remove != NULL); ecs_strbuf_list_appendlit(reply, "\"on_replace\":"); ecs_strbuf_appendbool(reply, cr->type_info->hooks.on_replace != NULL); ecs_strbuf_list_pop(reply, "}"); storage_bytes += entity_size * cr->type_info->size; } if (cr->sparse) { int32_t i, count = flecs_sparse_count(cr->sparse); ecs_strbuf_list_appendlit(reply, "\"sparse\":"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"count\":"); ecs_strbuf_appendint(reply, count); ecs_strbuf_list_appendlit(reply, "\"entities\":"); ecs_strbuf_list_push(reply, "[", ","); for (i = 0; i < count; i ++) { ecs_strbuf_list_next(reply); ecs_strbuf_appendint(reply, (int64_t) flecs_sparse_ids(cr->sparse)[i]); } ecs_strbuf_list_pop(reply, "]"); ecs_strbuf_list_pop(reply, "}"); if (cr->type_info) { storage_bytes += count * cr->type_info->size; } } flecs_rest_append_component_memory(world, cr, reply, storage_bytes); flecs_rest_append_component_traits(cr, reply); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_get_components( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); FLECS_EACH_COMPONENT_RECORD(cr, { flecs_rest_append_component(world, cr, &reply->body); }) ecs_strbuf_list_pop(&reply->body, "]"); return true; } static void flecs_rest_append_query_memory( ecs_world_t *world, ecs_query_t *query, ecs_strbuf_t *reply) { (void)world; (void)query; (void)reply; #ifdef FLECS_STATS if (!ecs_id(ecs_query_memory_t)) { return; } ecs_query_memory_t query_memory = {0}; ecs_query_memory_get(query, &query_memory); ecs_strbuf_list_appendlit(reply, "\"memory\":"); ecs_ptr_to_json_buf( world, ecs_id(ecs_query_memory_t), &query_memory, reply); #endif } static void flecs_rest_append_query( ecs_world_t *world, ecs_query_t *query, ecs_entity_t entity, ecs_strbuf_t *reply) { ecs_strbuf_list_next(reply); ecs_strbuf_appendlit(reply, "\n"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":"); char *str = ecs_get_path(world, entity); flecs_json_string_escape(reply, str); ecs_os_free(str); ecs_strbuf_list_appendlit(reply, "\"kind\":"); if (ecs_has_id(world, entity, EcsSystem)) { ecs_strbuf_appendlit(reply, "\"System\""); } else if (ecs_has_id(world, entity, EcsObserver)) { ecs_strbuf_appendlit(reply, "\"Observer\""); } else { ecs_strbuf_appendlit(reply, "\"Query\""); } ecs_strbuf_list_appendlit(reply, "\"batched\":"); bool batched = false; if ((query->flags & EcsQueryHasTableThisVar) && (query->flags & EcsQueryMatchThis)) { batched = true; ecs_strbuf_appendlit(reply, "true"); } else { ecs_strbuf_appendlit(reply, "false"); } ecs_iter_t it = ecs_query_iter(world, query); it.flags |= EcsIterMatchEmptyTables; ecs_strbuf_list_appendlit(reply, "\"eval_mode\":"); if (it.flags & EcsIterTrivialCached) ecs_strbuf_appendlit(reply, "\"TrivialCached\""); else if (it.flags & EcsIterCached) ecs_strbuf_appendlit(reply, "\"Cached\""); else if (it.flags & EcsIterTrivialSearch) ecs_strbuf_appendlit(reply, "\"TrivialUncached\""); else if (!query->term_count) ecs_strbuf_appendlit(reply, "\"Noop\""); else ecs_strbuf_appendlit(reply, "\"Plan\""); ecs_strbuf_list_appendlit(reply, "\"cache_kind\":"); switch(query->cache_kind) { case EcsQueryCacheDefault: ecs_strbuf_appendlit(reply, "\"Default\""); break; case EcsQueryCacheAuto: ecs_strbuf_appendlit(reply, "\"Auto\""); break; #ifdef FLECS_CACHED_QUERIES case EcsQueryCacheAll: ecs_strbuf_appendlit(reply, "\"All\""); break; #endif case EcsQueryCacheNone: ecs_strbuf_appendlit(reply, "\"None\""); break; default: ecs_strbuf_appendlit(reply, "\"!! Invalid !!\""); } int32_t results = 0, count = 0, empty_tables = 0; ecs_time_t t = {0}; ecs_time_measure(&t); while (ecs_query_next(&it)) { results ++; count += it.count; if (!count && batched) { empty_tables ++; } } /* Don't count own iteration */ query->eval_count --; double eval_time = ecs_time_measure(&t); ecs_strbuf_list_appendlit(reply, "\"eval_time\":"); ecs_strbuf_appendflt(reply, eval_time, '"'); ecs_strbuf_list_appendlit(reply, "\"eval_count\":"); ecs_strbuf_appendint(reply, query->eval_count); ecs_strbuf_list_appendlit(reply, "\"results\":"); ecs_strbuf_appendint(reply, results); ecs_strbuf_list_appendlit(reply, "\"count\":"); ecs_strbuf_appendint(reply, count); ecs_strbuf_list_appendlit(reply, "\"empty_tables\":"); ecs_strbuf_appendint(reply, empty_tables); ecs_strbuf_list_appendlit(reply, "\"count\":"); ecs_strbuf_appendint(reply, count); ecs_strbuf_list_appendlit(reply, "\"expr\":"); char *expr = ecs_query_str(query); flecs_json_string_escape(reply, expr); ecs_os_free(expr); ecs_strbuf_list_appendlit(reply, "\"plan_size\":"); ecs_strbuf_appendint(reply, flecs_query_impl(query)->op_count); char *plan = ecs_query_plan(query); ecs_strbuf_list_appendlit(reply, "\"plan\":"); flecs_json_string_escape(reply, plan); ecs_os_free(plan); const ecs_query_t *cache_query = ecs_query_get_cache_query(query); if (cache_query) { ecs_strbuf_list_appendlit(reply, "\"cache_plan\":"); char *cache_plan = ecs_query_plan(cache_query); flecs_json_string_escape(reply, cache_plan); ecs_os_free(cache_plan); } flecs_rest_append_query_memory(world, query, reply); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_get_queries( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); { ecs_iter_t it = ecs_each_pair_t(world, 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); flecs_rest_append_query( world, query, it.entities[i], &reply->body); } } } { ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsObserver); while (ecs_each_next(&it)) { EcsPoly *observers = ecs_field(&it, EcsPoly, 0); for (int32_t i = 0; i < it.count; i++) { ecs_observer_t *observer = observers[i].poly; if (!observer || !observer->query) { continue; } flecs_poly_assert(observer, ecs_observer_t); flecs_rest_append_query( world, observer->query, it.entities[i], &reply->body); } } } ecs_strbuf_list_pop(&reply->body, "]"); return true; } static void flecs_rest_reply_table_append_type( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = table->type.count; ecs_id_t *ids = table->type.array; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(reply); char *idstr = ecs_id_str(world, ids[i]); flecs_json_string_escape(reply, idstr); ecs_os_free(idstr); } ecs_strbuf_list_pop(reply, "]"); } static void flecs_rest_reply_table_append_memory( const ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { (void)world; (void)reply; (void)table; #ifdef FLECS_STATS if (!ecs_id(ecs_table_memory_t) || !ecs_id(ecs_component_memory_t)) { return; } ecs_table_memory_t table_memory = {0}; ecs_table_memory_get(table, &table_memory); ecs_component_memory_t component_memory = {0}; ecs_table_component_memory_get(table, &component_memory); ecs_strbuf_list_appendlit(reply, "\"memory\":"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"table\":"); ecs_ptr_to_json_buf( world, ecs_id(ecs_table_memory_t), &table_memory, reply); ecs_strbuf_list_appendlit(reply, "\"components\":"); ecs_ptr_to_json_buf( world, ecs_id(ecs_component_memory_t), &component_memory, reply); ecs_strbuf_list_pop(reply, "}"); #endif } static void flecs_rest_reply_table_append( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_next(reply); ecs_strbuf_appendlit(reply, "\n"); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"id\":"); ecs_strbuf_appendint(reply, (uint32_t)table->id); ecs_strbuf_list_appendlit(reply, "\"type\":"); flecs_rest_reply_table_append_type(world, reply, table); ecs_strbuf_list_appendlit(reply, "\"count\":"); ecs_strbuf_appendint(reply, ecs_table_count(table)); ecs_strbuf_list_appendlit(reply, "\"size\":"); ecs_strbuf_appendint(reply, ecs_table_size(table)); flecs_rest_reply_table_append_memory(world, reply, table); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_get_tables( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_rest_reply_table_append(world, &reply->body, table); } ecs_strbuf_list_pop(&reply->body, "]"); return true; } static const char* flecs_rest_cmd_kind_to_str( ecs_cmd_kind_t kind) { switch(kind) { case EcsCmdClone: return "Clone"; case EcsCmdBulkNew: return "BulkNew"; case EcsCmdAdd: return "Add"; case EcsCmdRemove: return "Remove"; case EcsCmdSetDontFragment: case EcsCmdSet: return "Set"; case EcsCmdEmplace: return "Emplace"; case EcsCmdEnsureDontFragment: case EcsCmdEnsure: return "Ensure"; case EcsCmdModified: return "Modified"; case EcsCmdModifiedNoHook: return "ModifiedNoHook"; case EcsCmdAddModified: return "AddModified"; case EcsCmdPath: return "Path"; case EcsCmdDelete: return "Delete"; case EcsCmdClear: return "Clear"; case EcsCmdOnDeleteAction: return "OnDeleteAction"; case EcsCmdEnable: return "Enable"; case EcsCmdDisable: return "Disable"; case EcsCmdEvent: return "Event"; case EcsCmdSkip: return "Skip"; default: return "Unknown"; } } static bool flecs_rest_cmd_has_id( const ecs_cmd_t *cmd) { switch(cmd->kind) { case EcsCmdClear: case EcsCmdDelete: case EcsCmdClone: case EcsCmdDisable: case EcsCmdPath: return false; default: return true; } } static void flecs_rest_server_garbage_collect_all( ecs_rest_ctx_t *impl) { ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); while (ecs_map_next(&it)) { ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_os_free(sync->cmds); } ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_os_free(capture); } ecs_map_fini(&impl->cmd_captures); } static void flecs_rest_server_garbage_collect( ecs_world_t *world, ecs_rest_ctx_t *impl) { const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); ecs_vec_t removed_frames = {0}; while (ecs_map_next(&it)) { int64_t frame = flecs_uto(int64_t, ecs_map_key(&it)); if ((wi->frame_count_total - frame) > FLECS_REST_COMMAND_RETAIN_COUNT) { ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_os_free(sync->cmds); } ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_os_free(capture); ecs_vec_init_if_t(&removed_frames, int64_t); ecs_vec_append_t(NULL, &removed_frames, int64_t)[0] = frame; } } int32_t i, count = ecs_vec_count(&removed_frames); if (count) { int64_t *frames = ecs_vec_first(&removed_frames); if (count) { for (i = 0; i < count; i ++) { ecs_map_remove(&impl->cmd_captures, flecs_ito(uint64_t, frames[i])); } } ecs_vec_fini_t(NULL, &removed_frames, int64_t); } } static void flecs_rest_cmd_to_json( ecs_world_t *world, ecs_strbuf_t *buf, ecs_cmd_t *cmd) { ecs_strbuf_list_push(buf, "{", ","); ecs_strbuf_list_appendlit(buf, "\"kind\":\""); ecs_strbuf_appendstr(buf, flecs_rest_cmd_kind_to_str(cmd->kind)); ecs_strbuf_appendlit(buf, "\""); if (flecs_rest_cmd_has_id(cmd)) { ecs_strbuf_list_appendlit(buf, "\"id\":\""); char *idstr = ecs_id_str(world, cmd->id); ecs_strbuf_appendstr(buf, idstr); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(idstr); } if (cmd->system) { ecs_strbuf_list_appendlit(buf, "\"system\":\""); char *sysstr = ecs_get_path(world, cmd->system); ecs_strbuf_appendstr(buf, sysstr); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(sysstr); } if (cmd->kind == EcsCmdBulkNew) { /* Todo */ } else if (cmd->kind == EcsCmdEvent) { /* Todo */ } else { if (cmd->entity) { ecs_strbuf_list_appendlit(buf, "\"entity\":\""); char *path = ecs_get_path_w_sep(world, 0, cmd->entity, ".", ""); ecs_strbuf_appendstr(buf, path); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(path); ecs_strbuf_list_appendlit(buf, "\"is_alive\":\""); if (ecs_is_alive(world, cmd->entity)) { ecs_strbuf_appendlit(buf, "true"); } else { ecs_strbuf_appendlit(buf, "false"); } ecs_strbuf_appendlit(buf, "\""); ecs_strbuf_list_appendlit(buf, "\"next_for_entity\":"); ecs_strbuf_appendint(buf, cmd->next_for_entity); } } ecs_strbuf_list_pop(buf, "}"); } static void flecs_rest_on_commands( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx) { ecs_world_t *world = stage->world; ecs_rest_cmd_capture_t *capture = ctx; ecs_assert(capture != NULL, ECS_INTERNAL_ERROR, NULL); if (commands) { ecs_vec_init_if_t(&capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_rest_cmd_sync_capture_t *sync = ecs_vec_append_t( NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); int32_t i, count = ecs_vec_count(commands); ecs_cmd_t *cmds = ecs_vec_first(commands); sync->buf = ECS_STRBUF_INIT; ecs_strbuf_list_push(&sync->buf, "{", ","); ecs_strbuf_list_appendlit(&sync->buf, "\"commands\":"); ecs_strbuf_list_push(&sync->buf, "[", ","); for (i = 0; i < count; i ++) { ecs_strbuf_list_next(&sync->buf); flecs_rest_cmd_to_json(world, &sync->buf, &cmds[i]); } ecs_strbuf_list_pop(&sync->buf, "]"); /* Measure how long it takes to process queue */ sync->start_time = (ecs_time_t){0}; ecs_time_measure(&sync->start_time); } else { /* Finished processing queue, measure duration */ ecs_rest_cmd_sync_capture_t *sync = ecs_vec_last_t( &capture->syncs, ecs_rest_cmd_sync_capture_t); double duration = ecs_time_measure(&sync->start_time); ecs_strbuf_list_appendlit(&sync->buf, "\"duration\":"); ecs_strbuf_appendflt(&sync->buf, duration, '"'); ecs_strbuf_list_pop(&sync->buf, "}"); sync->cmds = ecs_strbuf_get(&sync->buf); } } static bool flecs_rest_get_commands_capture( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_appendlit(&reply->body, "\"frame\":"); ecs_strbuf_appendint(&reply->body, wi->frame_count_total); ecs_strbuf_appendstr(&reply->body, "}"); ecs_map_init_if(&impl->cmd_captures, &world->allocator); ecs_rest_cmd_capture_t *capture = ecs_map_ensure_alloc_t( &impl->cmd_captures, ecs_rest_cmd_capture_t, flecs_ito(uint64_t, wi->frame_count_total)); world->on_commands = flecs_rest_on_commands; world->on_commands_ctx = capture; /* Run garbage collection so that requests don't linger */ flecs_rest_server_garbage_collect(world, impl); return true; } static bool flecs_rest_get_commands_request( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; char *frame_str = &req->path[15]; int32_t frame = atoi(frame_str); ecs_map_init_if(&impl->cmd_captures, &world->allocator); const ecs_rest_cmd_capture_t *capture = ecs_map_get_deref( &impl->cmd_captures, ecs_rest_cmd_capture_t, flecs_ito(uint64_t, frame)); if (!capture) { ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_append(&reply->body, "\"error\": \"no capture for frame %u\"", frame); ecs_strbuf_appendstr(&reply->body, "}"); reply->code = 404; return true; } ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_list_append(&reply->body, "\"syncs\":"); ecs_strbuf_list_push(&reply->body, "[", ","); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_strbuf_list_appendstr(&reply->body, sync->cmds); } ecs_strbuf_list_pop(&reply->body, "]"); ecs_strbuf_appendstr(&reply->body, "}"); return true; } static bool flecs_rest_get_root( ecs_http_reply_t *reply) { reply->content_type = "text/plain"; ecs_strbuf_appendlit(&reply->body, "You've reached the REST API for Flecs " FLECS_VERSION "!\n\n" "# API examples:\n" "Examples are shown without encoding special characters for readability.\n\n" "## Get entity flecs.core.World with component values:\n" "GET /entity/flecs/core/World&values=true\n\n" "## Get entity with id 1234 with component values:\n" "GET /entity/#1234&values=true\n\n" "## Get entities in root\n" "GET /query?expr=!(flecs.core.ChildOf,*)\n\n" "## Get entities for parent flecs.core\n" "GET /query?expr=(flecs.core.ChildOf,flecs.core)\n\n" "## Get all components added to an entity\n" "GET /query?expr=*(_)\n\n" "## Get all relationships added to an entity\n" "GET /query?expr=*(_, _)\n\n" "## Find all instantiated prefabs in the world\n" "GET /query?expr=IsA(_, *)\n\n" "## Get entities for example components transform.Position, movement.Velocity\n" "GET /query?expr=transform.Position,movement.Velocity\n\n" "## Create entity Sun.Earth\n" "PUT /entity/Sun/Earth\n\n" "## Delete entity Sun.Earth\n" "DELETE /entity/Sun/Earth\n\n" "## Get component planets.Mass from entity Sun.Earth\n" "GET /component/Sun/Earth?component=planets.Mass\n\n" "## Set component Position to value {\"x\":10} for entity Sun.Earth\n" "PUT /component/Sun/Earth?component=Position&value={\"x\":10}\n\n" "## Change code of script my_script.flecs (code in request body)\n" "PUT /script/my_script.flecs\n"); return true; } static bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) { ecs_rest_ctx_t *impl = ctx; ecs_world_t *world = impl->world; if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } if (req->method == EcsHttpGet) { /* Root endpoint */ if (!req->path[0]) { return flecs_rest_get_root(reply); /* Entity endpoint */ } else if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_get_entity(world, req, reply); /* Component GET endpoint */ } else if (!ecs_os_strncmp(req->path, "component/", 10)) { return flecs_rest_get_component(world, req, reply, &req->path[10]); /* Type info endpoint */ } else if (!ecs_os_strncmp(req->path, "type_info/", 10)) { return flecs_rest_get_type_info(world, reply, &req->path[10]); /* Call endpoint */ } else if (!ecs_os_strncmp(req->path, "call/", 5)) { return flecs_rest_call(world, req, reply, &req->path[5]); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_get_query(world, req, reply); /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_get_world(world, req, reply); /* Stats endpoint */ } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { return flecs_rest_get_stats(world, req, reply); /* Components endpoint */ } else if (!ecs_os_strncmp(req->path, "components", 10)) { return flecs_rest_get_components(world, req, reply); /* Queries endpoint */ } else if (!ecs_os_strncmp(req->path, "queries", 7)) { return flecs_rest_get_queries(world, req, reply); /* Tables endpoint */ } else if (!ecs_os_strncmp(req->path, "tables", 6)) { return flecs_rest_get_tables(world, req, reply); /* Commands capture endpoint */ } else if (!ecs_os_strncmp(req->path, "commands/capture", 16)) { return flecs_rest_get_commands_capture(world, impl, req, reply); /* Commands request endpoint (request commands from specific frame) */ } else if (!ecs_os_strncmp(req->path, "commands/frame/", 15)) { return flecs_rest_get_commands_request(world, impl, req, reply); } } else if (req->method == EcsHttpPut) { /* Entity PUT endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_put_entity(world, reply, &req->path[7]); /* Component PUT endpoint */ } else if (!ecs_os_strncmp(req->path, "component/", 10)) { return flecs_rest_put_component(world, req, reply, &req->path[10]); /* Enable endpoint */ } else if (!ecs_os_strncmp(req->path, "toggle/", 7)) { return flecs_rest_toggle(world, req, reply, &req->path[7]); /* Script endpoint */ } else if (!ecs_os_strncmp(req->path, "script/", 7)) { return flecs_rest_script(world, req, reply, &req->path[7]); /* Action endpoint */ } else if (!ecs_os_strncmp(req->path, "action/", 7)) { return flecs_rest_action(world, req, reply, &req->path[7]); } } else if (req->method == EcsHttpDelete) { /* Entity DELETE endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_delete_entity(world, reply, &req->path[7]); /* Component DELETE endpoint */ } else if (!ecs_os_strncmp(req->path, "component/", 10)) { return flecs_rest_delete_component(world, req, reply, &req->path[10]); } } return false; } static void flecs_rest_set_build_info( ecs_world_t *world) { ecs_id_t build_info = ecs_lookup(world, "flecs.core.BuildInfo"); if (build_info) { const ecs_build_info_t *bi = ecs_get_build_info(); ecs_set_id(world, EcsWorld, build_info, sizeof(ecs_build_info_t), bi); } } static ecs_http_server_t* flecs_rest_server_init( ecs_world_t *world, const ecs_http_server_desc_t *desc) { ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); ecs_http_server_desc_t private_desc = {0}; if (desc) { private_desc = *desc; } private_desc.callback = flecs_rest_reply; private_desc.ctx = srv_ctx; ecs_http_server_t *srv = ecs_http_server_init(&private_desc); if (!srv) { ecs_os_free(srv_ctx); return NULL; } srv_ctx->world = world; srv_ctx->srv = srv; srv_ctx->rc = 1; return srv; } ecs_http_server_t* ecs_rest_server_init( ecs_world_t *world, const ecs_http_server_desc_t *desc) { ecs_http_server_t *srv = flecs_rest_server_init(world, desc); if (srv) { /* Set build info on world so clients know which version they're using */ flecs_rest_set_build_info(world); } return srv; } void ecs_rest_server_fini( ecs_http_server_t *srv) { ecs_rest_ctx_t *impl = ecs_http_server_ctx(srv); flecs_rest_server_garbage_collect_all(impl); ecs_os_free(impl); ecs_http_server_fini(srv); } static void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = ecs_field(it, EcsRest, 0); int i; for(i = 0; i < it->count; i ++) { if (!rest[i].port) { rest[i].port = ECS_REST_DEFAULT_PORT; } ecs_http_server_t *srv = flecs_rest_server_init(it->world, &(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port, .cache_timeout = 0.2 }); if (!srv) { const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; ecs_err("failed to create REST server on %s:%u", ipaddr, rest[i].port); continue; } flecs_rest_set_build_info(it->stage); rest[i].impl = ecs_http_server_ctx(srv); ecs_http_server_start(srv); } } static void DequeueRest(ecs_iter_t *it) { EcsRest *rest = ecs_field(it, EcsRest, 0); if (it->delta_system_time > (ecs_ftime_t)1.0) { ecs_warn( "detected large progress interval (%.2fs), REST request may timeout", (double)it->delta_system_time); } const ecs_world_info_t *wi = ecs_get_world_info(it->stage); int32_t i; for(i = 0; i < it->count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; if (ctx) { float elapsed = (float)(wi->world_time_total_raw - ctx->last_time); ecs_http_server_dequeue(ctx->srv, (ecs_ftime_t)elapsed); flecs_rest_server_garbage_collect(it->world, ctx); ctx->last_time = wi->world_time_total_raw; } } } static void DisableRest(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_iter_t rit = ecs_each_id(world, ecs_id(EcsRest)); if (it->event == EcsOnAdd) { /* REST module was disabled */ while (ecs_each_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 0); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_stop(ctx->srv); } } } else if (it->event == EcsOnRemove) { /* REST module was enabled */ while (ecs_each_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 0); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_start(ctx->srv); } } } } void FlecsRestImport( ecs_world_t *world) { ECS_MODULE(world, FlecsRest); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsMeta); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsRest); ecs_set_hooks(world, EcsRest, { .ctor = flecs_default_ctor, .move = ecs_move(EcsRest), .copy = ecs_copy(EcsRest), .dtor = ecs_dtor(EcsRest), .on_set = flecs_on_set_rest }); ecs_system(world, { .entity = ecs_entity(world, { .name = "DequeueRest" }), .phase = EcsPostFrame, .query.terms = { { .id = ecs_id(EcsRest) }, }, .callback = DequeueRest, .immediate = true }); ecs_observer(world, { .query = { .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} }, .events = {EcsOnAdd, EcsOnRemove}, .callback = DisableRest, .global_observer = true }); ecs_set_name_prefix(world, "EcsRest"); /* Enable frame time measurements so we're guaranteed to have a delta time * value to pass into the HTTP server. */ if (ecs_os_has_time()) { ecs_measure_frame_time(world, true); } } #endif #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #ifdef FLECS_SYSTEM #define ecs_system_t_magic (0x65637383) #define ecs_system_t_tag EcsSystem extern ecs_mixins_t ecs_system_t_mixins; /* Internal function to run a system */ ecs_entity_t flecs_run_system( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_current, int32_t stage_count, ecs_ftime_t delta_time, void *param); #endif #endif #ifdef FLECS_TIMER static void ProgressTimers(ecs_iter_t *it) { EcsTimer *timer = ecs_field(it, EcsTimer, 0); EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 1); ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); int i; for (i = 0; i < it->count; i ++) { tick_source[i].tick = false; if (!timer[i].active) { continue; } const ecs_world_info_t *info = ecs_get_world_info(it->stage); ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; ecs_ftime_t timeout = timer[i].timeout; if (time_elapsed >= timeout) { ecs_ftime_t t = time_elapsed - timeout; if (t > timeout) { t = 0; } timer[i].time = t; /* Initialize with remainder */ tick_source[i].tick = true; tick_source[i].time_elapsed = time_elapsed - timer[i].overshoot; timer[i].overshoot = t; if (timer[i].single_shot) { timer[i].active = false; } } else { timer[i].time = time_elapsed; } } } static void ProgressRateFilters(ecs_iter_t *it) { EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 0); EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 1); int i; for (i = 0; i < it->count; i ++) { ecs_entity_t src = filter[i].src; bool inc = false; filter[i].time_elapsed += it->delta_time; if (src) { const EcsTickSource *tick_src = ecs_get( it->stage, src, EcsTickSource); if (tick_src) { inc = tick_src->tick; } else { inc = true; } } else { inc = true; } if (inc) { filter[i].tick_count ++; bool triggered = !(filter[i].tick_count % filter[i].rate); tick_dst[i].tick = triggered; tick_dst[i].time_elapsed = filter[i].time_elapsed; if (triggered) { filter[i].time_elapsed = 0; } } else { tick_dst[i].tick = false; } } } static void ProgressTickSource(ecs_iter_t *it) { EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 0); /* If tick source has no filters, tick unconditionally */ int i; for (i = 0; i < it->count; i ++) { tick_src[i].tick = true; tick_src[i].time_elapsed = it->delta_time; } } ecs_entity_t ecs_set_timeout( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t timeout) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { timer = ecs_entity(world, {0}); } ecs_set(world, timer, EcsTimer, { .timeout = timeout, .single_shot = true, .active = true }); ecs_system_t *system_data = flecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_timeout( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } ecs_entity_t ecs_set_interval( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t interval) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { timer = ecs_new_w(world, EcsTimer); } EcsTimer *t = ecs_ensure(world, timer, EcsTimer); ecs_check(t != NULL, ECS_INTERNAL_ERROR, NULL); t->timeout = interval; t->active = true; ecs_modified(world, timer, EcsTimer); ecs_system_t *system_data = flecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_interval( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { return 0; } const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } void ecs_start_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr->active = true; ptr->time = 0; error: return; } void ecs_stop_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr->active = false; error: return; } void ecs_reset_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr->time = 0; error: return; } ecs_entity_t ecs_set_rate( ecs_world_t *world, ecs_entity_t filter, int32_t rate, ecs_entity_t source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!filter) { filter = ecs_entity(world, {0}); } ecs_set(world, filter, EcsRateFilter, { .rate = rate, .src = source }); ecs_system_t *system_data = flecs_poly_get(world, filter, ecs_system_t); if (system_data) { system_data->tick_source = filter; } error: return filter; } void ecs_set_tick_source( ecs_world_t *world, ecs_entity_t system, ecs_entity_t tick_source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); system_data->tick_source = tick_source; error: return; } static void RandomizeTimers(ecs_iter_t *it) { EcsTimer *timer = ecs_field(it, EcsTimer, 0); int32_t i; for (i = 0; i < it->count; i ++) { timer[i].time = ((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout; } } void ecs_randomize_timers( ecs_world_t *world) { ecs_observer(world, { .entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }), .query.terms = {{ .id = ecs_id(EcsTimer) }}, .events = {EcsOnSet}, .yield_existing = true, .callback = RandomizeTimers }); } void FlecsTimerImport( ecs_world_t *world) { ECS_MODULE(world, FlecsTimer); ECS_IMPORT(world, FlecsPipeline); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTimer); flecs_bootstrap_component(world, EcsRateFilter); ecs_set_hooks(world, EcsTimer, { .ctor = flecs_default_ctor }); ecs_add_pair(world, ecs_id(EcsTimer), EcsWith, ecs_id(EcsTickSource)); ecs_add_pair(world, ecs_id(EcsRateFilter), EcsWith, ecs_id(EcsTickSource)); /* Timer handling */ ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressTimers"}), .phase = EcsPreFrame, .query.terms = { { .id = ecs_id(EcsTimer) }, { .id = ecs_id(EcsTickSource) } }, .callback = ProgressTimers }); /* Rate filter handling */ ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressRateFilters"}), .phase = EcsPreFrame, .query.terms = { { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .inout = EcsOut } }, .callback = ProgressRateFilters }); /* TickSource without a timer or rate filter just increases each frame */ ecs_system(world, { .entity = ecs_entity(world, { .name = "ProgressTickSource" }), .phase = EcsPreFrame, .query.terms = { { .id = ecs_id(EcsTickSource), .inout = EcsOut }, { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, { .id = ecs_id(EcsTimer), .oper = EcsNot } }, .callback = ProgressTickSource }); } #endif #ifdef FLECS_UNITS typedef struct flecs_unit_definition_t { ecs_entity_t *id; const char *name; ecs_entity_t *parent; ecs_entity_t *quantity; ecs_entity_t *base; ecs_entity_t *over; ecs_entity_t *prefix; const char *symbol; const char *brief; ecs_primitive_kind_t kind; int32_t factor; int32_t power; bool is_prefix; bool is_quantity; bool is_unit; bool module; } flecs_unit_definition_t; static const flecs_unit_definition_t flecs_unit_definitions[] = { {&EcsUnitPrefixes, "prefixes", .module = true}, {&EcsYocto, "Yocto", .is_prefix = true, .symbol = "y", .factor = 10, .power = -24}, {&EcsZepto, "Zepto", .is_prefix = true, .symbol = "z", .factor = 10, .power = -21}, {&EcsAtto, "Atto", .is_prefix = true, .symbol = "a", .factor = 10, .power = -18}, {&EcsFemto, "Femto", .is_prefix = true, .symbol = "f", .factor = 10, .power = -15}, {&EcsPico, "Pico", .is_prefix = true, .symbol = "p", .factor = 10, .power = -12}, {&EcsNano, "Nano", .is_prefix = true, .symbol = "n", .factor = 10, .power = -9}, {&EcsMicro, "Micro", .is_prefix = true, .symbol = "μ", .factor = 10, .power = -6}, {&EcsMilli, "Milli", .is_prefix = true, .symbol = "m", .factor = 10, .power = -3}, {&EcsCenti, "Centi", .is_prefix = true, .symbol = "c", .factor = 10, .power = -2}, {&EcsDeci, "Deci", .is_prefix = true, .symbol = "d", .factor = 10, .power = -1}, {&EcsDeca, "Deca", .is_prefix = true, .symbol = "da", .factor = 10, .power = 1}, {&EcsHecto, "Hecto", .is_prefix = true, .symbol = "h", .factor = 10, .power = 2}, {&EcsKilo, "Kilo", .is_prefix = true, .symbol = "k", .factor = 10, .power = 3}, {&EcsMega, "Mega", .is_prefix = true, .symbol = "M", .factor = 10, .power = 6}, {&EcsGiga, "Giga", .is_prefix = true, .symbol = "G", .factor = 10, .power = 9}, {&EcsTera, "Tera", .is_prefix = true, .symbol = "T", .factor = 10, .power = 12}, {&EcsPeta, "Peta", .is_prefix = true, .symbol = "P", .factor = 10, .power = 15}, {&EcsExa, "Exa", .is_prefix = true, .symbol = "E", .factor = 10, .power = 18}, {&EcsZetta, "Zetta", .is_prefix = true, .symbol = "Z", .factor = 10, .power = 21}, {&EcsYotta, "Yotta", .is_prefix = true, .symbol = "Y", .factor = 10, .power = 24}, {&EcsKibi, "Kibi", .is_prefix = true, .symbol = "Ki", .factor = 1024, .power = 1}, {&EcsMebi, "Mebi", .is_prefix = true, .symbol = "Mi", .factor = 1024, .power = 2}, {&EcsGibi, "Gibi", .is_prefix = true, .symbol = "Gi", .factor = 1024, .power = 3}, {&EcsTebi, "Tebi", .is_prefix = true, .symbol = "Ti", .factor = 1024, .power = 4}, {&EcsPebi, "Pebi", .is_prefix = true, .symbol = "Pi", .factor = 1024, .power = 5}, {&EcsExbi, "Exbi", .is_prefix = true, .symbol = "Ei", .factor = 1024, .power = 6}, {&EcsZebi, "Zebi", .is_prefix = true, .symbol = "Zi", .factor = 1024, .power = 7}, {&EcsYobi, "Yobi", .is_prefix = true, .symbol = "Yi", .factor = 1024, .power = 8}, {&EcsDuration, "Duration", .is_quantity = true, .brief = "Time amount (e.g. \"20 seconds\", \"2 hours\")"}, {&EcsSeconds, "Seconds", .is_unit = true, .quantity = &EcsDuration, .symbol = "s", .kind = EcsF32, .brief = "Time amount in seconds"}, {&EcsPicoSeconds, "PicoSeconds", .is_unit = true, .quantity = &EcsDuration, .base = &EcsSeconds, .prefix = &EcsPico, .kind = EcsF32}, {&EcsNanoSeconds, "NanoSeconds", .is_unit = true, .quantity = &EcsDuration, .base = &EcsSeconds, .prefix = &EcsNano, .kind = EcsF32}, {&EcsMicroSeconds, "MicroSeconds", .is_unit = true, .quantity = &EcsDuration, .base = &EcsSeconds, .prefix = &EcsMicro, .kind = EcsF32}, {&EcsMilliSeconds, "MilliSeconds", .is_unit = true, .quantity = &EcsDuration, .base = &EcsSeconds, .prefix = &EcsMilli, .kind = EcsF32}, {&EcsMinutes, "Minutes", .is_unit = true, .quantity = &EcsDuration, .base = &EcsSeconds, .symbol = "min", .factor = 60, .power = 1, .kind = EcsU32, .brief = "60 seconds"}, {&EcsHours, "Hours", .is_unit = true, .quantity = &EcsDuration, .base = &EcsMinutes, .symbol = "h", .factor = 60, .power = 1, .kind = EcsU32, .brief = "60 minutes"}, {&EcsDays, "Days", .is_unit = true, .quantity = &EcsDuration, .base = &EcsHours, .symbol = "d", .factor = 24, .power = 1, .kind = EcsU32, .brief = "24 hours"}, {&EcsTime, "Time", .is_quantity = true, .brief = "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"}, {&EcsDate, "Date", .is_unit = true, .quantity = &EcsTime, .kind = EcsU32, .brief = "Seconds passed since January 1st 1970"}, {&EcsMass, "Mass", .is_quantity = true, .brief = "Units of mass (e.g. \"5 kilograms\")"}, {&EcsGrams, "Grams", .is_unit = true, .quantity = &EcsMass, .symbol = "g", .kind = EcsF32}, {&EcsKiloGrams, "KiloGrams", .is_unit = true, .quantity = &EcsMass, .base = &EcsGrams, .prefix = &EcsKilo, .kind = EcsF32}, {&EcsElectricCurrent, "ElectricCurrent", .is_quantity = true, .brief = "Units of electrical current (e.g. \"2 ampere\")"}, {&EcsAmpere, "Ampere", .is_unit = true, .quantity = &EcsElectricCurrent, .symbol = "A", .kind = EcsF32}, {&EcsAmount, "Amount", .is_quantity = true, .brief = "Units of amount of substance (e.g. \"2 mole\")"}, {&EcsMole, "Mole", .is_unit = true, .quantity = &EcsAmount, .symbol = "mol", .kind = EcsF32}, {&EcsLuminousIntensity, "LuminousIntensity", .is_quantity = true, .brief = "Units of luminous intensity (e.g. \"1 candela\")"}, {&EcsCandela, "Candela", .is_unit = true, .quantity = &EcsLuminousIntensity, .symbol = "cd", .kind = EcsF32}, {&EcsForce, "Force", .is_quantity = true, .brief = "Units of force (e.g. \"10 newton\")"}, {&EcsNewton, "Newton", .is_unit = true, .quantity = &EcsForce, .symbol = "N", .kind = EcsF32}, {&EcsLength, "Length", .is_quantity = true, .brief = "Units of length (e.g. \"5 meters\", \"20 miles\")"}, {&EcsMeters, "Meters", .is_unit = true, .quantity = &EcsLength, .symbol = "m", .kind = EcsF32}, {&EcsPicoMeters, "PicoMeters", .is_unit = true, .quantity = &EcsLength, .base = &EcsMeters, .prefix = &EcsPico, .kind = EcsF32}, {&EcsNanoMeters, "NanoMeters", .is_unit = true, .quantity = &EcsLength, .base = &EcsMeters, .prefix = &EcsNano, .kind = EcsF32}, {&EcsMicroMeters, "MicroMeters", .is_unit = true, .quantity = &EcsLength, .base = &EcsMeters, .prefix = &EcsMicro, .kind = EcsF32}, {&EcsMilliMeters, "MilliMeters", .is_unit = true, .quantity = &EcsLength, .base = &EcsMeters, .prefix = &EcsMilli, .kind = EcsF32}, {&EcsCentiMeters, "CentiMeters", .is_unit = true, .quantity = &EcsLength, .base = &EcsMeters, .prefix = &EcsCenti, .kind = EcsF32}, {&EcsKiloMeters, "KiloMeters", .is_unit = true, .quantity = &EcsLength, .base = &EcsMeters, .prefix = &EcsKilo, .kind = EcsF32}, {&EcsMiles, "Miles", .is_unit = true, .quantity = &EcsLength, .symbol = "mi", .kind = EcsF32}, {&EcsPixels, "Pixels", .is_unit = true, .quantity = &EcsLength, .symbol = "px", .kind = EcsF32}, {&EcsPressure, "Pressure", .is_quantity = true, .brief = "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"}, {&EcsPascal, "Pascal", .is_unit = true, .quantity = &EcsPressure, .symbol = "Pa", .kind = EcsF32}, {&EcsBar, "Bar", .is_unit = true, .quantity = &EcsPressure, .symbol = "bar", .kind = EcsF32}, {&EcsSpeed, "Speed", .is_quantity = true, .brief = "Units of movement (e.g. \"5 meters/second\")"}, {&EcsMetersPerSecond, "MetersPerSecond", .is_unit = true, .quantity = &EcsSpeed, .base = &EcsMeters, .over = &EcsSeconds, .kind = EcsF32}, {&EcsKiloMetersPerSecond, "KiloMetersPerSecond", .is_unit = true, .quantity = &EcsSpeed, .base = &EcsKiloMeters, .over = &EcsSeconds, .kind = EcsF32}, {&EcsKiloMetersPerHour, "KiloMetersPerHour", .is_unit = true, .quantity = &EcsSpeed, .base = &EcsKiloMeters, .over = &EcsHours, .kind = EcsF32}, {&EcsMilesPerHour, "MilesPerHour", .is_unit = true, .quantity = &EcsSpeed, .base = &EcsMiles, .over = &EcsHours, .kind = EcsF32}, {&EcsAcceleration, "Acceleration", .is_unit = true, .base = &EcsMetersPerSecond, .over = &EcsSeconds, .is_quantity = true, .quantity = &EcsAcceleration, .kind = EcsF32, .brief = "Unit of speed increase (e.g. \"5 meters/second/second\")"}, {&EcsTemperature, "Temperature", .is_quantity = true, .brief = "Units of temperature (e.g. \"5 degrees Celsius\")"}, {&EcsKelvin, "Kelvin", .is_unit = true, .quantity = &EcsTemperature, .symbol = "K", .kind = EcsF32}, {&EcsCelsius, "Celsius", .is_unit = true, .quantity = &EcsTemperature, .symbol = "°C", .kind = EcsF32}, {&EcsFahrenheit, "Fahrenheit", .is_unit = true, .quantity = &EcsTemperature, .symbol = "F", .kind = EcsF32}, {&EcsData, "Data", .is_quantity = true, .brief = "Units of information (e.g. \"8 bits\", \"100 megabytes\")"}, {&EcsBits, "Bits", .is_unit = true, .quantity = &EcsData, .symbol = "bit", .kind = EcsU64}, {&EcsKiloBits, "KiloBits", .is_unit = true, .quantity = &EcsData, .base = &EcsBits, .prefix = &EcsKilo, .kind = EcsU64}, {&EcsMegaBits, "MegaBits", .is_unit = true, .quantity = &EcsData, .base = &EcsBits, .prefix = &EcsMega, .kind = EcsU64}, {&EcsGigaBits, "GigaBits", .is_unit = true, .quantity = &EcsData, .base = &EcsBits, .prefix = &EcsGiga, .kind = EcsU64}, {&EcsBytes, "Bytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBits, .symbol = "B", .factor = 8, .power = 1, .kind = EcsU64}, {&EcsKiloBytes, "KiloBytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBytes, .prefix = &EcsKilo, .kind = EcsU64}, {&EcsMegaBytes, "MegaBytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBytes, .prefix = &EcsMega, .kind = EcsU64}, {&EcsGigaBytes, "GigaBytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBytes, .prefix = &EcsGiga, .kind = EcsU64}, {&EcsKibiBytes, "KibiBytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBytes, .prefix = &EcsKibi, .kind = EcsU64}, {&EcsMebiBytes, "MebiBytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBytes, .prefix = &EcsMebi, .kind = EcsU64}, {&EcsGibiBytes, "GibiBytes", .is_unit = true, .quantity = &EcsData, .base = &EcsBytes, .prefix = &EcsGibi, .kind = EcsU64}, {&EcsDataRate, "DataRate", .is_quantity = true, .brief = "Units of data transmission (e.g. \"100 megabits/second\")"}, {&EcsBitsPerSecond, "BitsPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsBits, .over = &EcsSeconds, .kind = EcsU64}, {&EcsKiloBitsPerSecond, "KiloBitsPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsKiloBits, .over = &EcsSeconds, .kind = EcsU64}, {&EcsMegaBitsPerSecond, "MegaBitsPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsMegaBits, .over = &EcsSeconds, .kind = EcsU64}, {&EcsGigaBitsPerSecond, "GigaBitsPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsGigaBits, .over = &EcsSeconds, .kind = EcsU64}, {&EcsBytesPerSecond, "BytesPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsBytes, .over = &EcsSeconds, .kind = EcsU64}, {&EcsKiloBytesPerSecond, "KiloBytesPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsKiloBytes, .over = &EcsSeconds, .kind = EcsU64}, {&EcsMegaBytesPerSecond, "MegaBytesPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsMegaBytes, .over = &EcsSeconds, .kind = EcsU64}, {&EcsGigaBytesPerSecond, "GigaBytesPerSecond", .is_unit = true, .quantity = &EcsDataRate, .base = &EcsGigaBytes, .over = &EcsSeconds, .kind = EcsU64}, {&EcsPercentage, "Percentage", .is_quantity = true, .is_unit = true, .symbol = "%", .kind = EcsF32}, {&EcsAngle, "Angle", .is_quantity = true, .brief = "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"}, {&EcsRadians, "Radians", .is_unit = true, .quantity = &EcsAngle, .symbol = "rad", .kind = EcsF32}, {&EcsDegrees, "Degrees", .is_unit = true, .quantity = &EcsAngle, .symbol = "°", .kind = EcsF32}, {&EcsColor, "Color", .is_quantity = true}, {&EcsColorRgb, "Rgb", .parent = &EcsColor, .is_unit = true, .quantity = &EcsColor}, {&EcsColorHsl, "Hsl", .parent = &EcsColor, .is_unit = true, .quantity = &EcsColor}, {&EcsColorCss, "Css", .is_unit = true, .quantity = &EcsColor, .kind = EcsString}, {&EcsBel, "Bel", .is_unit = true, .symbol = "B", .kind = EcsF32}, {&EcsDeciBel, "DeciBel", .is_unit = true, .base = &EcsBel, .prefix = &EcsDeci, .kind = EcsF32}, {&EcsFrequency, "Frequency", .is_quantity = true, .brief = "The number of occurrences of a repeating event per unit of time."}, {&EcsHertz, "Hertz", .is_unit = true, .quantity = &EcsFrequency, .symbol = "Hz", .kind = EcsF32}, {&EcsKiloHertz, "KiloHertz", .parent = &EcsFrequency, .is_unit = true, .base = &EcsHertz, .prefix = &EcsKilo, .kind = EcsF32}, {&EcsMegaHertz, "MegaHertz", .parent = &EcsFrequency, .is_unit = true, .base = &EcsHertz, .prefix = &EcsMega, .kind = EcsF32}, {&EcsGigaHertz, "GigaHertz", .parent = &EcsFrequency, .is_unit = true, .base = &EcsHertz, .prefix = &EcsGiga, .kind = EcsF32}, {&EcsUri, "Uri", .is_quantity = true, .brief = "Universal resource identifier."}, {&EcsUriHyperlink, "Hyperlink", .is_unit = true, .quantity = &EcsUri, .kind = EcsString}, {&EcsUriImage, "Image", .is_unit = true, .quantity = &EcsUri, .kind = EcsString}, {&EcsUriFile, "File", .is_unit = true, .quantity = &EcsUri, .kind = EcsString}, }; void FlecsUnitsImport( ecs_world_t *world) { ECS_MODULE(world, FlecsUnits); ECS_IMPORT(world, FlecsMeta); ecs_set_name_prefix(world, "Ecs"); int32_t count = sizeof(flecs_unit_definitions) / sizeof(flecs_unit_definitions[0]); for (int32_t i = 0; i < count; i ++) { const flecs_unit_definition_t *def = &flecs_unit_definitions[i]; ecs_entity_t parent = ecs_id(FlecsUnits); if (def->parent) { parent = *def->parent; } else if (def->quantity && def->quantity != def->id) { parent = *def->quantity; } else if (def->is_prefix) { parent = EcsUnitPrefixes; } ecs_entity_t entity = *def->id = ecs_entity(world, { .name = def->name, .parent = parent }); if (def->module) { ecs_add_id(world, entity, EcsModule); } if (def->is_quantity) { ecs_quantity_init(world, &(ecs_entity_desc_t){.id = entity}); } if (def->is_prefix) { ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = entity, .symbol = def->symbol, .translation = {def->factor, def->power} }); } if (def->is_unit) { ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = entity, .symbol = def->symbol, .quantity = def->quantity ? *def->quantity : 0, .base = def->base ? *def->base : 0, .over = def->over ? *def->over : 0, .prefix = def->prefix ? *def->prefix : 0, .translation = {def->factor, def->power} }); } if (def->kind) { ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = entity, .kind = def->kind }); } } #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); for (int32_t i = 0; i < count; i ++) { const flecs_unit_definition_t *def = &flecs_unit_definitions[i]; if (def->brief) { ecs_doc_set_brief(world, *def->id, def->brief); } } #endif } #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 } 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_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) { result = ecs_os_malloc(ba->data_size); #ifdef FLECS_MEMSET_UNINITIALIZED ecs_os_memset(result, 0xAB, ba->data_size); #endif return result; } 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, 0xAB, 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), 0xAB, dst->data_size - src->data_size); } else if (dst && !src) { ecs_os_memset(result, 0xAB, dst->data_size); } #endif return result; } // This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) // main repo: https://github.com/wangyi-fudan/wyhash // author: 王一 Wang Yi // contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, // Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, // Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, // hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric /* quick example: string s="fjsakfdsjkf"; uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); */ #ifndef WYHASH_CONDOM //protections that produce different results: //1: normal valid behavior //2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" #define WYHASH_CONDOM 1 #endif #ifndef WYHASH_32BIT_MUM //0: normal version, slow on 32 bit systems //1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function #define WYHASH_32BIT_MUM 0 #endif //includes #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 ecs_hm_bucket_t* flecs_hashmap_find_key( const ecs_hashmap_t *map, ecs_hm_bucket_t *bucket, const void *key) { for (; bucket; bucket = bucket->next) { if (!map->compare(flecs_hm_bucket_key(bucket), key)) { return bucket; } } return NULL; } static ecs_hm_bucket_t* flecs_hm_bucket_new( ecs_hashmap_t *map) { return map->impl.allocator ? flecs_calloc(map->impl.allocator, map->bucket_size) : ecs_os_calloc(map->bucket_size); } static void flecs_hm_bucket_free( ecs_hashmap_t *map, ecs_hm_bucket_t *bucket) { if (map->impl.allocator) { flecs_free(map->impl.allocator, map->bucket_size, 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->value_offset = 16 + ECS_ALIGN(key_size, 16); map->bucket_size = map->value_offset + value_size; map->hash = hash; map->compare = compare; ecs_map_init(&map->impl, allocator); } void flecs_hashmap_fini( ecs_hashmap_t *map) { ecs_map_iter_t it = ecs_map_iter(&map->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); while (bucket) { ecs_hm_bucket_t *next = bucket->next; flecs_hm_bucket_free(map, bucket); bucket = next; } } 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_map_iter_t it = ecs_map_iter(&dst->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t **ptr = ecs_map_ref(&it, ecs_hm_bucket_t); for (ecs_hm_bucket_t *src_bucket = *ptr; src_bucket; src_bucket = src_bucket->next) { *ptr = flecs_hm_bucket_new(dst); ecs_os_memcpy(*ptr, src_bucket, dst->bucket_size); ptr = &(*ptr)->next; } *ptr = NULL; } } 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); (void)key_size; (void)value_size; uint64_t hash = map->hash(key); ecs_hm_bucket_t *bucket = flecs_hashmap_find_key( map, flecs_hashmap_get_bucket(map, hash), key); return bucket ? flecs_hm_bucket_value(map, bucket) : NULL; } 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); (void)value_size; uint64_t hash = map->hash(key); ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); while (*r && map->compare(flecs_hm_bucket_key(*r), key)) { r = &(*r)->next; } ecs_hm_bucket_t *bucket = *r; if (!bucket) { bucket = *r = flecs_hm_bucket_new(map); ecs_os_memcpy(flecs_hm_bucket_key(bucket), key, key_size); } return (flecs_hashmap_result_t){ .key = flecs_hm_bucket_key(bucket), .value = flecs_hm_bucket_value(map, bucket), .hash = hash }; } 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) { ecs_hm_bucket_t **head = ecs_map_get_ref(&map->impl, ecs_hm_bucket_t, hash); ecs_hm_bucket_t **ptr = head; while (*ptr != bucket) { ptr = &(*ptr)->next; } *ptr = bucket->next; if (!*head) { ecs_map_remove(&map->impl, hash); } 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)key_size; (void)value_size; ecs_hm_bucket_t *bucket = flecs_hashmap_find_key( map, flecs_hashmap_get_bucket(map, hash), key); if (bucket) { flecs_hm_bucket_remove(map, bucket, hash); } } flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map) { return (flecs_hashmap_iter_t){ .it = ecs_map_iter(&map->impl), .map = map }; } void* flecs_hashmap_next_( flecs_hashmap_iter_t *it, ecs_size_t key_size, void *key_out, ecs_size_t value_size) { (void)key_size; (void)value_size; ecs_hm_bucket_t *bucket = it->bucket; if (!bucket) { if (!ecs_map_next(&it->it)) { return NULL; } bucket = ecs_map_ptr(&it->it); } it->bucket = bucket->next; if (key_out) { *(void**)key_out = flecs_hm_bucket_key(bucket); } return flecs_hm_bucket_value(it->map, bucket); } /* 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) { if (!ecs_map_count(&map->impl)) { return NULL; } if (!length) { length = ecs_os_strlen(name); } if (!hash) { hash = flecs_hash(name, length); } ecs_hashed_string_t hs = { .value = ECS_CONST_CAST(char*, name), .length = length, .hash = hash }; ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); if (!b) { return NULL; } for (; b; b = b->next) { ecs_hashed_string_t *key = flecs_hm_bucket_key(b); ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); if (hs.length != key->length) { continue; } if (!ecs_os_memcmp(name, key->value, hs.length)) { uint64_t *e = flecs_hm_bucket_value(map, b); 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; } for (; b; b = b->next) { if (*(uint64_t*)flecs_hm_bucket_value(map, b) == e) { flecs_hm_bucket_remove(map, b, hash); 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; } for (; b; b = b->next) { if (*(uint64_t*)flecs_hm_bucket_value(map, b) == e) { ecs_hashed_string_t *key = flecs_hm_bucket_key(b); 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_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_create_id( ecs_sparse_t *sparse, int32_t dense) { uint64_t id = ++ sparse->max_id; ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); 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_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 && dense < sparse->count) { if (is_new) *is_new = false; } else { if (!dense) { dense = ecs_vec_count(&sparse->dense); ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); uint64_t *ids = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, ids, id, dense); if (index >= sparse->max_id) { sparse->max_id = index; } } int32_t count = sparse->count ++; if (dense != count) { flecs_sparse_swap_dense(sparse, page, dense, count); } ecs_vec_first_t(&sparse->dense, uint64_t)[count] = id; } 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)) { ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } 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); } static bool flecs_sparse_remove_id( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id, bool generation) { 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; } int32_t offset = FLECS_SPARSE_OFFSET(id); int32_t dense = page->sparse[offset]; if (!dense) { return false; } if (generation) { uint64_t *ids = ecs_vec_first_t(&sparse->dense, uint64_t); ecs_assert(ids[dense] == id, ECS_INVALID_PARAMETER, NULL); ids[dense] = ECS_GENERATION_INC(id); if (dense >= sparse->count) { return false; } } if (dense < sparse->count) { int32_t last = -- sparse->count; if (dense != last) { flecs_sparse_swap_dense(sparse, page, dense, last); } } if (sparse->size) { ecs_os_memset(DATA(page->data, sparse->size, offset), 0, sparse->size); } return true; } bool flecs_sparse_remove( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { return flecs_sparse_remove_id(sparse, size, id, false); } bool flecs_sparse_remove_w_gen( ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { return flecs_sparse_remove_id(sparse, size, id, true); } 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_w_check( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t id, bool checked) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(size == sparse->size, ECS_INVALID_PARAMETER, NULL); int32_t page_index = FLECS_SPARSE_PAGE(id); if (checked && page_index >= ecs_vec_count(&sparse->pages)) { return NULL; } ecs_sparse_page_t *page = ecs_vec_get_t( &sparse->pages, ecs_sparse_page_t, page_index); int32_t offset = FLECS_SPARSE_OFFSET(id); if (checked) { if (!page->sparse) { return NULL; } int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return NULL; } } return DATA(page->data, size, offset); } void* flecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t id) { return flecs_sparse_get_w_check(sparse, size, id, true); } 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); } 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; } #define FLECS_STACK_OVERSIZED_MSG \ "an allocation that exceeds FLECS_STACK_PAGE_SIZE was not freed: callers " \ "must pair flecs_stack_alloc with flecs_stack_free" #ifdef FLECS_DEBUG /* Allocations that are too large for a page are prefixed with the stack that * allocated them, so that flecs_stack_free can account for them and the * allocator can assert that callers honor the pairing contract. */ typedef struct ecs_stack_oversized_t { ecs_stack_t *owner; } ecs_stack_oversized_t; #define FLECS_STACK_OVERSIZED_OFFSET \ ECS_ALIGN(ECS_SIZEOF(ecs_stack_oversized_t), 16) #endif 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) { /* Too large for page */ #ifdef FLECS_DEBUG ecs_stack_oversized_t *hdr = ecs_os_malloc( FLECS_STACK_OVERSIZED_OFFSET + size); hdr->owner = stack; ++ stack->oversized_count; result = ECS_OFFSET(hdr, FLECS_STACK_OVERSIZED_OFFSET); #else result = ecs_os_malloc(size); #endif 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) { #ifdef FLECS_DEBUG ecs_stack_oversized_t *hdr = ECS_OFFSET( ptr, -FLECS_STACK_OVERSIZED_OFFSET); ecs_dbg_assert(hdr->owner->oversized_count > 0, ECS_DOUBLE_FREE, "double free detected in stack allocator"); -- hdr->owner->oversized_count; ecs_os_free(hdr); #else ecs_os_free(ptr); #endif } } 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; } ecs_stack_cursor_t* flecs_stack_get_cursor_w_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align, void **data_out) { ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(align >= ECS_ALIGNOF(ecs_stack_cursor_t), ECS_INTERNAL_ERROR, NULL); ecs_size_t hdr = ECS_ALIGN(ECS_SIZEOF(ecs_stack_cursor_t), align); ecs_assert((hdr + size) <= FLECS_STACK_PAGE_SIZE, 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 = page->sp; char *buf = flecs_stack_alloc(stack, hdr + size, align); ecs_stack_cursor_t *result = (ecs_stack_cursor_t*)(void*)buf; 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; *data_out = buf + hdr; 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); #ifdef FLECS_DEBUG if (!stack->cursor_count) { ecs_dbg_assert(stack->oversized_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_OVERSIZED_MSG); } #endif } void flecs_stack_reset( ecs_stack_t *stack) { ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_dbg_assert(stack->oversized_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_OVERSIZED_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); ecs_dbg_assert(stack->oversized_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_OVERSIZED_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 EXP_THRESHOLD (3) #define INT64_MAX_F ((double)INT64_MAX) 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, char nan_delim) { char buf[64]; char * ptr = buf; char c; int64_t intPart; int64_t exp = 0; bool nan = ecs_os_isnan(f); if (nan || ecs_os_isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); } ecs_strbuf_appendstrn(out, nan ? "NaN" : "Inf", 3); if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); } return; } if (f < 0) { f = -f; *ptr++ = '-'; } f += 0.00000000005; /* 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); *ptr++ = '.'; for (int32_t i = 0; i < 10; i ++) { 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, 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 }; } 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; } int32_t max_count = size ? (INT32_MAX / size) : INT32_MAX; ecs_assert(elem_count >= 0 && elem_count <= max_count, ECS_OUT_OF_RANGE, "vector with element size %d cannot hold %d elements", size, elem_count); int32_t new_count = flecs_next_pow_of_2(elem_count); if (new_count < 2) { new_count = 2; } if (new_count < elem_count || new_count > max_count) { new_count = max_count; } elem_count = new_count; 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_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; } bool has_ctor = ti->hooks.ctor != NULL && !(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL); bool has_dtor = ti->hooks.dtor != NULL && !(ti->hooks.flags & ECS_TYPE_HOOK_DTOR_ILLEGAL); if (!ti->hooks.ctor_move_dtor && !has_ctor && !has_dtor) { /* Trivial type, use regular set_count */ ecs_vec_set_count(allocator, v, size, elem_count); return; } int32_t old_count = v->count; if (elem_count < old_count) { flecs_type_info_dtor(ECS_ELEM(v->array, size, elem_count), old_count - elem_count, ti); } else { if (elem_count > v->size) { int32_t new_size = flecs_next_pow_of_2(elem_count); #ifdef FLECS_SANITIZE const char *type_name = v->type_name; #else const char *type_name = NULL; #endif void *array = flecs_vec_alloc(allocator, size, new_size, type_name); if (old_count) { flecs_type_info_ctor_move_dtor(array, v->array, old_count, ti); } flecs_vec_free(allocator, size, v->size, v->array); v->array = array; v->size = new_size; } flecs_type_info_ctor(ECS_ELEM(v->array, size, old_count), elem_count - old_count, ti); } 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; } ecs_mixins_t ecs_query_t_mixins = { .type_name = "ecs_query_t", .elems = { [EcsMixinWorld] = offsetof(ecs_query_impl_t, pub.world), [EcsMixinEntity] = offsetof(ecs_query_impl_t, pub.entity), [EcsMixinDtor] = offsetof(ecs_query_impl_t, dtor) } }; #ifdef FLECS_QUERY_PLANS /* 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; 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; } #endif // FLECS_QUERY_PLANS 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; #ifndef FLECS_CACHED_QUERIES /* Cached query policies are unavailable without the addon. */ kind = EcsQueryCacheNone; #endif 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; } #ifdef FLECS_CACHED_QUERIES /* 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; } } } #endif return 0; } #ifdef FLECS_CACHED_QUERIES static int flecs_query_create_cache( ecs_query_impl_t *impl, ecs_query_desc_t *desc) { ecs_query_t *q = &impl->pub; if ((q->cache_kind != EcsQueryCacheNone) && !q->entity) { /* Cached queries need an entity handle for observer components */ q->entity = ecs_new(q->stage); 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); } } return 0; error: return -1; } #endif // FLECS_CACHED_QUERIES 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); flecs_query_impl(q)->cr_cache = q->field_count ? flecs_calloc_n(a, ecs_component_record_t*, q->field_count) : NULL; } 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); flecs_free_n(a, ecs_component_record_t*, q->field_count, flecs_query_impl(q)->cr_cache); } 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); } #ifdef FLECS_QUERY_PLANS 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); #endif 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->stage) & EcsWorldQuit)) { if (!ecs_term_match_0(term)) { flecs_component_unlock(q->world, term->id); } } } } if (impl->tokens) { flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); } #ifdef FLECS_CACHED_QUERIES if (impl->cache) { flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->cache->field_map); flecs_query_cache_fini(impl); } #endif 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->stage, 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->stage, 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); bool main_scope = false; flecs_commands_begin(world, stage); 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.world = world; result->pub.stage = world_arg; result->stage = stage; ecs_assert(flecs_poly_is(result->pub.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; if (flecs_query_set_caching_policy(result, &desc)) { goto error; } if (result->pub.cache_kind != EcsQueryCacheNone) { flecs_check_exclusive_world_access_write(result->pub.world); if (stage != world->stages[0]) { flecs_commands_begin(world, world->stages[0]); main_scope = true; } } else { flecs_check_exclusive_world_access_read(result->pub.world); } #ifdef FLECS_CACHED_QUERIES result->cache = NULL; /* Initialize query cache if necessary */ if (flecs_query_create_cache(result, &desc)) { goto error; } #endif 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); } if (main_scope) { flecs_commands_end(world, world->stages[0]); } flecs_commands_end(world, stage); return &result->pub; error: result->pub.entity = 0; ecs_query_fini(&result->pub); if (main_scope) { flecs_commands_end(world, world->stages[0]); } flecs_commands_end(world, stage); 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(ecs_get_world(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(ecs_get_world(world)); /* Remove the existing query if any. */ ecs_remove_pair(ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)), entity, ecs_id(EcsPoly), EcsQuery); 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->stage, q); ecs_iter_set_var(it, 0, entity); return ecs_query_next(it); error: return false; } static bool flecs_query_table_has_self_ids( const ecs_query_t *q, const ecs_table_t *table) { if (!flecs_table_bloom_filter_test(table, q->bloom_filter)) { return false; } 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]; if (!(term->flags_ & EcsTermIsTrivial)) { continue; } if ((term->src.id & EcsTraverseFlags) != EcsSelf) { continue; } if (ecs_id_is_wildcard(term->id)) { continue; } if (ecs_table_get_type_index(q->world, table, term->id) == -1) { return false; } } return true; } 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_query_table_has_self_ids(q, table)) { /* Safe, only used for statistics */ ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; return false; } *it = ecs_query_iter(q->stage, q); ecs_table_range_t range = { .table = table }; ecs_iter_set_var_as_range(it, 0, &range); 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_query_table_has_self_ids(q, table)) { /* Safe, only used for statistics */ ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; return false; } *it = ecs_query_iter(q->stage, q); if (q->flags & EcsQueryMatchThis) { ecs_iter_set_var_as_range(it, 0, range); } return ecs_query_next(it); } #ifdef FLECS_CACHED_QUERIES /* Counting a query that is fully served by a trivial cache does not need an * iterator: the cache already stores the matched tables, and a trivial cache * yields one result per non-empty table. This matters for the stats addon, * which counts every system query every collection interval. */ static bool flecs_query_count_trivial_cache( const ecs_query_t *q, ecs_query_count_t *out) { ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_cache_t *cache = impl->cache; if (!cache || !(q->flags & EcsQueryIsCacheable) || (q->flags & (EcsQueryCacheWithFilter|EcsQueryHasChangeDetection)) || cache->order_by_callback || !(cache->query->flags & EcsQueryTrivialCache)) { return false; } #ifdef FLECS_QUERY_PLANS if (impl->ops) { return false; } #endif bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; ecs_query_cache_group_t *group = cache->first_group; while (group) { ecs_vec_t *tables = &group->tables; int32_t i, count = ecs_vec_count(tables); ecs_query_triv_cache_match_t *elems = ecs_vec_first_t( tables, ecs_query_triv_cache_match_t); for (i = 0; i < count; i ++) { int32_t entities = ecs_table_count(elems[i].table); if (!entities && !match_empty) { continue; } out->results ++; out->entities += entities; } group = group->next; } return true; } #endif 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}; #ifdef FLECS_CACHED_QUERIES if (!flecs_query_count_trivial_cache(q, &result)) #endif { ecs_iter_t it = flecs_query_iter(q->stage, q); it.flags |= EcsIterNoData; while (ecs_query_next(&it)) { result.results ++; result.entities += it.count; #ifdef FLECS_CACHED_QUERIES ecs_iter_skip(&it); #endif } } if ((q->flags & EcsQueryMatchOnlySelf) && !(q->flags & EcsQueryMatchWildcards)) { result.tables = result.results; } #ifdef FLECS_CACHED_QUERIES 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); } } #endif 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->stage, q); return ecs_iter_is_true(&it); } #ifdef FLECS_CACHED_QUERIES 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; } } #endif // FLECS_CACHED_QUERIES 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; } } #ifdef FLECS_CACHED_QUERIES 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; } #endif // FLECS_CACHED_QUERIES #ifdef FLECS_QUERY_PLANS ecs_var_id_t flecs_utovar(uint64_t val) { return flecs_uto(uint8_t, val); } 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 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 EcsQueryTreeUpNot: return "treeup_not "; case EcsQueryTreeSelfUpNot: return "treesup_not "; 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); } 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; } } 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 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.stage, 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); #ifdef FLECS_LOG char *str = ecs_strbuf_get(&buf); flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); ecs_os_free(str); #endif return ecs_strbuf_get(&buf); } char* ecs_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); #ifdef FLECS_CACHED_QUERIES 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); } #endif #ifdef FLECS_LOG char *str = ecs_strbuf_get(&buf); flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); ecs_os_free(str); #endif return ecs_strbuf_get(&buf); } #endif // FLECS_QUERY_PLANS 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->stage; 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); ECS_BIT_COND(it->flags, EcsIterComponentInheritance, ECS_BIT_IS_SET(query->flags, EcsQueryHasComponentInheritance)); } #ifdef FLECS_CACHED_QUERIES 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); } } #endif // FLECS_CACHED_QUERIES #ifdef FLECS_QUERY_DSL #endif 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->stage, 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; } ecs_size_t name_len = ecs_os_strlen(name); if (name[0] == '.' || name[name_len - 1] == '.' || strstr(name, "..") != NULL) { flecs_query_validator_error(ctx, "invalid variable name '%s'", name); return -1; } return 0; } else if (ref->id & EcsIsName) { 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); if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || !ecs_os_strcmp(name, "$")) { ref->id &= ~EcsIsEntity; ref->id |= EcsIsVariable; } if (!ecs_is_alive(world, e)) { flecs_query_validator_error(ctx, "identifier '%s' is not alive", name); return -1; } ref->name = NULL; 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; } ecs_term_ref_t *refs[] = { src, first, second }; const char *names[] = { "src", "first", "second" }; for (int32_t i = 0; i < 3; i ++) { if (flecs_term_ref_finalize_flags(refs[i], ctx, names[i])) { 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_decode_id( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { bool pair = ECS_HAS_ID_FLAG(term->id, PAIR); ecs_entity_t ids[2] = { pair ? ECS_PAIR_FIRST(term->id) : term->id & ECS_COMPONENT_MASK, pair ? ECS_PAIR_SECOND(term->id) : 0 }; ecs_term_ref_t *refs[2] = { &term->first, &term->second }; for (int32_t i = 0; i < 2; i ++) { ecs_term_ref_t *ref = refs[i]; ecs_entity_t id = ids[i]; ecs_entity_t existing = flecs_term_ref_get_entity(ref); if (existing) { if ((uint32_t)existing != (uint32_t)id) { flecs_query_validator_error(ctx, "mismatch between term.id and term.%s", i ? "second" : "first"); return -1; } } else if (id) { ecs_entity_t alive = ecs_get_alive(world, id); ref->id = (alive ? alive : id) | ECS_TERM_REF_FLAGS(ref); } else if (i && pair) { ref->id = EcsIsEntity; } } return 0; } static int flecs_term_encode_id( ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { 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; bool pair = (flags & ECS_PAIR) || second || (term->second.id & EcsIsEntity); bool predicate = first == EcsPredEq || first == EcsPredMatch || first == EcsPredLookup; if (!first || (first & ECS_ID_FLAGS_MASK) || (second & ECS_ID_FLAGS_MASK) || (!predicate && ((pair && !second && first != EcsChildOf && !ECS_IS_VALUE_PAIR(term->id)) || (!pair && ecs_term_ref_is_set(&term->second))))) { flecs_query_validator_error(ctx, "invalid component or pair id"); return -1; } ecs_id_t id = pair ? ecs_pair(first, second) | flags : first | flags; if (term->id & ~ECS_ID_FLAGS_MASK) { ecs_id_t mask = pair ? UINT64_MAX : ~ECS_GENERATION_MASK; if ((id & mask) != (term->id & mask)) { flecs_query_validator_error(ctx, "mismatch between term.id and term references"); return -1; } } else { term->id = id; } return 0; } static int flecs_term_verify_eq_pred( const ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { #ifdef FLECS_QUERY_PLANS 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; #else (void)term; (void)ctx; return 0; #endif } static bool flecs_term_ref_same( const ecs_term_ref_t *a, const ecs_term_ref_t *b, bool match_this) { ecs_flags64_t mask = EcsIsEntity | EcsIsVariable; if ((a->id & mask) != (b->id & mask)) { return false; } ecs_entity_t a_id = ECS_TERM_REF_ID(a), b_id = ECS_TERM_REF_ID(b); if (a_id != b_id) { return false; } if (a_id) { if (a_id == EcsWildcard || a_id == EcsAny) { return false; } if (a->id & EcsIsVariable) { return match_this; } return true; } return a->name && b->name && !ecs_os_strcmp(a->name, b->name); } 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 id = term->id; (void)id; if ((src->id & EcsIsName) && (second->id & EcsIsName)) { flecs_query_validator_error(ctx, "mismatch between term.cr_flags & term.id"); return -1; } 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(src)) { flecs_query_validator_error(ctx, "term.src is not initialized"); return -1; } #ifdef FLECS_CONSTRAINT_TRAITS if (!ECS_IS_PAIR(id) && !ecs_id_is_wildcard(id) && ecs_has_id(world, id & ECS_COMPONENT_MASK, EcsRelationship)) { flecs_query_validator_error(ctx, "cannot query for relationship '%s' as component", flecs_errstr(ecs_get_path(world, id & ECS_COMPONENT_MASK))); return -1; } #endif if (first_id) { if (ecs_term_ref_is_set(second)) { #ifdef FLECS_CONSTRAINT_TRAITS if (flecs_term_ref_same(src, second, false) && 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; } #endif } 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; } /* Test whether a component can be used as the base of other components. */ static bool flecs_table_can_isa( const ecs_world_t *world, const ecs_table_t *table) { (void)world; if (!table || !(table->flags & EcsTableHasIsA)) { return false; } #ifdef FLECS_CONSTRAINT_TRAITS return !ecs_table_has_id(world, table, EcsFinal); #else return true; #endif } 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 second_flags = ECS_TERM_REF_FLAGS(second); ecs_term_ref_t *refs[] = { src, first, second }; const char *names[] = { "src", "first", "second" }; for (int32_t i = 0; i < 3; i ++) { if (refs[i]->name && (refs[i]->id & ~EcsTermRefFlags)) { flecs_query_validator_error(ctx, "%s.name (%s) and %s.id have competing values", names[i], refs[i]->name, names[i]); return -1; } } if (ECS_IS_VALUE_PAIR(term->id) && !ecs_term_ref_is_set(second)) { second->id |= EcsIsEntity; } if (term->id & ~ECS_ID_FLAGS_MASK) { if (flecs_term_decode_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 (flecs_term_encode_id(term, ctx)) { 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 ((first->id | second->id) & (EcsCascade|EcsDesc)) { flecs_query_validator_error(ctx, "cascade and desc modifiers are only valid for term.src"); 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)) { ecs_record_t *first_record = flecs_entities_get(world, first_entity); ecs_table_t *first_table = first_record ? first_record->table : NULL; /* Only enable inheritance for ids which are inherited from at the time * of query creation. */ if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || (cr_flags & EcsIdInheritable) || flecs_table_can_isa(world, first_table)) { term->flags_ |= EcsTermIdInherited; } else { #ifdef FLECS_DEBUG if ((ECS_TERM_REF_FLAGS(first) & EcsTraverseFlags) != EcsSelf) { 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 */ #ifdef FLECS_META if (ecs_id(EcsMember) != 0) { if (first_entity) { if (ecs_has(world, first_entity, EcsMember)) { term->flags_ |= EcsTermIsMember; } } } #endif } 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; } if (ECS_IS_PAIR(term->id) || ecs_id_is_wildcard(term->id)) { flecs_query_validator_error(ctx, "invalid AndFrom/OrFrom/NotFrom term: id must be a regular entity"); return -1; } } bool childof = ECS_HAS_RELATION(term->id, EcsChildOf); if (childof && term->oper == EcsAnd) { term->flags_ |= EcsTermNonFragmentingChildOf; } bool table_match = !(term->flags_ & (EcsTermTransitive| EcsTermReflexive|EcsTermIsMember|EcsTermDontFragment)) && !(first_entity == EcsPredEq || first_entity == EcsPredMatch || first_entity == EcsPredLookup); bool cacheable_term = table_match && ECS_TERM_REF_ID(src) == EcsThis && term->id != ecs_childof(0); bool trivial_term = table_match && !(term->flags_ & EcsTermIdInherited) && term->oper == EcsAnd && !(term->flags_ & (EcsTermIsOr|EcsTermIsToggle)) && ecs_term_match_this(term) && (src->id & EcsSelf) && (!term->trav || term->trav == EcsIsA) && !(childof && ECS_PAIR_SECOND(term->id)); if (ecs_id_is_wildcard(term->id)) { bool first_variable = (first->id & EcsIsVariable) && first_id != EcsWildcard; bool second_variable = (second->id & EcsIsVariable) && second_id != EcsWildcard; bool dynamic_id = ECS_PAIR_FIRST(term->id) == EcsWildcard || first_variable; cacheable_term &= !dynamic_id && !(second_variable && second_id != EcsAny); trivial_term &= !dynamic_id && !second_variable && (cr_flags & EcsIdExclusive); } 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)); } #ifdef FLECS_QUERY_PLANS 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->stage; 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; } } } #endif static void flecs_query_set_self_trivial( ecs_query_t *q) { bool self_trivial = q->term_count && !q->row_fields && !(q->flags & (EcsQueryHasPred|EcsQueryHasScopes|EcsQueryHasRefs| EcsQueryMatchNothing|EcsQueryMatchWildcards)); for (int32_t i = 0; self_trivial && i < q->term_count; i ++) { const ecs_term_t *term = &q->terms[i]; if ((term->oper != EcsAnd && term->oper != EcsNot) || (term->flags_ & (EcsTermIsOr|EcsTermIsToggle|EcsTermDontFragment| EcsTermIsSparse|EcsTermTransitive|EcsTermReflexive|EcsTermIsMember| EcsTermIsScope|EcsTermMatchAny|EcsTermMatchAnySrc)) || !ecs_term_match_this(term) || !(term->src.id & EcsSelf) || (term->trav && term->trav != EcsIsA) || ecs_id_is_wildcard(term->id) || (ECS_HAS_RELATION(term->id, EcsChildOf) && ECS_PAIR_SECOND(term->id))) { self_trivial = false; } } ECS_BIT_COND(q->flags, EcsQuerySelfTrivial, self_trivial); } 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; #ifdef FLECS_CONSTRAINT_TRAITS bool default_src = term->src.id == 0 && term->src.name == NULL; #endif ctx.term_index = i; if (flecs_term_finalize(world, term, &ctx)) { return -1; } 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; } #ifdef FLECS_CONSTRAINT_TRAITS if ((cr_flags & EcsIdSingleton) && default_src) { term->src.id = term->first.id|EcsSelf|EcsIsEntity; } #endif if (prev_is_or && !flecs_term_ref_same(&term[-1].src, &term->src, true)) { flecs_query_validator_error(&ctx, "terms in an OR chain must have the same source"); return -1; } if (term->flags_ & EcsTermIdInherited) { q->flags |= EcsQueryHasComponentInheritance; } 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->flags_ & EcsTermIsOr) || term->oper == EcsAnd || term->oper == EcsOptional || term->oper == EcsNot)) { 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 (term->flags_ & EcsTermIsCacheable) { if (prev_is_or && !(term[-1].flags_ & EcsTermIsCacheable)) { ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } if (term->flags_ & EcsTermIsToggle) { cacheable = false; } } else if (prev_is_or) { for (int32_t j = i - 1; j >= 0 && terms[j].oper == EcsOr; j --) { 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); } #ifdef FLECS_PREFAB if (ECS_TERM_REF_ID(term) == EcsPrefab) { ECS_BIT_SET(q->flags, EcsQueryMatchPrefab); } #endif 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; } } const ecs_type_info_t *ti = cr ? cr->type_info : ecs_get_type_info(world, term->id); bool other_type = false; #ifdef FLECS_QUERY_PLANS if (term->flags_ & EcsTermIsOr) { other_type = flecs_query_or_other_type(q, i) != NULL; } #endif int32_t field = term->field_index; q->ids[field] = other_type ? 0 : term->id; if (other_type) { q->sizes[field] = 0; } else if (ti && (!cr || !ECS_HAS_RELATION(cr->id, EcsWildcard))) { q->sizes[field] = ti->size; q->ids[field] = cr ? cr->id : term->id; } nodata_term = term->src.id == EcsIsEntity || term->inout == EcsInOutNone || !ti || (term->flags_ & EcsTermIsMember) || scope_nesting; 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 && (term->flags_ & EcsTermIsOr)) { nodata_term = other_type; q->data_fields &= (ecs_termset_t)~(1llu << field); } 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); } } #ifdef FLECS_CONSTRAINT_TRAITS if ((cr_flags & EcsIdSingleton) && default_src) { ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } #endif /* 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); } #ifdef FLECS_QUERY_PLANS if ((term->src.id & EcsIsVariable) && (ECS_TERM_REF_ID(&term->src) != EcsThis)) { ECS_TERMSET_SET(q->var_fields, 1u << term->field_index); } #endif 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); 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) { if (term->oper != EcsAnd && term->oper != EcsNot) { flecs_query_validator_error(&ctx, "invalid operator for scope"); return -1; } q->flags |= EcsQueryHasScopes; scope_nesting ++; if (scope_nesting >= FLECS_QUERY_SCOPE_NESTING_MAX) { flecs_query_validator_error(&ctx, "query has too many nested scopes"); return -1; } } if (scope_nesting) { term->flags_ |= EcsTermIsScope; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); 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; } { int8_t cascade_count = 0; for (i = 0; i < term_count; i ++) { if (terms[i].src.id & EcsCascade) { cascade_count ++; if (terms[i].flags_ & EcsTermIsOr) { flecs_query_validator_error(&ctx, "cascade term cannot be part of an OR chain"); return -1; } } } if (cascade_count > 1) { flecs_query_validator_error(&ctx, "query can only have one cascade term"); return -1; } } q->field_count = flecs_ito(int8_t, field_count); bool is_trivial = term_count != 0, match_only_self = true; if (field_count) { for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; cacheable_terms += (term->flags_ & EcsTermIsCacheable) != 0; match_only_self &= !(term->src.id & EcsUp); is_trivial &= (term->flags_ & EcsTermIsTrivial) != 0; if (ECS_TERM_REF_ID(&term->first) == EcsChildOf && ECS_TERM_REF_ID(&term->second) != 0) { is_trivial = false; } if ((term->flags_ & EcsTermIsTrivial) && (term->src.id & EcsTraverseFlags) == EcsSelf && !ecs_id_is_wildcard(term->id)) { q->bloom_filter = flecs_table_bloom_filter_add( q->bloom_filter, term->id); } if (!ecs_term_match_0(term)) { flecs_component_lock(world, term->id); } } } ECS_BIT_COND(q->flags, EcsQueryHasCondSet, cond_set); if ((q->flags & EcsQueryMatchOnlyThis) && !(q->flags & (EcsQueryHasPred|EcsQueryMatchDisabled|EcsQueryMatchPrefab))) { ECS_BIT_COND(q->flags, EcsQueryMatchOnlySelf, match_only_self); if (is_trivial) { 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); flecs_query_set_self_trivial(q); #ifdef FLECS_QUERY_PLANS 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); } #endif 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]) { #ifdef FLECS_QUERY_DSL const char *name = desc->entity ? ecs_get_name(world, desc->entity) : NULL; /* Allocate buffer that's large enough to tokenize the query string */ ecs_size_t token_buffer_size = ecs_os_strlen(expr) * 2 + 1; char *token_buffer = flecs_alloc( &flecs_query_impl(q)->stage->allocator, token_buffer_size); if (flecs_terms_parse(world, name, expr, token_buffer, &q->terms[term_count], &term_count)) { flecs_free(&stage->allocator, token_buffer_size, token_buffer); goto error; } /* Store on query object so we can free it later */ flecs_query_impl(q)->tokens = token_buffer; flecs_query_impl(q)->tokens_len = token_buffer_size; #else (void)world; (void)stage; ecs_err("parsing query expressions requires the FLECS_QUERY_DSL addon"); goto error; #endif } 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; } #ifdef FLECS_PREFAB if (id == EcsPrefab) { return false; } #endif if (id == EcsDisabled) { return false; } #ifdef FLECS_CONSTRAINT_TRAITS ecs_entity_t component = id & ECS_COMPONENT_MASK; if (component && !ECS_IS_PAIR(id) && ecs_is_alive(world, component) && ecs_has_id(world, component, EcsRelationship)) { return false; /* Slow path reports the error */ } #endif if (term->src.id != 0 && term->src.id != EcsSelf && term->src.id != (EcsThis|EcsSelf|EcsIsVariable)) { return false; } if (term->first.id != 0 && term->first.id != (term->id|EcsSelf|EcsIsEntity)) { return false; } if (term->second.id || term->trav || term->inout || term->oper) { return false; } if (term->src.name || term->first.name || term->second.name) { 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 = UINT32_MAX >> (32 - term_count); /* 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; int8_t inherited_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) != 0; #ifdef FLECS_CONSTRAINT_TRAITS bool default_src = term->src.id == 0; #endif 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); } #ifdef FLECS_CONSTRAINT_TRAITS 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; } #else has_this = true; #endif 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; } } /* Enable inheritance for ids that are inherited from, that are marked * inheritable, and for ids that are part of an inheritance hierarchy, * as components can be derived from them after the query is created. */ ecs_record_t *first_r = flecs_entities_get(world, first); if (flecs_components_get(world, ecs_pair(EcsIsA, first)) || (cr_flags & EcsIdInheritable) || flecs_table_can_isa(world, first_r ? first_r->table : NULL)) { term->flags_ |= EcsTermIdInherited; q->flags |= EcsQueryHasComponentInheritance; trivial = false; inherited_count ++; } 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) { if ((trivial_count + inherited_count) == term_count) { q->flags |= EcsQueryIsCacheable; } if (trivial_count == term_count) { q->flags |= EcsQueryIsTrivial; } } if (!up_count) { q->flags |= EcsQueryMatchOnlySelf; } flecs_query_set_self_trivial(q); 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; 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 (!(q->flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled))) { 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); { ecs_query_impl_t *impl = flecs_query_impl(q); ecs_termset_t df_fields = 0, non_df_fields = 0; int8_t i; for (i = 0; i < q->term_count; i ++) { ecs_term_t *term = &q->terms[i]; ecs_termset_t bit = (ecs_termset_t)(1u << term->field_index); if (term->flags_ & EcsTermDontFragment) { df_fields |= bit; } else { non_df_fields |= bit; } } impl->dont_fragment_fields = df_fields & (ecs_termset_t)~non_df_fields; } /* Check if this is a query for which all terms are sparse, which can use * an iterator that directly iterates the sparse component storages. */ if (q->term_count && (q->term_count == q->field_count) && ((q->flags & (EcsQueryMatchOnlyThis|EcsQueryMatchOnlySelf)) == (EcsQueryMatchOnlyThis|EcsQueryMatchOnlySelf)) && !(q->flags & (EcsQueryHasPred|EcsQueryHasScopes|EcsQueryTableOnly| EcsQueryMatchDisabled|EcsQueryMatchPrefab))) { bool trivial_sparse = true; int8_t i; for (i = 0; i < q->term_count; i ++) { ecs_term_t *term = &q->terms[i]; if (term->oper != EcsAnd) { trivial_sparse = false; break; } if (!(term->flags_ & EcsTermIsSparse) || !(term->flags_ & EcsTermDontFragment)) { trivial_sparse = false; break; } if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc| EcsTermTransitive|EcsTermReflexive|EcsTermIdInherited| EcsTermIsScope|EcsTermIsMember|EcsTermIsToggle|EcsTermIsOr)) { trivial_sparse = false; break; } if (ecs_id_is_wildcard(term->id)) { trivial_sparse = false; break; } } ECS_BIT_COND(q->flags, EcsQueryTrivialSparse, trivial_sparse); } 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); bool up_notify = flecs_up_notify_observers_exist(o, id, EcsOnAdd); up_notify |= flecs_up_notify_observers_exist(o, id, EcsOnRemove); up_notify |= flecs_up_notify_observers_exist(o, id, EcsOnSet); result |= up_notify * EcsIdHasUpNotify; 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_iter(&cr->cache, &it, EcsTableEmpty|EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { elem->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 (!ECS_IS_PAIR(id)) { 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 if (!ECS_IS_VALUE_PAIR(id)) { /* 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); if (!ECS_IS_VALUE_PAIR(id)) { 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; } #ifdef FLECS_CONSTRAINT_TRAITS if (flecs_check_constraint_traits(world, cr, rel, tgt)) { goto error; } #endif if (tgt && !ecs_id_is_wildcard(tgt)) { if (rel == EcsIsA) { 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))); } } } } #ifdef FLECS_CONSTRAINT_TRAITS } else if (flecs_check_constraint_traits(world, cr, rel, tgt)) { goto error; #endif } 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; world->cr_flag_count += is_pair && (ECS_PAIR_FIRST(id) == EcsFlag); 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; world->cr_flag_count -= ECS_IS_PAIR(id) && (ECS_PAIR_FIRST(id) == EcsFlag); /* 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 < FLECS_HI_ID_RECORD_ID) { return world->id_index_lo[id]; } 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_t *cache = &cr->cache; int32_t i = 0; while (i < ecs_vec_count(&cache->records)) { ecs_table_cache_elem_t *elem = ecs_vec_get_t( &cache->records, ecs_table_cache_elem_t, i); ecs_table_t *table = elem->table; if (table->keep || ecs_table_count(table)) { remaining = true; i ++; continue; } flecs_table_fini(world, 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_sparse_t* flecs_component_get_sparse( const ecs_component_record_t *cr) { ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); return cr->sparse; } ecs_component_record_t* flecs_component_dont_fragment_first( const ecs_world_t *world) { world = ecs_get_world(world); return world->cr_non_fragmenting_head; } ecs_component_record_t* flecs_component_dont_fragment_next( const ecs_component_record_t *cr) { ecs_assert(cr != NULL, ECS_INVALID_PARAMETER, NULL); return cr->non_fragmenting.next; } 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); } static void flecs_component_set_has_bases( ecs_component_record_t *cr, bool has_bases) { if (has_bases) { cr->flags |= EcsIdHasBases; } else if (!(cr->flags & EcsIdMarkedForDelete)) { cr->flags &= ~EcsIdHasBases; } } void flecs_components_on_isa_change( ecs_world_t *world, const ecs_table_t *table, const ecs_entity_t *entities, int32_t count, bool has_bases) { /* Changing the bases of a component invalidates the base ids registered for * existing tables, so only allow it while the component isn't in use. * Components that are being deleted are exempt, as their tables go too. */ bool check_unused = !(world->flags & (EcsWorldInit|EcsWorldFini|EcsWorldQuit)) && !(table->flags & EcsTableMarkedForDelete); int32_t i; for (i = 0; i < count; i ++) { ecs_entity_t component = entities[i]; ecs_record_t *r = flecs_entities_get(world, component); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (!(r->row & EcsEntityIsId)) { continue; /* Entity isn't used as a component */ } if (check_unused) { flecs_assert_relation_unused(world, component, EcsIsA); } ecs_component_record_t *cr = flecs_components_get(world, component); if (cr) { flecs_component_set_has_bases(cr, has_bases); } cr = flecs_components_get(world, ecs_pair(component, EcsWildcard)); if (cr) { do { flecs_component_set_has_bases(cr, has_bases); } while ((cr = flecs_component_first_next(cr))); } } } 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_iter(&cr->cache, iter_out, EcsTableEmpty|EcsTableNotEmpty); } const ecs_table_record_t* flecs_component_next( ecs_table_cache_iter_t *iter) { const ecs_table_cache_elem_t *elem = flecs_table_cache_next(iter); return elem ? elem->tr : NULL; } 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(ecs_get_stage(world, 0), 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); } 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; 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); #ifdef FLECS_ENTITY_RANGES flecs_entity_ranges_init(allocator, index); #endif } 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*); #ifdef FLECS_ENTITY_RANGES flecs_entity_ranges_fini(index); #endif } 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; #ifdef FLECS_ENTITY_RANGES flecs_entity_ranges_on_ensure(index, id); #else index->max_id = id > index->max_id ? id : index->max_id; #endif } 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 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; } #ifdef FLECS_ENTITY_RANGES flecs_entity_ranges_on_delete(index, entity, r); #endif } 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); #ifdef FLECS_ENTITY_RANGES /* Make sure id hasn't been issued before after switching ranges. */ 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); #endif 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); #ifdef FLECS_ENTITY_RANGES /* Make sure id hasn't been issued before after switching ranges. */ 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); #endif 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 ++; world->non_fragmenting_child_count ++; } static void flecs_remove_non_fragmenting_child_from_table( ecs_world_t *world, 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 --; world->non_fragmenting_child_count --; ecs_assert(world->non_fragmenting_child_count >= 0, ECS_INTERNAL_ERROR, NULL); 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); #ifdef FLECS_PREFAB ecs_record_t *r_parent = flecs_entities_get(world, parent); if (r_parent->table->flags & EcsTableIsPrefab) { ecs_add_id(ecs_get_stage(world, 0), entity, EcsPrefab); } #endif 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(world, 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(it->stage, 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 /* Setting the parent a child already has is a no-op. Without this * check the child would be removed from and appended to the ordered * children vector, which would move it to the back. */ if (old_parent == new_parent) { continue; } 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_e = flecs_entities_get(world, e); if (r_e->row & EcsEntityIsTraversable) { flecs_emit_propagate_invalidate(world, r_e->table, ECS_RECORD_TO_ROW(r_e->row), 1); } 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(it->stage, 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); } } 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(world, 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(world, cr, src); } if (dst && (dst->flags & EcsTableHasParent)) { if (update_parent_records) { flecs_add_non_fragmenting_child_to_table(world, cr, e, dst); } } else { int32_t index = flecs_ordered_entities_remove(world, cr, e); /* When the child moves to a table with a ChildOf pair for the same * parent it switched representation without being reparented, and * has to keep its position in the ordered children vector. */ flecs_ordered_entities_keep_position(cr, e, (dst && ecs_table_has_id(world, ECS_CONST_CAST(ecs_table_t*, dst), ecs_childof(p))) ? index : -1); 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; int32_t count = ecs_vec_count(&pr->ordered_children); int32_t index = count - 1; if (pr->ordered_pending_entity == e) { /* Child switched representation without changing parent. Move it back * to the position it had before the table move. */ ecs_entity_t *array = ecs_vec_first_t( &pr->ordered_children, ecs_entity_t); int32_t i; index = pr->ordered_pending_index; for (i = count - 1; i > index; i --) { array[i] = array[i - 1]; } array[index] = e; } pr->ordered_pending_entity = 0; if (cr->flags & EcsIdPrefabChildren) { /* Register index of prefab child so that it can be used to lookup * corresponding instance child. Entities after the inserted child * shifted up, so their indices have to be updated as well. */ ecs_entity_t *array = ecs_vec_first_t( &pr->ordered_children, ecs_entity_t); int32_t i; for (i = index; i < count; i ++) { ecs_map_ensure(&world->prefab_child_indices, array[i])[0] = flecs_ito(uint64_t, i); } #ifdef FLECS_PREFAB } else { ecs_assert( !ecs_owns_id(world, ecs_pair_second(world, cr->id), EcsPrefab), ECS_INTERNAL_ERROR, NULL); #endif } } 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); } } int32_t 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); } return i; } } return -1; } void flecs_ordered_entities_keep_position( ecs_component_record_t *cr, ecs_entity_t e, int32_t index) { ecs_assert(cr->pair != NULL, ECS_INTERNAL_ERROR, NULL); cr->pair->ordered_pending_entity = index == -1 ? 0 : e; cr->pair->ordered_pending_index = index; } static void flecs_ordered_entities_unparent_internal( ecs_world_t *world, const ecs_table_t *entities_table, const ecs_table_t *table, const ecs_table_t *dst, 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); /* When the child moves to a table with a Parent component it may still * be a child of the same parent, in which case the Parent hooks add it * back. Only done for single entities, as switching a child to the * Parent component is a per-entity operation. */ bool keep_position = count == 1 && dst && (dst->flags & EcsTableHasParent); int32_t i = row, end = row + count; for (; i < end; i ++) { ecs_entity_t e = entities[i]; int32_t index = flecs_ordered_entities_remove(world, cr, e); flecs_ordered_entities_keep_position( cr, e, keep_position ? index : -1); } } } 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, dst, 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, const ecs_table_t *dst, int32_t row, int32_t count) { (void)world; flecs_ordered_entities_unparent_internal( world, src, src, dst, 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)) { ecs_sparse_t *sparse = cr->sparse; return flecs_sparse_get(sparse, sparse->size, 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)); } } ecs_sparse_t *sparse = cr->sparse; return flecs_sparse_get(sparse, sparse->size, 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, ti->size, entity); if (!ptr) { return 0; } ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_invoke_hook(world, table, cr, -1, 1, row, &entity, cr->id, ti, EcsOnRemove, on_remove, ptr); } 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, -1, 1, ECS_RECORD_TO_ROW(r->row), &entities[i], component_id, ti, EcsOnRemove, on_remove, NULL); } } 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; } flecs_invoke_hook(world, table, cr, -1, 1, row, &entity, component_id, ti, EcsOnAdd, on_add, ptr); 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; } flecs_invoke_hook(world, table, cr, -1, 1, row, &entity, cr->id, ti, EcsOnAdd, on_add, ptr); return ptr; } const int16_t flecs_table_empty_component_map[FLECS_HI_COMPONENT_ID] = {0}; /* 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); flecs_table_cache_set_column(&cr->cache, table, tr->column); 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; flecs_table_cache_set_column(&cr->cache, table, 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; #ifdef FLECS_PREFAB } else if (id == EcsPrefab) { table->flags |= EcsTableIsPrefab; #endif } 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); #ifdef FLECS_PREFAB if (inherit_kind == EcsInherit) { table->trait_flags |= EcsIdOnInstantiateInherit; } else if (inherit_kind == EcsOverride) { table->trait_flags |= EcsIdOnInstantiateOverride; } #endif if (inherit_kind == EcsDontInherit) { table->trait_flags |= EcsIdOnInstantiateDontInherit; } } else if (id == EcsCanToggle) { table->trait_flags |= EcsIdCanToggle; } else if (id == EcsInheritable) { table->trait_flags |= EcsIdInheritable; } #ifdef FLECS_CONSTRAINT_TRAITS else if (id == EcsSingleton) { table->trait_flags |= EcsIdSingleton; } #endif 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; table->trait_flags |= EcsIdHasBases; } 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 ecs_table_record_t* flecs_table_add_record( ecs_world_t *world, ecs_vec_t *records, ecs_component_record_t *cr, int32_t index, int32_t count) { ecs_table_record_t *tr = ecs_vec_append_t( &world->allocator, records, ecs_table_record_t); tr->hdr.cr = cr; tr->index = flecs_ito(int16_t, index); tr->count = flecs_ito(int16_t, count); return tr; } static void flecs_table_append_to_records( ecs_world_t *world, ecs_vec_t *records, ecs_id_t id, int32_t column, int32_t scan_start) { ecs_component_record_t *cr = flecs_components_ensure(world, id); int32_t i, count = ecs_vec_count(records); ecs_table_record_t *array = ecs_vec_first_t(records, ecs_table_record_t); ecs_table_record_t *tr = NULL; for (i = scan_start; i < count; i ++) { if (array[i].hdr.cr == cr) { tr = &array[i]; break; } } if (!tr) { flecs_table_add_record(world, records, cr, column, 1); } else { tr->count ++; } } static void flecs_table_init_overrides( ecs_world_t *world, ecs_table_t *table, const ecs_table_record_t *tr) { if (!table->column_count) { return; } ecs_size_t bases_size = ECS_SIZEOF(ecs_table_overrides_t) + (tr->count - 1) * ECS_SIZEOF(ecs_table_override_base_t); ecs_size_t size = bases_size + table->column_count * ECS_SIZEOF(ecs_ref_t); ecs_table_overrides_t *o = flecs_walloc(world, size); o->count = tr->count; o->size = size; o->refs = ECS_OFFSET(o, bases_size); ecs_os_memset_n(o->refs, 0, ecs_ref_t, table->column_count); for (int32_t i = 0; i < o->count; i ++) { o->bases[i].pair = table->_->records[tr->index + i].hdr.cr->pair; o->bases[i].generation = -1; } table->data.overrides = o; } static void flecs_table_fini_overrides( ecs_world_t *world, ecs_table_t *table) { ecs_table_overrides_t *o = table->data.overrides; if (o) { flecs_wfree(world, o->size, o); } } static void flecs_table_update_overrides( ecs_world_t *world, ecs_table_t *table) { ecs_table_overrides_t *o = table->data.overrides; if (!o) { return; } int32_t base_index = 0; for (; base_index < o->count; base_index ++) { ecs_table_override_base_t *base = &o->bases[base_index]; if (base->generation != base->pair->reachable.generation) { break; } } if (base_index == o->count) { return; } for (; base_index < o->count; base_index ++) { ecs_table_override_base_t *base = &o->bases[base_index]; base->generation = base->pair->reachable.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]); } } } /* Add a table record for a base id of a component in the table. Returns false * if the table already has the base id, or if a record was already added for * the same component. */ static bool flecs_table_add_inherited_record( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, int32_t inherited_start, int32_t type_index, ecs_id_t base_id) { ecs_component_record_t *cr = flecs_components_ensure(world, base_id); cr->flags |= EcsIdHasDerived; /* If the table stores the base id itself no record is added for it. Queries * find the derived ids by searching the table type. */ ecs_id_t *ids = table->type.array; int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { if (ecs_id_match(ids[i], base_id)) { return false; } } count = ecs_vec_count(records); for (i = inherited_start; i < count; i ++) { ecs_table_record_t *existing = ecs_vec_get_t( records, ecs_table_record_t, i); if (existing->hdr.cr == cr) { if (existing->index == type_index) { return false; } /* Ids are visited in reverse order, so index ends up pointing at * the first derived id in the table type. */ existing->count ++; existing->index = flecs_ito(int16_t, type_index); return true; } } flecs_table_add_record(world, records, cr, type_index, 1); if (base_id < FLECS_HI_COMPONENT_ID) { world->non_trivial_lookup[base_id] |= EcsNonTrivialIdInherit; } return true; } /* Add table records for the (transitive) base ids of a component in the table. * A component with a (IsA, Base) pair is matched by queries, get and has for * Base, which is implemented by registering the table with the component record * of Base. */ static void flecs_table_add_inherited_records( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, int32_t inherited_start, int32_t type_index, ecs_entity_t rel, ecs_entity_t tgt) { ecs_record_t *r = flecs_entities_get(world, rel); ecs_table_t *src = r ? r->table : NULL; if (!src || !(src->flags & EcsTableHasIsA)) { return; } const ecs_table_record_t *tr_isa = flecs_component_get_table( world->cr_isa_wildcard, src); ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = src->type.array; int32_t i = tr_isa->index, end = i + tr_isa->count; for (; i < end; i ++) { ecs_entity_t base = ecs_pair_second(world, ids[i]); if (!base) { continue; } bool recurse = flecs_table_add_inherited_record(world, table, records, inherited_start, type_index, tgt ? ecs_pair(base, tgt) : base); if (tgt) { recurse |= flecs_table_add_inherited_record(world, table, records, inherited_start, type_index, ecs_pair(base, EcsWildcard)); } if (recurse) { flecs_table_add_inherited_records(world, table, records, inherited_start, type_index, base, tgt); } } } /* Add table records for the base ids of all components in the table. */ static void flecs_table_init_inherited( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, int32_t inherited_start) { ecs_type_t type = table->type; int32_t ti; /* Iterate in reverse so that the record index of a base ends up pointing at * the first derived id in the table type. */ for (ti = type.count - 1; ti >= 0; ti --) { ecs_table_record_t *tr = ecs_vec_get_t( records, ecs_table_record_t, ti); if (!(tr->hdr.cr->flags & EcsIdHasBases)) { continue; } ecs_id_t id = type.array[ti]; ecs_entity_t rel = id, tgt = 0; if (id & ECS_ID_FLAGS_MASK) { if (!ECS_IS_PAIR(id)) { continue; } rel = ecs_pair_first(world, id); tgt = ECS_PAIR_SECOND(id); } flecs_table_add_inherited_records( world, table, records, inherited_start, ti, rel, tgt); } } static void flecs_table_emit( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { ecs_type_t ids = table->type; int32_t inherited_start = table->_->inherited_start; int32_t record_count = table->_->record_count; ecs_id_t *base_ids = NULL; if (inherited_start != record_count) { /* Also emit the event for the ids the table inherits from, so that * query caches get notified of tables with derived components. */ ecs_table_record_t *records = table->_->records; int32_t i, count = ids.count; base_ids = flecs_walloc_n(world, ecs_id_t, count + record_count - inherited_start); ecs_os_memcpy_n(base_ids, ids.array, ecs_id_t, count); for (i = inherited_start; i < record_count; i ++) { ecs_id_t base_id = records[i].hdr.cr->id; if (!ecs_id_is_wildcard(base_id)) { base_ids[count ++] = base_id; } } ids.array = base_ids; ids.count = count; } flecs_commands_begin(world, flecs_stage_from_readonly_world(world)); flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &ids, .event = event, .table = table, .flags = EcsEventTableOnly, .observable = world }); flecs_commands_end(world, flecs_stage_from_readonly_world(world)); if (base_ids) { flecs_wfree_n(world, ecs_id_t, table->type.count + record_count - inherited_start, base_ids); } } /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { flecs_table_init_flags(world, table); int32_t dst_count = table->type.count, src_i = 0; ecs_id_t *dst_ids = table->type.array; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(&world->allocator, records, ecs_table_record_t); int32_t first_pair = -1, pair_count = 0, id_count = 0; int32_t first_role = dst_count; bool has_low_id = false; ecs_flags32_t all_flags = 0; ecs_component_record_t *cr, *childof_cr = NULL; ecs_table_record_t *tr; for (int32_t i = 0; i < dst_count; i ++) { ecs_id_t id = dst_ids[i]; while (from && src_i < from->type.count && from->type.array[src_i] < id) { src_i ++; } cr = from && src_i < from->type.count && from->type.array[src_i] == id ? from->_->records[src_i].hdr.cr : flecs_components_ensure(world, id); flecs_table_add_record(world, records, cr, i, 1); all_flags |= cr->flags; if (!(id & ECS_ID_FLAGS_MASK)) { id_count ++; } else if (ECS_IS_PAIR(id)) { if (first_pair == -1) { first_pair = i; } pair_count ++; } else if (first_role == dst_count) { first_role = i; } has_low_id |= id < FLECS_HI_COMPONENT_ID; table->bloom_filter = flecs_table_bloom_filter_add(table->bloom_filter, id); } int32_t start = first_pair == -1 || first_role < first_pair ? first_role : first_pair; ecs_vec_set_min_size_t(&world->allocator, records, ecs_table_record_t, start + 3 * (dst_count - start) + 4); ecs_table_record_t *relationship = NULL; ecs_entity_t last_relationship = 0; for (int32_t i = start; i < dst_count; i ++) { ecs_id_t id = dst_ids[i]; if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); if (r != last_relationship) { cr = ecs_vec_get_t(records, ecs_table_record_t, i)->hdr.cr; if (r == EcsChildOf) { childof_cr = cr; } relationship = flecs_table_add_record(world, records, cr->pair->parent, i, 0); last_relationship = r; } relationship->count ++; if (!ECS_IS_VALUE_PAIR(id)) { flecs_table_append_to_records(world, records, ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)), i, dst_count); } } else { ecs_entity_t first = ECS_HAS_ID_FLAG(id, PAIR) ? ECS_PAIR_FIRST(id) : id & ECS_COMPONENT_MASK; ecs_entity_t second = ECS_HAS_ID_FLAG(id, PAIR) ? ECS_PAIR_SECOND(id) : 0; if (first) { flecs_table_append_to_records(world, records, ecs_pair(EcsFlag, first), i, dst_count); } if (second) { flecs_table_append_to_records(world, records, ecs_pair(EcsFlag, second), i, dst_count); } } } if (id_count) { flecs_table_add_record(world, records, world->cr_wildcard, 0, id_count); } if (pair_count) { flecs_table_add_record(world, records, world->cr_wildcard_wildcard, first_pair, pair_count); } if (!(table->flags & (EcsTableHasChildOf|EcsTableHasParent))) { childof_cr = world->cr_childof_0; flecs_table_add_record(world, records, childof_cr, -1, 0); table->bloom_filter = flecs_table_bloom_filter_add( table->bloom_filter, ecs_pair(EcsChildOf, 0)); } /* Add records for the base ids of components that inherit from a base */ int32_t inherited_start = ecs_vec_count(records); if (all_flags & EcsIdHasBases) { table->flags |= EcsTableHasDerived; flecs_table_init_inherited(world, table, records, inherited_start); } /* 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->_->inherited_start = flecs_ito(int16_t, inherited_start); 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); 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); if (i >= inherited_start) { /* Only propagate table event flags for base ids, so that query * caches get notified of tables with derived components. */ table->flags |= cr->flags & (EcsIdHasOnTableCreate|EcsIdHasOnTableDelete); } else { /* Initialize event flags */ table->flags |= cr->flags & EcsIdEventMask; #ifdef FLECS_PREFAB if (ECS_ID_ON_INSTANTIATE(cr->flags) == EcsOverride) { table->flags |= EcsTableHasOverrides; } #endif } /* Initialize column index (will be overwritten by init_data) */ tr->column = -1; 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; if (has_low_id) { table->component_map = ecs_os_calloc_n(int16_t, FLECS_HI_COMPONENT_ID); } else { table->component_map = ECS_CONST_CAST(int16_t*, flecs_table_empty_component_map); } 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); for (i = inherited_start; i < dst_record_count; i ++) { /* Base ids share the storage of the derived id they were added for */ tr = &dst_tr[i]; tr->column = dst_tr[tr->index].column; flecs_table_cache_set_column(&tr->hdr.cr->cache, table, tr->column); if (!ecs_id_is_wildcard(tr->hdr.cr->id)) { table->bloom_filter = flecs_table_bloom_filter_add( table->bloom_filter, tr->hdr.cr->id); } } 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); } /* 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->column, count, row, entities, table->type.array[type_index], column->ti, event, callback, NULL); } 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]; const ecs_type_info_t *ti = column->ti; void *ptr = ECS_ELEM(column->data, ti->size, row); ecs_table_overrides_t *o = table->data.overrides; ecs_ref_t *r = o ? &o->refs[column_index] : NULL; if (r && r->entity) { int32_t record_index = table->column_map[table->type.count + column_index]; const ecs_table_record_t *tr = &table->_->records[record_index]; void *base_ptr = ecs_ref_get_id(world, r, table->type.array[record_index]); ecs_assert(base_ptr != NULL, ECS_INTERNAL_ERROR, NULL); for (int32_t i = 0; i < count; i ++) { flecs_type_info_copy_ctor(ECS_ELEM(ptr, ti->size, i), base_ptr, 1, ti); } if (ti->hooks.on_set) { flecs_invoke_hook(world, table, tr->hdr.cr, tr->column, count, row, &table->data.entities[row], ti->component, ti, EcsOnSet, ti->hooks.on_set, ptr); } } else { flecs_type_info_ctor(ptr, 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->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); } } } 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) { /* Destruct components */ ECS_TABLE_LOCK(world, table); for (c = 0; c < column_count; c++) { flecs_table_invoke_dtor(&table->data.columns[c], 0, count); } ECS_TABLE_UNLOCK(world, table); 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 " failed because the table is locked, to defer pass a stage to the operation instead of a world" /* 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_os_free(column->data); 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; table->flags |= EcsTableEmpty; table->flags &= ~EcsTableNotEmpty; 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); if (table->component_map != flecs_table_empty_component_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; world->has_dirty_state = true; 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; } static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { ecs_table__t *src = src_table->_; ecs_table__t *dst = dst_table->_; int32_t dst_type = dst->bs_offset, dst_column = 0, src_column = 0; for (int32_t i = src->bs_offset; src_column < src->bs_count; i ++) { ecs_id_t id = src_table->type.array[i]; if (!ECS_HAS_ID_FLAG(id, TOGGLE)) { continue; } ecs_bitset_t *src_bs = &src->bs_columns[src_column ++]; while (dst_type < dst_table->type.count && dst_table->type.array[dst_type] < id) { dst_column += ECS_HAS_ID_FLAG( dst_table->type.array[dst_type], TOGGLE) != 0; dst_type ++; } if (dst_type < dst_table->type.count && dst_table->type.array[dst_type] == id) { ecs_bitset_t *dst_bs = &dst->bs_columns[dst_column]; flecs_bitset_ensure(dst_bs, dst_index + count); for (int32_t j = 0; j < count; j ++) { flecs_bitset_set(dst_bs, dst_index + j, flecs_bitset_get(src_bs, src_index + j)); } } if (clear) { ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } } static void flecs_table_resize_column( ecs_column_t *column, int32_t count, int32_t old_size, int32_t new_size) { if (old_size == new_size) { return; } const ecs_type_info_t *ti = column->ti; void *old = column->data; if (!new_size) { column->data = NULL; } else if (count && (ti->hooks.ctor_move_dtor || new_size < old_size)) { void *ptr = ecs_os_malloc(ti->size * new_size); flecs_type_info_ctor_move_dtor(ptr, old, count, ti); column->data = ptr; } else { column->data = ecs_os_realloc(old, ti->size * new_size); return; } ecs_os_free(old); } static FLECS_ALWAYS_INLINE int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, int32_t to_add, const ecs_entity_t *ids, bool construct, bool on_add) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG("table append")); flecs_table_check_sanity(table); int32_t count = table->data.count; int32_t prev_size = table->data.size; ecs_vec_t entities = ecs_vec_from_entities(table); ecs_vec_grow_t(NULL, &entities, ecs_entity_t, to_add); ecs_entity_t *e = ECS_ELEM_T(entities.array, ecs_entity_t, count); ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); table->data.entities = entities.array; if (!count && to_add) { table->flags &= ~EcsTableEmpty; table->flags |= EcsTableNotEmpty; } bool complex = table->flags & (EcsTableIsComplex|EcsTableHasIsA); if (!complex) { if (prev_size != entities.size) { for (int32_t i = 0; i < table->column_count; i ++) { ecs_column_t *column = &table->data.columns[i]; column->data = ecs_os_realloc( column->data, column->ti->size * entities.size); } } table->data.count = entities.count; table->data.size = entities.size; flecs_table_mark_table_dirty(world, table, 0); return count; } flecs_table_update_overrides(world, table); table->data.count = entities.count; table->data.size = entities.size; ECS_TABLE_LOCK(world, table); for (int32_t i = 0; i < table->column_count; i ++) { ecs_column_t *column = &table->data.columns[i]; const ecs_type_info_t *ti = column->ti; flecs_table_resize_column(column, count, prev_size, entities.size); if (construct) { flecs_table_invoke_ctor(world, table, i, count, to_add); } if (on_add && to_add && ti->hooks.on_add) { flecs_table_invoke_hook(world, table, ti->hooks.on_add, EcsOnAdd, column, e, count, to_add); } } ECS_TABLE_UNLOCK(world, table); for (int32_t i = 0; i < table->_->bs_count; i ++) { flecs_bitset_addn(&table->_->bs_columns[i], to_add); } flecs_table_mark_table_dirty(world, table, 0); flecs_table_check_sanity(table); return count; } static void flecs_table_copy_elem( void *dst_ptr, const void *src_ptr, ecs_size_t size) { char *dst = dst_ptr; const char *src = src_ptr; if (size == 16) { ecs_os_memcpy(dst, src, 16); } else if (size == 8) { ecs_os_memcpy(dst, src, 8); } else if (size == 4) { ecs_os_memcpy(dst, src, 4); } else if (size > 32) { ecs_os_memcpy(dst, src, size); } else if (size > 16) { ecs_os_memcpy(dst, src, 16); ecs_os_memcpy(&dst[size - 16], &src[size - 16], 16); } else if (size > 8) { ecs_os_memcpy(dst, src, 8); ecs_os_memcpy(&dst[size - 8], &src[size - 8], 8); } else if (size > 4) { ecs_os_memcpy(dst, src, 4); ecs_os_memcpy(&dst[size - 4], &src[size - 4], 4); } else if (size > 2) { ecs_os_memcpy(dst, src, 2); ecs_os_memcpy(&dst[size - 2], &src[size - 2], 2); } else if (size == 2) { ecs_os_memcpy(dst, src, 2); } else { dst[0] = src[0]; } } void flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, bool construct, bool on_add) { flecs_table_grow_data(world, table, 1, &entity, construct, on_add); } /* 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 last = table->data.count - 1; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; ecs_size_t size = column->ti->size; flecs_table_copy_elem( ECS_ELEM(column->data, size, row), ECS_ELEM(column->data, size, last), size); } } /* 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 --; if (!count) { table->flags |= EcsTableEmpty; table->flags &= ~EcsTableNotEmpty; } flecs_table_check_sanity(table); return; } ECS_TABLE_LOCK(world, table); /* 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))) { /* Invoke on_remove hooks for all columns before moving the last * row into the deleted row. A hook may read other columns of the * deleted entity (the identifier hook reads EcsParent to find the * name index), which must still hold the deleted entity's data. */ if (destruct) { for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_iter_action_t on_remove = column->ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, row, 1); } } } 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); /* 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); } } ECS_TABLE_UNLOCK(world, table); /* 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 --; if (!count) { table->flags |= EcsTableEmpty; table->flags &= ~EcsTableNotEmpty; } 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); flecs_table_copy_elem(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); ECS_TABLE_LOCK(world, dst_table); ECS_TABLE_LOCK(world, src_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); } ECS_TABLE_UNLOCK(world, src_table); ECS_TABLE_UNLOCK(world, dst_table); 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 result = flecs_table_grow_data( world, table, to_add, ids, true, true); 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); for (int32_t i = 0; i < table->column_count; i ++) { flecs_table_resize_column(&columns[i], count, table->data.size, count); } 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 */ ECS_TABLE_LOCK(world, table); 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); } } ECS_TABLE_UNLOCK(world, table); flecs_table_check_sanity(table); } void flecs_table_merge( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table) { 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); int32_t src_count = src_table->data.count; int32_t dst_count = dst_table->data.count; if (!src_count) { return; } int32_t src_traversable_count = src_table->_->traversable_count; ecs_entity_t *src_entities = src_table->data.entities; for (int32_t i = 0; i < src_count; i ++) { ecs_record_t *r = flecs_entities_get(world, src_entities[i]); r->row = ECS_ROW_TO_RECORD(dst_count + i, ECS_RECORD_TO_ROW_FLAGS(r->row)); r->table = dst_table; } ecs_vec_t entities = ecs_vec_from_entities(dst_table); if (dst_count) { ecs_vec_grow_t(NULL, &entities, ecs_entity_t, src_count); ecs_os_memcpy_n(ECS_ELEM_T(entities.array, ecs_entity_t, dst_count), src_entities, ecs_entity_t, src_count); ecs_os_free(src_entities); } else { ecs_os_free(entities.array); entities.array = src_entities; entities.count = src_count; entities.size = src_table->data.size; } ECS_TABLE_LOCK(world, dst_table); ECS_TABLE_LOCK(world, src_table); int32_t dst_i = 0; for (int32_t src_i = 0; src_i < src_table->column_count; src_i ++) { ecs_column_t *src = &src_table->data.columns[src_i]; const ecs_type_info_t *ti = src->ti; if (dst_i < dst_table->column_count && flecs_column_id(dst_table, dst_i) == flecs_column_id(src_table, src_i)) { ecs_column_t *dst = &dst_table->data.columns[dst_i]; ecs_assert(dst->ti == ti, ECS_INTERNAL_ERROR, NULL); if (dst_count) { flecs_table_resize_column(dst, dst_count, dst_table->data.size, entities.size); flecs_type_info_ctor_move_dtor( ECS_ELEM(dst->data, ti->size, dst_count), src->data, src_count, ti); ecs_os_free(src->data); } else { ecs_os_free(dst->data); dst->data = src->data; } flecs_table_mark_table_dirty(world, dst_table, ++ dst_i); } else { flecs_table_invoke_dtor(src, 0, src_count); ecs_os_free(src->data); } src->data = NULL; } ECS_TABLE_UNLOCK(world, src_table); ECS_TABLE_UNLOCK(world, dst_table); ecs_assert(dst_i == dst_table->column_count, ECS_INTERNAL_ERROR, NULL); flecs_table_move_bitset_columns( dst_table, dst_count, src_table, 0, src_count, true); flecs_table_mark_table_dirty(world, dst_table, 0); dst_table->data.entities = entities.array; dst_table->data.count = entities.count; dst_table->data.size = entities.size; src_table->data.entities = NULL; src_table->data.count = 0; src_table->data.size = 0; flecs_table_traversable_add(dst_table, src_table->_->traversable_count); flecs_table_traversable_add(src_table, -src_table->_->traversable_count); dst_table->flags &= ~EcsTableEmpty; dst_table->flags |= EcsTableNotEmpty; src_table->flags |= EcsTableEmpty; src_table->flags &= ~EcsTableNotEmpty; flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); if (src_traversable_count) { flecs_emit_propagate_invalidate(world, dst_table, dst_count, src_count); } } 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; } ecs_flags32_t flecs_table_flags( const ecs_table_t* table) { return table->flags; } 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); #ifdef FLECS_CONSTRAINT_TRAITS 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)"); #endif 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; } } #define FLECS_TABLE_CACHE_INDEX_THRESHOLD (4) static void flecs_table_cache_move( ecs_table_cache_t *cache, ecs_table_cache_elem_t *records, int32_t dst, int32_t src) { if (dst == src) { return; } records[dst] = records[src]; if (ecs_map_is_init(&cache->index)) { ecs_map_val_t *idx = ecs_map_get(&cache->index, records[dst].table->id); ecs_assert(idx != NULL, ECS_INTERNAL_ERROR, NULL); *idx = flecs_ito(uint64_t, dst); } } static void flecs_table_cache_swap( ecs_table_cache_t *cache, ecs_table_cache_elem_t *records, int32_t a, int32_t b) { if (a == b) { return; } ecs_table_cache_elem_t tmp = records[a]; records[a] = records[b]; records[b] = tmp; if (ecs_map_is_init(&cache->index)) { ecs_map_val_t *a_idx = ecs_map_get(&cache->index, records[a].table->id); ecs_map_val_t *b_idx = ecs_map_get(&cache->index, records[b].table->id); ecs_assert(a_idx != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(b_idx != NULL, ECS_INTERNAL_ERROR, NULL); *a_idx = flecs_ito(uint64_t, a); *b_idx = flecs_ito(uint64_t, b); } } static int32_t flecs_table_cache_find( const ecs_table_cache_t *cache, const ecs_table_t *table) { if (ecs_map_is_init(&cache->index)) { ecs_map_val_t *r = ecs_map_get(&cache->index, table->id); if (!r) { return -1; } return flecs_uto(int32_t, *r); } int32_t i, count = ecs_vec_count(&cache->records); const ecs_table_cache_elem_t *records = ecs_vec_first_t( ECS_CONST_CAST(ecs_vec_t*, &cache->records), ecs_table_cache_elem_t); for (i = 0; i < count; i ++) { if (records[i].table == table) { return i; } } return -1; } void ecs_table_cache_init( ecs_world_t *world, ecs_table_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_zeromem(&cache->index); cache->index.allocator = &world->allocator; ecs_vec_init_t(&world->allocator, &cache->records, ecs_table_cache_elem_t, 0); cache->queryable_count = 0; } void ecs_table_cache_fini( ecs_table_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_fini_t(cache->index.allocator, &cache->records, ecs_table_cache_elem_t); 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); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!ecs_map_is_init(&cache->index) || ecs_map_count(&cache->index) == ecs_vec_count(&cache->records), ECS_INTERNAL_ERROR, NULL); result->cr = (ecs_component_record_t*)cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); int32_t index = ecs_vec_count(&cache->records); ecs_table_cache_elem_t *slot = ecs_vec_append_t( cache->index.allocator, &cache->records, ecs_table_cache_elem_t); slot->table = ECS_CONST_CAST(ecs_table_t*, table); slot->tr = (ecs_table_record_t*)result; slot->column = -1; slot->index = ((ecs_table_record_t*)result)->index; slot->count = ((ecs_table_record_t*)result)->count; if (ecs_map_is_init(&cache->index)) { ecs_map_insert(&cache->index, table->id, flecs_ito(uint64_t, index)); } else if ((index + 1) > FLECS_TABLE_CACHE_INDEX_THRESHOLD) { ecs_map_init(&cache->index, cache->index.allocator); ecs_table_cache_elem_t *records = ecs_vec_first_t( &cache->records, ecs_table_cache_elem_t); int32_t i; for (i = 0; i <= index; i ++) { ecs_map_insert(&cache->index, records[i].table->id, flecs_ito(uint64_t, i)); } } if (!(table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { ecs_table_cache_elem_t *records = ecs_vec_first_t( &cache->records, ecs_table_cache_elem_t); flecs_table_cache_swap(cache, records, cache->queryable_count, index); cache->queryable_count ++; } } void flecs_table_cache_set_column( ecs_table_cache_t *cache, const ecs_table_t *table, int16_t column) { int32_t index = flecs_table_cache_find(cache, table); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_elem_t *slot = ecs_vec_get_t( &cache->records, ecs_table_cache_elem_t, index); ecs_assert(slot != NULL, ECS_INTERNAL_ERROR, NULL); slot->column = column; } 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); int32_t index = flecs_table_cache_find(cache, table); if (index == -1) { return NULL; } ecs_table_cache_elem_t *slot = ecs_vec_get_t( ECS_CONST_CAST(ecs_vec_t*, &cache->records), ecs_table_cache_elem_t, index); return slot ? slot->tr : NULL; } const ecs_table_cache_elem_t* flecs_table_cache_get_elem( 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); int32_t index = flecs_table_cache_find(cache, table); if (index == -1) { return NULL; } return ecs_vec_get_t( ECS_CONST_CAST(ecs_vec_t*, &cache->records), ecs_table_cache_elem_t, index); } 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); ecs_assert(!ecs_map_is_init(&cache->index) || ecs_map_count(&cache->index) == ecs_vec_count(&cache->records), ECS_INTERNAL_ERROR, NULL); int32_t last = ecs_vec_count(&cache->records) - 1; ecs_table_cache_elem_t *records = ecs_vec_first_t( &cache->records, ecs_table_cache_elem_t); int32_t index; if (ecs_map_is_init(&cache->index)) { ecs_assert(ecs_map_get(&cache->index, table_id) != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_val_t v = ecs_map_remove(&cache->index, table_id); index = flecs_uto(int32_t, v); } else { for (index = 0; index <= last; index ++) { if (records[index].tr == (ecs_table_record_t*)elem) { break; } } ecs_assert(index <= last, ECS_INTERNAL_ERROR, NULL); } ecs_assert(records[index].tr == (ecs_table_record_t*)elem, ECS_INTERNAL_ERROR, NULL); if (index < cache->queryable_count) { int32_t last_queryable = cache->queryable_count - 1; flecs_table_cache_move(cache, records, index, last_queryable); flecs_table_cache_move(cache, records, last_queryable, last); cache->queryable_count --; } else { flecs_table_cache_move(cache, records, index, last); } ecs_vec_remove_last(&cache->records); return elem; } bool flecs_table_cache_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out, ecs_flags32_t empty_flags) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->elems = ecs_vec_first_t( ECS_CONST_CAST(ecs_vec_t*, &cache->records), ecs_table_cache_elem_t); out->remaining = ecs_vec_count(&cache->records); out->cur = NULL; out->flags = empty_flags; return out->remaining != 0; } bool flecs_table_cache_queryable_iter( const ecs_table_cache_t *cache, ecs_table_cache_iter_t *out, ecs_flags32_t empty_flags) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->elems = ecs_vec_first_t( ECS_CONST_CAST(ecs_vec_t*, &cache->records), ecs_table_cache_elem_t); out->remaining = cache->queryable_count; out->cur = NULL; out->flags = empty_flags; return out->remaining != 0; } const ecs_table_cache_elem_t* flecs_table_cache_next( ecs_table_cache_iter_t *it) { ecs_flags32_t flags = it->flags; while (it->remaining > 0) { const ecs_table_cache_elem_t *next = it->elems; it->elems ++; it->remaining --; if (next->table->flags & flags) { it->cur = next; return next; } } it->cur = NULL; return NULL; } 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_new_with( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t with) { int32_t at = flecs_type_find_insert(src, 0, with); if (at == -1) { return -1; } int32_t count = src->count; ecs_id_t *array; if (dst == src) { array = flecs_wrealloc_n(world, ecs_id_t, count + 1, count, src->array); ecs_os_memmove_n(array + at + 1, array + at, ecs_id_t, count - at); } else { array = flecs_walloc_n(world, ecs_id_t, count + 1); if (at) { ecs_os_memcpy_n(array, src->array, ecs_id_t, at); } if (count != at) { ecs_os_memcpy_n(array + at + 1, src->array + at, ecs_id_t, count - at); } } array[at] = with; *dst = (ecs_type_t){ .array = array, .count = count + 1 }; return 0; } static int flecs_type_new_without( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t without) { int32_t at = flecs_type_find(src, without); if (at == -1) { return -1; } ecs_assert(src->array != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = src->count, removed = 1; bool wildcard = ecs_id_is_wildcard(without); if (wildcard) { for (int32_t i = at + 1; i < count; i ++) { removed += ecs_id_match(src->array[i], without); } } int32_t dst_count = count - removed; ecs_assert(dst_count >= at, ECS_INTERNAL_ERROR, NULL); ecs_id_t *array = src->array; if (dst != src) { array = dst_count ? flecs_walloc_n(world, ecs_id_t, dst_count) : NULL; if (at && array) { ecs_os_memcpy_n(array, src->array, ecs_id_t, at); } } if (dst_count > at) { ecs_assert(array != NULL, ECS_INTERNAL_ERROR, NULL); if (wildcard) { int32_t w = at; for (int32_t i = at + 1; i < count; i ++) { ecs_id_t id = src->array[i]; if (!ecs_id_match(id, without)) { array[w ++] = id; } } } else { ecs_os_memmove_n(array + at, src->array + at + 1, ecs_id_t, dst_count - at); } } if (dst == src) { if (dst_count) { array = flecs_wrealloc_n(world, ecs_id_t, dst_count, count, array); } else { flecs_wfree_n(world, ecs_id_t, count, array); array = NULL; } } *dst = (ecs_type_t){ .array = array, .count = dst_count }; 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); } } void flecs_type_add( ecs_world_t *world, ecs_type_t *type, ecs_id_t add) { flecs_type_new_with(world, type, type, add); } static void flecs_type_remove( ecs_world_t *world, ecs_type_t *type, ecs_id_t remove) { flecs_type_new_without(world, type, type, remove); } void flecs_type_remove_ignoring_generation( ecs_world_t *world, ecs_type_t *type, ecs_id_t remove) { ecs_assert(!ecs_id_is_wildcard(remove), ECS_INVALID_PARAMETER, NULL); ecs_assert(!ECS_IS_PAIR(remove), ECS_INVALID_PARAMETER, NULL); for (int32_t i = 0; i < type->count; i ++) { if ((uint32_t)type->array[i] == (uint32_t)remove) { flecs_type_remove(world, type, type->array[i]); return; } } } /* 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); } 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 ecs_table_diff_t* flecs_table_diff_new( ecs_world_t *world, int32_t added, int32_t removed) { ecs_table_diff_t *diff = flecs_wcalloc(world, ECS_SIZEOF(ecs_table_diff_t) + (added + removed) * ECS_SIZEOF(ecs_id_t)); ecs_id_t *ids = (ecs_id_t*)(diff + 1); diff->added = (ecs_type_t){ .array = added ? ids : NULL, .count = added }; diff->removed = (ecs_type_t){ .array = removed ? ids + added : NULL, .count = removed }; return diff; } static void flecs_table_diff_free( ecs_world_t *world, ecs_table_diff_t *diff) { flecs_wfree(world, ECS_SIZEOF(ecs_table_diff_t) + (diff->added.count + diff->removed.count) * ECS_SIZEOF(ecs_id_t), 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 = edges->hi ? ecs_map_get_ptr(edges->hi, id) : NULL; if (!edge) { 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; if (edge->next) { edge->next->prev = edge->prev; } if (edge->prev) { *edge->prev = edge->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_init_table( ecs_world_t *world, ecs_table_t *table, ecs_table_t *prev) { table->flags = EcsTableEmpty; table->dirty_state = NULL; table->_->lock = 0; table->_->generation = 0; ecs_os_zeromem(&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 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 ecs_flags32_t flecs_table_diff_ids( ecs_world_t *world, const ecs_type_t *src, const ecs_type_t *dst, ecs_type_t *result) { ecs_flags32_t flags = 0; int32_t count = 0, j = 0; for (int32_t i = 0; i < dst->count; i ++) { ecs_id_t id = dst->array[i]; while (j < src->count && src->array[j] < id) { j ++; } if (j < src->count && src->array[j] == id) { continue; } if (result->array) { result->array[count] = id; } else { flags |= flecs_id_flags_get(world, id); } count ++; } result->count = count; return flags; } 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) { if (!flecs_id_is_alive(world, id)) { return; } ecs_component_record_t *cr = flecs_components_ensure(world, id); if (cr->flags & EcsIdDontFragment) { ecs_table_diff_t *diff = flecs_table_diff_new(world, !is_remove, is_remove); if (is_remove) { diff->removed.array[0] = id; diff->removed_flags = EcsTableHasDontFragment|EcsTableHasSparse; } else { diff->added.array[0] = id; diff->added_flags = EcsTableHasDontFragment|EcsTableHasSparse; } edge->diff = diff; ecs_vec_append_t(&world->allocator, &cr->dont_fragment_tables, uint64_t)[0] = node->id; return; } ecs_type_t added = {0}, removed = {0}; ecs_flags32_t added_flags = flecs_table_diff_ids( world, &node->type, &next->type, &added) & EcsTableAddEdgeFlags; ecs_flags32_t removed_flags = flecs_table_diff_ids( world, &next->type, &node->type, &removed) & EcsTableRemoveEdgeFlags; bool childof = ECS_HAS_RELATION(id, EcsChildOf); if (!ECS_HAS_RELATION(id, EcsIsA) && !childof && !ecs_id_is_wildcard(id) && added.count + removed.count <= 1 && !(added_flags | removed_flags)) { return; } ecs_table_diff_t *diff = flecs_table_diff_new(world, added.count, removed.count); flecs_table_diff_ids(world, &node->type, &next->type, &diff->added); flecs_table_diff_ids(world, &next->type, &node->type, &diff->removed); diff->added_flags = added_flags; diff->removed_flags = removed_flags; if (childof) { if (added.count) { diff->added_flags |= EcsTableEdgeReparent; } else if (removed.count) { diff->removed_flags |= EcsTableEdgeReparent; } } edge->diff = diff; } 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 { #ifdef FLECS_PREFAB ecs_table_record_t *tr = &base_table->_->records[i]; if (ECS_ID_ON_INSTANTIATE(tr->hdr.cr->flags) == EcsOverride) { to_add = id; } #endif } 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 = ecs_get_alive(world, 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); #ifdef FLECS_PREFAB } else if (r == EcsChildOf) { o = ecs_get_alive(world, o); if (ecs_has_id(world, o, EcsPrefab)) { flecs_type_add(world, &dst_type, EcsPrefab); } #endif } 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 ecs_table_t* flecs_table_create_edge( ecs_world_t *world, ecs_table_t *table, ecs_graph_edges_t *edges, ecs_graph_edge_t *edge, ecs_id_t id, bool remove) { ecs_table_t *to = remove ? flecs_find_table_without(world, table, id) : flecs_find_table_with(world, table, id); edge->from = edges; edge->to = to; edge->id = id; if (id < FLECS_HI_COMPONENT_ID) { flecs_table_ensure_hi_edge(world, edges, id); } if (table != to || (table->flags & EcsTableHasDontFragment)) { ecs_graph_edge_t **incoming = &to->node.incoming[remove]; edge->next = *incoming; edge->prev = incoming; if (edge->next) { edge->next->prev = &edge->next; } *incoming = edge; flecs_compute_table_diff(world, table, to, edge, id, remove); } 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); 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_table_create_edge(world, node, &node->node.remove, edge, id, true); 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); 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_table_create_edge(world, node, &node->node.add, edge, id, false); 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) { if ((flags == EcsTableHasOnAdd) || (flags == EcsTableHasUpNotify)) { for (ecs_graph_edge_t *edge = table->node.incoming[0]; edge; edge = edge->next) { if ((id == EcsAny) || ecs_id_match(edge->id, id)) { if (!edge->diff) { edge->diff = flecs_table_diff_new(world, 1, 0); edge->diff->added.array[0] = edge->id; } edge->diff->added_flags |= flags; } } } /* Add flags to outgoing matching remove edges */ if ((flags == EcsTableHasOnRemove) || (flags == EcsTableHasUpNotify)) { 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_table_diff_new(world, 0, 1); edge->diff->removed.array[0] = edge->id; } edge->diff->removed_flags |= flags; } } } } static void flecs_table_clear_edge_set( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_graph_edge_t **incoming) { ecs_map_iter_t it = ecs_map_iter(edges->hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } while (*incoming) { ecs_graph_edge_t *edge = *incoming; ecs_map_remove(edge->from->hi, edge->id); flecs_table_disconnect_edge(world, edge->id, edge); } flecs_bfree(&world->allocators.graph_edge_lo, edges->lo); ecs_map_fini(edges->hi); flecs_free_t(&world->allocator, ecs_map_t, edges->hi); ecs_os_zeromem(edges); } void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table) { flecs_table_clear_edge_set(world, &table->node.add, &table->node.incoming[0]); flecs_table_clear_edge_set(world, &table->node.remove, &table->node.incoming[1]); } void flecs_table_clear_edges_for_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t component) { ecs_graph_edges_t *sets[2] = { &table->node.add, &table->node.remove }; for (int32_t i = 0; i < 2; i ++) { ecs_graph_edges_t *edges = sets[i]; ecs_graph_edge_t *edge = edges->hi ? ecs_map_get_ptr(edges->hi, component) : NULL; if (edge) { ecs_map_remove(edges->hi, component); flecs_table_disconnect_edge(world, component, edge); } } } 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); } #ifdef FLECS_CONSTRAINT_TRAITS 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_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->world, tgt, ecs_pair(rel, subj))) { ecs_add_pair(it->stage, tgt, rel, subj); } } else { if (ecs_has_id(it->world, tgt, ecs_pair(rel, subj))) { ecs_remove_pair(it->stage, tgt, rel, subj); } } } } } static void flecs_register_symmetric(ecs_iter_t *it) { ecs_world_t *world = it->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->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->stage, 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->stage, component)), flecs_errstr_1(ecs_get_path(it->stage, it->entities[i]))); } error: continue; } } #endif static void flecs_register_singleton(ecs_iter_t *it) { flecs_register_flag_for_trait( it, EcsSingleton, EcsIdSingleton, 0, 0); #ifdef FLECS_DEBUG ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t component = it->entities[i]; /* Create observer that enforces that singleton is only added to self */ 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 } 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; } } #ifdef FLECS_DEBUG int flecs_check_constraint_traits( ecs_world_t *world, ecs_component_record_t *cr, ecs_entity_t rel, ecs_entity_t tgt) { if (ECS_IS_PAIR(cr->id)) { if (tgt) { /* 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 && 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))); } } } 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))); } } } return 0; error: return -1; } #endif void flecs_bootstrap_constraint_traits( ecs_world_t *world) { flecs_bootstrap_make_alive(world, EcsFinal); flecs_bootstrap_make_alive(world, EcsTrait); flecs_bootstrap_make_alive(world, EcsRelationship); flecs_bootstrap_make_alive(world, EcsTarget); /* Traits */ flecs_bootstrap_trait(world, EcsSymmetric); flecs_bootstrap_trait(world, EcsSingleton); flecs_bootstrap_trait(world, EcsFinal); flecs_bootstrap_trait(world, EcsAcyclic); flecs_bootstrap_trait(world, EcsOneOf); flecs_bootstrap_trait(world, EcsTrait); flecs_bootstrap_trait(world, EcsRelationship); flecs_bootstrap_trait(world, EcsTarget); /* Observers */ ecs_observer(world, { .query.terms = {{ .id = EcsFinal }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_final, .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 }); /* Properties */ ecs_add_id(world, EcsChildOf, EcsTrait); ecs_add_id(world, EcsIsA, EcsTrait); ecs_add_id(world, EcsChildOf, EcsAcyclic); /* Relationships */ ecs_add_id(world, EcsChildOf, EcsRelationship); ecs_add_id(world, EcsIsA, 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, ecs_id(EcsIdentifier), EcsRelationship); ecs_add_id(world, EcsParentDepth, EcsRelationship); ecs_add_id(world, ecs_id(EcsPoly), EcsRelationship); /* Targets */ ecs_add_id(world, EcsDontInherit, EcsTarget); /* Traversable relationships are always acyclic */ ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); /* Modules are singletons */ ecs_add_pair(world, EcsModule, EcsWith, EcsSingleton); /* Acyclic relationships */ ecs_add_id(world, EcsWith, EcsAcyclic); /* Exclusive properties */ ecs_add_id(world, EcsOneOf, EcsExclusive); } #endif #ifdef FLECS_HTTP #ifndef FLECS_HTTP_IMPL_H #define FLECS_HTTP_IMPL_H #ifdef ECS_TARGET_MSVC #pragma comment(lib, "Ws2_32.lib") #endif #if defined(ECS_TARGET_WINDOWS) #include typedef SOCKET ecs_http_socket_t; #else #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif typedef int ecs_http_socket_t; #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL (0) #endif #endif /* Max length of request method */ #define ECS_HTTP_METHOD_LEN_MAX (8) /* Timeout (s) before connection purge */ #define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) /* Number of dequeues before purging */ #define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) /* Number of retries receiving request */ #define ECS_HTTP_REQUEST_RECV_RETRY (10) /* Minimum interval between dequeueing requests (ms) */ #define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) /* Minimum interval between printing statistics (ms) */ #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Receive buffer size */ #define ECS_HTTP_SEND_RECV_BUFFER_SIZE (64 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) /* Send request queue */ typedef struct ecs_http_send_request_t { ecs_http_socket_t sock; char *headers; int32_t header_length; char *content; int32_t content_length; } ecs_http_send_request_t; typedef struct ecs_http_send_queue_t { ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; int32_t head; int32_t tail; ecs_os_thread_t thread; int32_t wait_ms; } ecs_http_send_queue_t; typedef struct ecs_http_request_key_t { const char *array; ecs_size_t count; } ecs_http_request_key_t; typedef struct ecs_http_request_entry_t { char *content; int32_t content_length; int code; double time; } ecs_http_request_entry_t; /* HTTP server struct */ struct ecs_http_server_t { bool should_run; bool running; ecs_http_socket_t sock; ecs_os_mutex_t lock; ecs_os_thread_t thread; ecs_http_reply_action_t callback; void *ctx; double cache_timeout; double cache_purge_timeout; ecs_sparse_t connections; /* sparse */ bool initialized; uint16_t port; const char *ipaddr; double dequeue_timeout; /* used to not lock request queue too often */ double stats_timeout; /* used for periodic reporting of statistics */ double request_time; /* time spent on requests in last stats interval */ double request_time_total; /* total time spent on requests */ int32_t requests_processed; /* requests processed in last stats interval */ int32_t requests_processed_total; /* total requests processed */ int32_t dequeue_count; /* number of dequeues in last stats interval */ ecs_http_send_queue_t send_queue; ecs_hashmap_t request_cache; }; typedef struct { ecs_strbuf_t buf; ecs_http_method_t method; int32_t body_offset; int32_t line_offset; int32_t scan; int32_t content_length; bool invalid; } ecs_http_fragment_t; typedef struct { ecs_http_request_t pub; char *res; int32_t req_len; } ecs_http_request_impl_t; typedef struct { ecs_http_connection_t pub; ecs_http_socket_t sock; ecs_http_request_impl_t request; /* Connection is purged after both the timeout expires and the connection has * exceeded the retry count. This ensures that a connection does not * immediately timeout when a frame takes longer than usual */ double dequeue_timeout; int32_t dequeue_retries; } ecs_http_connection_impl_t; #endif /* Global statistics */ int64_t ecs_http_request_received_count = 0; int64_t ecs_http_request_invalid_count = 0; int64_t ecs_http_request_handled_ok_count = 0; int64_t ecs_http_request_handled_error_count = 0; int64_t ecs_http_request_not_handled_count = 0; int64_t ecs_http_request_preflight_count = 0; int64_t ecs_http_send_ok_count = 0; int64_t ecs_http_send_error_count = 0; int64_t ecs_http_busy_count = 0; static ecs_size_t http_send( ecs_http_socket_t sock, const void *buf, ecs_size_t size, int flags) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); #ifdef ECS_TARGET_POSIX ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags | MSG_NOSIGNAL); return flecs_itoi32(send_bytes); #else int send_bytes = send(sock, buf, size, flags); return flecs_itoi32(send_bytes); #endif } static ecs_size_t http_recv( ecs_http_socket_t sock, void *buf, ecs_size_t size, int flags) { ecs_size_t ret; #ifdef ECS_TARGET_POSIX ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); ret = flecs_itoi32(recv_bytes); #else int recv_bytes = recv(sock, buf, size, flags); ret = flecs_itoi32(recv_bytes); #endif if (ret == -1) { ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); } else if (ret == 0) { ecs_dbg("recv: received 0 bytes (sock = %d)", sock); } return ret; } static void http_sock_set_timeout( ecs_http_socket_t sock, int32_t timeout_ms) { int r; #ifdef ECS_TARGET_POSIX struct timeval tv; tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); #else DWORD t = (DWORD)timeout_ms; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); #endif if (r) { ecs_warn("http: failed to set socket timeout: %s", ecs_os_strerror(errno)); } } static void http_sock_keep_alive( ecs_http_socket_t sock) { int v = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { ecs_warn("http: failed to set socket KEEPALIVE: %s", ecs_os_strerror(errno)); } } static void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { (void)sock; (void)enable; #ifdef ECS_TARGET_POSIX int flags; flags = fcntl(sock,F_GETFL,0); if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } if (enable) { flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); } else { flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); } if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } #endif } static int http_getnameinfo( const struct sockaddr* addr, ecs_size_t addr_len, char *host, ecs_size_t host_len, char *port, ecs_size_t port_len, int flags) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return getnameinfo(addr, addr_len, host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #else return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #endif } static int http_bind( ecs_http_socket_t sock, const struct sockaddr* addr, ecs_size_t addr_len) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return bind(sock, addr, addr_len); #else return bind(sock, addr, flecs_ito(uint32_t, addr_len)); #endif } static bool http_socket_is_valid( ecs_http_socket_t sock) { #if defined(ECS_TARGET_WINDOWS) return sock != INVALID_SOCKET; #else return sock >= 0; #endif } #if defined(ECS_TARGET_WINDOWS) #define HTTP_SOCKET_INVALID INVALID_SOCKET #else #define HTTP_SOCKET_INVALID (-1) #endif static void http_close( ecs_http_socket_t *sock) { ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) closesocket(*sock); #else ecs_dbg_2("http: closing socket %u", *sock); shutdown(*sock, SHUT_RDWR); close(*sock); #endif *sock = HTTP_SOCKET_INVALID; } static ecs_http_socket_t http_accept( ecs_http_socket_t sock, struct sockaddr* addr, ecs_size_t *addr_len) { socklen_t len = (socklen_t)addr_len[0]; ecs_http_socket_t result = accept(sock, addr, &len); addr_len[0] = (ecs_size_t)len; return result; } static void http_reply_fini(ecs_http_reply_t* reply) { ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_free(reply->body.content); } static void http_connection_free(ecs_http_connection_impl_t *conn) { ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); uint64_t conn_id = conn->pub.id; if (http_socket_is_valid(conn->sock)) { http_close(&conn->sock); } ecs_os_free(conn->request.res); flecs_sparse_remove_t(&conn->pub.server->connections, ecs_http_connection_impl_t, conn_id); } // https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int static char http_hex_2_int(char a, char b){ a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); if (a < 0) { return 0; } return (char)((a << 4) + b); } static void http_decode_url_str( char *str) { char ch, *ptr, *dst = str; for (ptr = str; (ch = *ptr); ptr++) { if (ch == '%' && ptr[1]) { dst[0] = http_hex_2_int(ptr[1], ptr[2]); dst ++; if (!ptr[2]) { break; } ptr += 2; } else if (ch == '+') { dst[0] = ' '; dst ++; } else { dst[0] = ptr[0]; dst ++; } } dst[0] = '\0'; } static ecs_http_method_t http_parse_method( const char *method, int32_t length) { static const struct { const char *name; ecs_http_method_t kind; } methods[] = { {"GET", EcsHttpGet}, {"POST", EcsHttpPost}, {"PUT", EcsHttpPut}, {"DELETE", EcsHttpDelete}, {"OPTIONS", EcsHttpOptions} }; for (int32_t i = 0; i < 5; i ++) { if (ecs_os_strlen(methods[i].name) == length && !ecs_os_memcmp(method, methods[i].name, length)) { return methods[i].kind; } } return EcsHttpMethodUnsupported; } static uint64_t http_request_key_hash(const void *ptr) { const ecs_http_request_key_t *key = ptr; const char *array = key->array; int32_t count = key->count; return flecs_hash(array, count * ECS_SIZEOF(char)); } static int http_request_key_compare(const void *ptr_1, const void *ptr_2) { const ecs_http_request_key_t *type_1 = ptr_1; const ecs_http_request_key_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } return ecs_os_memcmp(type_1->array, type_2->array, count_1); } static ecs_http_request_entry_t* http_find_request_entry( ecs_http_server_t *srv, const char *array, int32_t count) { ecs_http_request_key_t key; key.array = array; key.count = count; ecs_time_t t = {0, 0}; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (entry) { double tf = ecs_time_measure(&t); if ((tf - entry->time) < srv->cache_timeout) { return entry; } } return NULL; } static void http_insert_request_entry( ecs_http_server_t *srv, ecs_http_request_impl_t *req, ecs_http_reply_t *reply) { int32_t content_length = ecs_strbuf_written(&reply->body); if (!content_length) { return; } ecs_http_request_key_t key; key.array = req->res; key.count = req->req_len; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (!entry) { flecs_hashmap_result_t elem = flecs_hashmap_ensure( &srv->request_cache, &key, ecs_http_request_entry_t); ecs_http_request_key_t *elem_key = elem.key; elem_key->array = ecs_os_memdup_n(key.array, char, key.count); entry = elem.value; } else { ecs_os_free(entry->content); } ecs_time_t t = {0, 0}; entry->time = ecs_time_measure(&t); entry->content_length = ecs_strbuf_written(&reply->body); entry->content = ecs_strbuf_get(&reply->body); entry->code = reply->code; ecs_strbuf_appendstrn(&reply->body, entry->content, entry->content_length); } static char* http_decode_request( ecs_http_request_impl_t *req, ecs_http_fragment_t *frag) { ecs_os_zeromem(req); char *res = ecs_strbuf_get(&frag->buf); if (!res || frag->invalid) { ecs_os_free(res); return NULL; } char *target = strchr(res, ' ') + 1; char *end = strchr(target, ' '); char *line = strstr(end, "\r\n") + 2; *end = '\0'; req->pub.method = frag->method; req->pub.path = target + 1; req->res = res; req->req_len = flecs_ito(int32_t, end - res + 1); char *key = target; for (char *p = target; p != end; p ++) { char c = *p; if (c != '?' && c != '&' && c != '=') { continue; } int32_t count = req->pub.param_count; if (count < ECS_HTTP_QUERY_PARAM_COUNT_MAX) { *p = '\0'; if (c == '=') { req->pub.params[count].key = key; req->pub.params[count].value = p + 1; req->pub.param_count ++; key = target; } else { key = p + 1; } } else if (c == '&') { *p = '\0'; } } http_decode_url_str(req->pub.path); for (int32_t i = 0; i < req->pub.param_count; i ++) { http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); } while (line < res + frag->body_offset - 2) { char *next = strstr(line, "\r\n"); *next = '\0'; char *value = strchr(line, ':'); if (value && req->pub.header_count < ECS_HTTP_HEADER_COUNT_MAX) { *value ++ = '\0'; if (*value == ' ') { value ++; } if (*value) { int32_t h = req->pub.header_count ++; req->pub.headers[h].key = line; req->pub.headers[h].value = value; } } line = next + 2; } if (frag->content_length) { req->pub.body = res + frag->body_offset; req->pub.body[frag->content_length] = '\0'; } return res; } static ecs_http_request_entry_t* http_enqueue_request( ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_fragment_t *frag) { ecs_http_server_t *srv = conn->pub.server; ecs_os_mutex_lock(srv->lock); bool is_alive = conn->pub.id == conn_id; if (!is_alive || frag->invalid) { /* Don't enqueue invalid requests or requests for purged connections */ ecs_strbuf_reset(&frag->buf); } else { ecs_http_request_impl_t req; char *res = http_decode_request(&req, frag); if (res) { req.pub.conn = (ecs_http_connection_t*)conn; /* Check cache for GET requests */ if (req.pub.method == EcsHttpGet) { ecs_http_request_entry_t *entry = http_find_request_entry(srv, res, req.req_len); if (entry) { /* If an entry is found, don't enqueue a request. Instead * return the cached response immediately. */ ecs_os_free(res); return entry; } } req.pub.id = conn->pub.id; conn->request = req; ecs_os_linc(&ecs_http_request_received_count); } } ecs_os_mutex_unlock(srv->lock); return NULL; } static bool http_parse_request( ecs_http_fragment_t *frag, const char *req_frag, ecs_size_t req_frag_len) { if (req_frag_len < 0 || req_frag_len > ECS_HTTP_REQUEST_LEN_MAX - ecs_strbuf_written(&frag->buf)) { frag->invalid = true; return false; } ecs_strbuf_appendstrn(&frag->buf, req_frag, req_frag_len); char *buf = frag->buf.content; int32_t length = ecs_strbuf_written(&frag->buf); while (!frag->body_offset && frag->scan < length) { char *nl = memchr(buf + frag->scan, '\n', flecs_ito(size_t, length - frag->scan)); if (!nl) { frag->scan = length; break; } int32_t end = flecs_ito(int32_t, nl - buf); frag->scan = end + 1; if (end == frag->line_offset || nl[-1] != '\r') { frag->invalid = true; return false; } char *line = buf + frag->line_offset; int32_t line_length = end - frag->line_offset - 1; if (memchr(line, 0, flecs_ito(size_t, line_length))) { frag->invalid = true; return false; } if (!frag->line_offset) { char *space = memchr(line, ' ', flecs_ito(size_t, line_length)); if (!space || !memchr(space + 1, ' ', flecs_ito(size_t, nl - space - 2))) { frag->invalid = true; return false; } frag->method = http_parse_method(line, flecs_ito(int32_t, space - line)); frag->invalid = frag->method == EcsHttpMethodUnsupported; } else if (!line_length) { frag->body_offset = end + 1; } else if (line_length >= 15 && !ecs_os_memcmp(line, "Content-Length:", 15)) { char saved = nl[-1]; nl[-1] = '\0'; int64_t content_length = strtoll(line + 15, NULL, 10); nl[-1] = saved; if (content_length < 0 || content_length > ECS_HTTP_REQUEST_LEN_MAX) { frag->invalid = true; return false; } frag->content_length = flecs_ito(int32_t, content_length); } frag->line_offset = end + 1; } return frag->body_offset && length - frag->body_offset >= frag->content_length; } static ecs_http_send_request_t* http_send_queue_post( ecs_http_server_t *srv) { /* This function should only be called while the server is locked. Before * the lock is released, the returned element should be populated. */ ecs_http_send_queue_t *sq = &srv->send_queue; int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; if (next == sq->tail) { return NULL; } /* Don't enqueue new requests if server is shutting down */ if (!srv->should_run) { return NULL; } /* Return element at end of the queue */ ecs_http_send_request_t *result = &sq->requests[sq->head]; sq->head = next; return result; } static ecs_http_send_request_t* http_send_queue_get( ecs_http_server_t *srv) { ecs_os_mutex_lock(srv->lock); ecs_http_send_queue_t *sq = &srv->send_queue; if (sq->tail == sq->head) { return NULL; } int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; ecs_http_send_request_t *result = &sq->requests[sq->tail]; sq->tail = next; return result; } static void* http_server_send_queue(void* arg) { ecs_http_server_t *srv = arg; int32_t wait_ms = srv->send_queue.wait_ms; /* Run for as long as the server is running or there are messages. When the * server is stopping, no new messages will be enqueued */ while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { ecs_http_send_request_t* r = http_send_queue_get(srv); if (!r) { ecs_os_mutex_unlock(srv->lock); /* If the queue is empty, wait so we don't run too fast */ if (srv->should_run) { ecs_os_sleep(0, wait_ms * 1000 * 1000); } } else { ecs_http_socket_t sock = r->sock; char *headers = r->headers; int32_t headers_length = r->header_length; char *content = r->content; int32_t content_length = r->content_length; ecs_os_mutex_unlock(srv->lock); if (http_socket_is_valid(sock)) { bool error = false; http_sock_nonblock(sock, false); /* Write headers */ ecs_size_t written = http_send(sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to write HTTP response headers: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } else if (content_length >= 0) { /* Write content */ written = http_send(sock, content, content_length, 0); if (written != content_length) { ecs_err("http: failed to write HTTP response body: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } } if (!error) { ecs_os_linc(&ecs_http_send_ok_count); } http_close(&sock); } else { ecs_err("http: invalid socket\n"); } ecs_os_free(content); ecs_os_free(headers); } } return NULL; } static void http_append_send_headers( ecs_strbuf_t *hdrs, int code, const char* status, const char* content_type, ecs_strbuf_t *extra_headers, ecs_size_t content_len, bool preflight) { ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); ecs_strbuf_appendint(hdrs, code); ecs_strbuf_appendch(hdrs, ' '); ecs_strbuf_appendstr(hdrs, status); ecs_strbuf_appendlit(hdrs, "\r\n"); if (content_type) { ecs_strbuf_appendlit(hdrs, "Content-Type: "); ecs_strbuf_appendstr(hdrs, content_type); ecs_strbuf_appendlit(hdrs, "\r\n"); } if (content_len >= 0) { ecs_strbuf_appendlit(hdrs, "Content-Length: "); ecs_strbuf_append(hdrs, "%d", content_len); ecs_strbuf_appendlit(hdrs, "\r\n"); } ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); if (preflight) { ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, DELETE, OPTIONS\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); } ecs_strbuf_mergebuff(hdrs, extra_headers); ecs_strbuf_appendlit(hdrs, "\r\n"); } static void http_send_reply( ecs_http_connection_impl_t* conn, ecs_http_reply_t* reply, bool preflight) { ecs_strbuf_t hdrs = ECS_STRBUF_INIT; int32_t content_length = reply->body.length; char *content = ecs_strbuf_get(&reply->body); /* Use asynchronous send queue for outgoing data so send operations won't * hold up main thread */ ecs_http_send_request_t *req = NULL; if (!preflight) { req = http_send_queue_post(conn->pub.server); if (!req) { reply->code = 503; /* queue full, server is busy */ ecs_os_linc(&ecs_http_busy_count); } } http_append_send_headers(&hdrs, reply->code, reply->status, reply->content_type, &reply->headers, content_length, preflight); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); char *headers = ecs_strbuf_get(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* Enqueue send request for response */ req->sock = conn->sock; req->headers = headers; req->header_length = headers_length; req->content = content; req->content_length = content_length; /* Take ownership of values */ reply->body.content = NULL; conn->sock = HTTP_SOCKET_INVALID; } static void http_recv_connection( ecs_http_server_t *srv, ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_socket_t sock) { ecs_size_t bytes_read; char *recv_buf = ecs_os_malloc(ECS_HTTP_SEND_RECV_BUFFER_SIZE); ecs_http_fragment_t frag = {0}; int32_t retries = 0; ecs_os_sleep(0, 10 * 1000 * 1000); do { if ((bytes_read = http_recv( sock, recv_buf, ECS_HTTP_SEND_RECV_BUFFER_SIZE, 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { /* Connection has been purged by main thread */ goto done; } if (http_parse_request(&frag, recv_buf, bytes_read)) { if (frag.method == EcsHttpOptions) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = 200; reply.content_type = NULL; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; http_send_reply(conn, &reply, true); ecs_os_linc(&ecs_http_request_preflight_count); } else { ecs_http_request_entry_t *entry = http_enqueue_request(conn, conn_id, &frag); if (entry) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = entry->code; reply.content_type = "application/json"; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; ecs_strbuf_appendstrn(&reply.body, entry->content, entry->content_length); http_send_reply(conn, &reply, false); http_connection_free(conn); /* Lock was transferred from enqueue_request */ ecs_os_mutex_unlock(srv->lock); } } } else { ecs_os_linc(&ecs_http_request_invalid_count); } } ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); if (bytes_read == ECS_HTTP_SEND_RECV_BUFFER_SIZE) { ecs_warn("request exceeded receive buffer size (%d)", ECS_HTTP_SEND_RECV_BUFFER_SIZE); } if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { ecs_os_mutex_lock(srv->lock); bool still_owns_sock = conn->pub.id == conn_id; if (still_owns_sock) { conn->sock = HTTP_SOCKET_INVALID; } ecs_os_mutex_unlock(srv->lock); if (still_owns_sock) { http_close(&sock); } else { sock = HTTP_SOCKET_INVALID; } } done: ecs_os_free(recv_buf); ecs_strbuf_reset(&frag.buf); } typedef struct { ecs_http_connection_impl_t *conn; uint64_t id; } http_conn_res_t; static http_conn_res_t http_init_connection( ecs_http_server_t *srv, ecs_http_socket_t sock_conn, struct sockaddr_storage *remote_addr, ecs_size_t remote_addr_len) { http_sock_set_timeout(sock_conn, 100); http_sock_keep_alive(sock_conn); http_sock_nonblock(sock_conn, true); /* Create new connection */ ecs_os_mutex_lock(srv->lock); ecs_http_connection_impl_t *conn = flecs_sparse_add_t( &srv->connections, ecs_http_connection_impl_t); uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); conn->pub.server = srv; conn->sock = sock_conn; ecs_os_mutex_unlock(srv->lock); char *remote_host = conn->pub.host; char *remote_port = conn->pub.port; /* Fetch name & port info */ if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, remote_host, ECS_SIZEOF(conn->pub.host), remote_port, ECS_SIZEOF(conn->pub.port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(remote_host, "unknown"); ecs_os_strcpy(remote_port, "unknown"); } ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", remote_host, remote_port, sock_conn); return (http_conn_res_t){ .conn = conn, .id = conn_id }; } static int http_accept_connections( ecs_http_server_t* srv, const struct sockaddr* addr, ecs_size_t addr_len) { #ifdef ECS_TARGET_WINDOWS /* If on Windows, test if winsock needs to be initialized */ SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ WSADATA data = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &data); if (result) { ecs_warn("http: WSAStartup failed with GetLastError = %d\n", GetLastError()); return 0; } } else { http_close(&testsocket); } #endif /* Resolve name + port (used for logging) */ char addr_host[256]; char addr_port[20]; int ret = 0; /* 0 = ok, 1 = port occupied */ ecs_http_socket_t sock = HTTP_SOCKET_INVALID; ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); if (http_getnameinfo( addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(addr_host, "unknown"); ecs_os_strcpy(addr_port, "unknown"); } ecs_os_mutex_lock(srv->lock); if (srv->should_run) { ecs_dbg_2("http: initializing connection socket"); sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); if (!http_socket_is_valid(sock)) { ecs_err("http: unable to create new connection socket: %s", ecs_os_strerror(errno)); ecs_os_mutex_unlock(srv->lock); goto done; } int reuse = 1, result; result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, ECS_SIZEOF(reuse)); if (result) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } if (addr->sa_family == AF_INET6) { int ipv6only = 0; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, ECS_SIZEOF(ipv6only))) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } } result = http_bind(sock, addr, addr_len); if (result) { if (errno == EADDRINUSE) { ret = 1; ecs_warn("http: address '%s:%s' in use, retrying with port %u", addr_host, addr_port, srv->port + 1); } else { ecs_err("http: failed to bind to '%s:%s': %s", addr_host, addr_port, ecs_os_strerror(errno)); } ecs_os_mutex_unlock(srv->lock); goto done; } http_sock_set_timeout(sock, 1000); srv->sock = sock; result = listen(srv->sock, SOMAXCONN); if (result) { ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", SOMAXCONN, ecs_os_strerror(errno)); } ecs_trace("http: listening for incoming connections on '%s:%s'", addr_host, addr_port); } else { ecs_dbg_2("http: server shut down while initializing"); } ecs_os_mutex_unlock(srv->lock); struct sockaddr_storage remote_addr; ecs_size_t remote_addr_len = 0; while (srv->should_run) { remote_addr_len = ECS_SIZEOF(remote_addr); ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, &remote_addr_len); if (!http_socket_is_valid(sock_conn)) { if (srv->should_run) { ecs_dbg("http: connection attempt failed: %s", ecs_os_strerror(errno)); } continue; } http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); http_recv_connection(srv, conn.conn, conn.id, sock_conn); } done: ecs_os_mutex_lock(srv->lock); if (http_socket_is_valid(sock) && errno != EBADF) { http_close(&sock); srv->sock = sock; } ecs_os_mutex_unlock(srv->lock); ecs_trace("http: no longer accepting connections on '%s:%s'", addr_host, addr_port); return ret; } static void* http_server_thread(void* arg) { ecs_http_server_t *srv = arg; struct sockaddr_in addr; ecs_os_zeromem(&addr); addr.sin_family = AF_INET; int retries = 0; retry: addr.sin_port = htons(srv->port); if (!srv->ipaddr) { addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } if (http_accept_connections( srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)) == 1) { srv->port ++; retries ++; if (retries < 10) { goto retry; } else { ecs_err("http: failed to connect (retried 10 times)"); } } return NULL; } static void http_do_request( ecs_http_server_t *srv, ecs_http_reply_t *reply, const ecs_http_request_impl_t *req) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, "missing request handler for server"); if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply->code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } error: return; } static void http_handle_request( ecs_http_server_t *srv, ecs_http_request_impl_t *req) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; if (req->pub.method != EcsHttpOptions) { if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { reply.code = 404; reply.status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply.code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } if (req->pub.method == EcsHttpGet) { http_insert_request_entry(srv, req, &reply); } http_send_reply(conn, &reply, false); ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); } else { /* Already taken care of */ } http_reply_fini(&reply); http_connection_free(conn); } static void http_purge_request_cache( ecs_http_server_t *srv, bool fini) { ecs_time_t t = {0, 0}; double time = ecs_time_measure(&t); ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); while (bucket) { ecs_hm_bucket_t *next = bucket->next; ecs_http_request_entry_t *entry = flecs_hm_bucket_value( &srv->request_cache, bucket); if (fini || ((time - entry->time) > srv->cache_purge_timeout)) { ecs_http_request_key_t *key = flecs_hm_bucket_key(bucket); ecs_os_free(ECS_CONST_CAST(char*, key->array)); ecs_os_free(entry->content); flecs_hm_bucket_remove(&srv->request_cache, bucket, ecs_map_key(&it)); } bucket = next; } } if (fini) { flecs_hashmap_fini(&srv->request_cache); } } static int32_t http_dequeue_requests( ecs_http_server_t *srv, double delta_time) { ecs_os_mutex_lock(srv->lock); int32_t request_count = 0; int32_t count = flecs_sparse_count(&srv->connections); for (int32_t i = count - 1; i >= 1; i --) { ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i); if (conn->request.res) { http_handle_request(srv, &conn->request); request_count ++; continue; } conn->dequeue_timeout += delta_time; conn->dequeue_retries ++; if ((conn->dequeue_timeout > (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) { ecs_dbg("http: purging connection '%s:%s' (sock = %d)", conn->pub.host, conn->pub.port, conn->sock); http_connection_free(conn); } } http_purge_request_cache(srv, false); ecs_os_mutex_unlock(srv->lock); return request_count; } const char* ecs_http_get_header( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->header_count; i++) { if (!ecs_os_strcmp(req->headers[i].key, name)) { return req->headers[i].value; } } return NULL; } const char* ecs_http_get_param( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->param_count; i++) { if (!ecs_os_strcmp(req->params[i].key, name)) { return req->params[i].value; } } return NULL; } ecs_http_server_t* ecs_http_server_init( const ecs_http_server_desc_t *desc) { ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); if (ecs_os_has_threading()) { srv->lock = ecs_os_mutex_new(); } srv->sock = HTTP_SOCKET_INVALID; srv->should_run = false; srv->initialized = true; srv->cache_timeout = desc->cache_timeout; srv->cache_purge_timeout = desc->cache_purge_timeout; if (!ECS_EQZERO(srv->cache_timeout) && ECS_EQZERO(srv->cache_purge_timeout)) { srv->cache_purge_timeout = srv->cache_timeout * 10; } srv->callback = desc->callback; srv->ctx = desc->ctx; srv->port = desc->port; srv->ipaddr = desc->ipaddr; srv->send_queue.wait_ms = desc->send_queue_wait_ms; if (!srv->send_queue.wait_ms) { srv->send_queue.wait_ms = 1; } flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); /* Start at id 1 */ flecs_sparse_new_id(&srv->connections); /* Initialize request cache */ flecs_hashmap_init(&srv->request_cache, ecs_http_request_key_t, ecs_http_request_entry_t, http_request_key_hash, http_request_key_compare, NULL); #ifndef ECS_TARGET_WINDOWS /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client * but the client already disconnected. */ signal(SIGPIPE, SIG_IGN); #endif return srv; } void ecs_http_server_fini( ecs_http_server_t* srv) { if (srv->should_run) { ecs_http_server_stop(srv); } if (ecs_os_has_threading()) { ecs_os_mutex_free(srv->lock); } http_purge_request_cache(srv, true); flecs_sparse_fini(&srv->connections); ecs_os_free(srv); } int ecs_http_server_start( ecs_http_server_t *srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); srv->should_run = true; ecs_dbg("http: starting server thread"); srv->thread = ecs_os_thread_new(http_server_thread, srv); if (!srv->thread) { goto error; } srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); if (!srv->send_queue.thread) { goto error; } return 0; error: return -1; } void ecs_http_server_stop( ecs_http_server_t* srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_OPERATION, "cannot stop HTTP server: not initialized"); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, "cannot stop HTTP server: already stopped/stopping"); ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); /* Stop server thread */ ecs_dbg("http: shutting down server thread"); ecs_os_mutex_lock(srv->lock); srv->should_run = false; if (http_socket_is_valid(srv->sock)) { http_close(&srv->sock); } ecs_os_mutex_unlock(srv->lock); ecs_os_thread_join(srv->thread); ecs_os_thread_join(srv->send_queue.thread); ecs_trace("http: server threads shut down"); int32_t i, count = flecs_sparse_count(&srv->connections); for (i = count - 1; i >= 1; i --) { http_connection_free(flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i)); } ecs_assert(flecs_sparse_count(&srv->connections) == 1, ECS_INTERNAL_ERROR, NULL); srv->thread = 0; error: return; } void ecs_http_server_dequeue( ecs_http_server_t* srv, ecs_ftime_t delta_time) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); srv->dequeue_timeout += (double)delta_time; srv->stats_timeout += (double)delta_time; if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { double elapsed = srv->dequeue_timeout; srv->dequeue_timeout = 0; ecs_time_t t = {0}; ecs_time_measure(&t); int32_t request_count = http_dequeue_requests(srv, elapsed); srv->requests_processed += request_count; srv->requests_processed_total += request_count; double time_spent = ecs_time_measure(&t); srv->request_time += time_spent; srv->request_time_total += time_spent; srv->dequeue_count ++; } if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { srv->stats_timeout = 0; ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", srv->requests_processed, srv->request_time, (srv->request_time / (double)srv->dequeue_count)); srv->requests_processed = 0; srv->request_time = 0; srv->dequeue_count = 0; } error: return; } int ecs_http_server_http_request( ecs_http_server_t* srv, const char *req, ecs_size_t len, ecs_http_reply_t *reply_out) { if (!len) { len = ecs_os_strlen(req); } ecs_http_fragment_t frag = {0}; if (!http_parse_request(&frag, req, len)) { ecs_strbuf_reset(&frag.buf); reply_out->code = 400; return -1; } ecs_http_request_impl_t request; char *res = http_decode_request(&request, &frag); if (!res) { reply_out->code = 400; return -1; } ecs_http_request_entry_t *entry = http_find_request_entry(srv, request.res, request.req_len); if (entry) { reply_out->body = ECS_STRBUF_INIT; reply_out->code = entry->code; reply_out->content_type = "application/json"; reply_out->headers = ECS_STRBUF_INIT; reply_out->status = "OK"; ecs_strbuf_appendstrn(&reply_out->body, entry->content, entry->content_length); } else { http_do_request(srv, reply_out, &request); if (request.pub.method == EcsHttpGet) { http_insert_request_entry(srv, &request, reply_out); } } ecs_os_free(res); http_purge_request_cache(srv, false); return (reply_out->code >= 400) ? -1 : 0; } int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, const char *body, ecs_http_reply_t *reply_out) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(method != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(req != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(reply_out != NULL, ECS_INVALID_PARAMETER, NULL); const char *http_ver = " HTTP/1.1\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); int32_t body_len = body ? ecs_os_strlen(body) : 0; int32_t http_ver_len = ecs_os_strlen(http_ver); char reqbuf[1024], *reqstr = reqbuf; char content_length[32] = {0}; if (body_len) { ecs_os_snprintf(content_length, 32, "Content-Length: %d\r\n\r\n", body_len); } int32_t content_length_len = ecs_os_strlen(content_length); int32_t len = method_len + req_len + content_length_len + body_len + http_ver_len; len += 3; if (len >= 1024) { reqstr = ecs_os_malloc(len); } char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; if (body) { ecs_os_memcpy(ptr, content_length, content_length_len); ptr += content_length_len; ecs_os_memcpy(ptr, body, body_len); ptr += body_len; } ptr[0] = '\r'; ptr[1] = '\n'; int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); if (reqbuf != reqstr) { ecs_os_free(reqstr); } return result; error: return -1; } void* ecs_http_server_ctx( ecs_http_server_t* srv) { return srv->ctx; } #endif #ifdef FLECS_JSON typedef struct { ecs_allocator_t *a; ecs_vec_t ids; ecs_map_t anonymous_ids; ecs_map_t missing_reflection; const char *expr; } ecs_from_json_ctx_t; static void flecs_from_json_ctx_init( ecs_allocator_t *a, ecs_from_json_ctx_t *ctx) { ctx->a = a; ecs_vec_init_t(a, &ctx->ids, ecs_id_t, 0); ecs_map_init(&ctx->anonymous_ids, a); ecs_map_init(&ctx->missing_reflection, a); } static void flecs_from_json_ctx_fini( ecs_from_json_ctx_t *ctx) { ecs_vec_fini_t(ctx->a, &ctx->ids, ecs_id_t); ecs_map_fini(&ctx->anonymous_ids); ecs_map_fini(&ctx->missing_reflection); } static ecs_entity_t flecs_json_new_id( ecs_world_t *world, ecs_entity_t ser_id) { /* Try to honor low id requirements */ if (ser_id < FLECS_HI_COMPONENT_ID) { return ecs_new_low_id(world); } else { return ecs_new(world); } } static void flecs_json_missing_reflection( ecs_world_t *world, ecs_id_t id, const char *json, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { if (!desc->strict || ecs_map_get(&ctx->missing_reflection, id)) { return; } /* Don't spam log when multiple values of a type can't be deserialized */ ecs_map_ensure(&ctx->missing_reflection, id); char *id_str = ecs_id_str(world, id); ecs_parser_error(desc->name, desc->expr, json - desc->expr, "missing reflection for '%s'", id_str); ecs_os_free(id_str); } static ecs_entity_t flecs_json_lookup( ecs_world_t *world, ecs_entity_t parent, const char *name, const ecs_from_json_desc_t *desc) { ecs_entity_t scope = 0; if (parent) { scope = ecs_set_scope(world, parent); } ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); if (parent) { ecs_set_scope(world, scope); } return result; } static ecs_entity_t flecs_json_lookup_id( ecs_world_t *world, ecs_entity_t id, const ecs_from_json_desc_t *desc) { if (ecs_is_alive(world, id) && !ecs_get_name(world, id)) { return id; } char name[32]; ecs_os_snprintf(name, 32, "#%u", (uint32_t)id); return flecs_json_lookup(world, 0, name, desc); } static void flecs_json_mark_reserved( ecs_map_t *anonymous_ids, ecs_entity_t e) { ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); reserved[0] = 0; } static ecs_entity_t flecs_json_ensure_entity( ecs_world_t *world, const char *name, void *ctx) { ecs_entity_t e = 0; ecs_map_t *anonymous_ids = ctx; if (flecs_name_is_id(name)) { /* Anonymous entity, find or create mapping to new id */ ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(&name[1])); ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); if (deser_id) { if (!deser_id[0]) { /* Id is already issued by deserializer, create new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } else { /* Id mapping exists */ } } else { /* Id has not yet been issued by deserializer, which means it's safe * to use. This allows the deserializer to bind to existing * anonymous ids, as they will never be reissued. */ deser_id = ecs_map_ensure(anonymous_ids, ser_id); if (!ecs_exists(world, ser_id) || (ecs_is_alive(world, ser_id) && !ecs_get_name(world, ser_id))) { /* Only use existing id if it's alive or doesn't exist yet. The * id could have been recycled for another entity * Also don't use existing id if the existing entity is not * anonymous. */ deser_id[0] = ser_id; ecs_make_alive(world, ser_id); } else { /* If id exists and is not alive, create a new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } } e = deser_id[0]; } else { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); if (!e) { e = ecs_entity(world, { .name = name }); flecs_json_mark_reserved(anonymous_ids, e); } } return e; } static void flecs_json_track_id( ecs_from_json_ctx_t *ctx, ecs_id_t id) { if (id != ecs_pair_t(EcsIdentifier, EcsName) && !(ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf)) { ecs_vec_append_t(ctx->a, &ctx->ids, ecs_id_t)[0] = id; } } static bool flecs_json_has_id( const ecs_from_json_ctx_t *ctx, ecs_id_t id) { return bsearch(&id, ecs_vec_first(&ctx->ids), flecs_itosize(ecs_vec_count(&ctx->ids)), sizeof(ecs_id_t), flecs_id_qsort_cmp) != NULL; } static const char* flecs_json_deser_ids( ecs_world_t *world, ecs_entity_t e, ecs_entity_t rel, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { char token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t kind; const char *next = flecs_json_parse(json, &kind, token); bool array = kind == JsonArrayOpen; if (array) { json = next; next = flecs_json_parse(json, &kind, token); if (kind == JsonArrayClose) { return next; } } else if (!rel) { return flecs_json_expect(json, JsonArrayOpen, token, desc); } do { char *str = NULL; json = flecs_json_expect_string(json, token, &str, desc); if (!json) { return NULL; } ecs_entity_t target = flecs_json_lookup(world, 0, str, desc); if (str != token) ecs_os_free(str); ecs_id_t id = rel ? ecs_pair(rel, target) : target; ecs_add_id(world, e, id); flecs_json_track_id(ctx, id); if (!array) { return json; } next = flecs_json_parse(json, &kind, token); if (kind != JsonComma) { return flecs_json_expect(json, JsonArrayClose, token, desc); } json = next; } while (json); return NULL; } static const char* flecs_json_deser_pairs( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { char token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t kind; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { return NULL; } const char *next = flecs_json_parse(json, &kind, token); if (kind == JsonObjectClose) { return next; } json = flecs_json_expect_member(json, token, desc); while (json) { ecs_entity_t rel = flecs_json_lookup(world, 0, token, desc); json = flecs_json_deser_ids(world, e, rel, json, desc, ctx); if (!json) { return NULL; } json = flecs_json_parse_next_member(json, token, &kind, desc); if (kind == JsonObjectClose) { return json; } } return NULL; } static const char* flecs_json_deser_components( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; const char *expr = ctx->expr, *lah; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonObjectClose) { json = lah; goto end; } do { json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } ecs_id_t id = 0; if (token[0] != '(') { id = flecs_json_lookup(world, 0, token, desc); } else { char token_buffer[256]; ecs_term_t term = {0}; if (!flecs_term_parse(world, NULL, token, token_buffer, &term)) { goto error; } ecs_assert(term.first.name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(term.second.name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t rel = flecs_json_lookup( world, 0, term.first.name, desc); ecs_entity_t tgt = flecs_json_lookup( world, 0, term.second.name, desc); id = ecs_pair(rel, tgt); } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonNull) { ecs_add_id(world, e, id); json = lah; } else { const ecs_type_info_t *ti = ecs_get_type_info(world, id); bool skip = ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == ecs_id(EcsIdentifier) && ECS_PAIR_SECOND(id) != EcsAlias; const char *next = NULL; if (ti) { void *ptr = ecs_ensure_id(world, e, id, flecs_ito(size_t, ti->size)); if (!skip) { next = ecs_ptr_from_json(world, ti->component, ptr, json, desc); if (next) { ecs_modified_id(world, e, id); } } } if (!next) { if (!skip || !ti) { flecs_json_missing_reflection(world, id, json, ctx, desc); if (desc->strict) { goto error; } } json = flecs_parse_ws_eol(json); next = flecs_json_skip_object(json + 1, token, desc); if (!next) { goto error; } if (!ti) { ecs_add_id(world, e, id); } } json = next; } /* Don't add ids that have their own fields in serialized data. */ flecs_json_track_id(ctx, id); json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonComma) { break; } } while (true); if (token_kind != JsonObjectClose) { ecs_parser_error(NULL, expr, json - expr, "expected }"); goto error; } end: return json; error: return NULL; } static const char* flecs_entity_from_json( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; const char *expr = ctx->expr; static const char *fields[] = { "parent", "name", "id", "version", "has_alerts", "tags", "pairs", "components" }; enum { Parent, Name, Id, Version, Alerts, Tags, Pairs, Components }; ecs_entity_t parent = 0, serialized_id = 0; bool has_id = false, replace_table = false; int32_t previous = -1; ecs_vec_clear(&ctx->ids); json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } const char *next = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonObjectClose) { json = next; goto end; } json = flecs_json_expect_member(json, token, desc); while (json) { int32_t field; for (field = previous + 1; field < 8; field ++) { if (!ecs_os_strcmp(token, fields[field])) { break; } } if (field == 8 || (field == Version && previous != Id)) { ecs_parser_error(desc->name, expr, json - expr, "unexpected entity member '%s'", token); goto error; } if (!e && has_id && field > Version) { e = flecs_json_lookup_id(world, serialized_id, desc); } if (!e && field > Version) { ecs_parser_error(desc->name, expr, json - expr, "failed to create entity"); goto error; } switch (field) { case Parent: case Name: { char *str = NULL; json = flecs_json_expect_string(json, token, &str, desc); if (!json) goto error; if (field == Parent) { parent = flecs_json_lookup(world, 0, str, desc); if (e) ecs_add_pair(world, e, EcsChildOf, parent); ecs_vec_append_t(ctx->a, &ctx->ids, ecs_id_t)[0] = ecs_pair(EcsChildOf, parent); } else if (str[0] != '#') { if (!e) e = flecs_json_lookup(world, parent, str, desc); else ecs_set_name(world, e, str); ecs_vec_append_t(ctx->a, &ctx->ids, ecs_id_t)[0] = ecs_pair_t(EcsIdentifier, EcsName); } if (str != token) ecs_os_free(str); break; } case Id: case Version: json = flecs_json_parse(json, &token_kind, token); if (!json) goto error; if (token_kind != JsonNumber && token_kind != JsonLargeInt) { ecs_parser_error(desc->name, expr, json - expr, "expected %s", fields[field]); goto error; } serialized_id |= flecs_ito(uint64_t, atoll(token)) << (field == Version ? 32 : 0); has_id = true; break; case Alerts: json = flecs_json_expect(json, JsonBoolean, token, desc); break; case Tags: json = flecs_json_deser_ids(world, e, 0, json, desc, ctx); break; case Pairs: json = flecs_json_deser_pairs(world, e, json, desc, ctx); break; case Components: json = flecs_json_deser_components(world, e, json, desc, ctx); replace_table = true; break; } if (!json) goto error; previous = field; json = flecs_json_parse_next_member(json, token, &token_kind, desc); if (!json) goto error; if (token_kind == JsonObjectClose) { break; } } if (!e && has_id) { e = flecs_json_lookup_id(world, serialized_id, desc); } end: if (e) { int32_t count = ecs_vec_count(&ctx->ids); if (count > 1) { qsort(ecs_vec_first(&ctx->ids), flecs_itosize(count), sizeof(ecs_id_t), flecs_id_qsort_cmp); } ecs_record_t *r = flecs_entities_get(world, e); ecs_world_t *stage = ecs_get_stage(world, 0); flecs_commands_begin(world, world->stages[0]); if (replace_table && r && r->table) { const ecs_type_t *type = &r->table->type; for (int32_t i = 0; i < type->count; i ++) { ecs_id_t id = type->array[i]; if (!flecs_json_has_id(ctx, id)) { ecs_assert(ecs_get_target(world, e, EcsChildOf, 0) != EcsFlecsCore, ECS_INVALID_OPERATION, NULL); ecs_remove_id(stage, e, id); } } } if (r && (r->row & EcsEntityHasDontFragment)) { ecs_component_record_t *cr = world->cr_non_fragmenting_head; for (; cr; cr = cr->non_fragmenting.next) { if (cr->sparse && !ecs_id_is_wildcard(cr->id) && flecs_sparse_has(cr->sparse, e) && !flecs_json_has_id(ctx, cr->id)) { ecs_remove_id(stage, e, cr->id); } } } flecs_commands_end(world, world->stages[0]); } return json; error: return NULL; } const char* ecs_entity_from_json( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_assert(!ecs_is_stage(world), ECS_INVALID_OPERATION, "cannot deserialize while world is deferred"); ecs_from_json_desc_t desc = {0}; if (desc_arg) { desc = *desc_arg; } desc.expr = json; ecs_allocator_t *a = &world->allocator; ecs_from_json_ctx_t ctx; flecs_from_json_ctx_init(a, &ctx); ctx.expr = json; if (!desc.lookup_action) { desc.lookup_action = flecs_json_ensure_entity; desc.lookup_ctx = &ctx.anonymous_ids; } json = flecs_entity_from_json(world, e, json, &desc, &ctx); flecs_from_json_ctx_fini(&ctx); return json; } const char* ecs_world_from_json( ecs_world_t *world, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; if (desc_arg) { desc = *desc_arg; } desc.expr = json; ecs_allocator_t *a = &world->allocator; ecs_from_json_ctx_t ctx; flecs_from_json_ctx_init(a, &ctx); const char *expr = json, *lah; ctx.expr = expr; if (!desc.lookup_action) { desc.lookup_action = flecs_json_ensure_entity; desc.lookup_ctx = &ctx.anonymous_ids; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "results", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { json = lah; goto end; } do { json = flecs_entity_from_json(world, 0, json, &desc, &ctx); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonComma) { if (token_kind != JsonArrayClose) { ecs_parser_error(NULL, expr, json - expr, "expected ']'"); goto error; } break; } } while (true); end: json = flecs_json_expect(json, JsonObjectClose, token, &desc); flecs_from_json_ctx_fini(&ctx); return json; error: flecs_from_json_ctx_fini(&ctx); return NULL; } const char* ecs_world_from_json_file( ecs_world_t *world, const char *filename, const ecs_from_json_desc_t *desc) { char *json = flecs_load_from_file(filename); if (!json) { ecs_err("file not found: %s", filename); return NULL; } const char *result = ecs_world_from_json(world, json, desc); ecs_os_free(json); return result; } #endif #ifndef FLECS_SCRIPT_PRIVATE_H #define FLECS_SCRIPT_PRIVATE_H #ifndef FLECS_PARSER_H #define FLECS_PARSER_H #ifdef FLECS_PARSER 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; const char *token_ends[8]; int32_t token_ends_i; const char *expr_pos; const char *expr_end; char *token_cur; char *token_end; char *token_keep; bool significant_newline; bool merge_variable_members; int16_t scope_depth; int16_t expr_depth; int16_t elem_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 #ifndef ECS_PARSER_MAX_TOKENS #define ECS_PARSER_MAX_TOKENS (32) #endif /* 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 = '\'', EcsTokGt = '>', EcsTokLt = '<', EcsTokEq = 128, EcsTokNeq, EcsTokGtEq, EcsTokLtEq, EcsTokAnd, EcsTokOr, EcsTokMatch, EcsTokRange, EcsTokShiftLeft, EcsTokShiftRight, EcsTokAddAssign, EcsTokMulAssign, EcsTokHasBracketOpen, EcsTokArrow, EcsTokIdentifier, EcsTokString, EcsTokNumber, EcsTokKeywordWith, EcsTokKeywordUsing, EcsTokKeywordProp, EcsTokKeywordMut, EcsTokKeywordConst, EcsTokKeywordMatch, EcsTokKeywordNew, EcsTokKeywordExport, EcsTokKeywordIf, EcsTokKeywordElse, EcsTokKeywordFor, EcsTokKeywordIn, EcsTokKeywordTemplate, EcsTokKeywordModule, EcsTokKeywordInclude, EcsTokKeywordFn, EcsTokKeywordAwait, EcsTokKeywordTry, EcsTokKeywordCatch, EcsTokKeywordContinue, EcsTokKeywordAsync, EcsTokKeywordWhile, EcsTokKeywordScript, EcsTokLast } ecs_token_kind_t; typedef struct ecs_token_t { const char *value; ecs_token_kind_t kind; } ecs_token_t; typedef struct ecs_tokenizer_t { int32_t count; ecs_token_t stack[ECS_PARSER_MAX_TOKENS]; ecs_token_t *tokens; } ecs_tokenizer_t; const char* flecs_tokenizer_line( ecs_parser_t *parser, const char *ptr, ecs_token_t *out, bool stmt_end); 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); void flecs_token_restore( ecs_parser_t *parser, const char *token_cur); const char* flecs_scan_whitespace( ecs_parser_t *parser, const char *pos); const char* flecs_scan_whitespace_and_comment( ecs_parser_t *parser, const char *pos, bool *newline); const char* flecs_parser_skip_newlines( 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); const char* flecs_parser_stmt_end( const ecs_parser_t *parser, const char *pos); #endif #endif #endif #ifdef FLECS_SCRIPT typedef struct ecs_script_entity_t ecs_script_entity_t; typedef struct ecs_script_ir_t ecs_script_ir_t; #define flecs_script_impl(script) ((ecs_script_impl_t*)script) #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #define FLECS_SCRIPT_NOINLINE __attribute__((noinline)) #elif defined(ECS_TARGET_MSVC) #define FLECS_SCRIPT_NOINLINE __declspec(noinline) #else #define FLECS_SCRIPT_NOINLINE #endif /* Context passed to script visitor callbacks. */ typedef struct ecs_script_visitor_ctx_t { ecs_world_t *world; ecs_entity_t entity; /* Entity being defined */ ecs_entity_t kind; /* Kind with the visitor */ struct ecs_expr_initializer_t *initializer; /* Initializer AST */ struct ecs_script_eval_visitor_t *eval; /* Script evaluation context */ void *ctx; /* User context */ } ecs_script_visitor_ctx_t; /* Script visitor callback. */ typedef int(*ecs_script_visitor_action_t)( const ecs_script_visitor_ctx_t *ctx); /* Component that customizes how initializer syntax is interpreted. */ typedef struct EcsScriptVisitor { ecs_script_visitor_action_t visit; void *ctx; } EcsScriptVisitor; FLECS_API extern ECS_COMPONENT_DECLARE(EcsScriptVisitor); /* Enum used to classify symbol kind for type resolver. */ typedef enum flecs_script_symbol_kind_t { FlecsScriptSymbolNone, FlecsScriptSymbolEntity, FlecsScriptSymbolEntitySlot, FlecsScriptSymbolVariable, FlecsScriptSymbolGlobalVariable } flecs_script_symbol_kind_t; /* Represents symbol during type resolver. */ typedef struct flecs_script_symbol_t { flecs_script_symbol_kind_t kind; ecs_entity_t entity; int32_t slot; int32_t sp; } flecs_script_symbol_t; typedef enum flecs_script_lookup_kind_t { FlecsScriptLookupEntity = 1, FlecsScriptLookupVariable = 2, FlecsScriptLookupAll = 3, FlecsScriptLookupDynamic = 4 } flecs_script_lookup_kind_t; typedef enum flecs_script_unresolved_kind_t { FlecsScriptUnresolvedEntity, FlecsScriptUnresolvedComponent, FlecsScriptUnresolvedVariable } flecs_script_unresolved_kind_t; /* Stores unresolved reference so it can be resolved later. */ typedef struct ecs_script_unresolved_ref_t { const char *name; flecs_script_unresolved_kind_t kind; int32_t line; int32_t column; int32_t offset; } ecs_script_unresolved_ref_t; /* Stores unresolved component reference so that it can be resolved later. */ typedef struct ecs_script_unresolved_component_ref_t { ecs_entity_t entity; ecs_id_t component; bool is_has; int32_t line; int32_t column; } ecs_script_unresolved_component_ref_t; typedef struct ecs_script_region_t { int32_t scope_first; int32_t scope_count; int32_t for_first; int32_t for_count; } ecs_script_region_t; #ifndef FLECS_SCRIPT_STATE_H #define FLECS_SCRIPT_STATE_H /* Storage for slots inside of for loops */ typedef struct ecs_script_for_component_t { ecs_id_t component; int32_t visit; } ecs_script_for_component_t; typedef struct ecs_script_for_entry_t { int32_t visit; ecs_vec_t components; /* vec */ } ecs_script_for_entry_t; typedef struct ecs_script_for_slot_t { ecs_vec_t entities; /* vec, anonymous entities */ ecs_map_t named; ecs_entity_t cache_entity; ecs_script_for_entry_t *cache_entry; int32_t scope_slot; } ecs_script_for_slot_t; /* Stores reference to entity in same script */ typedef struct ecs_script_symbol_slot_t { ecs_entity_t entity; int32_t scope_slot; } ecs_script_symbol_slot_t; /* Stores reference to component in same script */ typedef struct ecs_script_component_slot_t { int32_t entity_slot; ecs_id_t component; int32_t scope_slot; } ecs_script_component_slot_t; /* Stores value of const variable so it can be diffed in reactive update */ typedef struct ecs_script_computed_t { void *ptr; const ecs_type_info_t *ti; bool valid; } ecs_script_computed_t; typedef struct ecs_script_state_t { ecs_vec_t symbol_slots; ecs_vec_t component_slots; ecs_vec_t scope_slots; ecs_vec_t for_slots; ecs_vec_t computed; int32_t visit; bool initialized; } ecs_script_state_t; void flecs_script_slots_reset( ecs_vec_t *slots, ecs_size_t size, int32_t count); #define flecs_script_slots_reset_t(slots, T, count)\ flecs_script_slots_reset(slots, ECS_SIZEOF(T), count) void flecs_script_state_init( ecs_script_state_t *state); void flecs_script_state_fini( ecs_script_state_t *state); void flecs_script_state_clear_computed( ecs_script_state_t *state); void flecs_script_state_resize_computed( ecs_script_state_t *state, int32_t count); void flecs_script_state_resize( ecs_script_state_t *state, int32_t scope_count, int32_t component_count, int32_t for_count); int32_t flecs_script_state_next( ecs_script_state_t *state); void flecs_script_state_mark( ecs_script_state_t *state, const ecs_script_region_t *region, int32_t visit); void flecs_script_for_slot_clear( ecs_world_t *world, ecs_script_for_slot_t *slot, bool delete_named); void flecs_script_for_slot_purge( ecs_world_t *world, ecs_script_for_slot_t *slot, int32_t visit); void flecs_script_for_slot_track( ecs_world_t *world, ecs_script_for_slot_t *slot, ecs_entity_t entity, int32_t visit, bool *named); void flecs_script_for_slot_track_component( ecs_script_for_slot_t *slot, ecs_entity_t entity, ecs_id_t component, int32_t visit); #endif struct ecs_script_impl_t { ecs_script_t pub; ecs_entity_t entity; /* Set if script is managed (has EcsScript) */ ecs_allocator_t allocator; ecs_script_scope_t *root; ecs_expr_node_t *expr; /* Only set if script is just an expression */ char *token_buffer; char *token_remaining; /* Remaining space in token buffer */ const char *next_token; /* First character after expression */ int32_t token_buffer_size; int32_t refcount; int32_t task_refcount; ecs_vec_t async_tasks; /* vec, owned */ ecs_vec_t refs; ecs_vec_t run_refs; ecs_script_state_t state; ecs_vec_t regions; ecs_vec_t unresolved_refs; ecs_vec_t unresolved_component_refs; ecs_vec_t skip_unknown_warned; /* vec */ ecs_script_ir_t *ir; ecs_map_t entity_index; int32_t entity_index_visit; bool entity_index_valid; ecs_vec_t line_index; /* vec with the offset of each line start */ const char *line_index_code; /* Code the line index was built for */ int32_t line_index_len; /* Length of the code the line index was built for */ int32_t input_count; uint64_t pending_inputs; bool evaluating; bool compiled; bool skip_unknown; bool ir_enabled; }; #define flecs_script_is_skip_unknown(script)\ (flecs_script_impl(script)->skip_unknown) void flecs_script_skip_unknown_warn( ecs_script_t *script, const char *name, const char *msg); typedef struct ecs_function_calldata_t { ecs_entity_t function; struct { ecs_function_callback_t callback; ecs_vector_function_callback_t vector_callback; } is; int32_t vector_elem_count; #ifdef FLECS_SCRIPT_ASYNC ecs_async_function_callback_t async_callback; ecs_async_function_cancel_t async_cancel; #endif void *ctx; } ecs_function_calldata_t; #ifndef FLECS_SCRIPT_AST_H #define FLECS_SCRIPT_AST_H typedef enum ecs_script_node_kind_t { EcsAstScope, EcsAstTag, EcsAstComponent, EcsAstWithTag, EcsAstWithComponent, EcsAstWith, EcsAstUsing, EcsAstModule, EcsAstAnnotation, EcsAstTemplate, EcsAstProp, EcsAstMut, EcsAstConst, EcsAstExportConst, EcsAstExportMut, EcsAstEntity, EcsAstPairScope, EcsAstIf, EcsAstFor, EcsAstInclude, EcsAstFunction, EcsAstAwait, EcsAstTry, EcsAstContinue, EcsAstAsync, EcsAstWhile, EcsAstAssign } ecs_script_node_kind_t; typedef struct ecs_script_node_t { ecs_script_node_kind_t kind; ecs_size_t alloc_size; const char *pos; const char *end; uint64_t input; uint64_t direct_input; /* Dependencies on computed template consts (secondary bitset) */ uint64_t internal; uint64_t direct_internal; bool skip; int32_t region; } ecs_script_node_t; struct ecs_script_scope_t { ecs_script_node_t node; ecs_vec_t stmts; ecs_script_scope_t *parent; ecs_script_scope_t *alternative; const char *open; int32_t scope_slot; /* Array with component ids that are added in scope. Used to limit * archetype moves. */ ecs_vec_t components; /* vec */ ecs_vec_t set_components; /* vec */ }; typedef struct ecs_script_id_t { const char *first; const char *second; ecs_id_t flag; ecs_id_t eval; ecs_entity_t first_eval; ecs_entity_t second_eval; /* If first or second refer to a variable, these are the cached variable * stack pointers so we don't have to lookup variables by name. */ int32_t first_sp; int32_t second_sp; int32_t first_symbol; int32_t second_symbol; /* If first is a template prop typed by an interface struct, this is the * struct the resolved template must derive from. */ ecs_entity_t interface; /* In case first/second are specified as interpolated strings. */ ecs_expr_node_t *first_expr; ecs_expr_node_t *second_expr; /* If first refers to a vector template prop, this is the index expression * and the stack pointer of the vector variable. */ ecs_expr_node_t *index_expr; int32_t index_sp; /* If true, the lookup result for this id cannot be cached. This is the case * for entities that are defined inside of templates, which have different * values for each instantiation. */ bool dynamic; } ecs_script_id_t; typedef struct ecs_script_tag_t { ecs_script_node_t node; ecs_script_id_t id; int32_t component_slot; } ecs_script_tag_t; typedef struct ecs_script_component_t { ecs_script_node_t node; ecs_script_id_t id; ecs_expr_node_t *expr; const char *value_pos; const char *value_end; int32_t component_slot; bool is_collection; } ecs_script_component_t; struct ecs_script_entity_t { ecs_script_node_t node; ecs_script_entity_t *parent; const char *kind; const char *name; bool kind_w_expr; bool non_fragmenting_parent; int32_t type_index; ecs_script_scope_t *scope; ecs_expr_node_t *name_expr; ecs_entity_t eval; ecs_entity_t eval_kind; int32_t kind_symbol; int32_t kind_sp; int32_t symbol; bool is_type; }; typedef struct ecs_script_with_t { ecs_script_node_t node; ecs_script_scope_t *expressions; ecs_script_scope_t *scope; } ecs_script_with_t; typedef struct ecs_script_pair_scope_t { ecs_script_node_t node; ecs_script_id_t id; ecs_script_scope_t *scope; } ecs_script_pair_scope_t; typedef struct ecs_script_using_t { ecs_script_node_t node; const char *name; ecs_entity_t eval; } ecs_script_using_t; typedef struct ecs_script_module_t { ecs_script_node_t node; const char *name; ecs_entity_t eval; int32_t symbol; } ecs_script_module_t; typedef struct ecs_script_annot_t { ecs_script_node_t node; const char *name; const char *expr; } ecs_script_annot_t; typedef struct ecs_script_template_node_t { ecs_script_node_t node; const char *name; const char *base; ecs_entity_t eval_base; const char *parent; ecs_entity_t eval_parent; ecs_script_scope_t* scope; int32_t symbol; int32_t symbol_offset; int32_t symbol_count; int32_t root_symbol; } ecs_script_template_node_t; typedef struct ecs_script_var_node_t { ecs_script_node_t node; const char *name; const char *type; bool type_is_template; bool type_is_vector; ecs_expr_node_t *expr; ecs_entity_t eval_type; ecs_entity_t eval_interface; int32_t sp; int32_t symbol; /* 0 if not cached, otherwise computed slot index + 1 */ int32_t computed; int32_t base_member; bool is_await; } ecs_script_var_node_t; typedef struct ecs_script_await_t { ecs_script_node_t node; ecs_expr_node_t *expr; } ecs_script_await_t; typedef struct ecs_script_continue_t { ecs_script_node_t node; } ecs_script_continue_t; typedef struct ecs_script_catch_t { const char *error; /* Error entity to catch. NULL for catch-all clause. */ ecs_script_scope_t *scope; ecs_entity_t eval_error; int32_t error_symbol; } ecs_script_catch_t; typedef struct ecs_script_try_t { ecs_script_node_t node; ecs_script_scope_t *try_scope; ecs_vec_t catches; /* vec */ } ecs_script_try_t; typedef struct ecs_script_async_t { ecs_script_node_t node; ecs_script_scope_t *scope; } ecs_script_async_t; typedef struct ecs_script_while_t { ecs_script_node_t node; ecs_expr_node_t *expr; ecs_script_scope_t *scope; } ecs_script_while_t; typedef struct ecs_script_assign_t { ecs_script_node_t node; const char *name; ecs_expr_node_t *expr; ecs_entity_t eval_type; ecs_entity_t component; int32_t offset; int32_t sp; bool parent; } ecs_script_assign_t; typedef struct ecs_script_if_t { ecs_script_node_t node; ecs_script_scope_t *if_true; ecs_script_scope_t *if_false; ecs_expr_node_t *expr; } ecs_script_if_t; typedef struct ecs_script_for_t { ecs_script_node_t node; const char *loop_vars[3]; int32_t loop_var_sp[3]; int32_t loop_var_count; ecs_expr_node_t *from; ecs_expr_node_t *to; ecs_expr_node_t *expr; ecs_script_scope_t *scope; int32_t for_slot; } ecs_script_for_t; typedef struct ecs_script_include_t { ecs_script_node_t node; const char *filename; } ecs_script_include_t; typedef struct ecs_script_fn_param_t { ecs_script_node_t node; const char *name; const char *type; ecs_entity_t eval_type; int32_t sp; } ecs_script_fn_param_t; typedef struct ecs_script_function_node_t { ecs_script_node_t node; ecs_script_node_t return_type_node; const char *name; const char *return_type; ecs_vec_t params; ecs_script_scope_t *body; ecs_expr_node_t *return_expr; ecs_entity_t eval_return_type; int32_t symbol; } ecs_script_function_node_t; bool flecs_scope_is_empty( ecs_script_scope_t *scope); const char* flecs_script_node_kind_str( const ecs_script_node_t *node); ecs_script_entity_t* flecs_script_insert_entity( ecs_parser_t *parser, const char *name); ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_parser_t *parser, const char *first, const char *second); ecs_script_with_t* flecs_script_insert_with( ecs_parser_t *parser); ecs_script_using_t* flecs_script_insert_using( ecs_parser_t *parser, const char *name); ecs_script_module_t* flecs_script_insert_module( ecs_parser_t *parser, const char *name); ecs_script_template_node_t* flecs_script_insert_template( ecs_parser_t *parser, const char *name); ecs_script_annot_t* flecs_script_insert_annot( ecs_parser_t *parser, const char *name, const char *expr); ecs_script_var_node_t* flecs_script_insert_var( ecs_parser_t *parser, const char *name); ecs_script_await_t* flecs_script_insert_await( ecs_parser_t *parser); ecs_script_continue_t* flecs_script_insert_continue( ecs_parser_t *parser); ecs_script_try_t* flecs_script_insert_try( ecs_parser_t *parser); ecs_script_catch_t* flecs_script_try_add_catch( ecs_parser_t *parser, ecs_script_try_t *stmt); ecs_script_tag_t* flecs_script_insert_tag( ecs_parser_t *parser, const char *name); ecs_script_tag_t* flecs_script_insert_pair_tag( ecs_parser_t *parser, const char *first, const char *second); ecs_script_component_t* flecs_script_insert_component( ecs_parser_t *parser, const char *name); ecs_script_component_t* flecs_script_insert_pair_component( ecs_parser_t *parser, const char *first, const char *second); ecs_script_if_t* flecs_script_insert_if( ecs_parser_t *parser); ecs_script_async_t* flecs_script_insert_async( ecs_parser_t *parser); ecs_script_while_t* flecs_script_insert_while( ecs_parser_t *parser); ecs_script_assign_t* flecs_script_insert_assign( ecs_parser_t *parser, const char *name); ecs_script_for_t* flecs_script_insert_for( ecs_parser_t *parser); ecs_script_include_t* flecs_script_insert_include( ecs_parser_t *parser, const char *filename); ecs_script_function_node_t* flecs_script_insert_function( ecs_parser_t *parser, const char *name); #endif #ifndef FLECS_EXPR_SCRIPT_H #define FLECS_EXPR_SCRIPT_H #ifndef FLECS_SCRIPT_EXPR_STACK_H #define FLECS_SCRIPT_EXPR_STACK_H #define FLECS_EXPR_STACK_MAX (256) #define FLECS_EXPR_SMALL_DATA_SIZE (24) typedef union ecs_expr_small_value_t { bool bool_; char char_; int64_t i64; uint64_t u64; double f64; char *string; ecs_entity_t entity; /* Avoid allocations for small trivial types */ char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; } ecs_expr_small_value_t; typedef struct ecs_expr_value_t { ecs_value_t value; const ecs_type_info_t *type_info; bool owned; /* Is value owned by the runtime */ } ecs_expr_value_t; typedef struct ecs_expr_stack_frame_t { ecs_stack_page_t *page; int32_t page_sp; int32_t sp; } ecs_expr_stack_frame_t; typedef struct ecs_expr_stack_t { ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; ecs_stack_t stack; int32_t frame; } ecs_expr_stack_t; void flecs_expr_stack_init( ecs_expr_stack_t *stack); void flecs_expr_stack_fini( ecs_expr_stack_t *stack); ecs_expr_value_t* flecs_expr_stack_result( ecs_expr_stack_t *stack, ecs_expr_node_t *node); void flecs_expr_stack_storage( ecs_expr_stack_t *stack, ecs_expr_value_t *v); void flecs_expr_stack_push( ecs_expr_stack_t *stack); void flecs_expr_stack_pop( ecs_expr_stack_t *stack); #endif #ifndef FLECS_SCRIPT_EXPR_AST_H #define FLECS_SCRIPT_EXPR_AST_H #define FLECS_EXPR_SWIZZLE_MAX (16) typedef enum ecs_expr_node_kind_t { EcsExprValue, EcsExprInterpolatedString, EcsExprInitializer, EcsExprEmptyInitializer, EcsExprUnary, EcsExprBinary, EcsExprIdentifier, EcsExprVariable, EcsExprGlobalVariable, EcsExprFunction, EcsExprMethod, EcsExprTemplate, EcsExprMember, EcsExprSwizzle, EcsExprElement, EcsExprComponent, EcsExprHas, EcsExprCast, EcsExprCastNumber, EcsExprMatch, EcsExprRange, EcsExprNew, EcsExprScript } ecs_expr_node_kind_t; struct ecs_expr_node_t { ecs_expr_node_kind_t kind; ecs_size_t alloc_size; ecs_entity_t type; const ecs_type_info_t *type_info; const char *pos; const char *end; }; typedef struct ecs_expr_value_node_t { ecs_expr_node_t node; void *ptr; ecs_expr_small_value_t storage; } ecs_expr_value_node_t; typedef struct ecs_expr_format_t { const char *pos; ecs_expr_node_t *width; ecs_expr_node_t *precision; char fill; char alignment; char notation; bool sign; bool leading_zero; bool is_present; } ecs_expr_format_t; typedef struct ecs_expr_fragment_t { char *text; ecs_expr_node_t *expr; ecs_expr_format_t format; } ecs_expr_fragment_t; typedef struct ecs_expr_interpolated_string_t { ecs_expr_node_t node; char *value; /* modified by parser */ char *buffer; /* for storing expr tokens */ ecs_size_t buffer_size; ecs_vec_t fragments; } ecs_expr_interpolated_string_t; typedef struct ecs_expr_initializer_element_t { const char *member; ecs_expr_node_t *key; ecs_expr_node_t *value; uintptr_t offset; ecs_token_kind_t operator; } ecs_expr_initializer_element_t; typedef struct ecs_expr_initializer_t { ecs_expr_node_t node; ecs_vec_t elements; const ecs_type_info_t *type_info; bool is_collection; bool is_dynamic; bool is_partial; } ecs_expr_initializer_t; typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; ecs_entity_t global; /* Entity of the global variable, if any */ ecs_id_t global_component; /* Component that stores the global value. Is * EcsScriptConstVar or EcsScriptMutVar. */ int32_t sp; /* For fast variable lookups */ bool owns_name; /* Whether name is allocated with script allocator */ } ecs_expr_variable_t; typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; ecs_expr_node_t *expr; int32_t symbol; } ecs_expr_identifier_t; typedef struct ecs_expr_unary_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_token_kind_t operator; } ecs_expr_unary_t; typedef struct ecs_expr_binary_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *right; ecs_token_kind_t operator; /* For vector operations */ ecs_entity_t vector_type; int32_t vector_count; } ecs_expr_binary_t; typedef struct ecs_expr_member_t { ecs_expr_node_t node; ecs_expr_node_t *left; const char *member_name; uintptr_t offset; } ecs_expr_member_t; typedef struct ecs_expr_swizzle_t { ecs_expr_node_t node; ecs_expr_node_t *left; const char *name; int32_t count; ecs_size_t elem_size; uint16_t src[FLECS_EXPR_SWIZZLE_MAX]; uint16_t dst[FLECS_EXPR_SWIZZLE_MAX]; bool expand; } ecs_expr_swizzle_t; typedef struct ecs_expr_function_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_initializer_t *args; const char *function_name; ecs_function_calldata_t calldata; } ecs_expr_function_t; typedef struct ecs_expr_element_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *index; uint64_t dyn_input; ecs_size_t elem_size; int32_t elem_count; } ecs_expr_element_t; typedef struct ecs_expr_has_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *first; ecs_expr_node_t *second; ecs_id_t id; uint64_t dyn_input; } ecs_expr_has_t; typedef void (*flecs_expr_cast_t)(void *dst, const void *src); typedef struct ecs_expr_cast_t { ecs_expr_node_t node; ecs_expr_node_t *expr; flecs_expr_cast_t cast; } ecs_expr_cast_t; typedef struct ecs_expr_match_element_t { ecs_expr_node_t *compare; ecs_expr_node_t *expr; } ecs_expr_match_element_t; typedef struct ecs_expr_match_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_vec_t elements; ecs_expr_match_element_t any; } ecs_expr_match_t; typedef struct ecs_expr_range_t { ecs_expr_node_t node; ecs_expr_node_t *from; ecs_expr_node_t *to; } ecs_expr_range_t; typedef struct ecs_expr_new_t { ecs_expr_node_t node; ecs_script_entity_t *entity; } ecs_expr_new_t; typedef struct ecs_expr_script_t { ecs_expr_node_t node; ecs_script_t *script; } ecs_expr_script_t; ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type); ecs_expr_variable_t* flecs_expr_variable_from( ecs_script_t *script, ecs_expr_node_t *node, const char *name); ecs_expr_member_t* flecs_expr_member_from( ecs_script_t *script, ecs_expr_node_t *node, const char *name); ecs_expr_swizzle_t* flecs_expr_swizzle_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_expr_node_t *left, const char *name); ecs_expr_value_node_t* flecs_expr_bool( ecs_parser_t *parser, bool value); ecs_expr_value_node_t *flecs_expr_char( ecs_parser_t *parser, const char *value); ecs_expr_value_node_t* flecs_expr_int( ecs_parser_t *parser, int64_t value); ecs_expr_value_node_t* flecs_expr_uint( ecs_parser_t *parser, uint64_t value); ecs_expr_value_node_t* flecs_expr_float( ecs_parser_t *parser, double value); ecs_expr_value_node_t* flecs_expr_string( ecs_parser_t *parser, const char *value); ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( ecs_parser_t *parser, const char *value); ecs_expr_initializer_t* flecs_expr_initializer( ecs_parser_t *parser); ecs_expr_identifier_t* flecs_expr_identifier( ecs_parser_t *parser, const char *value); ecs_expr_variable_t* flecs_expr_variable( ecs_parser_t *parser, const char *value); ecs_expr_unary_t* flecs_expr_unary( ecs_parser_t *parser); ecs_expr_binary_t* flecs_expr_binary( ecs_parser_t *parser); ecs_expr_member_t* flecs_expr_member( ecs_parser_t *parser); ecs_expr_function_t* flecs_expr_function( ecs_parser_t *parser); ecs_expr_element_t* flecs_expr_element( ecs_parser_t *parser); ecs_expr_has_t* flecs_expr_has( ecs_parser_t *parser); ecs_expr_match_t* flecs_expr_match( ecs_parser_t *parser); ecs_expr_range_t* flecs_expr_range( ecs_parser_t *parser); ecs_expr_new_t* flecs_expr_new( ecs_parser_t *parser); ecs_expr_script_t* flecs_expr_script( ecs_parser_t *parser); bool flecs_expr_explicit_cast_allowed( ecs_world_t *world, ecs_entity_t from, ecs_entity_t to); ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type); #endif #ifndef FLECS_EXPR_SCRIPT_VISIT_H #define FLECS_EXPR_SCRIPT_VISIT_H typedef struct { ecs_primitive_kind_t kind; ecs_size_t size; int8_t expressiveness; int8_t storage; bool integer; bool signed_integer; bool floating_point; } flecs_expr_type_info_t; const flecs_expr_type_info_t* flecs_expr_type_info( ecs_entity_t type); void flecs_expr_visit_error_( const ecs_script_t *script, const void *node, const char *fmt, ...); #define flecs_expr_visit_error(script, node, ...) \ flecs_expr_visit_error_(script, node, __VA_ARGS__) int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t **node, const ecs_expr_eval_desc_t *desc); int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node, const ecs_expr_eval_desc_t *desc); int flecs_expr_visit_fold_const( ecs_script_t *script, ecs_expr_node_t **node, const ecs_expr_eval_desc_t *desc); int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, const ecs_expr_eval_desc_t *desc, ecs_value_t *out); void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node); typedef int (*flecs_expr_visit_action_t)( ecs_expr_node_t **node, void *ctx); int flecs_expr_visit_children( ecs_expr_node_t *node, flecs_expr_visit_action_t action, void *ctx); struct ecs_script_ref_t; typedef int (*flecs_expr_ref_action_t)( const struct ecs_script_ref_t *ref, ecs_expr_node_t *dynamic, void *ctx); int flecs_expr_visit_refs( const ecs_script_t *script, ecs_expr_node_t *node, flecs_expr_ref_action_t action, void *ctx); #endif int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, const ecs_expr_value_t *src); int flecs_value_binary( const ecs_script_t *script, const ecs_expr_node_t *node, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, ecs_token_kind_t operator); int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_token_kind_t operator); const char* flecs_script_parse_expr( ecs_parser_t *parser, const char *pos, ecs_token_kind_t left_oper, ecs_expr_node_t **out); const char* flecs_script_parse_initializer( ecs_parser_t *parser, const char *pos, char until, ecs_expr_node_t **node_out); const char* flecs_expr_format_parse( ecs_parser_t *parser, const char *pos, ecs_expr_format_t *format, const ecs_expr_eval_desc_t *desc); int flecs_expr_format_value( const ecs_script_t *script, const ecs_expr_node_t *node, const ecs_value_t *value, const ecs_expr_format_t *format, int32_t width, int32_t precision, ecs_strbuf_t *buf); void flecs_expr_to_str_buf( const ecs_script_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf, bool colors); bool flecs_string_is_interpolated( const char *str); char* flecs_string_escape( char *str); bool flecs_value_is_0( const ecs_value_t *value); bool flecs_expr_is_type_integer( ecs_entity_t type); bool flecs_expr_is_type_number( ecs_entity_t type); bool flecs_expr_is_type_signed_integer( ecs_entity_t type); bool flecs_expr_is_type_unsigned_integer( ecs_entity_t type); bool flecs_expr_is_type_float( ecs_entity_t type); bool flecs_expr_is_type_string( ecs_entity_t type); ecs_size_t flecs_expr_storage_size( ecs_entity_t type); typedef enum flecs_expr_num_class_t { EcsExprNumNone, EcsExprNumSigned, EcsExprNumUnsigned, EcsExprNumFloat, EcsExprNumChar } flecs_expr_num_class_t; flecs_expr_num_class_t flecs_expr_num_class( ecs_entity_t type, ecs_size_t *size); flecs_expr_cast_t flecs_expr_cast_handler( ecs_entity_t from, ecs_entity_t to); static inline FLECS_ALWAYS_INLINE int flecs_expr_binary_eval( const ecs_script_t *script, const ecs_expr_binary_t *node, const ecs_value_t *left, const ecs_value_t *right, void *out, ecs_token_kind_t operator) { if (!node->vector_count) { ecs_value_t out_value = { .type = node->node.type, .ptr = out }; return flecs_value_binary(script, &node->node, left, right, &out_value, operator); } ecs_entity_t vector_type = node->vector_type; ecs_value_t left_value = { .ptr = left->ptr, .type = vector_type }; ecs_value_t right_value = { .ptr = right->ptr, .type = vector_type }; ecs_value_t out_value = { .ptr = out, .type = vector_type }; ecs_size_t size = flecs_expr_storage_size(vector_type); ecs_size_t right_offset = 0; if (left->type == right->type) { right_offset = size; } int32_t i; for (i = 0; i < node->vector_count; i ++) { if (flecs_value_binary(script, &node->node, &left_value, &right_value, &out_value, operator)) { return -1; } left_value.ptr = ECS_OFFSET(left_value.ptr, size); right_value.ptr = ECS_OFFSET(right_value.ptr, right_offset); out_value.ptr = ECS_OFFSET(out_value.ptr, size); } return 0; } int flecs_expr_identifier_set( const ecs_script_t *script, const ecs_expr_identifier_t *node, ecs_entity_t entity, const ecs_value_t *out); const ecs_script_var_t* flecs_expr_var_check( const ecs_script_t *script, const ecs_expr_variable_t *node, const ecs_script_var_t *var); int flecs_expr_element_get( const ecs_script_t *script, const ecs_expr_element_t *node, ecs_type_kind_t kind, const ecs_value_t *expr, const ecs_value_t *index, void **out); const void* flecs_expr_component_get( const ecs_script_t *script, struct ecs_script_eval_visitor_t *v, const ecs_expr_element_t *node, ecs_entity_t entity, ecs_entity_t component); bool flecs_expr_has_eval( const ecs_script_t *script, struct ecs_script_eval_visitor_t *v, const ecs_expr_has_t *node, ecs_entity_t entity); void* flecs_expr_template_ref_value( const ecs_script_t *script, const ecs_expr_function_t *node, const ecs_script_template_ref_t *ref); int flecs_expr_range_eval( ecs_world_t *world, ecs_entity_t type, int32_t from, int32_t to, void *out); ecs_entity_t flecs_expr_script_new( ecs_world_t *world, ecs_script_t *script); static inline int flecs_expr_initializer_check( const ecs_script_t *script, const void *node, ecs_entity_t type, uintptr_t offset, ecs_size_t size, uintptr_t dst_size) { if (offset > dst_size || flecs_uto(uintptr_t, size) > (dst_size - offset)) { flecs_expr_visit_error(script, node, "initializer of type '%s' writes past end of target value", flecs_errstr(ecs_get_path(script->world, type))); return -1; } return 0; } int flecs_expr_swizzle_set( ecs_meta_cursor_t *cur, const ecs_expr_swizzle_t *swizzle, const void *src); ecs_expr_swizzle_t* flecs_expr_expand_swizzle_get( ecs_expr_node_t *node); const ecs_expr_variable_t* flecs_expr_template_ref_var( const ecs_expr_node_t *left); #endif #ifndef FLECS_SCRIPT_VISIT_H #define FLECS_SCRIPT_VISIT_H typedef struct ecs_script_visit_t ecs_script_visit_t; /* Visitors track both scope nodes and statement nodes on the traversal stack. * For deeply nested scopes this requires roughly 2x the parser nesting depth, * plus the root scope node. */ #define ECS_SCRIPT_VISIT_MAX_DEPTH ((ECS_PARSER_MAX_RECURSION_DEPTH * 2) + 4) struct ecs_script_visit_t { ecs_script_impl_t *script; ecs_script_node_t* nodes[ECS_SCRIPT_VISIT_MAX_DEPTH]; ecs_script_node_t *prev, *next; int32_t depth; }; #endif #ifndef FLECS_SCRIPT_EVAL_H #define FLECS_SCRIPT_EVAL_H #ifndef FLECS_SCRIPT_RUNTIME_H #define FLECS_SCRIPT_RUNTIME_H typedef struct ecs_script_with_value_t { ecs_value_t value; const ecs_type_info_t *ti; } ecs_script_with_value_t; struct ecs_script_runtime_t { ecs_script_runtime_t *parent; ecs_allocator_t *allocator; ecs_expr_stack_t *expr_stack; ecs_stack_t stack; ecs_vec_t using; ecs_vec_t with; ecs_vec_t annot; ecs_vec_t pending_resolves; ecs_vec_t ir_vms; ecs_vec_t call_runtimes; /* Template instances with changed props whose re-evaluation is deferred * until the command queue is flushed. One marker event is enqueued per * flush instead of one event per instance. */ ecs_vec_t template_pending; bool template_pending_marker; bool template_pending_active; /* Tasks created by async blocks. Only used by the world runtime, and * advanced by ecs_script_tasks_progress(). */ ecs_vec_t async_tasks; bool async_progressing; /* Tag added to entities created by the currently evaluating managed * script. Carried on the world runtime so evaluation triggered from hooks * (such as template instantiation) inherits it. */ ecs_id_t current_tag; char *error_name; int32_t include_depth; /* Nesting level of include statements. Guards against scripts that * (indirectly) include themselves. */ int32_t include_nesting; /* Nesting level of template instantiations. Guards against templates that * (indirectly) instantiate themselves. */ int32_t template_depth; bool resolving; bool error; }; ecs_script_runtime_t* flecs_script_runtime_get( ecs_world_t *world); ecs_script_runtime_t* flecs_script_runtime_new_shared( ecs_script_runtime_t *parent); ecs_expr_stack_t* flecs_script_runtime_expr_stack( ecs_script_runtime_t *r); ecs_script_runtime_t* flecs_script_runtime_acquire_call( ecs_script_runtime_t *r); void flecs_script_runtime_release_call( ecs_script_runtime_t *r, ecs_script_runtime_t *call); void flecs_script_runtime_error_reset( ecs_script_runtime_t *r); void ecs_script_runtime_clear( ecs_script_runtime_t *r); static inline FLECS_ALWAYS_INLINE int flecs_function_calldata_invoke( ecs_world_t *world, const ecs_function_calldata_t *calldata, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { ecs_function_ctx_t ctx = { .world = world, .function = calldata->function, .ctx = calldata->ctx }; if (calldata->vector_elem_count) { calldata->is.vector_callback(&ctx, argc, argv, result, calldata->vector_elem_count); } else { calldata->is.callback(&ctx, argc, argv, result); } ecs_script_runtime_t *r = flecs_script_runtime_get(world); if (r->error) { r->error = false; return -1; } return 0; } static inline FLECS_ALWAYS_INLINE int flecs_expr_function_call( const ecs_script_t *script, const ecs_expr_function_t *node, int32_t argc, const ecs_value_t *argv, ecs_value_t *out) { const char *kind = node->node.kind == EcsExprMethod ? "method" : "function"; #ifdef FLECS_SCRIPT_ASYNC if (node->calldata.async_callback) { flecs_expr_visit_error(script, node, "async %s '%s' requires await", kind, node->function_name); return -1; } #endif if (flecs_function_calldata_invoke( script->world, &node->calldata, argc, argv, out)) { flecs_expr_visit_error(script, node, "error in script %s '%s'", kind, node->function_name); return -1; } return 0; } #endif #ifndef FLECS_SCRIPT_VISIT_EVAL_H #define FLECS_SCRIPT_VISIT_EVAL_H typedef struct flecs_script_entity_state_t flecs_script_entity_state_t; typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; ecs_script_runtime_t *r; ecs_script_template_t *template; /* Set when creating template */ ecs_script_template_t *instance_template; ecs_entity_t template_entity; /* Set when creating template instance */ ecs_entity_t template_instance; ecs_entity_t body_template; /* Template of the body being instantiated. Set * for the entire body, at any scope depth. */ ecs_entity_t script_entity; ecs_id_t script_tag; /* Added to entities created by managed scripts */ ecs_entity_t module; ecs_entity_t parent; flecs_script_entity_state_t *entity; ecs_entity_t with_relationship; int32_t with_relationship_sp; bool is_with_scope; ecs_script_vars_t *vars; void *type_visitor; uint64_t input; uint64_t internal; ecs_script_state_t *state; int32_t symbol_offset; int32_t visit; int32_t scope_slot; int32_t for_slot; bool force; const ecs_script_template_ref_t *id_ref; } ecs_script_eval_visitor_t; int flecs_script_eval( const ecs_script_t *script, const ecs_script_eval_desc_t *desc, ecs_id_t tag, uint64_t input, ecs_script_eval_result_t *result); /* Statement evaluation state. Owned by the frame stack of the runner (or the * C stack for the check visitor), never by the AST, so that concurrent * evaluations of the same script cannot interfere. */ typedef struct flecs_script_scope_state_t { ecs_entity_t parent; int32_t scope_slot; } flecs_script_scope_state_t; struct flecs_script_entity_state_t { ecs_script_entity_t *node; ecs_entity_t eval; ecs_entity_t eval_kind; flecs_script_entity_state_t *prev_entity; ecs_entity_t prev_template_entity; bool prev_force; bool prev_is_with_scope; int32_t symbol; int32_t for_slot; bool created; }; void flecs_script_scope_add_ids( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_script_scope_t *scope, const ecs_id_t *ids, int32_t count); FLECS_API int flecs_script_eval_entity_enter( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node, flecs_script_entity_state_t *state); int flecs_script_eval_entity_enter_w_name( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node, const ecs_value_t *name, flecs_script_entity_state_t *state); void flecs_script_eval_entity_leave( ecs_script_eval_visitor_t *v, flecs_script_entity_state_t *state); typedef struct flecs_script_with_state_t { ecs_stack_cursor_t *cursor; int32_t with_count; bool force; bool is_with_scope; } flecs_script_with_state_t; int flecs_script_eval_with_enter( ecs_script_eval_visitor_t *v, ecs_script_with_t *node, flecs_script_with_state_t *state); void flecs_script_eval_with_leave( ecs_script_eval_visitor_t *v, const flecs_script_with_state_t *state); typedef struct flecs_script_pair_scope_state_t { ecs_entity_t with_relationship; ecs_entity_t second; int32_t with_relationship_sp; bool force; } flecs_script_pair_scope_state_t; void flecs_script_eval_pair_scope_leave( ecs_script_eval_visitor_t *v, const flecs_script_pair_scope_state_t *state); typedef enum flecs_script_for_kind_t { FlecsScriptForRange, FlecsScriptForArray, FlecsScriptForVector, FlecsScriptForMap } flecs_script_for_kind_t; typedef struct flecs_script_for_state_t { flecs_script_for_kind_t kind; ecs_script_vars_t *vars; int32_t var_base; int32_t elem_var; int32_t key_var; int32_t index_var; ecs_entity_t var_types[3]; int32_t index; int32_t from; int32_t count; ecs_size_t elem_size; ecs_size_t key_size; void *elems; ecs_value_t collection; ecs_map_iter_t map_it; int32_t for_slot; bool force; } flecs_script_for_state_t; typedef struct flecs_script_frame_t { ecs_script_node_t *node; int32_t pc; union { flecs_script_scope_state_t scope; flecs_script_entity_state_t entity; flecs_script_for_state_t for_; flecs_script_with_state_t with; flecs_script_pair_scope_state_t pair_scope; struct { bool force; } if_; } state; } flecs_script_frame_t; typedef enum flecs_script_run_status_t { FlecsScriptRunDone, FlecsScriptRunError, FlecsScriptRunSuspended } flecs_script_run_status_t; #define ECS_SCRIPT_FRAME_CHUNK_SIZE (16) #ifdef FLECS_SCRIPT_ASYNC typedef struct flecs_script_async_state_t { ecs_script_future_t *future; ecs_entity_t entity; ecs_script_future_t *thrown; const ecs_script_node_t *throw_node; } flecs_script_async_state_t; #endif typedef struct ecs_script_runner_t { ecs_script_eval_visitor_t v; flecs_script_frame_t *frames[(ECS_SCRIPT_VISIT_MAX_DEPTH + ECS_SCRIPT_FRAME_CHUNK_SIZE - 1) / ECS_SCRIPT_FRAME_CHUNK_SIZE]; flecs_script_frame_t frame_storage[ECS_SCRIPT_FRAME_CHUNK_SIZE]; int32_t frame_count; ecs_entity_t last_entity; /* Result of last completed entity frame */ bool can_suspend; #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_state_t async; #endif } ecs_script_runner_t; void flecs_script_runner_init( ecs_script_runner_t *r, const ecs_script_eval_visitor_t *v); void flecs_script_runner_fini( ecs_script_runner_t *r, const ecs_script_eval_desc_t *desc); flecs_script_run_status_t flecs_script_runner_run_scope( ecs_script_runner_t *r, ecs_script_scope_t *scope); /* Evaluate an entity statement embedded in an expression, either in the * context of an in-progress evaluation (v != NULL) or standalone. */ int flecs_script_eval_entity( ecs_script_eval_visitor_t *v, const ecs_script_t *script, const ecs_script_vars_t *vars, ecs_script_entity_t *node, ecs_entity_t *out); void flecs_script_runner_abandon( ecs_script_runner_t *r); const char* flecs_script_runner_stmt_pos( const ecs_script_runner_t *r); void flecs_script_scope_push( ecs_script_runner_t *r, ecs_script_scope_t *scope); void flecs_script_frame_pop( ecs_script_runner_t *r); void flecs_script_eval_error_( ecs_script_eval_visitor_t *v, const ecs_script_node_t *node, const char *fmt, ...); #define flecs_script_eval_error(v, node, ...)\ flecs_script_eval_error_(v, (const ecs_script_node_t*)node, __VA_ARGS__) bool flecs_script_is_builtin( const ecs_world_t *world, ecs_entity_t e); bool flecs_script_can_own_entity( const ecs_world_t *world, ecs_entity_t script, ecs_entity_t e); ecs_entity_t flecs_script_new_entity( ecs_script_eval_visitor_t *v, ecs_entity_t parent, const char *name, bool non_fragmenting_parent); ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, const char *name); const ecs_type_info_t* flecs_script_get_type_info( ecs_script_eval_visitor_t *v, const void *node, ecs_id_t id); ecs_expr_eval_desc_t flecs_script_expr_desc( ecs_script_eval_visitor_t *v, ecs_entity_t type); int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, ecs_expr_node_t **expr_ptr, ecs_value_t *value); int flecs_script_assign_value( ecs_script_eval_visitor_t *v, ecs_script_assign_t *node, const void *src); ecs_entity_t flecs_script_lookup_name( ecs_script_eval_visitor_t *v, const char *name); int flecs_script_eval_id_elem( ecs_script_eval_visitor_t *v, const void *node, const char *const *name, ecs_entity_t eval, int32_t slot, int32_t sp, ecs_entity_t *elem); int flecs_script_eval_id_resolve( ecs_script_eval_visitor_t *v, const void *node, const ecs_script_id_t *id, const char *first_name, const char *second_name, int32_t index, ecs_id_t *out); int flecs_script_eval_interface_id( ecs_script_eval_visitor_t *v, const void *node, const ecs_script_id_t *id, ecs_id_t *out); int flecs_script_eval_tag_id( ecs_script_eval_visitor_t *v, const ecs_script_tag_t *node, ecs_id_t id, bool resolved); int flecs_script_eval_with_tag_id( ecs_script_eval_visitor_t *v, const ecs_script_tag_t *node, ecs_id_t id); int flecs_script_eval_component_check( ecs_script_eval_visitor_t *v, const ecs_script_component_t *node, ecs_id_t id, bool resolved); int flecs_script_eval_component_visit( ecs_script_eval_visitor_t *v, const ecs_script_component_t *node, ecs_id_t id, const EcsScriptVisitor *visitor); int flecs_script_eval_component_add( ecs_script_eval_visitor_t *v, const ecs_script_component_t *node, ecs_id_t id); int flecs_script_value_to_bool( ecs_script_eval_visitor_t *v, const void *node, const ecs_value_t *value, bool *out); bool flecs_script_stmt_support( const ecs_script_node_t *node); static inline bool flecs_script_eval_force( const ecs_script_eval_visitor_t *v, const ecs_script_node_t *node, bool prev) { return prev || (node->direct_input & v->input) || (node->direct_internal & v->internal); } static inline void flecs_script_const_var_init( ecs_script_var_t *var, const ecs_type_info_t *ti, ecs_entity_t type, void *ptr, bool owned) { var->is_const = true; var->type_info = ti; var->value.type = type; var->value.ptr = ptr; var->owned = owned; } FLECS_API void flecs_script_eval_visit_init( const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc); /* Push/pop a variable scope that exposes external variables to evaluation. * Used when evaluating entity statements embedded in expressions, which can * reference variables of the expression they're embedded in. */ void flecs_script_eval_push_vars( ecs_script_eval_visitor_t *v, const ecs_script_vars_t *vars); void flecs_script_eval_pop_vars( ecs_script_eval_visitor_t *v); FLECS_API void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc); void flecs_script_eval_begin( ecs_script_eval_visitor_t *v, uint64_t input, int32_t visit); void flecs_script_eval_cleanup( ecs_script_eval_visitor_t *v, bool dirty); FLECS_API int flecs_script_eval_node( ecs_script_visit_t *v, ecs_script_node_t *node); int flecs_script_symbol_lookup( const ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_entity_t from, const char *name, ecs_flags32_t lookup_kind, flecs_script_symbol_t *symbol); int flecs_script_id_elem_lookup( const ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_entity_t first, const char *name, ecs_flags32_t lookup_kind, ecs_entity_t *from_out, flecs_script_symbol_t *symbol); int flecs_script_id_lookup( const ecs_script_t *script, const ecs_expr_eval_desc_t *desc, const char *first_name, const char *second_name, ecs_flags32_t lookup_kind, ecs_entity_t *first_out, ecs_id_t *id_out, const char **unresolved); ecs_entity_t flecs_script_symbol_entity( const ecs_script_eval_visitor_t *v, int32_t slot); void flecs_script_symbol_set( ecs_script_eval_visitor_t *v, int32_t slot, ecs_entity_t entity); int flecs_script_type_symbol_lookup( void *ctx, const char *name, flecs_script_symbol_t *symbol); int flecs_script_visit_type( ecs_script_eval_visitor_t *v, ecs_script_scope_t *scope); int flecs_script_visit_type_entity_expr( ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_script_eval_visitor_t *v, ecs_script_entity_t *entity); /* For statement (see visit_for.c) */ int flecs_script_step_for( ecs_script_runner_t *r, flecs_script_frame_t *frame); void flecs_script_eval_for_leave( ecs_script_eval_visitor_t *v, flecs_script_for_state_t *state); void flecs_script_for_prepare( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state); int flecs_script_for_init( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state, const ecs_value_t *collection); int flecs_script_for_declare_vars( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state, bool alloc_elem); bool flecs_script_for_next( flecs_script_for_state_t *state); /* Runtime bookkeeping helpers shared with the IR runtime */ ecs_value_t* flecs_script_with_append( ecs_script_eval_visitor_t *v, const ecs_type_info_t *ti); void flecs_script_with_set_count( ecs_script_eval_visitor_t *v, int32_t count); ecs_entity_t flecs_script_get_src( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_id_t id); bool flecs_script_can_default_ctor( ecs_world_t *world, ecs_id_t component); void flecs_script_track_component( ecs_script_eval_visitor_t *v, int32_t component_slot, ecs_id_t component); bool flecs_script_scope_visited( ecs_script_eval_visitor_t *v, int32_t slot); /* Functions shared between type and eval visitor */ ecs_script_computed_t* flecs_script_computed_get( ecs_script_eval_visitor_t *v, const ecs_script_var_node_t *node); bool flecs_script_computed_store( ecs_script_eval_visitor_t *v, ecs_script_computed_t *slot, int32_t index, const ecs_type_info_t *ti, const void *value); int flecs_script_eval_const_cached( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node); int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node, bool export); int flecs_script_eval_pair_elem( ecs_script_eval_visitor_t *v, const void *node, const char *const *name, const char *str, ecs_entity_t eval, int32_t symbol, int32_t sp, ecs_entity_t *out); static inline FLECS_ALWAYS_INLINE int flecs_script_eval_pair_scope_enter( ecs_script_eval_visitor_t *v, const ecs_script_pair_scope_t *node, const char *first_name, const char *second_name, flecs_script_pair_scope_state_t *state) { const ecs_script_id_t *id = &node->id; ecs_entity_t first, second; if (flecs_script_eval_pair_elem(v, node, id->first_expr ? &first_name : NULL, id->first, id->first_eval, id->first_symbol, id->first_sp, &first)) { return -1; } if (flecs_script_eval_pair_elem(v, node, id->second_expr ? &second_name : NULL, id->second, id->second_eval, id->second_symbol, id->second_sp, &second)) { return -1; } state->with_relationship = v->with_relationship; state->second = 0; state->with_relationship_sp = v->with_relationship_sp; state->force = v->force; v->with_relationship = first; if (state->with_relationship != first) { ecs_value_t *value = flecs_script_with_append(v, NULL); value->type = ecs_pair(first, second); value->ptr = NULL; v->with_relationship_sp = ecs_vec_count(&v->r->with) - 1; } else { ecs_value_t *value = &ecs_vec_get_t( &v->r->with, ecs_script_with_value_t, v->with_relationship_sp)->value; ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, ECS_INTERNAL_ERROR, NULL); state->second = ECS_PAIR_SECOND(value->type); value->type = ecs_pair(first, second); value->ptr = NULL; } v->force = flecs_script_eval_force(v, &node->node, state->force); return 0; } #endif #ifndef FLECS_SCRIPT_VISIT_TYPE_H #define FLECS_SCRIPT_VISIT_TYPE_H typedef struct ecs_script_type_table_t { int32_t parent; ecs_script_entity_t *owner; } ecs_script_type_table_t; typedef struct ecs_script_type_entity_t { ecs_script_entity_t *node; ecs_script_entity_t *parent_node; ecs_entity_t parent; int32_t child_table; int32_t slot; bool in_template; bool in_for; } ecs_script_type_entity_t; typedef struct ecs_script_type_interface_var_t { int32_t sp; ecs_entity_t interface; } ecs_script_type_interface_var_t; typedef struct ecs_script_type_visitor_t { ecs_script_eval_visitor_t *v; ecs_vec_t tables; ecs_vec_t entities; ecs_vec_t skipped_vars; ecs_vec_t interface_vars; ecs_hashmap_t names; int32_t table; int32_t control_depth; int32_t for_depth; int32_t skip_count; int32_t annot_start; bool template_scope; bool function_scope; bool async_scope; } ecs_script_type_visitor_t; int flecs_script_type_scope( ecs_script_type_visitor_t *t, ecs_script_scope_t *scope, int32_t table, bool push_vars, bool allow_type); int32_t flecs_script_type_table_new( ecs_script_type_visitor_t *t, int32_t parent, ecs_script_entity_t *owner); int32_t flecs_script_type_slot_new( ecs_script_type_visitor_t *t, int32_t expected); bool flecs_script_type_is_skipped_var( ecs_script_type_visitor_t *t, const char *name); #endif #ifndef FLECS_SCRIPT_ASYNC_PRIVATE_H #define FLECS_SCRIPT_ASYNC_PRIVATE_H #ifdef FLECS_SCRIPT_ASYNC typedef enum flecs_script_future_state_t { FlecsScriptFuturePending, FlecsScriptFutureResolved, FlecsScriptFutureRejected, FlecsScriptFutureCancelled } flecs_script_future_state_t; struct ecs_script_future_t { ecs_entity_t type; ecs_value_t value; char *error; ecs_entity_t error_id; void *ctx; ecs_ctx_free_t ctx_free; ecs_async_function_cancel_t cancel; ecs_function_ctx_t function_ctx; ecs_os_mutex_t mutex; int32_t refs; flecs_script_future_state_t state; }; ecs_expr_function_t* flecs_script_await_call( ecs_script_eval_visitor_t *v, const ecs_script_node_t *stmt, ecs_expr_node_t *expr); void flecs_script_await_launch( ecs_world_t *world, flecs_script_async_state_t *async, const ecs_expr_function_t *call, ecs_value_t *argv); flecs_script_future_state_t flecs_script_future_poll( const ecs_script_future_t *future); /* Async statement evaluation, invoked by the script runner */ int flecs_script_step_await( ecs_script_runner_t *r, ecs_script_node_t *stmt); /* Enter a catch clause if the frame is a try block that catches the pending * thrown error. Returns false if the error should continue to unwind. */ bool flecs_script_try_catch( ecs_script_runner_t *r, flecs_script_frame_t *frame); int32_t flecs_script_catch( ecs_script_eval_visitor_t *v, flecs_script_async_state_t *state, const ecs_script_try_t *node); int flecs_script_await_poll( ecs_script_eval_visitor_t *v, flecs_script_async_state_t *async, const ecs_script_node_t *stmt, ecs_script_future_t **ready); void flecs_script_async_fini( flecs_script_async_state_t *state); void flecs_script_throw_clear( flecs_script_async_state_t *state); void flecs_script_report_throw( ecs_script_eval_visitor_t *v, flecs_script_async_state_t *state); void flecs_script_async_import( ecs_world_t *world); /* Create a task for an async block. Invoked when the async statement is * evaluated. The task captures the variables visible at the statement and is * owned by the template instance (for templates) or the script. */ int flecs_script_async_spawn( ecs_script_eval_visitor_t *v, ecs_script_async_t *node); /* Free all tasks in a vector of async block tasks. */ void flecs_script_async_tasks_free( ecs_vec_t *tasks); void flecs_script_async_cleanup( ecs_script_eval_visitor_t *v); #endif // FLECS_SCRIPT_ASYNC #endif // FLECS_SCRIPT_ASYNC_PRIVATE_H int flecs_script_apply_annot( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node, ecs_entity_t entity, ecs_script_annot_t *annot); void flecs_script_apply_tree_annot( ecs_script_annot_t *annot, ecs_script_entity_t *node); int flecs_script_compile( ecs_script_eval_visitor_t *v); /* Include visitor (runs before script evaluation, see visit_include.c) */ int flecs_script_visit_include( ecs_script_eval_visitor_t *v, ecs_script_scope_t *scope); int flecs_script_eval_include( ecs_script_eval_visitor_t *v, ecs_script_include_t *node); /* Type visitors (implement struct/enum/bitmask initializer syntax) */ int flecs_script_struct_visit( const ecs_script_visitor_ctx_t *ctx); bool flecs_script_struct_member_is_inherited( ecs_world_t *world, ecs_entity_t struct_type, const char *name); int flecs_script_enum_visit( const ecs_script_visitor_ctx_t *ctx); int flecs_script_bitmask_visit( const ecs_script_visitor_ctx_t *ctx); /* User functions (functions defined in scripts) */ typedef struct ecs_script_user_function_t { ecs_script_t *script; ecs_script_function_node_t *node; ecs_vec_t refs; ecs_vec_t using; } ecs_script_user_function_t; void flecs_script_user_function_bind( ecs_script_eval_visitor_t *v, const ecs_script_user_function_t *uf, int32_t argc, const ecs_value_t *argv, bool named); void flecs_script_user_function_callback( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result); void flecs_script_user_function_ctx_free( void *ctx); int flecs_script_eval_function( ecs_script_eval_visitor_t *v, ecs_script_function_node_t *node, ecs_entity_t fn_entity); #endif #ifndef FLECS_SCRIPT_REFS_H #define FLECS_SCRIPT_REFS_H typedef struct ecs_script_ref_t { ecs_entity_t entity; const char *name; ecs_id_t component; ecs_entity_t observer; uint64_t input; bool is_has; bool is_resolve; } ecs_script_ref_t; typedef struct ecs_script_ref_ctx_t { ecs_entity_t script; ecs_entity_t instance; char *name; uint64_t input; } ecs_script_ref_ctx_t; typedef struct EcsScriptUpdateEvent { ecs_entity_t script; uint64_t input; } EcsScriptUpdateEvent; extern ECS_COMPONENT_DECLARE(EcsScriptUpdateEvent); ecs_script_ref_t* flecs_script_ref_add( ecs_vec_t *refs, const ecs_script_ref_t *value, bool match_input); ecs_script_ref_t* flecs_script_ref_ensure( ecs_vec_t *refs, const ecs_script_ref_t *value); const char* flecs_script_name_leaf( const char *name); void flecs_script_update_ref_observers( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance, ecs_vec_t *refs, ecs_vec_t *observers, ecs_iter_action_t callback); void flecs_script_record_dyn_ref( ecs_script_impl_t *impl, ecs_entity_t entity, ecs_id_t component, uint64_t input, bool is_has); void flecs_script_update_dyn_observers( ecs_world_t *world, ecs_entity_t script, ecs_script_impl_t *impl, ecs_vec_t *observers, uint64_t input); void flecs_script_update_resolve_observers( ecs_world_t *world, ecs_entity_t script, ecs_script_impl_t *impl, ecs_vec_t *observers); void flecs_script_ref_observers_fini( ecs_vec_t *observers); void flecs_script_ref_observers_clear( ecs_world_t *world, ecs_vec_t *observers); void flecs_script_ref_on_set( ecs_iter_t *it); void flecs_script_run_pending_resolves( ecs_world_t *world); void flecs_script_pending_resolve_remove( ecs_world_t *world, ecs_entity_t script); void flecs_script_refs_import( ecs_world_t *world); #endif #ifndef FLECS_SCRIPT_TEMPLATE_H #define FLECS_SCRIPT_TEMPLATE_H /* Maximum number of template instantiations that can be nested. Guards against * templates that (indirectly) instantiate themselves. */ #ifndef FLECS_SCRIPT_TEMPLATE_DEPTH_MAX #define FLECS_SCRIPT_TEMPLATE_DEPTH_MAX (64) #endif extern ECS_COMPONENT_DECLARE(EcsScriptTemplateRoot); typedef struct ecs_script_template_member_t { int32_t index; int32_t sp; uint64_t input; bool is_mut; bool is_vector; ecs_entity_t interface; } ecs_script_template_member_t; typedef struct { ecs_entity_t type; ecs_vec_t defaults; } ecs_script_template_vars_t; typedef struct ecs_script_template_capture_t { uint64_t outer_input; uint64_t outer_internal; uint64_t input; } ecs_script_template_capture_t; struct ecs_script_template_t { ecs_script_impl_t *script; ecs_script_template_vars_t props; ecs_script_template_vars_t muts; /* Template AST node */ ecs_script_template_node_t *node; /* Hoisted using statements */ ecs_vec_t using_; /* Hoisted variables */ ecs_script_vars_t *vars; ecs_vec_t capture_sp; ecs_vec_t capture_input; /* Prop and mut members in variable declaration order */ ecs_vec_t members; /* Type info for template component */ const ecs_type_info_t *type_info; /* Annotations to apply to template instance */ ecs_vec_t annot; /* Statically known component references used in the template body */ ecs_vec_t refs; /* Observers monitoring the refs, shared by all template instances */ ecs_vec_t observers; ecs_vec_t dynamic_refs; uint64_t async_input; int32_t symbol_offset; int32_t symbol_count; int32_t root_symbol; int32_t input_count; int32_t computed_count; int32_t scope_count; int32_t component_count; int32_t for_count; int32_t inherited_count; int32_t inherited_mut_count; ecs_entity_t base; ecs_entity_t parent_type; int32_t parent_sp; int32_t refcount; int32_t generation; /* Use non-fragmenting hierarchy */ bool non_fragmenting_parent; /* Template has interface-typed members that need input validation */ bool has_interface_members; }; /* Deferred template instance update, queued by the template on_set hook */ typedef struct ecs_script_template_pending_t { ecs_entity_t entity; ecs_entity_t template_entity; uint64_t input; int32_t depth; } ecs_script_template_pending_t; void flecs_script_template_pending_fini( ecs_vec_t *pending); typedef struct ecs_script_async_block_t { ecs_entity_t entity; ecs_script_scope_t *scope; int32_t scope_slot; } ecs_script_async_block_t; typedef struct EcsScriptTemplateRoot { ecs_script_state_t state; ecs_vec_t observers; ecs_vec_t tasks; /* vec, owned. Created by async blocks */ ecs_vec_t async_blocks; uint64_t changed; int32_t generation; } EcsScriptTemplateRoot; const ecs_member_t* flecs_script_template_parent_member( ecs_world_t *world, const ecs_script_template_t *template, const char *name, ecs_entity_t *component); int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *template); int flecs_script_template_eval_var( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node, bool mut); ecs_entity_t flecs_script_template_member_interface( const ecs_script_template_t *template, int32_t sp); ecs_entity_t flecs_script_template_prop_interface( const ecs_world_t *world, ecs_entity_t type, const char *member); bool flecs_script_template_member_is_vector( const ecs_script_template_t *template, int32_t sp); bool flecs_script_template_interface_accepts( const ecs_world_t *world, ecs_entity_t value, ecs_entity_t interface); char* flecs_script_template_expected_str( const ecs_world_t *world, ecs_entity_t interface); const char* flecs_script_template_interface_error( const ecs_world_t *world, ecs_entity_t value, ecs_entity_t interface); void flecs_script_template_ref_set( ecs_world_t *world, ecs_script_template_ref_t *ref, ecs_entity_t type, void *value); int flecs_script_template_ref_apply( ecs_world_t *world, const ecs_script_template_ref_t *ref, const ecs_type_info_t *ti, void *dst); int flecs_script_template_update_vars( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *node); void flecs_script_template_fini( ecs_script_impl_t *script, ecs_script_template_t *template); void flecs_script_template_import( ecs_world_t *world); #endif #ifndef FLECS_SCRIPT_DEPS_H #define FLECS_SCRIPT_DEPS_H int flecs_script_analyze_dependencies( ecs_script_eval_visitor_t *v, ecs_script_scope_t *scope); #endif #ifndef FLECS_SCRIPT_IR_H #define FLECS_SCRIPT_IR_H typedef enum ecs_script_ir_op_kind_t { EcsIrEnd, EcsIrLeave, EcsIrJump, EcsIrStmt, EcsIrStmtBlock, EcsIrMark, EcsIrAnnotClear, EcsIrScopeEnter, EcsIrEntityEnter, EcsIrWithEnter, EcsIrWithBody, EcsIrWithTag, EcsIrWithComponentBegin, EcsIrWithComponentEnd, EcsIrPairScopeEnter, EcsIrIfEnter, EcsIrForEnter, EcsIrForNext, EcsIrContinue, EcsIrWhileEnter, EcsIrWhileNext, EcsIrTryEnter, EcsIrAwaitStart, EcsIrAwaitLaunch, EcsIrAwaitPoll, EcsIrTag, EcsIrComponentBegin, EcsIrComponentEnd, EcsIrComponentVisitor, EcsIrUsing, EcsIrModule, EcsIrAnnot, EcsIrTemplate, EcsIrMutCheck, EcsIrAsync, EcsIrAssign, EcsIrConstEnd, EcsIrConstCached, EcsIrExprBegin, EcsIrExprEnd, EcsIrLoadConst, EcsIrLoadVar, EcsIrLoadGlobal, EcsIrLoadSymbol, EcsIrLookup, EcsIrMember, EcsIrSwizzle, EcsIrElement, EcsIrComponentGet, EcsIrHas, EcsIrUnary, EcsIrBinary, EcsIrBinaryI8, EcsIrBinaryI16, EcsIrBinaryI32, EcsIrBinaryI64, EcsIrBinaryU8, EcsIrBinaryU16, EcsIrBinaryU32, EcsIrBinaryU64, EcsIrBinaryF32, EcsIrBinaryF64, EcsIrJumpIfFalse, EcsIrJumpIfTrue, EcsIrCast, EcsIrCastNumber, EcsIrCall, EcsIrInitAlloc, EcsIrInitConst, EcsIrInitAssign, EcsIrInitOp, EcsIrInitSwizzle, EcsIrDynBegin, EcsIrDynPush, EcsIrDynPop, EcsIrDynNext, EcsIrDynMember, EcsIrDynKey, EcsIrDynSet, EcsIrDynSwizzle, EcsIrDynEnd, EcsIrStrBegin, EcsIrStrFrag, EcsIrStrValue, EcsIrStrCast, EcsIrStrFormat, EcsIrStrEnd, EcsIrMatchCmp, EcsIrMatchFail, EcsIrRange, EcsIrNew, EcsIrScript, EcsIrTemplateRef, EcsIrTemplateRefValue, EcsIrToBool, EcsIrReturn, EcsIrOpKindLast } ecs_script_ir_op_kind_t; #define EcsIrStmtAlways (1u << 0) #define EcsIrStmtSkip (1u << 1) #define EcsIrStmtCached (1u << 2) #define EcsIrScopeEntity (1u << 0) #define EcsIrIdDirect (1u << 1) #define EcsIrInPlace (1u << 0) #define EcsIrCallMethod (1u << 1) #define EcsIrAwaitVar (1u << 1) #define EcsIrAnnotNoTarget (1u << 0) #define EcsIrAnnotBadTarget (1u << 1) #define EcsIrComponentPartial (1u << 2) #define EcsIrTemplateRefVar (1u << 1) #define EcsIrElementMap (1u << 0) #define EcsIrElementVector (1u << 1) #define EcsIrEndsExpr (1u << 0) #define EcsIrVisitorStatic (1u << 0) #define EcsIrGlobalMut (1u << 0) #define EcsIrBinaryInPlace (1u << 15) typedef enum ecs_script_ir_profile_stat_t { EcsIrProfileRun, EcsIrProfileStmtRun, EcsIrProfileStmtSkipped, EcsIrProfileBatchAdd, EcsIrProfileBatchSkip, EcsIrProfileEntityCreate, EcsIrProfileEntityReuse, EcsIrProfileComponentSet, EcsIrProfileTagAdd, EcsIrProfileLast } ecs_script_ir_profile_stat_t; const char* flecs_script_ir_op_name( ecs_script_ir_op_kind_t kind); typedef struct ecs_script_ir_op_t { uint16_t kind; uint16_t flags; int32_t a; int32_t b; int32_t c; union { int64_t i64; uint64_t u64; double f64; ecs_id_t id; ecs_entity_t entity; void *ptr; const char *str; flecs_expr_cast_t cast; } imm; const void *node; } ecs_script_ir_op_t; typedef struct ecs_script_ir_id_t { const ecs_script_id_t *id; ecs_id_t eval; ecs_id_t flag; ecs_entity_t first_eval; ecs_entity_t second_eval; const char *first; const char *second; const ecs_type_info_t *ti; int32_t first_symbol; int32_t second_symbol; int32_t first_sp; int32_t second_sp; int32_t first_reg; int32_t second_reg; int32_t index_reg; int32_t component_slot; ecs_id_t cache_id; const ecs_type_info_t *cache_ti; bool cache_visitor; bool resolved; bool dynamic; bool has_second; bool needs_set; bool visitor; } ecs_script_ir_id_t; typedef enum ecs_script_ir_entry_kind_t { EcsIrEntryRoot, EcsIrEntryTemplate, EcsIrEntryEntity, EcsIrEntryFunction, EcsIrEntryAsync } ecs_script_ir_entry_kind_t; typedef struct ecs_script_ir_entry_t { const void *node; ecs_script_ir_entry_kind_t kind; int32_t pc; int32_t reg_count; } ecs_script_ir_entry_t; struct ecs_script_ir_t { ecs_vec_t ops; ecs_vec_t ids; ecs_vec_t catches; ecs_vec_t entries; ecs_map_t entry_index; int32_t root_entry; }; typedef struct ecs_script_ir_reg_t { ecs_value_t value; const ecs_type_info_t *ti; void *storage; bool owned; } ecs_script_ir_reg_t; typedef enum ecs_script_ir_frame_kind_t { EcsIrFrameBlock, EcsIrFrameScope, EcsIrFrameEntity, EcsIrFrameWith, EcsIrFramePairScope, EcsIrFrameIf, EcsIrFrameFor, EcsIrFrameTry, EcsIrFrameWhile, EcsIrFrameExpr } ecs_script_ir_frame_kind_t; typedef struct ecs_script_ir_frame_t { int16_t kind; int16_t state; int32_t pc; union { struct { flecs_script_scope_state_t state; int32_t var_count; int32_t vscratch_top; int32_t vheap_count; } scope; flecs_script_entity_state_t entity; flecs_script_with_state_t with; flecs_script_pair_scope_state_t pair_scope; struct { bool force; } if_; struct { flecs_script_for_state_t state; int32_t vscratch_top; int32_t vheap_count; } for_; struct { int32_t scratch_top; int32_t owned_count; int32_t heap_count; int32_t strbuf_count; int32_t cursor_count; } expr; struct { int32_t reg_base; int32_t entry; bool vars; } block; } u; } ecs_script_ir_frame_t; #define ECS_SCRIPT_IR_MAX_FRAMES (ECS_SCRIPT_VISIT_MAX_DEPTH * 3) #define ECS_SCRIPT_IR_FRAME_CHUNK_SIZE (16) typedef struct ecs_script_ir_arena_t { char *data; int32_t size; int32_t top; ecs_vec_t heap; } ecs_script_ir_arena_t; typedef struct ecs_script_ir_vm_t { ecs_script_eval_visitor_t v; const ecs_script_ir_t *ir; const ecs_script_ir_op_t *ops; int32_t pc; int32_t entry; int32_t reg_base; ecs_script_ir_reg_t *regs; int32_t reg_count; ecs_script_ir_arena_t scratch; ecs_script_ir_arena_t vscratch; ecs_vec_t owned; ecs_script_ir_frame_t *frames[(ECS_SCRIPT_IR_MAX_FRAMES + ECS_SCRIPT_IR_FRAME_CHUNK_SIZE - 1) / ECS_SCRIPT_IR_FRAME_CHUNK_SIZE]; int32_t frame_count; ecs_vec_t strbufs; ecs_vec_t cursors; ecs_vec_t pending_marks; ecs_entity_t last_entity; ecs_value_t *result; bool cond; bool dirty; bool can_suspend; #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_state_t async; #endif } ecs_script_ir_vm_t; ecs_script_ir_t* flecs_script_ir_compile( ecs_script_impl_t *script); void flecs_script_ir_free( ecs_script_ir_t *ir); ecs_script_ir_t* flecs_script_ir_ensure( ecs_script_impl_t *script); const ecs_script_ir_entry_t* flecs_script_ir_entry( const ecs_script_ir_t *ir, const void *node); void flecs_script_ir_vm_init( ecs_script_ir_vm_t *vm); void flecs_script_ir_vm_fini( ecs_script_ir_vm_t *vm, const ecs_script_eval_desc_t *desc); ecs_script_ir_vm_t* flecs_script_ir_vm_new( ecs_script_impl_t *script, const ecs_script_eval_desc_t *desc); void flecs_script_ir_vm_free( ecs_script_ir_vm_t *vm, const ecs_script_eval_desc_t *desc); void flecs_script_ir_vm_pool_fini( ecs_script_runtime_t *r); void flecs_script_ir_vm_abandon( ecs_script_ir_vm_t *vm); flecs_script_run_status_t flecs_script_ir_vm_run( ecs_script_ir_vm_t *vm, const ecs_script_ir_entry_t *entry); flecs_script_run_status_t flecs_script_ir_vm_resume( ecs_script_ir_vm_t *vm); const char* flecs_script_ir_vm_stmt_pos( const ecs_script_ir_vm_t *vm); int flecs_script_ir_eval_root( ecs_script_eval_visitor_t *v, ecs_script_impl_t *script); int flecs_script_ir_eval_template( ecs_script_eval_visitor_t *v, ecs_script_impl_t *script, ecs_script_template_t *template); void flecs_script_ir_call_function( const ecs_function_ctx_t *ctx, ecs_script_user_function_t *uf, int32_t argc, const ecs_value_t *argv, ecs_value_t *result); void flecs_script_ir_to_buf( const ecs_script_impl_t *script, const ecs_script_ir_t *ir, ecs_strbuf_t *buf); #endif ecs_script_t* flecs_script_new( ecs_world_t *world); void flecs_script_pos_to_line_col( const char *code, const char *pos, int32_t *line, int32_t *column); void flecs_script_unresolved_ref_add( ecs_script_impl_t *impl, const char *pos, const char *name, flecs_script_unresolved_kind_t kind); int flecs_script_update( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *code, ecs_script_runtime_t *eval_runtime); ecs_script_scope_t* flecs_script_scope_new( ecs_parser_t *parser); typedef int (*flecs_script_scope_action_t)(ecs_script_scope_t *scope, void *ctx); int flecs_script_visit_scopes( ecs_script_node_t *node, flecs_script_scope_action_t action, void *ctx); int flecs_script_visit_free( ecs_script_t *script); int flecs_script_visit_free_node( ecs_script_t *script, ecs_script_node_t *node); ecs_script_vars_t* flecs_script_vars_push( ecs_script_vars_t *parent, ecs_stack_t *stack, ecs_allocator_t *allocator); void flecs_script_register_builtin_functions( ecs_world_t *world); void flecs_function_import( ecs_world_t *world); /* Returns the value of a global variable, which is either a const or a mut * variable. When component is provided it is set to the component that stores * the value, which tells const and mut variables apart. */ ecs_value_t flecs_script_global_var_get( const ecs_world_t *world, ecs_entity_t var, ecs_id_t *component); ecs_entity_t flecs_script_array_type( ecs_world_t *world, ecs_entity_t elem_type, int32_t count); ecs_entity_t flecs_script_vector_type( ecs_world_t *world, ecs_entity_t elem_type); const char* flecs_script_stmt( ecs_parser_t *parser, const char *pos); void flecs_script_edit_cache_fini( ecs_script_impl_t *impl); /* Write the shortest decimal string that parses back to the same value. */ /* Same as ecs_ptr_to_expr(), but writes floating point members with the * shortest decimal string that roundtrips to the same value. */ char* flecs_script_ptr_to_expr_precise( const ecs_world_t *world, ecs_entity_t type, const void *ptr, bool positional); const char* flecs_script_stmt_w_separator( ecs_parser_t *parser, const char *pos); ecs_script_t* flecs_script_parse_init( ecs_world_t *world, const char *name, const char *code, ecs_parser_t *parser); ecs_script_t* flecs_script_parse_nested( const ecs_parser_t *outer, const char *code, const char **next); int ecs_script_ast_node_to_buf( const ecs_script_t *script, ecs_script_node_t *node, ecs_strbuf_t *buf, bool colors, int32_t depth); /* Script functions */ double flecs_lerp( double a, double b, double t); void FlecsScriptMathPerlinImport( ecs_world_t *world); #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H #ifdef FLECS_JSON const char* ecs_ptr_from_json( const ecs_world_t *world, ecs_entity_t type, void *ptr, const char *json, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; char *token = token_buffer; int depth = 0; bool strict = desc ? desc->strict : false; bool skip = false; int skip_depth = 0; const char *name = NULL; const char *expr = NULL; ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); if (cur.valid == false) { return NULL; } if (desc) { name = desc->name; expr = desc->expr; cur.lookup_action = desc->lookup_action; cur.lookup_ctx = desc->lookup_ctx; } while ((json = flecs_json_parse(json, &token_kind, token))) { if (skip) { /* Skip over tokens in case an unknown member was encountered */ if (token_kind == JsonObjectOpen || token_kind == JsonArrayOpen) { skip_depth ++; } else if (token_kind == JsonObjectClose || token_kind == JsonArrayClose) { skip_depth --; if (!skip_depth) { skip = false; } } else { if (!skip_depth) { skip = false; } } continue; } if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } token = ecs_strbuf_get(&large_token); token_kind = JsonString; } if (token_kind == JsonObjectOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '['"); return NULL; } } else if (token_kind == JsonObjectClose) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonArrayOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '{'"); return NULL; } } else if (token_kind == JsonArrayClose) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonComma) { if (ecs_meta_next(&cur) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonString) { const char *lah = flecs_json_parse( json, &token_kind, t_lah); if (token_kind == JsonColon) { /* Member assignment */ json = lah; if (strict) { if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else { if (ecs_meta_try_dotmember(&cur, token) != 0) { skip = true; } } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } } else if (token_kind == JsonNumber) { double number = atof(token); if (ecs_meta_set_float(&cur, number) != 0) { goto error; } } else if (token_kind == JsonLargeInt) { int64_t number = flecs_ito(int64_t, atoll(token)); if (ecs_meta_set_int(&cur, number) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonTrue) { if (ecs_meta_set_bool(&cur, true) != 0) { goto error; } } else if (token_kind == JsonFalse) { if (ecs_meta_set_bool(&cur, false) != 0) { goto error; } } else { goto error; } if (token != token_buffer) { ecs_os_free(token); token = token_buffer; } if (!depth) { break; } } return json; error: return NULL; } #endif #ifdef FLECS_JSON static const char* flecs_json_skip_scope( const char *json, char *token, const ecs_from_json_desc_t *desc, ecs_json_token_t close_kind); static const char* flecs_json_token_str( ecs_json_token_t token_kind) { switch(token_kind) { case JsonObjectOpen: return "{"; case JsonObjectClose: return "}"; case JsonArrayOpen: return "["; case JsonArrayClose: return "]"; case JsonColon: return ":"; case JsonComma: return ","; case JsonNumber: return "number"; case JsonLargeInt: return "large integer"; case JsonLargeString: case JsonString: return "string"; case JsonBoolean: return "bool"; case JsonTrue: return "true"; case JsonFalse: return "false"; case JsonNull: return "null"; case JsonInvalid: return "invalid"; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return "<>"; } const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); json = flecs_parse_ws_eol(json); char ch = json[0]; switch (ch) { case '{': token_kind[0] = JsonObjectOpen; return json + 1; case '}': token_kind[0] = JsonObjectClose; return json + 1; case '[': token_kind[0] = JsonArrayOpen; return json + 1; case ']': token_kind[0] = JsonArrayClose; return json + 1; case ':': token_kind[0] = JsonColon; return json + 1; case ',': token_kind[0] = JsonComma; return json + 1; } if (ch == '"') { const char *start = json; char *token_ptr = token; json ++; for (; (ch = json[0]); ) { if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { /* Token doesn't fit in buffer, signal to app to try again with * dynamic buffer. */ token_kind[0] = JsonLargeString; return start; } if (ch == '"') { json ++; token_ptr[0] = '\0'; break; } json = flecs_chrparse(json, token_ptr ++); if (!json) { token_kind[0] = JsonInvalid; return NULL; } } if (!ch) { token_kind[0] = JsonInvalid; return NULL; } else { token_kind[0] = JsonString; return json; } } else if (isdigit(ch) || (ch == '-')) { token_kind[0] = JsonNumber; const char *result = flecs_parse_digit(json, token, ECS_MAX_TOKEN_SIZE); if (!result) { token_kind[0] = JsonInvalid; return NULL; } /* Cheap initial check if parsed token could represent large int */ if (result - json > 15) { /* Less cheap secondary check to see if number is integer */ if (!strchr(token, '.')) { token_kind[0] = JsonLargeInt; } } return result; } else if (isalpha(ch)) { if (!ecs_os_strncmp(json, "null", 4)) { token_kind[0] = JsonNull; json += 4; } else if (!ecs_os_strncmp(json, "true", 4)) { token_kind[0] = JsonTrue; json += 4; } else if (!ecs_os_strncmp(json, "false", 5)) { token_kind[0] = JsonFalse; json += 5; } if (isalpha(json[0]) || isdigit(json[0])) { token_kind[0] = JsonInvalid; return NULL; } return json; } else { token_kind[0] = JsonInvalid; return NULL; } } const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf) { if (json[0] != '"') { return NULL; /* can only parse strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = flecs_chrparse(json, &ch_out); if (!json) { return NULL; } ecs_strbuf_appendch(buf, ch_out); } return ch ? json : NULL; } const char* flecs_json_parse_next_member( const char *json, char *token, ecs_json_token_t *token_kind, const ecs_from_json_desc_t *desc) { json = flecs_json_parse(json, token_kind, token); if (*token_kind == JsonObjectClose) { return json; } if (*token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected } or ,"); return NULL; } json = flecs_json_parse(json, token_kind, token); if (*token_kind != JsonString) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected member name"); return NULL; } char temp_token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t temp_token_kind; json = flecs_json_parse(json, &temp_token_kind, temp_token); if (temp_token_kind != JsonColon) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected :"); return NULL; } return json; } const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc) { /* Strings must be handled by flecs_json_expect_string for LargeString */ ecs_assert(token_kind != JsonString, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t kind = 0; const char *lah = flecs_json_parse(json, &kind, token); if (kind == JsonInvalid) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (kind != token_kind) { if (token_kind == JsonBoolean && (kind == JsonTrue || kind == JsonFalse)) { /* ok */ } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s, got %s", flecs_json_token_str(token_kind), flecs_json_token_str(kind)); flecs_dump_backtrace(stdout); return NULL; } } return lah; } const char* flecs_json_expect_string( const char *json, char *token, char **out, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonInvalid) { ecs_parser_error( desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (token_kind != JsonString && token_kind != JsonLargeString) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected string"); return NULL; } if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { return NULL; } if (out) { *out = ecs_strbuf_get(&large_token); } else { ecs_strbuf_reset(&large_token); } } else if (out) { *out = token; } return json; } const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc) { char *out = NULL; json = flecs_json_expect_string(json, token, &out, desc); if (!json) { return NULL; } if (out != token) { ecs_os_free(out); } json = flecs_json_expect(json, JsonColon, token, desc); if (!json) { return NULL; } return json; } const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc) { json = flecs_json_expect_member(json, token, desc); if (!json) { return NULL; } if (ecs_os_strcmp(token, member_name)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected member '%s'", member_name); return NULL; } return json; } static const char* flecs_json_skip_string( const char *json) { if (json[0] != '"') { return NULL; /* can only skip strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = flecs_chrparse(json, &ch_out); if (!json) { return NULL; } } return ch ? json : NULL; } static const char* flecs_json_skip_scope( const char *json, char *token, const ecs_from_json_desc_t *desc, ecs_json_token_t close_kind) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); const char *expect = (close_kind == JsonObjectClose) ? "}" : "]"; ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_scope(json, token, desc, JsonObjectClose); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_scope(json, token, desc, JsonArrayClose); } else if (token_kind == JsonLargeString) { json = flecs_json_skip_string(json); } else if (token_kind == close_kind) { return json; } else if (token_kind == JsonObjectClose || token_kind == JsonArrayClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s", expect); return NULL; } ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_parser_error(desc->name, desc->expr, json ? json - desc->expr : 0, "expected %s, got end of string", expect); return NULL; } const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc) { return flecs_json_skip_scope(json, token, desc, JsonObjectClose); } void flecs_json_next( ecs_strbuf_t *buf) { ecs_strbuf_list_next(buf); } void flecs_json_number( ecs_strbuf_t *buf, double value) { ecs_strbuf_appendflt(buf, value, '"'); } void flecs_json_u32( ecs_strbuf_t *buf, uint32_t value) { ecs_strbuf_appendint(buf, flecs_uto(int64_t, value)); } void flecs_json_true( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "true"); } void flecs_json_false( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "false"); } void flecs_json_bool( ecs_strbuf_t *buf, bool value) { if (value) { flecs_json_true(buf); } else { flecs_json_false(buf); } } void flecs_json_null( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "null"); } void flecs_json_array_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "[", ", "); } void flecs_json_array_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "]"); } void flecs_json_object_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "{", ", "); } void flecs_json_object_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "}"); } void flecs_json_string( ecs_strbuf_t *buf, const char *value) { flecs_json_string_escape(buf, value); } void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value) { if (!value) { ecs_strbuf_appendlit(buf, "null"); return; } ecs_size_t length = flecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, value, length); ecs_strbuf_appendch(buf, '"'); } else { char *out = ecs_os_malloc(length + 3); flecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr(buf, out); ecs_os_free(out); } } void flecs_json_string_escape_ctrl( ecs_strbuf_t *buf, const char *value) { if (!value) { ecs_strbuf_appendlit(buf, "null"); return; } ecs_strbuf_appendch(buf, '"'); const char *ptr; for (ptr = value; ptr[0]; ptr ++) { char ch = ptr[0]; switch(ch) { case '"': ecs_strbuf_appendlit(buf, "\\\""); break; case '\\': ecs_strbuf_appendlit(buf, "\\\\"); break; case '\b': ecs_strbuf_appendlit(buf, "\\b"); break; case '\f': ecs_strbuf_appendlit(buf, "\\f"); break; case '\n': ecs_strbuf_appendlit(buf, "\\n"); break; case '\r': ecs_strbuf_appendlit(buf, "\\r"); break; case '\t': ecs_strbuf_appendlit(buf, "\\t"); break; default: if ((unsigned char)ch < 0x20) { ecs_strbuf_append(buf, "\\u%04x", (unsigned char)ch); } else { ecs_strbuf_appendch(buf, ch); } break; } } ecs_strbuf_appendch(buf, '"'); } void flecs_json_member( ecs_strbuf_t *buf, const char *name) { flecs_json_membern(buf, name, ecs_os_strlen(name)); } void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len) { ecs_strbuf_list_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, name, name_len); ecs_strbuf_appendlit(buf, "\":"); } void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } static const char* flecs_json_entity_label( const ecs_world_t *world, ecs_entity_t e) { const char *lbl = NULL; if (!e) { return "#0"; } #ifdef FLECS_DOC lbl = ecs_doc_get_name(world, e); #else lbl = ecs_get_name(world, e); #endif return lbl; } void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { const char *lbl = flecs_json_entity_label(world, e); if (lbl) { flecs_json_string_escape(buf, lbl); } else { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, (uint32_t)e); ecs_strbuf_appendch(buf, '"'); } } void flecs_json_path_or_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e, bool path) { if (!path) { flecs_json_label(buf, world, e); } else { flecs_json_path(buf, world, e); } } void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_appendch(buf, '['); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, '"'); if (ECS_IS_VALUE_PAIR(id)) { ecs_strbuf_appendch(buf, '@'); ecs_strbuf_appendint(buf, ECS_PAIR_SECOND(id)); } else { ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); } ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } ecs_strbuf_appendch(buf, ']'); } static void flecs_json_id_member_fullpath( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); } } void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id, bool fullpath) { ecs_id_t flags = id & ECS_ID_FLAGS_MASK; if (flags & ECS_AUTO_OVERRIDE) { ecs_strbuf_appendlit(buf, "auto_override|"); id &= ~ECS_AUTO_OVERRIDE; } if (flags & ECS_TOGGLE) { ecs_strbuf_appendlit(buf, "toggle|"); id &= ~ECS_TOGGLE; } if (fullpath) { flecs_json_id_member_fullpath(buf, world, id); return; } if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); { const char *lbl = flecs_json_entity_label(world, first); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ','); { const char *lbl = flecs_json_entity_label(world, second); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ')'); } else { const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } } ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_op_kind_t kind) { return kind - EcsOpPrimitive; } bool flecs_json_should_serialize( ecs_entity_t entity, const ecs_iter_t *it, const ecs_json_ser_ctx_t *ser_ctx) { if (!ecs_map_is_init(&ser_ctx->serialized)) { return true; } if (ecs_map_get(&ser_ctx->serialized, entity) != NULL) { return false; } if (it->query) { ecs_iter_t temp_it; bool result = ecs_query_has(it->query, entity, &temp_it); if (result) { ecs_iter_fini(&temp_it); } else { return false; } } return true; } void flecs_json_mark_serialized( ecs_entity_t entity, ecs_json_ser_ctx_t *ser_ctx) { if (ecs_map_is_init(&ser_ctx->serialized)) { ecs_map_ensure(&ser_ctx->serialized, entity); } } void flecs_json_accum_type_info( const ecs_world_t *world, ecs_entity_t typeid, ecs_json_ser_ctx_t *ser_ctx) { if (!typeid || !ser_ctx || !ser_ctx->type_info_buf) { return; } if (ecs_map_get(&ser_ctx->type_info_seen, typeid) != NULL) { return; } ecs_map_ensure(&ser_ctx->type_info_seen, typeid); ecs_strbuf_t *buf = ser_ctx->type_info_buf; flecs_json_next(buf); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, typeid, ".", "", buf, true); ecs_strbuf_appendlit(buf, "\":"); if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { ecs_strbuf_appendlit(buf, "0"); } } void flecs_json_type_info_accum_init( ecs_json_ser_ctx_t *ser_ctx, ecs_strbuf_t *type_info_buf, const ecs_world_t *world) { ser_ctx->type_info_buf = type_info_buf; ecs_map_init(&ser_ctx->type_info_seen, &ECS_CONST_CAST(ecs_world_t*, world)->allocator); flecs_json_object_push(type_info_buf); } void flecs_json_type_info_accum_fini( ecs_json_ser_ctx_t *ser_ctx, ecs_strbuf_t *type_info_buf) { if (ser_ctx->type_info_buf) { flecs_json_object_pop(type_info_buf); ecs_map_fini(&ser_ctx->type_info_seen); } } void flecs_json_assemble_output( ecs_strbuf_t *out, ecs_strbuf_t *type_info_buf, ecs_strbuf_t *body_buf) { flecs_json_object_push(out); if (type_info_buf) { flecs_json_memberl(out, "type_info"); ecs_strbuf_mergebuff(out, type_info_buf); } int32_t body_len = ecs_strbuf_written(body_buf); if (body_len > 2) { ecs_strbuf_list_next(out); ecs_strbuf_appendstrn(out, body_buf->content + 1, body_len - 2); } flecs_json_object_pop(out); ecs_strbuf_reset(body_buf); } #endif #ifdef FLECS_JSON int flecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { ecs_record_t *r = ecs_record_find(world, entity); if (!r || !r->table) { flecs_json_object_push(buf); flecs_json_member(buf, "name"); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, (uint32_t)entity); ecs_strbuf_appendch(buf, '"'); flecs_json_object_pop(buf); return 0; } /* Create iterator that's populated just with entity */ int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_iter_t it = { .stage = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, world), .table = r->table, .offset = row, .count = 1, .entities = &ecs_table_entities(r->table)[row], .field_count = 0 }; if (flecs_json_serialize_iter_result( world, &it, buf, desc, ser_ctx)) { return -1; } return 0; } int ecs_entity_to_json_buf( ecs_world_t *stage, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_entity_to_json_desc_t *desc) { const ecs_world_t *world = ecs_get_world(stage); if (!entity || !ecs_is_valid(world, entity)) { return -1; } /* Cache component record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC ser_ctx.cr_doc_name = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsName)); ser_ctx.cr_doc_color = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif /* Initialize iterator parameters */ ecs_iter_to_json_desc_t iter_desc = { .serialize_table = true, .serialize_entity_ids = desc ? desc->serialize_entity_id : false, .serialize_values = desc ? desc->serialize_values : true, .serialize_builtin = desc ? desc->serialize_builtin : false, .serialize_doc = desc ? desc->serialize_doc : false, .serialize_matches = desc ? desc->serialize_matches : false, .serialize_refs = desc ? desc->serialize_refs : 0, .serialize_alerts = desc ? desc->serialize_alerts : false, .serialize_full_paths = desc ? desc->serialize_full_paths : true, .serialize_inherited = desc ? desc->serialize_inherited : false, .serialize_type_info = desc ? desc->serialize_type_info : false, .component_filter = desc ? desc->component_filter : NULL, }; ecs_strbuf_t type_info_buf = ECS_STRBUF_INIT; if (iter_desc.serialize_type_info) { flecs_json_type_info_accum_init(&ser_ctx, &type_info_buf, world); } ecs_strbuf_t entity_buf = ECS_STRBUF_INIT; int result = flecs_entity_to_json_buf( world, entity, &entity_buf, &iter_desc, &ser_ctx); flecs_json_type_info_accum_fini(&ser_ctx, &type_info_buf); if (result) { ecs_strbuf_reset(&entity_buf); ecs_strbuf_reset(&type_info_buf); return result; } flecs_json_assemble_output(buf, ser_ctx.type_info_buf, &entity_buf); return 0; } char* ecs_entity_to_json( ecs_world_t *world, ecs_entity_t entity, const ecs_entity_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif #ifdef FLECS_JSON static bool flecs_json_serialize_get_field_ctx( const ecs_world_t *world, const ecs_iter_t *it, int32_t f, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc) { ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; if (it->query) { return flecs_json_serialize_get_value_ctx( world, it->query->ids[f], value_ctx, desc); } else if (it->ids[f]) { return flecs_json_serialize_get_value_ctx( world, it->ids[f], value_ctx, desc); } else { return false; } } void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, const ecs_query_t *q, int field, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx) { flecs_json_object_push(buf); flecs_json_memberl(buf, "id"); flecs_json_serialize_get_field_ctx(world, it, field, ctx, NULL); ecs_json_value_ser_ctx_t *value_ctx = &ctx->value_ctx[field]; if (value_ctx->id_label) { flecs_json_string(buf, value_ctx->id_label); const ecs_term_t *term = &q->terms[0]; int t; for (t = 0; t < q->term_count; t ++) { if (q->terms[t].field_index == field) { term = &q->terms[t]; break; } } if (term->oper != EcsNot) { if (term->oper == EcsOptional) { flecs_json_memberl(buf, "optional"); flecs_json_bool(buf, true); } if (ECS_IS_PAIR(term->id)) { if ((term->first.id & EcsIsEntity) && ECS_TERM_REF_ID(&term->first)) { if (ecs_has_id(world, ECS_TERM_REF_ID(&term->first), EcsExclusive)) { flecs_json_memberl(buf, "exclusive"); flecs_json_bool(buf, true); } } } if (term->inout != EcsInOutNone) { if (value_ctx->type) { flecs_json_memberl(buf, "type"); flecs_json_label(buf, world, value_ctx->type); const char *symbol = ecs_get_symbol(world, value_ctx->type); if (symbol) { flecs_json_memberl(buf, "symbol"); flecs_json_string(buf, symbol); } } if (value_ctx->ser) { flecs_json_memberl(buf, "schema"); ecs_type_info_to_json_buf(world, value_ctx->type, buf); } flecs_json_accum_type_info(world, value_ctx->type, ctx); } } else { flecs_json_memberl(buf, "not"); flecs_json_bool(buf, true); } } else { ecs_strbuf_appendlit(buf, "0"); } flecs_json_object_pop(buf); } #endif #ifdef FLECS_JSON static void flecs_json_serialize_field_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx) { int32_t field_count = it->field_count; if (!field_count || !it->query) { return; } const ecs_query_t *q = it->query; flecs_json_memberl(buf, "field_info"); flecs_json_array_push(buf); int f; for (f = 0; f < field_count; f ++) { flecs_json_next(buf); flecs_json_serialize_field(world, it, q, f, buf, ctx); } flecs_json_array_pop(buf); } static void flecs_json_serialize_query_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->query) { return; } const ecs_query_t *q = it->query; flecs_json_memberl(buf, "query_info"); flecs_json_serialize_query(world, q, buf); } static void flecs_json_serialize_query_plan( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { (void)world; (void)buf; (void)desc; if (!desc->query) { return; } const ecs_query_t *q = desc->query; flecs_poly_assert(q, ecs_query_t); const ecs_query_t *cq = NULL; #ifdef FLECS_CACHED_QUERIES cq = ecs_query_get_cache_query(q); #endif flecs_json_memberl(buf, "query_plan"); bool prev_color = ecs_log_enable_colors(true); char *plan = ecs_query_plan(q); char *cache_plan = NULL; if (cq) { flecs_poly_assert(cq, ecs_query_t); cache_plan = ecs_query_plan(cq); } ecs_strbuf_t plan_buf = ECS_STRBUF_INIT; if (plan) { ecs_strbuf_appendstr(&plan_buf, plan); } else { if (q->term_count) { ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); } } if (cq) { ecs_strbuf_appendstr(&plan_buf, "\n\n"); ecs_strbuf_appendstr(&plan_buf, " Cache plan\n"); ecs_strbuf_appendstr(&plan_buf, " ---\n"); if (cache_plan) { ecs_strbuf_appendstr(&plan_buf, cache_plan); } else { ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); } } char *plan_str = ecs_strbuf_get(&plan_buf); if (plan_str) { flecs_json_string_escape(buf, plan_str); ecs_os_free(plan_str); } else { flecs_json_null(buf); } ecs_os_free(plan); ecs_os_free(cache_plan); ecs_log_enable_colors(prev_color); } static void flecs_json_serialize_query_profile( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { if (!desc->query) { return; } ecs_time_t t = {0}; int32_t result_count = 0, entity_count = 0, i, sample_count = 100; ecs_size_t component_bytes = 0, shared_component_bytes = 0; double eval_time = 0, eval_min = 0, eval_max = 0; ecs_time_measure(&t); for (i = 0; i < sample_count; i ++) { result_count = 0; entity_count = 0; component_bytes = 0; shared_component_bytes = 0; ecs_iter_t qit = ecs_query_iter(world, desc->query); while (ecs_query_next(&qit)) { result_count ++; entity_count += qit.count; int8_t f, field_count = qit.field_count; for (f = 0; f < field_count; f ++) { size_t size = ecs_field_size(&qit, f); if (ecs_field_is_set(&qit, f) && size) { if (ecs_field_is_self(&qit, f)) { component_bytes += flecs_uto(ecs_size_t, size) * qit.count; } else { shared_component_bytes += flecs_uto(ecs_size_t, size); } } } } double time_measure = ecs_time_measure(&t); if (!i || time_measure < eval_min) { eval_min = time_measure; } if (time_measure > eval_max) { eval_max = time_measure; } eval_time += time_measure; /* Don't profile for too long */ if (eval_time > 0.001) { i ++; break; } } eval_time /= i; flecs_json_memberl(buf, "query_profile"); flecs_json_object_push(buf); if (it->query) { /* Correct for profiler */ ECS_CONST_CAST(ecs_query_t*, it->query)->eval_count -= i; flecs_json_memberl(buf, "eval_count"); flecs_json_number(buf, it->query->eval_count); } flecs_json_memberl(buf, "result_count"); flecs_json_number(buf, result_count); flecs_json_memberl(buf, "entity_count"); flecs_json_number(buf, entity_count); flecs_json_memberl(buf, "eval_time_avg_us"); flecs_json_number(buf, eval_time * 1000.0 * 1000.0); flecs_json_memberl(buf, "eval_time_min_us"); flecs_json_number(buf, eval_min * 1000.0 * 1000.0); flecs_json_memberl(buf, "eval_time_max_us"); flecs_json_number(buf, eval_max * 1000.0 * 1000.0); flecs_json_memberl(buf, "component_bytes"); flecs_json_number(buf, component_bytes); flecs_json_memberl(buf, "shared_component_bytes"); flecs_json_number(buf, shared_component_bytes); flecs_json_object_pop(buf); } static void flecs_iter_free_ser_ctx( ecs_iter_t *it, ecs_json_ser_ctx_t *ser_ctx) { int32_t f, field_count = it->field_count; for (f = 0; f < field_count; f ++) { ecs_os_free(ser_ctx->value_ctx[f].id_label); } } static void flecs_json_accum_query_term_type_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_json_ser_ctx_t *ser_ctx) { if (!ser_ctx->type_info_buf || !it->query) { return; } const ecs_query_t *q = it->query; int32_t i, term_count = q->term_count; for (i = 0; i < term_count; i ++) { const ecs_term_t *term = &q->terms[i]; if (term->inout == EcsInOutNone) { continue; } ecs_entity_t typeid = ecs_get_typeid(world, term->id); if (typeid) { flecs_json_accum_type_info(world, typeid, ser_ctx); } } } int ecs_iter_to_json_buf( ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_world_t *world = it->world; /* Cache component record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC ser_ctx.cr_doc_name = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsName)); ser_ctx.cr_doc_color = flecs_components_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif ecs_strbuf_t type_info_buf = ECS_STRBUF_INIT; if (desc && desc->serialize_type_info) { flecs_json_type_info_accum_init(&ser_ctx, &type_info_buf, world); flecs_json_accum_query_term_type_info(world, it, &ser_ctx); } ecs_strbuf_t body_buf = ECS_STRBUF_INIT; flecs_json_object_push(&body_buf); /* Serialize field info if enabled */ if (desc && desc->serialize_field_info) { flecs_json_serialize_field_info(world, it, &body_buf, &ser_ctx); } /* Serialize query info if enabled */ if (desc && desc->serialize_query_info) { flecs_json_serialize_query_info(world, it, &body_buf); } /* Serialize query plan if enabled */ if (desc && desc->serialize_query_plan) { flecs_json_serialize_query_plan(world, &body_buf, desc); } /* Profile query */ if (desc && desc->serialize_query_profile) { flecs_json_serialize_query_profile(world, &body_buf, it, desc); } bool failed = false; /* Serialize results */ if (!desc || !desc->dont_serialize_results) { flecs_json_memberl(&body_buf, "results"); flecs_json_array_push(&body_buf); /* If serializing entire table, don't bother letting the iterator populate * data fields as we'll be iterating all columns. */ if (desc && desc->serialize_table) { ECS_BIT_SET(it->flags, EcsIterNoData); } /* Keep track of serialized entities. This allows entities to be * serialized depth first, which avoids weird side effects from children * being created before parents. */ if (desc && desc->serialize_parents_before_children) { ecs_map_init(&ser_ctx.serialized, &world->allocator); } ecs_iter_next_action_t next = it->next; while (next(it)) { if (flecs_json_serialize_iter_result( world, it, &body_buf, desc, &ser_ctx)) { ecs_iter_fini(it); failed = true; break; } } if (desc && desc->serialize_parents_before_children) { ecs_map_fini(&ser_ctx.serialized); } flecs_json_array_pop(&body_buf); } else { ecs_iter_fini(it); } flecs_iter_free_ser_ctx(it, &ser_ctx); flecs_json_object_pop(&body_buf); flecs_json_type_info_accum_fini(&ser_ctx, &type_info_buf); if (failed) { ecs_strbuf_reset(&body_buf); ecs_strbuf_reset(&type_info_buf); ecs_strbuf_reset(buf); return -1; } flecs_json_assemble_output(buf, ser_ctx.type_info_buf, &body_buf); return 0; } char* ecs_iter_to_json( ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_iter_to_json_buf(it, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif #ifdef FLECS_JSON static bool flecs_json_skip_variable( const char *name) { return !name || name[0] == '_' || !ecs_os_strcmp(name, "this"); } bool flecs_json_serialize_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { if (!it->query) { return 0; } char **variable_names = it->query->vars; int32_t var_count = it->query->var_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; ecs_entity_t var = ecs_iter_get_vars(it)[i].entity; if (!var) { /* Can't happen, but not the place of the serializer to complain */ continue; } if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_object_push(buf); actual_count ++; } flecs_json_member(buf, var_name); flecs_json_path_or_label(buf, world, var, desc ? desc->serialize_full_paths : true); } if (actual_count) { flecs_json_object_pop(buf); } return actual_count != 0; } int flecs_json_serialize_matches( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { flecs_json_memberl(buf, "matches"); flecs_json_array_push(buf); ecs_component_record_t *cr = flecs_components_get(world, ecs_pair_t(EcsPoly, EcsQuery)); if (cr) { ecs_table_cache_iter_t it; if (cr && flecs_table_cache_iter((ecs_table_cache_t*)cr, &it, EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_table_t *table = elem->table; EcsPoly *queries = ecs_table_get_column(table, elem->column, 0); const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_query_t *q = queries[i].poly; if (!q) { continue; } ecs_assert(flecs_poly_is(q, ecs_query_t), ECS_INTERNAL_ERROR, NULL); if (!(q->flags & EcsQueryMatchThis)) { continue; } ecs_iter_t qit = ecs_query_iter(world, q); if (!ecs_iter_get_vars(&qit)) { ecs_iter_fini(&qit); continue; } ecs_iter_set_var(&qit, 0, entity); if (ecs_iter_is_true(&qit)) { flecs_json_next(buf); flecs_json_path(buf, world, entities[i]); } } } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_serialize_refs_cr( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_component_record_t *cr) { char *id_str = ecs_id_str(world, ecs_pair_first(world, cr->id)); flecs_json_member(buf, id_str); ecs_os_free(id_str); flecs_json_array_push(buf); ecs_table_cache_iter_t it; if (cr && flecs_table_cache_iter((ecs_table_cache_t*)cr, &it, EcsTableEmpty|EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_table_t *table = elem->table; 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]; flecs_json_next(buf); flecs_json_path(buf, world, e); } } } flecs_json_array_pop(buf); return 0; } int flecs_json_serialize_refs( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, ecs_entity_t relationship) { flecs_json_memberl(buf, "refs"); flecs_json_object_push(buf); ecs_component_record_t *cr = flecs_components_get(world, ecs_pair(relationship, entity)); if (cr) { if (relationship == EcsWildcard) { ecs_component_record_t *cur = cr; while ((cur = flecs_component_second_next(cur))) { flecs_json_serialize_refs_cr(world, buf, cur); } } else { flecs_json_serialize_refs_cr(world, buf, cr); } } flecs_json_object_pop(buf); return 0; } #ifdef FLECS_ALERTS static int flecs_json_serialize_entity_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, const EcsAlertsActive *alerts, bool self) { ecs_assert(alerts != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); while (ecs_map_next(&it)) { flecs_json_next(buf); flecs_json_object_push(buf); ecs_entity_t ai = ecs_map_value(&it); char *alert_name = ecs_get_path(world, ai); flecs_json_memberl(buf, "alert"); flecs_json_string(buf, alert_name); ecs_os_free(alert_name); ecs_entity_t severity_id = ecs_get_target( world, ai, ecs_id(EcsAlert), 0); const char *severity = ecs_get_name(world, severity_id); const EcsAlertInstance *alert = ecs_get( world, ai, EcsAlertInstance); if (alert) { if (alert->message) { flecs_json_memberl(buf, "message"); flecs_json_string(buf, alert->message); } flecs_json_memberl(buf, "severity"); flecs_json_string(buf, severity); if (!self) { char *path = ecs_get_path(world, entity); flecs_json_memberl(buf, "path"); flecs_json_string(buf, path); ecs_os_free(path); } } flecs_json_object_pop(buf); } return 0; } static int flecs_json_serialize_children_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { ecs_query_t *q = ecs_query(ECS_CONST_CAST(ecs_world_t*, world), { .terms = {{ .id = ecs_pair(EcsChildOf, entity) }} }); ecs_iter_t it = ecs_query_iter(world, q); while (ecs_query_next(&it)) { EcsAlertsActive *alerts = ecs_table_get_id( world, it.table, ecs_id(EcsAlertsActive), it.offset); int32_t i; for (i = 0; i < it.count; i ++) { ecs_entity_t child = it.entities[i]; if (alerts) { if (flecs_json_serialize_entity_alerts( world, buf, child, &alerts[i], false)) { goto error; } } ecs_record_t *r = flecs_entities_get(world, it.entities[i]); if (r->row & EcsEntityIsTraversable) { if (flecs_json_serialize_children_alerts( world, buf, child)) { goto error; } } } } ecs_query_fini(q); return 0; error: return -1; } #endif int flecs_json_serialize_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { (void)world; (void)buf; (void)entity; #ifdef FLECS_ALERTS if (!ecs_id(EcsAlertsActive)) { return 0; /* Alert module not imported */ } flecs_json_memberl(buf, "alerts"); flecs_json_array_push(buf); const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); if (alerts) { flecs_json_serialize_entity_alerts(world, buf, entity, alerts, true); } flecs_json_serialize_children_alerts(world, buf, entity); flecs_json_array_pop(buf); #endif return 0; } bool flecs_json_serialize_get_value_ctx( const ecs_world_t *world, ecs_id_t id, ecs_json_value_ser_ctx_t *ctx, const ecs_iter_to_json_desc_t *desc) { if (!id) { return false; } if (ctx->id != id) { ecs_os_free(ctx->id_label); *ctx = (ecs_json_value_ser_ctx_t){ .id = id }; ecs_strbuf_t idlbl = ECS_STRBUF_INIT; flecs_json_id_member(&idlbl, world, id, desc ? desc->serialize_full_paths : true); ctx->id_label = ecs_strbuf_get(&idlbl); ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { return false; } ctx->type = type; ctx->ser = ecs_get(world, type, EcsTypeSerializer); if (!ctx->ser) { return false; } return true; } else { return ctx->ser != NULL; } } bool flecs_json_serialize_iter_this( const ecs_iter_t *it, const char *parent_path, const ecs_json_this_data_t *this_data, int32_t row, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { ecs_assert(row < it->count, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = it->world; ecs_assert(this_data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(this_data->ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = this_data->ids[row]; ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); /* Skip entity if it already has been serialized */ if (!flecs_json_should_serialize(e, it, ser_ctx)) { return false; } flecs_json_mark_serialized(e, ser_ctx); /* Serialize parent first. Only necessary if this is a table with Parent * component, as this will have been done at the table level for fragmenting * relationships. */ ecs_entity_t parent = 0; if (this_data->parents) { parent = this_data->parents[row].value; ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); if (ecs_map_is_init(&ser_ctx->serialized)) { flecs_entity_to_json_buf(world, parent, buf, desc, ser_ctx); } } flecs_json_next(buf); flecs_json_object_push(buf); if (parent_path) { flecs_json_memberl(buf, "parent"); flecs_json_string(buf, parent_path); } else if (parent) { flecs_json_memberl(buf, "parent"); char *path = ecs_get_path_w_sep(world, 0, parent, ".", ""); flecs_json_string(buf, path); ecs_os_free(path); } flecs_json_memberl(buf, "name"); if (this_data->names) { if (this_data->table) { ecs_assert(this_data->names[row].value != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_string(buf, this_data->names[row].value); } } else { bool name_set = false; if (!this_data->table) { const char *name = ecs_get_name(world, e); if (name) { flecs_json_string(buf, name); name_set = true; } } if (!name_set) { ecs_strbuf_appendlit(buf, "\"#"); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)it->entities[row])); ecs_strbuf_appendlit(buf, "\""); } } if (desc && desc->serialize_entity_ids) { flecs_json_memberl(buf, "id"); flecs_json_u32(buf, (uint32_t)e); if (e != (uint32_t)e) { flecs_json_memberl(buf, "version"); flecs_json_u32(buf, (uint32_t)(e >> 32)); } } #ifdef FLECS_DOC if (desc && desc->serialize_doc) { flecs_json_memberl(buf, "doc"); flecs_json_object_push(buf); if (this_data->label) { flecs_json_memberl(buf, "label"); flecs_json_string_escape(buf, this_data->label[row].value); } else { flecs_json_memberl(buf, "label"); if (this_data->names) { ecs_assert(this_data->names[row].value != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_string(buf, this_data->names[row].value); } else { ecs_strbuf_appendlit(buf, "\"#"); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)it->entities[row])); ecs_strbuf_appendlit(buf, "\""); } } if (this_data->brief) { flecs_json_memberl(buf, "brief"); flecs_json_string_escape(buf, this_data->brief[row].value); } if (this_data->detail) { flecs_json_memberl(buf, "detail"); flecs_json_string_escape(buf, this_data->detail[row].value); } if (this_data->color) { flecs_json_memberl(buf, "color"); flecs_json_string_escape(buf, this_data->color[row].value); } if (this_data->link) { flecs_json_memberl(buf, "link"); flecs_json_string_escape(buf, this_data->link[row].value); } flecs_json_object_pop(buf); } #endif if (this_data->has_alerts) { flecs_json_memberl(buf, "has_alerts"); flecs_json_true(buf); } return true; } int flecs_json_serialize_iter_result( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { char *parent_path = NULL; ecs_json_this_data_t this_data = { .table = it->table }; int32_t count = it->count; bool has_this = true; if (!count) { count = 1; /* Query without this variable */ has_this = false; } else { ecs_table_t *table = it->table; if (table) { ecs_assert((it->offset + it->count) <= ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); this_data.ids = &ecs_table_entities(table)[it->offset]; /* Get path to parent once for entire table */ if (table->flags & EcsTableHasChildOf) { const ecs_table_record_t *tr = flecs_component_get_table( world->cr_childof_wildcard, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t parent = ecs_pair_second( world, table->type.array[tr->index]); parent_path = ecs_get_path_w_sep(world, 0, parent, ".", ""); /* If matching a query check if we need to serialize the parent * of the table before its children. Don't do this if query * isn't set, as that means that we're serializing a single * entity. */ if (ecs_map_is_init(&ser_ctx->serialized)) { if (flecs_json_should_serialize(parent, it, ser_ctx)) { if (flecs_entity_to_json_buf( world, parent, buf, desc, ser_ctx)) { return -1; } flecs_json_mark_serialized(parent, ser_ctx); } } } /* Fetch name column once vs. calling ecs_get_name for each row */ if (table->flags & EcsTableHasName) { this_data.names = ecs_table_get_id(it->stage, it->table, ecs_pair_t(EcsIdentifier, EcsName), it->offset); } /* Same for Parent column */ if (table->flags & EcsTableHasParent) { this_data.parents = ecs_table_get_id(it->stage, it->table, ecs_id(EcsParent), it->offset); } /* Get entity labels */ #ifdef FLECS_DOC if (desc && desc->serialize_doc) { this_data.label = ecs_table_get_id(it->stage, it->table, ecs_pair_t(EcsDocDescription, EcsName), it->offset); this_data.brief = ecs_table_get_id(it->stage, it->table, ecs_pair_t(EcsDocDescription, EcsDocBrief), it->offset); this_data.detail = ecs_table_get_id(it->stage, it->table, ecs_pair_t(EcsDocDescription, EcsDocDetail), it->offset); this_data.color = ecs_table_get_id(it->stage, it->table, ecs_pair_t(EcsDocDescription, EcsDocColor), it->offset); this_data.link = ecs_table_get_id(it->stage, it->table, ecs_pair_t(EcsDocDescription, EcsDocLink), it->offset); } #endif #ifdef FLECS_ALERTS if (it->table && (ecs_id(EcsAlertsActive) != 0)) { /* Only add field if alerts addon is imported */ if (ecs_table_has_id(world, table, ecs_id(EcsAlertsActive))) { this_data.has_alerts = true; } } #endif } else { this_data.ids = it->entities; } } if (desc && desc->serialize_table) { if (flecs_json_serialize_iter_result_table(world, it, buf, desc, ser_ctx, count, has_this, parent_path, &this_data)) { goto error; } } else { if (flecs_json_serialize_iter_result_query(world, it, buf, ser_ctx, desc, count, has_this, parent_path, &this_data)) { goto error; } } ecs_os_free(parent_path); return 0; error: ecs_os_free(parent_path); return -1; } #endif #ifdef FLECS_JSON static bool flecs_json_serialize_iter_result_is_set( const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!(it->flags & EcsIterHasCondSet)) { return false; } flecs_json_memberl(buf, "is_set"); flecs_json_array_push(buf); int8_t i, count = it->field_count; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(buf); flecs_json_bool(buf, ecs_field_is_set(it, i)); } flecs_json_array_pop(buf); return true; } static bool flecs_json_serialize_iter_result_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { const ecs_query_t *q = it->query; if (!q) { return false; } ecs_world_t *world = it->world; int16_t f, field_count = flecs_ito(int16_t, it->field_count); uint32_t field_mask = (uint32_t)((1llu << field_count) - 1); if (q->static_id_fields == field_mask) { /* All matched ids are static, nothing to serialize */ return false; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(it->set_fields & field_bit) || (q->static_id_fields & field_bit)) { ecs_strbuf_list_appendlit(buf, "0"); continue; } flecs_json_next(buf); flecs_json_id(buf, world, it->ids[f]); } flecs_json_array_pop(buf); return true; } static bool flecs_json_serialize_iter_result_sources( const ecs_iter_t *it, ecs_strbuf_t *buf) { const ecs_query_t *q = it->query; if (!q) { return false; } ecs_world_t *world = it->world; int32_t f, field_count = it->field_count; for (f = 0; f < field_count; f ++) { if (it->sources[f]) { break; } } if (f == field_count) { /* All fields are matched on $this */ return false; } flecs_json_memberl(buf, "sources"); flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(it->set_fields & field_bit) || !it->sources[f]) { ecs_strbuf_list_appendlit(buf, "0"); continue; } flecs_json_next(buf); flecs_json_path(buf, world, it->sources[f]); } flecs_json_array_pop(buf); return true; } static bool flecs_json_serialize_common_for_table( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_strbuf_list_push(buf, "", ","); flecs_json_serialize_vars(world, it, buf, desc); bool result = false; if (!desc || desc->serialize_fields) { ecs_strbuf_list_appendlit(buf, "\"fields\":"); flecs_json_object_push(buf); result |= flecs_json_serialize_iter_result_is_set(it, buf); result |= flecs_json_serialize_iter_result_ids(it, buf); result |= flecs_json_serialize_iter_result_sources(it, buf); } ecs_strbuf_list_pop(buf, ""); return result; } static int flecs_json_serialize_iter_result_field_values( const ecs_world_t *world, const ecs_iter_t *it, int32_t i, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { int8_t f, field_count = it->field_count; if (!field_count) { return 0; } ecs_strbuf_appendlit(buf, "\"values\":"); flecs_json_array_push(buf); ecs_termset_t fields = it->set_fields; if (it->query) { fields &= it->query->data_fields; } ecs_termset_t row_fields = it->query ? it->query->row_fields : 0; for (f = 0; f < field_count; f ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(fields & field_bit)) { ecs_strbuf_list_appendlit(buf, "0"); continue; } ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; bool has_ser = flecs_json_serialize_get_value_ctx( world, it->ids[f], value_ctx, desc); flecs_json_accum_type_info(world, value_ctx->type, ser_ctx); if (!has_ser) { ecs_strbuf_list_appendlit(buf, "0"); continue; } void *ptr; if (row_fields & field_bit) { ptr = ecs_field_at_w_size(it, flecs_itosize(it->sizes[f]), f, i); } else { ecs_size_t size = it->sizes[f]; ptr = ecs_base_field_w_size(it, flecs_itosize(size), f); if (!ptr) { ecs_strbuf_list_appendlit(buf, "0"); continue; } if (!it->sources[f]) { ptr = ECS_ELEM(ptr, flecs_uto(ecs_size_t, ecs_field_stride(it, f)), i); } } flecs_json_next(buf); if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { return -1; } } flecs_json_array_pop(buf); return 0; } int flecs_json_serialize_iter_result_query( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data) { /* Serialize tags, pairs, vars once, since they're the same for each row */ ecs_strbuf_t common_data_buf = ECS_STRBUF_INIT; bool common_field_data = flecs_json_serialize_common_for_table( world, it, &common_data_buf, desc); int32_t common_data_len = ecs_strbuf_written(&common_data_buf); char *common_data = NULL; if (!common_data_len) { ecs_strbuf_reset(&common_data_buf); } else { common_data = ecs_strbuf_get(&common_data_buf); } int32_t i; for (i = 0; i < count; i ++) { if (has_this) { if (!flecs_json_serialize_iter_this( it, parent_path, this_data, i, buf, desc, ser_ctx)) { continue; } } else { flecs_json_next(buf); flecs_json_object_push(buf); } if (common_data) { ecs_strbuf_list_appendstrn(buf, common_data, common_data_len); } if (!desc || desc->serialize_fields) { bool has_values = !desc || desc->serialize_values; if (it->flags & EcsIterNoData || !it->field_count) { has_values = false; } const ecs_query_t *q = it->query; if (q && !q->data_fields) { has_values = false; } if (has_values) { if (common_field_data) { flecs_json_next(buf); } if (flecs_json_serialize_iter_result_field_values( world, it, i, buf, desc, ser_ctx)) { ecs_os_free(common_data); return -1; } } ecs_strbuf_appendstr(buf, "}"); /* end "fields" */ } flecs_json_object_pop(buf); } ecs_os_free(common_data); return 0; } #endif #ifdef FLECS_JSON typedef struct flecs_json_type_iter_t { const ecs_world_t *world; const ecs_table_t *table; const ecs_iter_to_json_desc_t *desc; ecs_entity_t entity; bool inherited; int32_t index; int32_t column; ecs_component_record_t *cr; } flecs_json_type_iter_t; static ecs_component_record_t* flecs_json_type_next( flecs_json_type_iter_t *it, int kind) { const ecs_table_t *table = it->table; for (;;) { ecs_component_record_t *cr; if (it->index < table->type.count) { int32_t index = it->index ++; cr = table->_->records[index].hdr.cr; it->column = table->column_map ? table->column_map[index] : -1; } else { if (it->index == table->type.count) { it->index ++; ecs_record_t *r = it->entity ? ecs_record_find(it->world, it->entity) : NULL; cr = r && (r->row & EcsEntityHasDontFragment) ? it->world->cr_non_fragmenting_head : NULL; } else { cr = it->cr ? it->cr->non_fragmenting.next : NULL; } it->column = -1; if (!cr) { return NULL; } it->cr = cr; if (!cr->sparse || ecs_id_is_wildcard(cr->id) || !flecs_sparse_has(cr->sparse, it->entity)) { continue; } } it->cr = cr; ecs_id_t id = cr->id; bool data = it->column != -1 || ((cr->flags & EcsIdSparse) && cr->type_info); if ((data ? 2 : ECS_IS_PAIR(id) ? 1 : 0) != kind || (it->inherited && !(cr->flags & EcsIdOnInstantiateInherit))) { continue; } if ((!it->desc || !it->desc->serialize_builtin) && ECS_IS_PAIR(id) && (ECS_PAIR_FIRST(id) == EcsChildOf || id == ecs_pair_t(EcsIdentifier, EcsName))) { continue; } if (kind == 2 && it->desc && it->desc->component_filter && !it->desc->component_filter(it->world, id)) { continue; } return cr; } } static void flecs_json_serialize_table_tags( const ecs_world_t *world, const ecs_table_t *table, const ecs_table_t *src_table, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { flecs_json_type_iter_t it = { .world = world, .table = table, .desc = desc, .entity = entity, .inherited = src_table != NULL }; ecs_component_record_t *cr = flecs_json_type_next(&it, 0); if (!cr) { return; } flecs_json_memberl(buf, "tags"); flecs_json_array_push(buf); do { flecs_json_next(buf); ecs_strbuf_appendch(buf, '"'); flecs_json_id_member(buf, world, cr->id, desc ? desc->serialize_full_paths : true); ecs_strbuf_appendch(buf, '"'); } while ((cr = flecs_json_type_next(&it, 0))); flecs_json_array_pop(buf); } static void flecs_json_emit_pair_entry( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_id_t id, ecs_id_t prev_id, ecs_id_t next_id, int32_t *pair_count, bool *same_first) { bool full_paths = desc ? desc->serialize_full_paths : true; if (!*pair_count) { flecs_json_memberl(buf, "pairs"); flecs_json_object_push(buf); } bool is_same = ECS_PAIR_FIRST(next_id) == ECS_PAIR_FIRST(id); if (*same_first && ECS_PAIR_FIRST(prev_id) != ECS_PAIR_FIRST(id)) { flecs_json_array_pop(buf); *same_first = false; } if (!*same_first) { ecs_entity_t first = flecs_entities_get_alive( world, ECS_PAIR_FIRST(id)); flecs_json_next(buf); flecs_json_path_or_label(buf, world, first, full_paths); ecs_strbuf_appendlit(buf, ":"); if (is_same) { flecs_json_array_push(buf); *same_first = true; } } if (*same_first) { flecs_json_next(buf); } if (ECS_IS_VALUE_PAIR(id)) { ecs_strbuf_appendch(buf, '\"'); ecs_strbuf_appendch(buf, '@'); ecs_strbuf_appendint(buf, ECS_PAIR_SECOND(id)); ecs_strbuf_appendch(buf, '\"'); } else { ecs_entity_t second = flecs_entities_get_alive( world, ECS_PAIR_SECOND(id)); flecs_json_path_or_label(buf, world, second, full_paths); } (*pair_count) ++; } static void flecs_json_serialize_table_pairs( const ecs_world_t *world, const ecs_table_t *table, const ecs_table_t *src_table, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { flecs_json_type_iter_t it = { .world = world, .table = table, .desc = desc, .entity = entity, .inherited = src_table != NULL }; ecs_component_record_t *cr = flecs_json_type_next(&it, 1); int32_t count = 0; ecs_id_t prev = 0; bool same_first = false; while (cr) { ecs_id_t id = cr->id; int32_t index = it.index - 1; cr = flecs_json_type_next(&it, 1); ecs_id_t next = cr ? cr->id : 0; if (index < table->type.count) { prev = index ? table->type.array[index - 1] : 0; next = index + 1 < table->type.count ? table->type.array[index + 1] : 0; } flecs_json_emit_pair_entry(world, buf, desc, id, prev, next, &count, &same_first); if (index < table->type.count && it.index > table->type.count && same_first) { flecs_json_array_pop(buf); same_first = false; } prev = id; } if (same_first) { flecs_json_array_pop(buf); } if (count) { flecs_json_object_pop(buf); } } static int flecs_json_serialize_component_value( const ecs_world_t *world, ecs_id_t id, void *ptr, const ecs_type_info_t *ti, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, ecs_json_value_ser_ctx_t *value_ctx, const ecs_iter_to_json_desc_t *desc, int32_t *component_count) { if (ti) { flecs_json_accum_type_info(world, ti->component, ser_ctx); } if (!component_count[0]) { flecs_json_memberl(buf, "components"); flecs_json_object_push(buf); } bool has_reflection; const EcsTypeSerializer *type_ser; if (value_ctx) { has_reflection = flecs_json_serialize_get_value_ctx( world, id, value_ctx, desc); flecs_json_member(buf, value_ctx->id_label); type_ser = value_ctx->ser; } else { ecs_strbuf_list_next(buf); ecs_strbuf_appendlit(buf, "\""); flecs_json_id_member(buf, world, id, desc ? desc->serialize_full_paths : true); ecs_strbuf_appendlit(buf, "\":"); type_ser = NULL; if (!desc || desc->serialize_values) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); type_ser = ecs_get(world, ti->component, EcsTypeSerializer); } has_reflection = type_ser != NULL; } component_count[0] ++; if (has_reflection && (!desc || desc->serialize_values)) { ecs_assert(type_ser != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_json_ser_type( world, &type_ser->ops, ptr, buf) != 0) { return -1; } } else { ecs_strbuf_appendlit(buf, "null"); } return 0; } static int flecs_json_serialize_table_components( const ecs_world_t *world, ecs_table_t *table, const ecs_table_t *src_table, ecs_entity_t entity, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, ecs_vec_t *values_ctx, const ecs_iter_to_json_desc_t *desc, int32_t row, int32_t *component_count) { flecs_json_type_iter_t it = { .world = world, .table = table, .desc = desc, .entity = entity, .inherited = src_table != NULL }; ecs_component_record_t *cr; while ((cr = flecs_json_type_next(&it, 2))) { const ecs_type_info_t *ti = cr->type_info; void *ptr; if (it.column != -1) { ptr = ECS_ELEM(table->data.columns[it.column].data, ti->size, row); } else { ecs_entity_t e = it.index <= table->type.count ? ecs_table_entities(table)[row] : entity; ptr = flecs_sparse_get(cr->sparse, ti->size, e); } if (!ptr) { continue; } ecs_json_value_ser_ctx_t *value_ctx = NULL; if (values_ctx) { int32_t index = component_count[0]; if (index == ecs_vec_count(values_ctx)) { value_ctx = ecs_vec_append_t( NULL, values_ctx, ecs_json_value_ser_ctx_t); *value_ctx = (ecs_json_value_ser_ctx_t){0}; } else { value_ctx = ecs_vec_get_t( values_ctx, ecs_json_value_ser_ctx_t, index); } } if (flecs_json_serialize_component_value(world, cr->id, ptr, ti, buf, ser_ctx, value_ctx, desc, component_count)) { return -1; } } if (component_count[0]) { flecs_json_object_pop(buf); } return 0; } static int flecs_json_serialize_table_inherited_type( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc, bool *has_inherited) { if (!(table->flags & EcsTableHasIsA)) { return 0; } const ecs_table_record_t *tr = flecs_component_get_table( world->cr_isa_wildcard, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* Table has IsA flag */ int32_t i, start = tr->index, end = start + tr->count; for (i = start; i < end; i ++) { ecs_entity_t base = ecs_pair_second(world, table->type.array[i]); ecs_record_t *base_record = ecs_record_find(world, base); if (!base_record || !base_record->table) { continue; } ecs_table_t *base_table = base_record->table; if (flecs_json_serialize_table_inherited_type( world, base_table, buf, ser_ctx, desc, has_inherited)) { return -1; } char *base_name = ecs_get_path(world, base); flecs_json_member(buf, base_name); flecs_json_object_push(buf); ecs_os_free(base_name); flecs_json_serialize_table_tags( world, base_table, table, base, buf, desc); flecs_json_serialize_table_pairs( world, base_table, table, base, buf, desc); int32_t component_count = 0; if (flecs_json_serialize_table_components( world, base_table, table, base, buf, ser_ctx, NULL, desc, ECS_RECORD_TO_ROW(base_record->row), &component_count)) { return -1; } flecs_json_object_pop(buf); } *has_inherited = true; return 0; } static int flecs_json_serialize_table_inherited( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc, bool *has_inherited) { if (!(table->flags & EcsTableHasIsA)) { return 0; } flecs_json_memberl(buf, "inherited"); flecs_json_object_push(buf); if (flecs_json_serialize_table_inherited_type( world, table, buf, ser_ctx, desc, has_inherited)) { return -1; } flecs_json_object_pop(buf); return 0; } int flecs_json_serialize_iter_result_table( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data) { ecs_table_t *table = it->table; if (!table || !count) { return 0; } ecs_vec_t values_ctx; ecs_vec_init_t(NULL, &values_ctx, ecs_json_value_ser_ctx_t, 0); int32_t component_count = 0; const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, end = it->offset + count; int result = 0; for (i = it->offset; i < end; i ++) { ecs_entity_t e = entities[i]; if (has_this) { ecs_json_this_data_t this_data_cpy = *this_data; if (!flecs_json_serialize_iter_this(it, parent_path, &this_data_cpy, i - it->offset, buf, desc, ser_ctx)) { continue; } } else { flecs_json_next(buf); flecs_json_object_push(buf); } flecs_json_serialize_table_tags(world, table, NULL, e, buf, desc); flecs_json_serialize_table_pairs(world, table, NULL, e, buf, desc); flecs_json_serialize_vars(world, it, buf, desc); if (desc->serialize_inherited) { bool has_inherited = false; if (flecs_json_serialize_table_inherited( world, table, buf, ser_ctx, desc, &has_inherited)) { result = -1; break; } } component_count = 0; if (flecs_json_serialize_table_components( world, table, NULL, e, buf, ser_ctx, &values_ctx, desc, i, &component_count)) { result = -1; break; } if (desc->serialize_matches) { flecs_json_serialize_matches( world, buf, it->entities[i - it->offset]); } if (desc->serialize_refs) { flecs_json_serialize_refs(world, buf, it->entities[i - it->offset], desc->serialize_refs); } if (desc->serialize_alerts) { flecs_json_serialize_alerts(world, buf, it->entities[i - it->offset]); } flecs_json_object_pop(buf); } ecs_json_value_ser_ctx_t *contexts = ecs_vec_first(&values_ctx); for (i = 0; i < ecs_vec_count(&values_ctx); i ++) { ecs_os_free(contexts[i].id_label); } ecs_vec_fini_t(NULL, &values_ctx, ecs_json_value_ser_ctx_t); return result; } #endif #ifdef FLECS_JSON static const char* flecs_json_inout_str( int16_t kind) { switch(kind) { case EcsIn: return "in"; case EcsOut: return "out"; case EcsInOut: return "inout"; case EcsInOutNone: return "none"; case EcsInOutFilter: return "filter"; case EcsInOutDefault: return "default"; default: return "unknown"; } } static const char* flecs_json_oper_str( int16_t kind) { switch(kind) { case EcsAnd: return "and"; case EcsNot: return "not"; case EcsOr: return "or"; case EcsOptional: return "optional"; case EcsAndFrom: return "andfrom"; case EcsNotFrom: return "notfrom"; case EcsOrFrom: return "orfrom"; default: return "unknown"; } } static void flecs_json_serialize_term_entity( const ecs_world_t *world, ecs_entity_t e, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "entity"); flecs_json_path(buf, world, e); if (e) { const char *symbol = ecs_get_symbol(world, e); if (symbol) { flecs_json_memberl(buf, "symbol"); flecs_json_string(buf, symbol); } if (ecs_has(world, e, EcsComponent)) { flecs_json_memberl(buf, "type"); flecs_json_true(buf); } } } static void flecs_json_serialize_term_ref( const ecs_world_t *world, const ecs_term_ref_t *ref, ecs_strbuf_t *buf) { flecs_json_object_push(buf); if (ref->id & EcsIsEntity) { flecs_json_serialize_term_entity(world, ECS_TERM_REF_ID(ref), buf); } else if (ref->id & EcsIsVariable) { flecs_json_memberl(buf, "var"); if (ref->name) { flecs_json_string(buf, ref->name); } else if (ref->id) { if (ECS_TERM_REF_ID(ref) == EcsThis) { flecs_json_string(buf, "this"); } else { flecs_json_path(buf, world, ECS_TERM_REF_ID(ref)); } } } else if (ref->id & EcsIsName) { flecs_json_memberl(buf, "name"); flecs_json_string(buf, ref->name); } flecs_json_object_pop(buf); } static void flecs_json_serialize_term_trav( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf) { if (term->trav) { flecs_json_memberl(buf, "trav"); flecs_json_object_push(buf); flecs_json_serialize_term_entity(world, term->trav, buf); flecs_json_object_pop(buf); } flecs_json_memberl(buf, "flags"); flecs_json_array_push(buf); if (term->src.id & EcsSelf) { flecs_json_next(buf); flecs_json_string(buf, "self"); } if (term->src.id & EcsCascade) { flecs_json_next(buf); flecs_json_string(buf, "cascade"); } else if (term->src.id & EcsUp) { flecs_json_next(buf); flecs_json_string(buf, "up"); } flecs_json_array_pop(buf); } static void flecs_json_serialize_term( const ecs_world_t *world, const ecs_query_t *q, int t, ecs_strbuf_t *buf) { const ecs_term_t *term = &q->terms[t]; flecs_json_object_push(buf); flecs_json_memberl(buf, "inout"); flecs_json_string(buf, flecs_json_inout_str(term->inout)); flecs_json_memberl(buf, "has_value"); flecs_json_bool(buf, !!((1llu << term->field_index) & q->data_fields)); #ifdef FLECS_PREFAB ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); if (term->first.id & EcsIsEntity && first_id) { if (ecs_has_pair(world, first_id, EcsOnInstantiate, EcsInherit)) { flecs_json_memberl(buf, "can_inherit"); flecs_json_true(buf); } } #endif flecs_json_memberl(buf, "oper"); flecs_json_string(buf, flecs_json_oper_str(term->oper)); flecs_json_memberl(buf, "src"); flecs_json_serialize_term_ref(world, &term->src, buf); flecs_json_memberl(buf, "first"); flecs_json_serialize_term_ref(world, &term->first, buf); if (ECS_TERM_REF_ID(&term->second) || term->second.name || term->second.id & EcsIsEntity) { flecs_json_memberl(buf, "second"); flecs_json_serialize_term_ref(world, &term->second, buf); } flecs_json_serialize_term_trav(world, term, buf); flecs_json_object_pop(buf); } void flecs_json_serialize_query( const ecs_world_t *world, const ecs_query_t *q, ecs_strbuf_t *buf) { flecs_json_object_push(buf); if (q->var_count) { flecs_json_memberl(buf, "vars"); flecs_json_array_push(buf); int32_t v, first = 0; if (!(q->flags & EcsQueryMatchThis)) { first = 1; } for (v = first; v < q->var_count; v ++) { flecs_json_next(buf); if (q->vars[v]) { flecs_json_string_escape(buf, q->vars[v]); } else { flecs_json_string(buf, "this"); } } flecs_json_array_pop(buf); } flecs_json_memberl(buf, "terms"); flecs_json_array_push(buf); int t; for (t = 0; t < q->term_count; t ++) { flecs_json_next(buf); flecs_json_serialize_term(world, q, t, buf); } flecs_json_array_pop(buf); flecs_json_object_pop(buf); } #endif #ifdef FLECS_JSON static int flecs_json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf); static int flecs_json_typeinfo_ser_primitive( ecs_primitive_kind_t kind, ecs_strbuf_t *str) { switch(kind) { case EcsBool: flecs_json_string(str, "bool"); break; case EcsChar: case EcsString: flecs_json_string(str, "text"); break; case EcsByte: flecs_json_string(str, "byte"); break; case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsIPtr: case EcsUPtr: flecs_json_string(str, "int"); break; case EcsF32: case EcsF64: flecs_json_string(str, "float"); break; case EcsEntity: flecs_json_string(str, "entity"); break; case EcsId: flecs_json_string(str, "id"); break; default: return -1; } ecs_strbuf_list_next(str); return 0; } static void flecs_json_typeinfo_ser_constants( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsChildOf, type)); while (ecs_each_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { flecs_json_next(str); flecs_json_string(str, ecs_get_name(world, it.entities[i])); } } } /* Serialize unit information */ static int flecs_json_typeinfo_ser_unit( const ecs_world_t *world, ecs_strbuf_t *str, ecs_entity_t unit) { flecs_json_memberl(str, "unit"); flecs_json_path(str, world, unit); const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); if (uptr) { if (uptr->symbol) { flecs_json_memberl(str, "symbol"); flecs_json_string(str, uptr->symbol); } ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); if (quantity) { flecs_json_memberl(str, "quantity"); flecs_json_path(str, world, quantity); } } return 0; } static void flecs_json_typeinfo_ser_range( ecs_strbuf_t *str, const char *kind, const ecs_member_value_range_t *range) { flecs_json_member(str, kind); flecs_json_array_push(str); flecs_json_next(str); flecs_json_number(str, range->min); flecs_json_next(str); flecs_json_number(str, range->max); flecs_json_array_pop(str); } static int flecs_json_typeinfo_ser_op( const ecs_world_t *world, const ecs_meta_op_t *op, const ecs_member_t *member, ecs_strbuf_t *str); static int flecs_json_typeinfo_ser_scope( const ecs_world_t *world, const ecs_meta_op_t *op, ecs_strbuf_t *str) { const EcsStruct *st = op->kind == EcsOpPushStruct ? ecs_get(world, op->type, EcsStruct) : NULL; for (int32_t i = 1; i < op->op_count - 1; ) { const ecs_meta_op_t *child = &op[i]; const ecs_member_t *member = NULL; if (child->name) { flecs_json_member(str, child->name); member = ecs_vec_get_t(&st->members, ecs_member_t, child->member_index); } if (flecs_json_typeinfo_ser_op(world, child, member, str)) { return -1; } i += child->op_count; } ecs_entity_t unit = ecs_get_target_for(world, op->type, EcsIsA, EcsUnit); if (unit) { flecs_json_member(str, "@self"); flecs_json_array_push(str); flecs_json_object_push(str); flecs_json_typeinfo_ser_unit(world, str, unit); flecs_json_object_pop(str); flecs_json_array_pop(str); } return 0; } static int flecs_json_typeinfo_ser_op( const ecs_world_t *world, const ecs_meta_op_t *op, const ecs_member_t *member, ecs_strbuf_t *str) { if (op->kind == EcsOpForward) { return flecs_json_typeinfo_ser_type(world, op->type, str); } if (op->kind == EcsOpOpaqueStruct || op->kind == EcsOpOpaqueArray || op->kind == EcsOpOpaqueVector || op->kind == EcsOpOpaqueValue) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); return flecs_json_typeinfo_ser_type(world, ct->as_type, str); } if (op->kind == EcsOpPushStruct) { flecs_json_object_push(str); if (flecs_json_typeinfo_ser_scope(world, op, str)) { return -1; } flecs_json_object_pop(str); return 0; } flecs_json_array_push(str); switch (op->kind) { case EcsOpPushMap: { ecs_strbuf_list_appendstr(str, "\"map\""); flecs_json_next(str); ecs_meta_op_t key = { .kind = op->underlying_kind }; if (key.kind == EcsOpEnum || key.kind == EcsOpBitmask) { key.type = ecs_get(world, op->type, EcsMap)->key_type; } if (flecs_json_typeinfo_ser_op(world, &key, NULL, str)) { return -1; } flecs_json_next(str); if (flecs_json_typeinfo_ser_scope(world, op, str)) { return -1; } break; } case EcsOpPushArray: case EcsOpPushVector: ecs_strbuf_list_appendstr(str, op->kind == EcsOpPushArray ? "\"array\"" : "\"vector\""); flecs_json_next(str); if (flecs_json_typeinfo_ser_scope(world, op, str)) { return -1; } if (op->kind == EcsOpPushArray) { ecs_strbuf_list_append(str, "%u", ecs_meta_op_get_elem_count(op, NULL)); } break; case EcsOpPushValue: ecs_strbuf_list_appendstr(str, "\"value\""); break; case EcsOpEnum: case EcsOpBitmask: ecs_strbuf_list_appendstr(str, op->kind == EcsOpEnum ? "\"enum\"" : "\"bitmask\""); flecs_json_typeinfo_ser_constants(world, op->type, str); break; default: if (flecs_json_typeinfo_ser_primitive( flecs_json_op_to_primitive_kind(op->kind), str)) { return -1; } break; } if (member) { bool value_range = ECS_NEQ(member->range.min, member->range.max); bool error_range = ECS_NEQ(member->error_range.min, member->error_range.max); bool warning_range = ECS_NEQ(member->warning_range.min, member->warning_range.max); if (member->unit || value_range || error_range || warning_range) { flecs_json_next(str); flecs_json_object_push(str); if (member->unit) { flecs_json_typeinfo_ser_unit(world, str, member->unit); } if (value_range) { flecs_json_typeinfo_ser_range(str, "range", &member->range); } if (error_range) { flecs_json_typeinfo_ser_range(str, "error_range", &member->error_range); } if (warning_range) { flecs_json_typeinfo_ser_range(str, "warning_range", &member->warning_range); } flecs_json_object_pop(str); } } flecs_json_array_pop(str); return 0; } static int flecs_json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { const EcsTypeSerializer *ser = ecs_get(world, type, EcsTypeSerializer); if (!ecs_has(world, type, EcsComponent) || !ser) { ecs_strbuf_appendch(buf, '0'); return 0; } return flecs_json_typeinfo_ser_op(world, ecs_vec_first(&ser->ops), NULL, buf); } int ecs_type_info_to_json_buf( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { return flecs_json_typeinfo_ser_type(world, type, buf); } char* ecs_type_info_to_json( const ecs_world_t *world, ecs_entity_t type) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_type_info_to_json_buf(world, type, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } #endif #ifndef FLECS_META_PRIVATE_H #define FLECS_META_PRIVATE_H #ifdef FLECS_META #define flecs_meta_set_t(T, ptr, value)\ ((T*)ptr)[0] = ((T)value) bool flecs_meta_valid_digit( const char *str); bool flecs_meta_type_is_integer( const ecs_world_t *world, ecs_entity_t type); void flecs_meta_type_serializer_init( ecs_iter_t *it); void flecs_type_serializer_dtor( EcsTypeSerializer *ptr); ecs_meta_op_kind_t flecs_meta_primitive_to_op_kind( ecs_primitive_kind_t kind); ecs_entity_t flecs_meta_primitive_type( ecs_primitive_kind_t kind); ecs_entity_t flecs_meta_op_kind_to_type( ecs_meta_op_kind_t kind); const char* flecs_meta_op_kind_str( ecs_meta_op_kind_t kind); int flecs_value_blit_u64( const ecs_world_t *world, const ecs_value_t *key_value, uint64_t *key_out); typedef enum flecs_meta_format_t { EcsMetaStr, EcsMetaExpr, EcsMetaExprPrecise, EcsMetaJson, EcsMetaExprPositional, EcsMetaExprPrecisePositional } flecs_meta_format_t; int flecs_meta_serialize( const ecs_world_t *world, const ecs_vec_t *ops, const void *ptr, ecs_strbuf_t *str, flecs_meta_format_t format); int flecs_meta_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str, bool is_expr); int flecs_meta_ser_enum( const ecs_world_t *world, ecs_entity_t enum_type, ecs_meta_op_kind_t op_kind, ecs_map_t *constants, const void *ptr, ecs_strbuf_t *str); int flecs_meta_ser_bitmask( const ecs_world_t *world, ecs_entity_t bitmask_type, ecs_map_t *constants, const void *ptr, const char *delim, ecs_strbuf_t *str); int flecs_meta_parse_bitmask( const ecs_world_t *world, ecs_entity_t type, ecs_map_t *constants, void *out, const char *value); int flecs_meta_parse_enum( const ecs_world_t *world, ecs_entity_t type, ecs_map_t *constants, void *out, const char *value); int flecs_meta_find_constant( const ecs_world_t *world, const ecs_map_t *constants, const char *name, uint64_t *value_out); int flecs_meta_ser_map_key( const ecs_world_t *world, const ecs_meta_op_t *op, ecs_map_key_t key, ecs_strbuf_t *str); void flecs_meta_map_clear( ecs_map_t *map, const ecs_type_info_t *value_ti); int flecs_meta_map_set_key( ecs_meta_cursor_t *cursor, const ecs_value_t *key_value); void flecs_rtt_init_default_hooks( ecs_iter_t *it); int flecs_value_ensure_type( const ecs_world_t *world, ecs_value_t *v, ecs_entity_t type); void flecs_meta_value_init( ecs_world_t *world); int flecs_meta_value_type_str( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str); void flecs_meta_import_definitions( ecs_world_t *world); int32_t flecs_struct_inherited_count( ecs_world_t *world, ecs_entity_t struct_type, const EcsStruct *s); bool flecs_struct_is_derived_from( const ecs_world_t *world, ecs_entity_t derived, ecs_entity_t base); #endif #endif #ifdef FLECS_JSON int flecs_json_ser_type( const ecs_world_t *world, const ecs_vec_t *ops, const void *ptr, ecs_strbuf_t *str) { return flecs_meta_serialize(world, ops, ptr, str, EcsMetaJson); } static int flecs_array_to_json_buf_w_type_data( const ecs_world_t *world, const void *ptr, int32_t count, ecs_strbuf_t *buf, const EcsComponent *comp, const EcsTypeSerializer *ser) { if (count) { ecs_size_t size = comp->size; flecs_json_array_push(buf); do { ecs_strbuf_list_next(buf); if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } ptr = ECS_OFFSET(ptr, size); } while (-- count); flecs_json_array_pop(buf); } else { if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } } return 0; } int ecs_array_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, int32_t count, ecs_strbuf_t *buf) { ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); const EcsTypeSerializer *ser = comp ? ecs_get(world, type, EcsTypeSerializer) : NULL; if (!comp || !ser) { char *path = ecs_get_path(world, type); ecs_err("cannot serialize to JSON, '%s' %s", path, !comp ? "is not a component" : "has no reflection data"); ecs_os_free(path); return -1; } return flecs_array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } char* ecs_array_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr, int32_t count) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf) { return ecs_array_to_json_buf(world, type, ptr, 0, buf); } char* ecs_ptr_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { return ecs_array_to_json(world, type, ptr, 0); } #endif #ifdef FLECS_JSON int ecs_world_to_json_buf( ecs_world_t *world, ecs_strbuf_t *buf_out, const ecs_world_to_json_desc_t *desc) { ecs_query_desc_t query_desc = {0}; if (desc && desc->serialize_builtin && desc->serialize_modules) { query_desc.terms[0].id = EcsAny; } else { bool serialize_builtin = desc && desc->serialize_builtin; bool serialize_modules = desc && desc->serialize_modules; int32_t term_id = 0; if (!serialize_builtin) { query_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); query_desc.terms[term_id].oper = EcsNot; query_desc.terms[term_id].src.id = EcsSelf | EcsUp; term_id ++; } if (!serialize_modules) { query_desc.terms[term_id].id = EcsModule; query_desc.terms[term_id].oper = EcsNot; query_desc.terms[term_id].src.id = EcsSelf | EcsUp; } } query_desc.flags = EcsQueryMatchDisabled|EcsQueryMatchPrefab; ecs_query_t *q = ecs_query_init(world, &query_desc); if (!q) { return -1; } ecs_iter_t it = ecs_query_iter(world, q); ecs_iter_to_json_desc_t json_desc = { .serialize_table = true, .serialize_full_paths = true, .serialize_entity_ids = true, .serialize_values = true, .serialize_parents_before_children = true }; int ret = ecs_iter_to_json_buf(&it, buf_out, &json_desc); ecs_query_fini(q); return ret; } char* ecs_world_to_json( ecs_world_t *world, const ecs_world_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_world_to_json_buf(world, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif #ifdef FLECS_META typedef struct flecs_meta_c_parser_t { ecs_world_t *world; const char *name; const char *code; const char *ptr; int32_t depth; bool is_private; } flecs_meta_c_parser_t; #define flecs_meta_c_error(p, ...)\ ecs_parser_error((p)->name, (p)->code, (p)->ptr - (p)->code, __VA_ARGS__) static bool flecs_meta_c_expect( flecs_meta_c_parser_t *p, char ch) { p->ptr = flecs_parse_ws_eol(p->ptr); if (p->ptr[0] != ch) { flecs_meta_c_error(p, "expected '%c'", ch); return false; } p->ptr = flecs_parse_ws_eol(p->ptr + 1); return true; } static bool flecs_meta_c_identifier( flecs_meta_c_parser_t *p, char *name) { p->ptr = flecs_parse_ws_eol(p->ptr); if (!isalpha((unsigned char)p->ptr[0]) && p->ptr[0] != '_') { flecs_meta_c_error(p, "expected identifier"); return false; } int32_t length = 0; while (isalnum((unsigned char)p->ptr[0]) || p->ptr[0] == '_' || p->ptr[0] == ':' || p->ptr[0] == '.') { if (length == 255) { flecs_meta_c_error(p, "identifier is too long"); return false; } name[length ++] = *p->ptr ++; } name[length] = '\0'; p->ptr = flecs_parse_ws_eol(p->ptr); return true; } static bool flecs_meta_c_number( flecs_meta_c_parser_t *p, int64_t *value, int base) { p->ptr = flecs_parse_ws_eol(p->ptr); char *end; errno = 0; *value = strtoll(p->ptr, &end, base); if (end == p->ptr || errno == ERANGE) { flecs_meta_c_error(p, "invalid numeric value"); return false; } p->ptr = flecs_parse_ws_eol(end); return true; } static ecs_entity_t flecs_meta_c_lookup( ecs_world_t *world, const char *name) { static const struct { const char *name; const ecs_entity_t *type; } aliases[] = { {"char", &ecs_id(ecs_char_t)}, {"bool", &ecs_id(ecs_bool_t)}, {"_Bool", &ecs_id(ecs_bool_t)}, {"int8_t", &ecs_id(ecs_i8_t)}, {"int16_t", &ecs_id(ecs_i16_t)}, {"int32_t", &ecs_id(ecs_i32_t)}, {"int64_t", &ecs_id(ecs_i64_t)}, {"uint8_t", &ecs_id(ecs_u8_t)}, {"uint16_t", &ecs_id(ecs_u16_t)}, {"uint32_t", &ecs_id(ecs_u32_t)}, {"uint64_t", &ecs_id(ecs_u64_t)}, {"float", &ecs_id(ecs_f32_t)}, {"double", &ecs_id(ecs_f64_t)}, {"ecs_entity_t", &ecs_id(ecs_entity_t)}, {"ecs_id_t", &ecs_id(ecs_id_t)}, {"flecs::byte", &ecs_id(ecs_byte_t)} }; for (int32_t i = 0; i < (int32_t)(sizeof(aliases) / sizeof(aliases[0])); i ++) { if (!ecs_os_strcmp(name, aliases[i].name)) { return *aliases[i].type; } } return ecs_lookup_symbol(world, name, true, true); } static const char* flecs_meta_c_scope_end( flecs_meta_c_parser_t *p) { char scopes[256]; int32_t depth = 0; const char *ptr = p->ptr; do { char ch = *ptr ++; if (ch == '(' || ch == '<') { if (depth == 256) { break; } scopes[depth ++] = ch == '(' ? ')' : '>'; } else if (ch == ')' || ch == '>') { if (!depth || ch != scopes[-- depth]) { break; } } else if (!ch) { break; } if (!depth) { return flecs_parse_ws_eol(ptr); } } while (true); flecs_meta_c_error(p, "invalid type parameters"); return NULL; } static ecs_entity_t flecs_meta_c_type( flecs_meta_c_parser_t *p) { char name[256]; if (!flecs_meta_c_identifier(p, name)) { return 0; } if (!ecs_os_strcmp(name, "ECS_PRIVATE")) { p->is_private = p->depth == 0; return 0; } if (!ecs_os_strcmp(name, "const") && !flecs_meta_c_identifier(p, name)) { return 0; } ecs_world_t *world = p->world; ecs_entity_t type = 0; char close = p->ptr[0] == '(' ? ')' : p->ptr[0] == '<' ? '>' : 0; if (close) { ecs_type_kind_t kind = EcsOpaqueType; if (!ecs_os_strcmp(name, "ecs_array")) { kind = EcsArrayType; } else if (!ecs_os_strcmp(name, "ecs_vector") || !ecs_os_strcmp(name, "ecs_vec") || !ecs_os_strcmp(name, "flecs::vector")) { kind = EcsVectorType; } else if (!ecs_os_strcmp(name, "ecs_map")) { kind = EcsMapType; } else if (!ecs_os_strcmp(name, "flecs::bitmask")) { kind = EcsBitmaskType; } const char *end = flecs_meta_c_scope_end(p); if (!end) { return 0; } if (*end == '*' || kind == EcsOpaqueType) { p->ptr = end; if (*end == '*') { p->ptr = flecs_parse_ws_eol(end + 1); return ecs_id(ecs_uptr_t); } return flecs_meta_c_lookup(world, name); } if (++ p->depth >= 256) { flecs_meta_c_error(p, "maximum level of nesting reached"); return 0; } p->ptr ++; ecs_entity_t first = flecs_meta_c_type(p), second = 0; if (!first) { return 0; } int64_t count = 0; if (p->ptr[0] == ',') { p->ptr = flecs_parse_ws_eol(p->ptr + 1); if (isdigit((unsigned char)p->ptr[0])) { if (!flecs_meta_c_number(p, &count, 0) || count <= 0 || count > INT32_MAX) { flecs_meta_c_error(p, "invalid array size"); return 0; } } else { second = flecs_meta_c_type(p); if (!second) { return 0; } } } if (!flecs_meta_c_expect(p, close)) { return 0; } p->depth --; if (kind == EcsArrayType && count && !second) { type = ecs_insert(world, ecs_value(EcsArray, {first, (int32_t)count})); } else if (kind == EcsVectorType && !second) { type = ecs_insert(world, ecs_value(EcsVector, {first})); } else if (kind == EcsMapType && second) { type = ecs_insert(world, ecs_value(EcsMap, {.key_type = first, .type = second})); } else if (kind == EcsBitmaskType && !second && !count && ecs_has(world, first, EcsBitmask)) { type = ecs_insert(world, ecs_value(EcsArray, {first, 1})); } else { flecs_meta_c_error(p, "invalid collection type '%s'", name); return 0; } } else if (p->ptr[0] == '*') { p->ptr = flecs_parse_ws_eol(p->ptr + 1); type = !ecs_os_strcmp(name, "char") ? ecs_id(ecs_string_t) : ecs_id(ecs_uptr_t); } else { type = flecs_meta_c_lookup(world, name); } if (!type) { flecs_meta_c_error(p, "unknown type '%s'", name); } return type; } static int flecs_meta_c_struct( flecs_meta_c_parser_t *p, ecs_entity_t component) { ecs_struct_desc_t desc = {.entity = component}; int32_t count = 0; int result = -1; if (!flecs_meta_c_expect(p, '{')) { return -1; } while (p->ptr[0] != '}') { ecs_entity_t type = flecs_meta_c_type(p); if (p->is_private) { break; } if (!type) { goto done; } char name[256]; if (!flecs_meta_c_identifier(p, name)) { goto done; } int64_t elem_count = 0; if (p->ptr[0] == '[') { p->ptr ++; if (!flecs_meta_c_number(p, &elem_count, 10) || elem_count <= 0 || elem_count > INT32_MAX || !flecs_meta_c_expect(p, ']')) { flecs_meta_c_error(p, "invalid array size"); goto done; } } if (!flecs_meta_c_expect(p, ';')) { goto done; } if (count == ECS_MEMBER_DESC_CACHE_SIZE - 1) { flecs_meta_c_error(p, "too many struct members"); goto done; } desc.members[count ++] = (ecs_member_t){ .name = ecs_os_strdup(name), .type = type, .count = (int32_t)elem_count }; } if (!p->is_private && (!flecs_meta_c_expect(p, '}') || p->ptr[0])) { flecs_meta_c_error(p, "stray characters after struct definition"); goto done; } result = ecs_struct_init(p->world, &desc) ? 0 : -1; done: for (int32_t i = 0; i < count; i ++) { ecs_os_free(ECS_CONST_CAST(char*, desc.members[i].name)); } return result; } static int flecs_meta_c_constants( flecs_meta_c_parser_t *p, ecs_entity_t component, bool bitmask) { ecs_world_t *world = p->world; ecs_add_id(world, component, bitmask ? ecs_id(EcsBitmask) : ecs_id(EcsEnum)); if (!flecs_meta_c_expect(p, '{')) { return -1; } const char *prefix = ecs_get_world_info(world)->name_prefix; int32_t prefix_len = prefix ? ecs_os_strlen(prefix) : 0; int32_t name_len = ecs_os_strlen(p->name); int64_t value = 0; ecs_entity_t old_scope = ecs_set_scope(world, component); int result = -1; while (p->ptr[0] != '}') { char name[256]; if (!flecs_meta_c_identifier(p, name)) { goto done; } if (p->ptr[0] == '=') { p->ptr ++; if (!flecs_meta_c_number(p, &value, 0)) { goto done; } } else if (bitmask) { flecs_meta_c_error(p, "bitmask requires explicit value assignment"); goto done; } if (p->ptr[0] != '}' && p->ptr[0] != ',') { flecs_meta_c_error(p, "missing , after enum constant"); goto done; } const char *constant = name; if (prefix && !ecs_os_strncmp(constant, prefix, prefix_len)) { constant += prefix_len; } if (!ecs_os_strncmp(constant, p->name, name_len)) { constant += name_len; } ecs_entity_t e = ecs_entity(world, {.name = constant}); if (bitmask) { ecs_set_pair_second(world, e, EcsConstant, ecs_u32_t, {(ecs_u32_t)value}); } else { ecs_set_pair_second(world, e, EcsConstant, ecs_i32_t, {(ecs_i32_t)value}); } value ++; if (p->ptr[0] == ',') { p->ptr = flecs_parse_ws_eol(p->ptr + 1); } } if (!flecs_meta_c_expect(p, '}') || p->ptr[0]) { flecs_meta_c_error(p, "stray characters after enum definition"); goto done; } result = 0; done: ecs_set_scope(world, old_scope); return result; } int ecs_meta_from_desc( ecs_world_t *world, ecs_entity_t component, ecs_type_kind_t kind, const char *desc) { flecs_meta_c_parser_t p = { .world = world, .name = ecs_get_name(world, component), .code = desc, .ptr = desc }; switch (kind) { case EcsStructType: return flecs_meta_c_struct(&p, component); case EcsEnumType: case EcsBitmaskType: return flecs_meta_c_constants(&p, component, kind == EcsBitmaskType); case EcsPrimitiveType: case EcsArrayType: case EcsVectorType: case EcsMapType: case EcsValueType: case EcsOpaqueType: return 0; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid type kind"); } } #endif #include #ifdef FLECS_META #ifdef FLECS_QUERY_DSL #endif ecs_size_t ecs_meta_op_get_elem_count( const ecs_meta_op_t *op, const void *ptr) { ecs_assert(op->kind == EcsOpPushArray || op->kind == EcsOpPushVector || op->kind == EcsOpPushMap, ECS_INTERNAL_ERROR, NULL); if (op->kind == EcsOpPushArray) { const ecs_meta_op_t *pop = &op[op->op_count - 1]; ecs_assert(pop != NULL, ECS_INTERNAL_ERROR, NULL); return pop->elem_size; } else if (op->kind == EcsOpPushVector) { return ((const ecs_vec_t*)ptr)->count; } else if (op->kind == EcsOpPushMap) { return ecs_map_count((const ecs_map_t*)ptr); } ecs_throw(ECS_INVALID_OPERATION, "cannot get element count for %s operation", flecs_meta_op_kind_str(op->kind)); error: return 0; } /* Get current scope */ static ecs_meta_scope_t* flecs_cursor_get_scope( const ecs_meta_cursor_t *cursor) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); error: return NULL; } /* Restore scope, if dotmember was used */ static ecs_meta_scope_t* flecs_cursor_restore_scope( ecs_meta_cursor_t *cursor, const ecs_meta_scope_t* scope) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); if (scope->prev_depth) { cursor->depth = scope->prev_depth; } error: return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; } /* Get current operation for scope */ static ecs_meta_op_t* flecs_cursor_get_op( ecs_meta_scope_t *scope) { ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, "type serializer is missing instructions"); return &scope->ops[scope->ops_cur]; } static ecs_meta_op_kind_t flecs_cursor_get_collection_kind( ecs_meta_scope_t *scope) { /* Can only get collection kind for collection scope */ ecs_assert(scope->is_collection, ECS_INTERNAL_ERROR, NULL); /* If this is a collection scope, the operation preceding the * current one must be PushArray or PushVector. * We can't get this from parent->ops, because if the collection was * pushed from a struct/other collection scope, it will have done so * with a Forward instruction. */ ecs_meta_op_t *push = &scope->ops[-1]; ecs_assert(push->kind == EcsOpPushArray || push->kind == EcsOpPushVector, ECS_INTERNAL_ERROR, NULL); return push->kind; } static ecs_size_t flecs_cursor_get_elem_size( ecs_meta_scope_t *scope) { /* Can only get element size for collection scope */ ecs_assert(scope->is_collection, ECS_INTERNAL_ERROR, NULL); /* The first operation in a collection scope always has the element size. * The reason is that if this is a primitive scope it only contains a single * operation with the type and size, and if it's a composite type the first * operation is a Push for the element type. */ ecs_assert(scope->ops[0].type_info != NULL, ECS_INTERNAL_ERROR, NULL); return scope->ops[0].type_info->size; } /* Get pointer to current field/element */ static void* flecs_meta_cursor_get_ptr( const ecs_world_t *world, const ecs_meta_cursor_t *cursor, ecs_meta_scope_t *scope) { ecs_meta_op_t *op = flecs_cursor_get_op(scope); const EcsOpaque *opaque = scope->opaque; if (!opaque) { if (scope->is_collection) { ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); /* Parent scope contains the index to the current element and total * number of elements in collection. */ ecs_meta_scope_t *parent = &scope[-1]; ecs_size_t elem_size = flecs_cursor_get_elem_size(scope); ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); ecs_meta_op_kind_t kind = flecs_cursor_get_collection_kind(scope); if (kind == EcsOpPushVector) { if (parent->elem >= parent->elem_count) { ecs_vec_t *vec = cursor->scope[0].ptr ? flecs_meta_cursor_get_ptr(world, cursor, parent) : NULL; if (vec) { ecs_vec_init_if(vec, elem_size); ecs_vec_set_min_count_w_type_info( NULL, vec, elem_size, parent->elem + 1, op->type_info); scope->ptr = vec->array; if (parent->elem >= parent->elem_count) { parent->elem_count = parent->elem + 1; } } } } scope->is_empty_scope = false; void *elem = ECS_ELEM(scope->ptr, elem_size, parent->elem); return ECS_OFFSET(elem, op->offset); } else if (scope->is_value && op->kind != EcsOpPushValue) { /* A type was selected for the value scope. Return pointer to * the value of the value. */ ecs_value_t *v = scope->ptr; if (!v) { return NULL; } ecs_assert(v->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return ECS_OFFSET(v->ptr, op->offset); } else { return ECS_OFFSET(scope->ptr, op->offset); } } /* Opaque type */ if (scope->is_collection) { ecs_assert(scope != cursor->scope, ECS_INTERNAL_ERROR, NULL); ecs_meta_scope_t *parent = &scope[-1]; if (!opaque->ensure_element) { char *str = ecs_get_path(world, scope->type); ecs_err("missing ensure_element() for opaque type %s", str); ecs_os_free(str); return NULL; } scope->is_empty_scope = false; if (scope->ptr) { void *opaque_ptr = opaque->ensure_element(scope->ptr, flecs_itosize(parent->elem)); ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, "ensure_element() returned NULL"); return opaque_ptr; } return NULL; } else if (op->name) { if (!opaque->ensure_member) { char *str = ecs_get_path(world, scope->type); ecs_err("missing ensure_member() for opaque type %s", str); ecs_os_free(str); return NULL; } if (!scope->ptr) { return NULL; } return opaque->ensure_member(scope->ptr, op->name); } else { ecs_err("invalid operation for opaque type"); return NULL; } } static int flecs_meta_cursor_push_type( const ecs_world_t *world, ecs_meta_scope_t *scope, ecs_entity_t type, void *ptr) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); ecs_os_free(str); return -1; } scope[0] = (ecs_meta_scope_t) { .type = type, .ops = ecs_vec_first_t(&ser->ops, ecs_meta_op_t), .ptr = ptr }; return 0; } ecs_meta_cursor_t ecs_meta_cursor( const ecs_world_t *world, ecs_entity_t type, void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_meta_cursor_t result = { .world = world, .valid = true }; if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) < 0) { result.valid = false; } return result; error: return (ecs_meta_cursor_t){ 0 }; } void* ecs_meta_get_ptr( ecs_meta_cursor_t *cursor) { return flecs_meta_cursor_get_ptr(cursor->world, cursor, flecs_cursor_get_scope(cursor)); } static int flecs_meta_array_bounds_check( ecs_meta_cursor_t *cursor, ecs_meta_scope_t *scope) { ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); ecs_meta_scope_t *parent = &scope[-1]; ecs_meta_op_kind_t kind = flecs_cursor_get_collection_kind(scope); if (parent->ptr && kind != EcsOpPushVector) { if (parent->elem >= parent->elem_count || parent->elem < 0) { char *str = ecs_get_path(cursor->world, parent->type); ecs_err("out of collection bounds for type '%s' (%d elements vs. size %d)", str, parent->elem + 1, parent->elem_count); ecs_os_free(str); return -1; } } return 0; } int ecs_meta_next( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); scope = flecs_cursor_restore_scope(cursor, scope); ecs_meta_op_t *op = flecs_cursor_get_op(scope); if (scope->is_map || scope->is_value) { scope->ops_cur = 0; return 0; } if (scope->is_collection) { ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); ecs_meta_scope_t *parent = &scope[-1]; parent->elem ++; scope->ops_cur = 0; if (scope->opaque) { return 0; } if (flecs_meta_array_bounds_check(cursor, scope)) { return -1; } return 0; } scope->ops_cur += op->op_count; if (scope->ops_cur >= scope->ops_count) { char *str = ecs_get_path(cursor->world, scope->type); ecs_err("too many elements for scope for type %s", str); ecs_os_free(str); return -1; } return 0; } int ecs_meta_elem( ecs_meta_cursor_t *cursor, int32_t elem) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); if (!scope->is_collection) { ecs_err("ecs_meta_elem can be used for collections only"); return -1; } ecs_assert(scope != cursor->scope, ECS_INTERNAL_ERROR, NULL); ecs_meta_scope_t *parent = &scope[-1]; parent->elem = elem; scope->ops_cur = 0; scope->is_moved_scope = true; if (flecs_meta_array_bounds_check(cursor, scope)) { return -1; } return 0; } static int flecs_meta_cursor_lookup( ecs_meta_cursor_t *cursor, const char *value, ecs_entity_t *out) { if (ecs_os_strcmp(value, "#0")) { if (cursor->lookup_action) { *out = cursor->lookup_action( ECS_CONST_CAST(ecs_world_t*, cursor->world), value, cursor->lookup_ctx); } else { *out = ecs_lookup_from(cursor->world, 0, value); } if (!*out) { ecs_err("unresolved entity identifier '%s'", value); return -1; } } return 0; } static int flecs_meta_cursor_from_str( ecs_meta_cursor_t *cursor, ecs_map_t *constants, ecs_meta_op_kind_t op_kind, ecs_entity_t op_type, const char *value, void *ptr) { if (!value) { return ecs_meta_set_null(cursor); } switch(op_kind) { case EcsOpI8: case EcsOpU8: case EcsOpByte: case EcsOpI16: case EcsOpU16: case EcsOpI32: case EcsOpU32: case EcsOpI64: case EcsOpU64: case EcsOpIPtr: case EcsOpUPtr: if (!flecs_meta_valid_digit(value)) { ecs_err("expected number, got '%s'", value); goto error; } break; case EcsOpF32: case EcsOpF64: { char *endptr; strtod(value, &endptr); if (endptr == value) { ecs_err("expected number, got '%s'", value); goto error; } break; } case EcsOpEnum: case EcsOpBitmask: case EcsOpPushStruct: case EcsOpPushArray: case EcsOpPushVector: case EcsOpPushMap: case EcsOpPushValue: case EcsOpPop: case EcsOpForward: case EcsOpOpaqueStruct: case EcsOpOpaqueArray: case EcsOpOpaqueVector: case EcsOpOpaqueValue: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpString: case EcsOpEntity: case EcsOpId: case EcsOpScope: break; } switch(op_kind) { case EcsOpBool: if (!ecs_os_strcmp(value, "true")) { flecs_meta_set_t(ecs_bool_t, ptr, true); } else if (!ecs_os_strcmp(value, "false")) { flecs_meta_set_t(ecs_bool_t, ptr, false); } else if (isdigit(value[0])) { if (!ecs_os_strcmp(value, "0")) { flecs_meta_set_t(ecs_bool_t, ptr, false); } else { flecs_meta_set_t(ecs_bool_t, ptr, true); } } else { ecs_err("invalid value for boolean '%s'", value); goto error; } break; case EcsOpI8: case EcsOpU8: case EcsOpByte: flecs_meta_set_t(ecs_i8_t, ptr, atol(value)); break; case EcsOpChar: flecs_meta_set_t(char, ptr, value[0]); break; case EcsOpI16: case EcsOpU16: flecs_meta_set_t(ecs_i16_t, ptr, atol(value)); break; case EcsOpI32: flecs_meta_set_t(ecs_i32_t, ptr, atoll(value)); break; case EcsOpU32: flecs_meta_set_t(ecs_u32_t, ptr, strtoull(value, NULL, 10)); break; case EcsOpI64: flecs_meta_set_t(ecs_i64_t, ptr, atoll(value)); break; case EcsOpU64: flecs_meta_set_t(ecs_u64_t, ptr, strtoull(value, NULL, 10)); break; case EcsOpIPtr: flecs_meta_set_t(ecs_iptr_t, ptr, atoll(value)); break; case EcsOpUPtr: flecs_meta_set_t(ecs_uptr_t, ptr, strtoull(value, NULL, 10)); break; case EcsOpF32: flecs_meta_set_t(ecs_f32_t, ptr, strtod(value, NULL)); break; case EcsOpF64: flecs_meta_set_t(ecs_f64_t, ptr, strtod(value, NULL)); break; case EcsOpString: { if (*(ecs_string_t*)ptr == value) { break; } ecs_os_free(*(ecs_string_t*)ptr); char *result = ecs_os_strdup(value); flecs_meta_set_t(ecs_string_t, ptr, result); break; } case EcsOpEnum: { if (flecs_meta_parse_enum( cursor->world, op_type, constants, ptr, value)) { goto error; } break; } case EcsOpBitmask: if (flecs_meta_parse_bitmask( cursor->world, op_type, constants, ptr, value)) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { goto error; } flecs_meta_set_t(ecs_entity_t, ptr, e); break; } case EcsOpId: { #ifdef FLECS_QUERY_DSL ecs_id_t id = 0; if (flecs_id_parse(cursor->world, NULL, value, &id) == NULL) { goto error; } flecs_meta_set_t(ecs_id_t, ptr, id); #else ecs_err("cannot parse component expression: script addon required"); #endif break; } case EcsOpPop: ecs_err("excess element '%s' in scope", value); goto error; case EcsOpOpaqueValue: { const EcsOpaque *opaque = ecs_get(cursor->world, op_type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, "entity %s is not an opaque type but serializer thinks so", ecs_get_name(cursor->world, op_type)); if (opaque->assign_string) { /* preferred */ opaque->assign_string(ptr, value); break; } else if (opaque->assign_char && value[0] && !value[1]) { opaque->assign_char(ptr, value[0]); break; } else if (opaque->assign_entity) { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { goto error; } opaque->assign_entity(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), e); break; } } /* fall through */ case EcsOpOpaqueStruct: case EcsOpOpaqueArray: case EcsOpOpaqueVector: case EcsOpPushStruct: case EcsOpPushArray: case EcsOpPushVector: case EcsOpPushMap: case EcsOpPushValue: case EcsOpForward: case EcsOpPrimitive: case EcsOpScope: ecs_err("unsupported conversion from string '%s' to '%s'", value, flecs_meta_op_kind_str(op_kind)); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } static int flecs_meta_cursor_parse_map_key( ecs_meta_cursor_t *cursor, const ecs_meta_op_t *op, const char *value, ecs_map_key_t *key_out) { ecs_meta_op_kind_t kind = op->underlying_kind; if (kind == EcsOpEnum) { uint64_t key = 0; if (flecs_meta_parse_enum( cursor->world, 0, op->is.constants, &key, value)) { ecs_err("unresolved enum constant '%s' for map key", value); return -1; } *key_out = key; } else if (kind == EcsOpBitmask) { uint32_t key = 0; if (flecs_meta_parse_bitmask( cursor->world, 0, op->is.constants, &key, value)) { return -1; } *key_out = key; return 0; } else { ecs_entity_t type = flecs_meta_op_kind_to_type(kind); ecs_map_key_t key = 0; if (flecs_meta_cursor_from_str( cursor, op->is.constants, kind, type, value, &key)) { return -1; } *key_out = key; } return 0; } static int flecs_meta_map_ensure_key( ecs_meta_cursor_t *cursor, ecs_meta_scope_t *scope, ecs_map_key_t key) { ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); ecs_meta_scope_t *parent = &scope[-1]; ecs_map_t *map = flecs_meta_cursor_get_ptr(cursor->world, cursor, parent); ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init_if(map, NULL); const ecs_type_info_t *vti = scope->ops[0].type_info; ecs_assert(vti != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_map_count(map); ecs_map_val_t *val = ecs_map_ensure(map, key); void *ptr; if (vti->size <= ECS_SIZEOF(ecs_map_val_t)) { ptr = val; } else { if (!*val) { *val = (ecs_map_val_t)(uintptr_t)ecs_os_calloc(vti->size); } ptr = (void*)(uintptr_t)*val; } if (count != ecs_map_count(map)) { if (vti->hooks.ctor) { vti->hooks.ctor(ptr, 1, vti); } } scope->ptr = ptr; return 0; } static int flecs_meta_map_member( ecs_meta_cursor_t *cursor, ecs_meta_scope_t *scope, const char *name) { scope->ops_cur = 0; if (!cursor->scope[0].ptr) { scope->ptr = NULL; return 0; } ecs_meta_op_t *op = &scope->ops[-1]; ecs_assert(op->kind == EcsOpPushMap, ECS_INTERNAL_ERROR, NULL); ecs_map_key_t key = 0; if (flecs_meta_cursor_parse_map_key(cursor, op, name, &key)) { return -1; } return flecs_meta_map_ensure_key(cursor, scope, key); } static bool flecs_meta_op_is_value( const ecs_meta_op_t *op) { if (op->kind == EcsOpPushValue) { return true; } if (op->kind == EcsOpForward && op->type == ecs_id(ecs_value_t)) { return true; } return false; } static void* flecs_meta_cursor_value_ensure( ecs_meta_cursor_t *cursor, void *ptr, ecs_entity_t type) { ecs_value_t *v = ptr; if (!v) { ecs_err("no object to assign"); return NULL; } if (flecs_value_ensure_type(cursor->world, v, type)) { return NULL; } return v->ptr; } static int flecs_meta_value_member( ecs_meta_cursor_t *cursor, ecs_meta_scope_t *scope, const char *name, bool try) { const ecs_world_t *world = cursor->world; ecs_entity_t type = ecs_lookup_symbol(world, name, true, true); if (!type) { if (!try) ecs_err("unresolved type identifier '%s' for value", name); return -1; } if (type == ecs_id(ecs_value_t)) { if (!try) ecs_err("value cannot be assigned a value"); return -1; } const EcsTypeSerializer *ts = ecs_get(world, type, EcsTypeSerializer); if (!ts) { if (!try) ecs_err("entity '%s' is not a valid type for value", name); return -1; } scope->ops = ecs_vec_first_t(&ts->ops, ecs_meta_op_t); scope->ops_count = flecs_ito(int16_t, ecs_vec_count(&ts->ops)); scope->ops_cur = 0; if (cursor->scope[0].ptr) { ecs_value_t *v = scope->ptr; ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_value_ensure_type(world, v, type)) { return -1; } } return 0; } static int flecs_meta_member( ecs_meta_cursor_t *cursor, const char *name, bool try) { if (cursor->depth == 0) { if (!try) ecs_err("cannot move to member in root scope (push first)"); return -1; } ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); scope = flecs_cursor_restore_scope(cursor, scope); if (scope->is_map) { return flecs_meta_map_member(cursor, scope, name); } if (scope->is_value) { return flecs_meta_value_member(cursor, scope, name, try); } ecs_hashmap_t *members = scope->members; const ecs_world_t *world = cursor->world; if (!members) { if (!try) ecs_err("cannot move to member '%s' for non-struct type '%s'", name, flecs_errstr(ecs_get_path(world, scope->type))); return -1; } const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); if (!cur_ptr) { if (!try) ecs_err("unknown member '%s' for type '%s'", name, flecs_errstr(ecs_get_path(world, scope->type))); return -1; } scope->ops_cur = flecs_uto(int16_t, cur_ptr[0]); const EcsOpaque *opaque = scope->opaque; if (opaque) { if (!opaque->ensure_member) { if (!try) ecs_err("missing ensure_member for opaque type %s", flecs_errstr(ecs_get_path(world, scope->type))); } } return 0; } int ecs_meta_member( ecs_meta_cursor_t *cursor, const char *name) { return flecs_meta_member(cursor, name, false); } int ecs_meta_try_member( ecs_meta_cursor_t *cursor, const char *name) { return flecs_meta_member(cursor, name, true); } int ecs_meta_key( ecs_meta_cursor_t *cursor, const ecs_value_t *key) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(key != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(key->type != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(key->ptr != NULL, ECS_INVALID_PARAMETER, NULL); if (cursor->depth == 0) { ecs_err("cannot move to key in root scope (push first)"); return -1; } ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); scope = flecs_cursor_restore_scope(cursor, scope); if (!scope->is_map) { ecs_err("cannot set key for non-map type"); return -1; } scope->ops_cur = 0; if (!cursor->scope[0].ptr) { scope->ptr = NULL; return 0; } ecs_map_key_t key_u64 = 0; if (flecs_value_blit_u64(cursor->world, key, &key_u64)) { return -1; } return flecs_meta_map_ensure_key(cursor, scope, key_u64); error: return -1; } static const char* flecs_meta_parse_member( const char *start, char *token_out) { const char *ptr; char ch; for (ptr = start; (ch = *ptr); ptr ++) { if (ch == '.' || ch == '[') { break; } } int32_t len = flecs_ito(int32_t, ptr - start); if (len >= ECS_MAX_TOKEN_SIZE) { len = ECS_MAX_TOKEN_SIZE - 1; } ecs_os_memcpy(token_out, start, len); token_out[len] = '\0'; return ptr; } static const char* flecs_meta_parse_elem( const char *start, int32_t *elem_out) { if (start[0] != '[') { ecs_err("expected '[' in member expression"); return NULL; } start ++; if (!isdigit((unsigned char)start[0])) { ecs_err("expected array index in member expression"); return NULL; } int32_t elem = 0; const char *ptr = start; char ch; while ((ch = ptr[0]) && ch != ']') { if (!isdigit((unsigned char)ch)) { ecs_err("invalid array index in member expression"); return NULL; } elem *= 10; elem += ch - '0'; ptr ++; } if (ptr[0] != ']') { ecs_err("missing ']' in member expression"); return NULL; } elem_out[0] = elem; return ptr + 1; } static int flecs_meta_dotmember( ecs_meta_cursor_t *cursor, const char *name, bool try) { ecs_meta_scope_t *cur_scope = flecs_cursor_get_scope(cursor); cur_scope = flecs_cursor_restore_scope(cursor, cur_scope); if (cur_scope->is_map || cur_scope->is_value) { return flecs_meta_member(cursor, name, try); } int16_t prev_depth = cursor->depth; bool moved = false; char token[ECS_MAX_TOKEN_SIZE]; const char *ptr = name; while (ptr[0]) { if (ptr[0] != '[') { ptr = flecs_meta_parse_member(ptr, token); if (!ptr) { goto error; } if (moved) { if (ecs_meta_push(cursor) != 0) { goto error; } } if (flecs_meta_member(cursor, token, try)) { goto error; } moved = true; } while (ptr[0] == '[') { int32_t elem; if (ecs_meta_push(cursor) != 0) { goto error; } ptr = flecs_meta_parse_elem(ptr, &elem); if (!ptr) { goto error; } if (ecs_meta_elem(cursor, elem) != 0) { goto error; } moved = true; } if (ptr[0] == '.') { ptr ++; continue; } if (ptr[0] != '\0') { ecs_err("invalid token '%c' in member expression", ptr[0]); goto error; } } cur_scope = flecs_cursor_get_scope(cursor); if (moved && (cursor->depth != prev_depth)) { cur_scope->prev_depth = prev_depth; } return 0; error: cursor->depth = prev_depth; return -1; } int ecs_meta_dotmember( ecs_meta_cursor_t *cursor, const char *name) { return flecs_meta_dotmember(cursor, name, false); } int ecs_meta_try_dotmember( ecs_meta_cursor_t *cursor, const char *name) { return flecs_meta_dotmember(cursor, name, true); } void flecs_meta_map_clear( ecs_map_t *map, const ecs_type_info_t *value_ti) { if (!ecs_map_is_init(map) || !ecs_map_count(map)) { return; } bool is_alloc = value_ti->size > ECS_SIZEOF(ecs_map_val_t); ecs_map_iter_t it = ecs_map_iter(map); while (ecs_map_next(&it)) { void *ptr; if (is_alloc) { ptr = ecs_map_ptr(&it); } else { ptr = &it.res[1]; } if (value_ti->hooks.dtor) { value_ti->hooks.dtor(ptr, 1, value_ti); } if (is_alloc) { ecs_os_free(ptr); } } ecs_map_clear(map); } int ecs_meta_push( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); const ecs_world_t *world = cursor->world; /* Allow push for primitive scope if it's the root. Useful for scenarios * where a value always has to be surrounded by { }. */ if (cursor->depth == 0) { if (!cursor->is_primitive_scope && (op->kind > EcsOpScope)) { cursor->is_primitive_scope = true; return 0; } } void *ptr = flecs_meta_cursor_get_ptr(world, cursor, scope); cursor->depth ++; ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, ECS_INVALID_PARAMETER, NULL); ecs_meta_scope_t *next_scope = flecs_cursor_get_scope(cursor); *next_scope = (ecs_meta_scope_t) {0}; /* Check if we need to forward to serializer of other type. */ if (op->kind == EcsOpForward) { const EcsTypeSerializer *ts = ecs_get( world, op->type, EcsTypeSerializer); ecs_assert(ts != NULL, ECS_INTERNAL_ERROR, NULL); op = ecs_vec_first(&ts->ops); } else if (op->kind == EcsOpOpaqueStruct) { const EcsOpaque *opaque = ecs_get(world, op->type, EcsOpaque); next_scope->opaque = opaque; const EcsTypeSerializer *ts = ecs_get( world, opaque->as_type, EcsTypeSerializer); ecs_assert(ts != NULL, ECS_INTERNAL_ERROR, NULL); op = ecs_vec_first(&ts->ops); } else if (op->kind == EcsOpOpaqueVector || op->kind == EcsOpOpaqueArray) { const EcsOpaque *opaque = ecs_get(world, op->type, EcsOpaque); next_scope->opaque = opaque; const EcsTypeSerializer *ts = ecs_get( world, opaque->as_type, EcsTypeSerializer); ecs_assert(ts != NULL, ECS_INTERNAL_ERROR, NULL); op = ecs_vec_first(&ts->ops); next_scope->is_collection = true; next_scope->is_empty_scope = true; next_scope->is_moved_scope = false; scope->elem = 0; } if (next_scope->opaque && !cursor->scope[0].ptr) { ptr = NULL; } if (op->kind == EcsOpPushValue) { next_scope->ops = op; next_scope->ops_count = op->op_count; next_scope->ptr = ptr; next_scope->type = op->type; next_scope->is_value = true; return 0; } next_scope->ops = &op[1]; /* op after push */ next_scope->ops_count = op->op_count - 1; next_scope->ptr = ptr; next_scope->type = op->type; next_scope->members = op->is.members; if (!next_scope->opaque) { if (op->kind == EcsOpPushStruct) { ecs_assert(next_scope->members != NULL, ECS_INTERNAL_ERROR, NULL); } else if (op->kind == EcsOpPushArray) { next_scope->is_collection = true; ecs_meta_op_t *pop = &op[op->op_count - 1]; ecs_assert(pop->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); scope->elem = 0; scope->elem_count = pop->elem_size; /* Array count is encoded on pop */ } else if (op->kind == EcsOpPushVector) { ecs_vec_t *vec = cursor->scope[0].ptr ? ptr : NULL; next_scope->ptr = vec ? vec->array : NULL; next_scope->is_collection = true; next_scope->is_empty_scope = true; next_scope->is_moved_scope = false; scope->elem = 0; scope->elem_count = vec ? vec->count : 0; } else if (op->kind == EcsOpPushMap) { ecs_map_t *map = cursor->scope[0].ptr ? ptr : NULL; if (map) { ecs_map_init_if(map, NULL); flecs_meta_map_clear(map, next_scope->ops[0].type_info); } next_scope->ptr = NULL; next_scope->is_map = true; } else { goto error; } } return 0; error: { char *path = ecs_get_path(world, scope->type); ecs_err("cannot open scope for type '%s'", path); ecs_os_free(path); return -1; } } int ecs_meta_pop( ecs_meta_cursor_t *cursor) { if (cursor->is_primitive_scope) { cursor->is_primitive_scope = false; return 0; } ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); scope = flecs_cursor_restore_scope(cursor, scope); cursor->depth --; if (cursor->depth < 0) { ecs_err("unexpected end of scope"); return -1; } ecs_meta_scope_t *next_scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(next_scope); if (op->kind == EcsOpPushStruct || op->kind == EcsOpPushArray || op->kind == EcsOpPushVector || op->kind == EcsOpPushMap || op->kind == EcsOpPushValue) { next_scope->ops_cur += flecs_ito(int16_t, op->op_count - 1); if (op->kind == EcsOpPushVector) { /* If scope got moved around, this is a partially assigned vector * so don't shrink it. */ if (!scope->is_moved_scope) { ecs_assert(cursor->scope != scope, ECS_INTERNAL_ERROR, NULL); ecs_meta_scope_t *parent = &scope[-1]; ecs_vec_t *vec = flecs_meta_cursor_get_ptr( cursor->world, cursor, parent); if (vec) { ecs_size_t elem_size = flecs_cursor_get_elem_size(scope); ecs_assert(elem_size > 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_init_if(vec, elem_size); ecs_meta_op_t *elem_op = &scope->ops[0]; if (scope->is_empty_scope) { ecs_vec_set_count_w_type_info( NULL, vec, elem_size, 0, elem_op->type_info); } else { ecs_vec_set_count_w_type_info(NULL, vec, elem_size, parent->elem + 1, elem_op->type_info); } } } } /* push + op_count should point to the operation after pop */ op = flecs_cursor_get_op(next_scope); ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); } else if (op->kind == EcsOpForward || op->kind == EcsOpOpaqueStruct || op->kind == EcsOpOpaqueArray) { next_scope->ops_cur += flecs_ito(int16_t, op->op_count - 1); } else if (op->kind == EcsOpOpaqueVector) { const EcsOpaque *opaque = scope->opaque; /* When popping an opaque collection type, call resize to make * sure the vector isn't larger than the number of elements we * deserialized. If the opaque type represents an array, don't call * resize. */ ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); if (!opaque->resize) { char *str = ecs_get_path(cursor->world, scope->type); ecs_err("missing resize for opaque type %s", str); ecs_os_free(str); return -1; } if (scope->ptr) { if (scope->is_empty_scope) { /* If no values were serialized for scope, resize * collection to 0 elements. */ opaque->resize(scope->ptr, 0); } else { /* Otherwise resize collection to the index of the last * deserialized element + 1. */ opaque->resize(scope->ptr, flecs_ito(size_t, next_scope->elem + 1)); } } } else { /* should not have been able to push if the previous scope was not * a complex or collection type */ ecs_assert(false, ECS_INTERNAL_ERROR, "invalid operation for pop: %s", flecs_meta_op_kind_str(op->kind)); } return 0; } bool ecs_meta_is_collection( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); /* If the scope was reached through dotmember with array indexing, the * current scope can still be the collection that contains the selected * element. In that case, report whether the selected element is a * collection, not whether its parent container is. */ if (scope->prev_depth && scope->is_collection) { ecs_meta_op_t *op = flecs_cursor_get_op(scope); return op->kind == EcsOpPushArray || op->kind == EcsOpPushVector || op->kind == EcsOpOpaqueArray || op->kind == EcsOpOpaqueVector; } return scope->is_collection; } bool ecs_meta_is_map( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); return scope->is_map; } ecs_entity_t ecs_meta_get_type( const ecs_meta_cursor_t *cursor) { if (cursor->depth == 0) { return cursor->scope[0].type; } ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); return op->type; } ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_entity_t type = scope->type; const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); if (!st) { return 0; } ecs_meta_op_t *op = flecs_cursor_get_op(scope); ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); return m->unit; } const char* ecs_meta_get_member( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); return op->name; } ecs_entity_t ecs_meta_get_member_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_entity_t type = scope->type; const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); if (!st) { return 0; } ecs_meta_op_t *op = flecs_cursor_get_op(scope); ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); return m->member; } /* Utilities for type conversions and bounds checking */ static struct { int64_t min, max; } ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, INT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) }, [EcsOpEntity] = {0, INT64_MAX}, [EcsOpId] = {0, INT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, INT32_MAX} }; static struct { uint64_t min, max; } ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {0, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, UINT64_MAX}, [EcsOpI8] = {0, INT8_MAX}, [EcsOpI16] = {0, INT16_MAX}, [EcsOpI32] = {0, INT32_MAX}, [EcsOpI64] = {0, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, [EcsOpEntity] = {0, UINT64_MAX}, [EcsOpId] = {0, UINT64_MAX}, [EcsOpEnum] = {0, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; static struct { double min, max; } ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, (double)UINT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) }, [EcsOpEntity] = {0, (double)UINT64_MAX}, [EcsOpId] = {0, (double)UINT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; #define case_T(kind, T, dst, src)\ case kind:\ flecs_meta_set_t(T, dst, src);\ break #define case_T_checked(kind, T, dst, src, bounds)\ case kind:\ if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ ecs_err("value %.0f is out of bounds for type %s", (double)src,\ flecs_meta_op_kind_str(kind));\ return -1;\ }\ flecs_meta_set_t(T, dst, src);\ break #define cases_T_float(dst, src)\ case_T(EcsOpF32, ecs_f32_t, dst, src);\ case_T(EcsOpF64, ecs_f64_t, dst, src) #define cases_T_signed(dst, src, bounds)\ case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds); #define cases_T_unsigned(dst, src, bounds)\ case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpId, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) #define cases_T_bool(dst, src)\ case EcsOpBool:\ flecs_meta_set_t(ecs_bool_t, dst, value != 0);\ break static void flecs_meta_conversion_error( ecs_meta_cursor_t *cursor, ecs_meta_op_t *op, const char *from) { if (op->kind == EcsOpPop) { ecs_err("cursor: out of bounds"); } else { char *path = ecs_get_path(cursor->world, op->type); ecs_err("unsupported conversion from %s to '%s' (for %s)", from, path, flecs_meta_op_kind_str(op->kind)); ecs_os_free(path); } } typedef union flecs_meta_number_t { int64_t i; uint64_t u; double f; } flecs_meta_number_t; static int flecs_meta_assign_opaque_number( ecs_meta_cursor_t *cursor, ecs_meta_op_t *op, void *ptr, ecs_primitive_kind_t kind, flecs_meta_number_t number) { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); switch (kind) { case EcsBool: { bool value = number.i; if (opaque->assign_bool) { opaque->assign_bool(ptr, value); return 0; } break; } case EcsChar: { char value = flecs_ito(char, number.i); if (opaque->assign_char) { opaque->assign_char(ptr, value); return 0; } else if (opaque->assign_uint) { opaque->assign_uint(ptr, (uint64_t)value); return 0; } else if (opaque->assign_int) { opaque->assign_int(ptr, value); return 0; } break; } case EcsI64: { int64_t value = number.i; if (opaque->assign_int) { opaque->assign_int(ptr, value); return 0; } else if (opaque->assign_float) { opaque->assign_float(ptr, (double)value); return 0; } else if (opaque->assign_uint && (value > 0)) { opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); return 0; } else if (opaque->assign_char && (value > 0) && (value < 256)) { opaque->assign_char(ptr, flecs_ito(char, value)); return 0; } break; } case EcsU64: { uint64_t value = number.u; if (opaque->assign_uint) { opaque->assign_uint(ptr, value); return 0; } else if (opaque->assign_float) { opaque->assign_float(ptr, (double)value); return 0; } else if (opaque->assign_int && (value < INT64_MAX)) { opaque->assign_int(ptr, flecs_uto(int64_t, value)); return 0; } else if (opaque->assign_char && (value < 256)) { opaque->assign_char(ptr, flecs_uto(char, value)); return 0; } break; } case EcsF64: { double value = number.f; if (opaque->assign_float) { opaque->assign_float(ptr, value); return 0; } else if (opaque->assign_int && (value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) { opaque->assign_int(ptr, (int64_t)value); return 0; } else if (opaque->assign_uint && (value >= 0)) { opaque->assign_uint(ptr, (uint64_t)value); return 0; } else if (opaque->assign_entity && (value >= 0)) { opaque->assign_entity( ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), (ecs_entity_t)value); return 0; } break; } case EcsEntity: { ecs_entity_t value = number.u; if (opaque && opaque->assign_entity) { opaque->assign_entity(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), value); return 0; } break; } case EcsId: { ecs_entity_t value = number.u; if (opaque && opaque->assign_id) { opaque->assign_id(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), value); return 0; } break; } default: break; } return -1; } static FLECS_ALWAYS_INLINE int flecs_meta_set_number( ecs_meta_cursor_t *cursor, ecs_primitive_kind_t from, flecs_meta_number_t number) { static const ecs_entity_t *const types[EcsId + 1] = { [EcsBool] = &ecs_id(ecs_bool_t), [EcsChar] = &ecs_id(ecs_char_t), [EcsI64] = &ecs_id(ecs_i64_t), [EcsU64] = &ecs_id(ecs_u64_t), [EcsF64] = &ecs_id(ecs_f64_t), [EcsEntity] = &ecs_id(ecs_entity_t), [EcsId] = &ecs_id(ecs_id_t) }; ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); ecs_meta_op_kind_t kind = op->kind; if (from == EcsI64 || from == EcsU64 || from == EcsF64) { ecs_assert(ptr != NULL, ECS_INVALID_OPERATION, "no object to assign"); } if (flecs_meta_op_is_value(op)) { kind = (ecs_meta_op_kind_t)(EcsOpPrimitive + (int)from); ptr = flecs_meta_cursor_value_ensure(cursor, ptr, *types[from]); if (!ptr) { return -1; } } else if (kind == EcsOpEnum) { kind = op->underlying_kind; } if (kind == EcsOpOpaqueValue) { if (!flecs_meta_assign_opaque_number(cursor, op, ptr, from, number)) { return 0; } goto error; } if (kind == EcsOpString) { char *result = NULL; switch (from) { case EcsBool: result = ecs_os_strdup(number.i ? "true" : "false"); break; case EcsChar: result = flecs_asprintf("%c", (char)number.i); break; case EcsI64: result = flecs_asprintf("%"PRId64, number.i); break; case EcsU64: result = flecs_asprintf("%"PRIu64, number.u); break; case EcsF64: result = flecs_asprintf("%f", number.f); break; case EcsEntity: result = ecs_get_path(cursor->world, number.u); break; case EcsId: result = ecs_id_str(cursor->world, number.u); break; default: goto error; } ecs_os_free(*(char**)ptr); *(char**)ptr = result; return 0; } if ((from == EcsEntity && kind != EcsOpEntity && kind != EcsOpId && kind != EcsOpBool) || (from == EcsId && kind != EcsOpId)) { goto error; } if (from == EcsChar && op->kind != EcsOpEnum && kind != EcsOpBool && kind != EcsOpChar && kind != EcsOpI8 && kind != EcsOpI16 && kind != EcsOpI32 && kind != EcsOpI64 && kind != EcsOpIPtr) { goto error; } switch (from) { case EcsBool: case EcsChar: case EcsI64: { int64_t value = number.i; switch (kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); cases_T_float(ptr, value); default: goto error; } return 0; } case EcsU64: case EcsEntity: case EcsId: { uint64_t value = number.u; switch (kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); cases_T_float(ptr, value); default: goto error; } return 0; } case EcsF64: { double value = number.f; switch (kind) { case EcsOpBool: *(bool*)ptr = !ECS_EQZERO(value); break; cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); default: goto error; } return 0; } default: break; } error: if ((from == EcsI64 || from == EcsU64) && !number.u) { return ecs_meta_set_null(cursor); } const char *name = from == EcsI64 ? "int" : from == EcsU64 ? "uint" : from == EcsF64 ? "float" : flecs_meta_op_kind_str( (ecs_meta_op_kind_t)(EcsOpPrimitive + (int)from)); flecs_meta_conversion_error(cursor, op, name); return -1; } int ecs_meta_set_bool( ecs_meta_cursor_t *cursor, bool value) { return flecs_meta_set_number(cursor, EcsBool, (flecs_meta_number_t){.i = value}); } int ecs_meta_set_char( ecs_meta_cursor_t *cursor, char value) { return flecs_meta_set_number(cursor, EcsChar, (flecs_meta_number_t){.i = value}); } int ecs_meta_set_int( ecs_meta_cursor_t *cursor, int64_t value) { return flecs_meta_set_number(cursor, EcsI64, (flecs_meta_number_t){.i = value}); } int ecs_meta_set_uint( ecs_meta_cursor_t *cursor, uint64_t value) { return flecs_meta_set_number(cursor, EcsU64, (flecs_meta_number_t){.u = value}); } int ecs_meta_set_float( ecs_meta_cursor_t *cursor, double value) { return flecs_meta_set_number(cursor, EcsF64, (flecs_meta_number_t){.f = value}); } int ecs_meta_set_entity( ecs_meta_cursor_t *cursor, ecs_entity_t value) { return flecs_meta_set_number(cursor, EcsEntity, (flecs_meta_number_t){.u = value}); } int ecs_meta_set_id( ecs_meta_cursor_t *cursor, ecs_entity_t value) { return flecs_meta_set_number(cursor, EcsId, (flecs_meta_number_t){.u = value}); } int ecs_meta_set_value( ecs_meta_cursor_t *cursor, const ecs_value_t *value) { ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t type = value->type; ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); if (!value->ptr) { ecs_err("value pointer is null"); return -1; } ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); if (flecs_meta_op_is_value(op)) { void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); if (!ptr) { ecs_err("no object to assign"); return -1; } if (type == ecs_id(ecs_value_t)) { ecs_value_copy(cursor->world, ptr, value->ptr); } else { ecs_value_set(cursor->world, ptr, type, value->ptr); } return 0; } const EcsType *mt = ecs_get(cursor->world, type, EcsType); if (!mt) { ecs_err("type of value does not have reflection data"); return -1; } if (mt->kind == EcsValueType) { return ecs_meta_set_value(cursor, value->ptr); } if (mt->kind == EcsPrimitiveType) { const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); switch(prim->kind) { case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); case EcsString: { char *str = *(char**)value->ptr; if (!str) { return ecs_meta_set_null(cursor); } return ecs_meta_set_string(cursor, str); } case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); case EcsId: return ecs_meta_set_id(cursor, *(ecs_id_t*)value->ptr); default: ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); goto error; } } else if (mt->kind == EcsEnumType) { const EcsEnum *et = ecs_get(cursor->world, type, EcsEnum); ecs_entity_t ut = et ? et->underlying_type : 0; const EcsPrimitive *prim = ut ? ecs_get(cursor->world, ut, EcsPrimitive) : NULL; ecs_primitive_kind_t kind = prim ? prim->kind : EcsI32; switch(kind) { case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); case EcsI32: case EcsBool: case EcsChar: case EcsByte: case EcsF32: case EcsF64: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: default: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); } } else if (mt->kind == EcsBitmaskType) { return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); } else { void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); if (op->type != value->type) { if (flecs_struct_is_derived_from( cursor->world, value->type, op->type)) { return ecs_ptr_copy(cursor->world, op->type, ptr, value->ptr); } #ifdef FLECS_SCRIPT if (mt->kind == EcsOpaqueType && op->kind == EcsOpString) { char *str = ecs_ptr_to_str( cursor->world, value->type, value->ptr); int result = ecs_meta_set_string(cursor, str); ecs_os_free(str); return result; } #endif char *type_str = ecs_get_path(cursor->world, value->type); flecs_meta_conversion_error(cursor, op, type_str); ecs_os_free(type_str); goto error; } return ecs_ptr_copy(cursor->world, value->type, ptr, value->ptr); } error: return -1; } int ecs_meta_set_string( ecs_meta_cursor_t *cursor, const char *value) { if (!value) { return ecs_meta_set_null(cursor); } ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); if (!ptr) { ecs_err("no object to assign"); goto error; } if (flecs_meta_op_is_value(op)) { ptr = flecs_meta_cursor_value_ensure( cursor, ptr, ecs_id(ecs_string_t)); if (!ptr) { goto error; } ecs_os_free(*(ecs_string_t*)ptr); flecs_meta_set_t(ecs_string_t, ptr, ecs_os_strdup(value)); return 0; } return flecs_meta_cursor_from_str( cursor, op->is.constants, op->kind, op->type, value, ptr); error: return -1; } int ecs_meta_set_string_literal( ecs_meta_cursor_t *cursor, const char *value) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); if (!value) { return -1; } ecs_size_t len = ecs_os_strlen(value); if (len < 2 || value[0] != '\"' || value[len - 1] != '\"') { ecs_err("invalid string literal '%s'", value); goto error; } switch(op->kind) { case EcsOpChar: flecs_meta_set_t(ecs_char_t, ptr, value[1]); break; default: len -= 2; char *result = ecs_os_malloc(len + 1); ecs_os_memcpy(result, value + 1, len); result[len] = '\0'; if (ecs_meta_set_string(cursor, result)) { ecs_os_free(result); return -1; } ecs_os_free(result); break; } return 0; error: return -1; } int ecs_meta_set_null( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); if (!ptr) { ecs_err("no object to assign"); goto error; } switch (op->kind) { case EcsOpString: ecs_os_free(*(char**)ptr); flecs_meta_set_t(ecs_string_t, ptr, NULL); return 0; case EcsOpOpaqueValue: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_null) { ot->assign_null(ptr); return 0; } break; } default: break; } flecs_meta_conversion_error(cursor, op, "null"); error: return -1; } static FLECS_ALWAYS_INLINE flecs_meta_number_t flecs_meta_to_number( ecs_meta_op_kind_t kind, const void *ptr, ecs_primitive_kind_t to) { flecs_meta_number_t value = {0}; ecs_primitive_kind_t from = EcsI64; const char *name = to == EcsBool ? "bool" : to == EcsI64 ? "int" : to == EcsU64 ? "uint" : to == EcsF64 ? "float" : to == EcsChar ? "char" : "entity"; (void)name; if ((to == EcsChar && kind != EcsOpChar) || (to == EcsEntity && kind != EcsOpEntity) || (to == EcsId && kind != EcsOpEntity && kind != EcsOpId)) { ecs_throw(ECS_INVALID_PARAMETER, "invalid element for %s", name); } if ((kind == EcsOpEntity || kind == EcsOpId) && (to == EcsI64 || to == EcsF64)) { ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from %s to %s", kind == EcsOpEntity ? "entity" : "id", name); } switch (kind) { case EcsOpBool: value.i = *(const ecs_bool_t*)ptr; break; case EcsOpChar: value.i = *(const ecs_char_t*)ptr; break; case EcsOpI8: value.i = *(const ecs_i8_t*)ptr; break; case EcsOpByte: case EcsOpU8: value.i = *(const ecs_u8_t*)ptr; break; case EcsOpI16: value.i = *(const ecs_i16_t*)ptr; break; case EcsOpU16: value.i = *(const ecs_u16_t*)ptr; break; case EcsOpEnum: case EcsOpI32: value.i = *(const ecs_i32_t*)ptr; break; case EcsOpBitmask: case EcsOpU32: value.i = *(const ecs_u32_t*)ptr; break; case EcsOpI64: value.i = *(const ecs_i64_t*)ptr; break; case EcsOpIPtr: value.i = *(const ecs_iptr_t*)ptr; break; case EcsOpEntity: case EcsOpId: case EcsOpU64: value.u = *(const ecs_u64_t*)ptr; from = EcsU64; break; case EcsOpUPtr: value.u = *(const ecs_uptr_t*)ptr; from = EcsU64; break; case EcsOpF32: value.f = (double)*(const ecs_f32_t*)ptr; from = EcsF64; break; case EcsOpF64: value.f = *(const ecs_f64_t*)ptr; from = EcsF64; break; case EcsOpString: if (to == EcsBool) { value.i = *(const char*const*)ptr != NULL; } else if (to == EcsF64) { value.f = atof(*(const char*const*)ptr); from = EcsF64; } else { value.i = atoi(*(const char*const*)ptr); } break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for %s", name); } switch (to) { case EcsBool: value.i = from == EcsF64 ? ECS_NEQZERO(value.f) : value.u != 0; break; case EcsChar: case EcsI64: value.i = from == EcsF64 ? (int64_t)value.f : from == EcsU64 ? flecs_uto(int64_t, value.u) : value.i; break; case EcsEntity: case EcsId: case EcsU64: value.u = from == EcsF64 ? flecs_ito(uint64_t, value.f) : from == EcsI64 ? flecs_ito(uint64_t, value.i) : value.u; break; case EcsF64: value.f = from == EcsI64 ? (double)value.i : from == EcsU64 ? (double)value.u : value.f; break; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } return value; error: return (flecs_meta_number_t){0}; } static FLECS_ALWAYS_INLINE flecs_meta_number_t flecs_meta_get_number( const ecs_meta_cursor_t *cursor, ecs_primitive_kind_t to) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); const void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); return flecs_meta_to_number(op->kind, ptr, to); } bool ecs_meta_get_bool( const ecs_meta_cursor_t *cursor) { return flecs_meta_get_number(cursor, EcsBool).i != 0; } int64_t ecs_meta_get_int( const ecs_meta_cursor_t *cursor) { return flecs_meta_get_number(cursor, EcsI64).i; } uint64_t ecs_meta_get_uint( const ecs_meta_cursor_t *cursor) { return flecs_meta_get_number(cursor, EcsU64).u; } double ecs_meta_get_float( const ecs_meta_cursor_t *cursor) { return flecs_meta_get_number(cursor, EcsF64).f; } char ecs_meta_get_char( const ecs_meta_cursor_t *cursor) { return (char)flecs_meta_get_number(cursor, EcsChar).i; } /* Value handler to get string from opaque (see ecs_meta_get_string below) */ static int ecs_meta_get_string_value_from_opaque( const struct ecs_serializer_t *ser, ecs_entity_t type, const void *value) { if(type != ecs_id(ecs_string_t)) { ecs_err("Expected value call for opaque type to be a string"); return -1; } char*** ctx = (char ***) ser->ctx; *ctx = ECS_CONST_CAST(char**, value); return 0; } /* Member handler to get string from opaque (see ecs_meta_get_string below) */ static int ecs_meta_get_string_member_from_opaque( const struct ecs_serializer_t* ser, const char* name) { (void)ser; // silence unused warning (void)name; // silence unused warning ecs_err("Unexpected member call when serializing string from opaque"); return -1; } const char* ecs_meta_get_string( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); ecs_meta_op_t *op = flecs_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, cursor, scope); switch(op->kind) { case EcsOpString: return *(const char**)ptr; case EcsOpOpaqueValue: { /* If opaque type happens to map to a string, retrieve it. * Otherwise, fall back to default case (error). */ const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if(opaque && opaque->as_type == ecs_id(ecs_string_t) && opaque->serialize) { char** str = NULL; ecs_serializer_t ser = { .world = cursor->world, .value = ecs_meta_get_string_value_from_opaque, .member = ecs_meta_get_string_member_from_opaque, .ctx = &str }; opaque->serialize(&ser, ptr); if(str && *str) return *str; /* invalid string, so fall through */ } /* Not a compatible opaque type, so fall through */ } /* fall through */ default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); } error: return 0; } ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor) { return flecs_meta_get_number(cursor, EcsEntity).u; } ecs_entity_t ecs_meta_get_id( const ecs_meta_cursor_t *cursor) { return flecs_meta_get_number(cursor, EcsId).u; } double ecs_meta_ptr_to_float( ecs_primitive_kind_t type_kind, const void *ptr) { ecs_meta_op_kind_t kind = flecs_meta_primitive_to_op_kind(type_kind); return flecs_meta_to_number(kind, ptr, EcsF64).f; } #endif #ifndef FLECS_META_TYPE_SUPPORT_H #define FLECS_META_TYPE_SUPPORT_H #ifdef FLECS_META int flecs_init_type( ecs_world_t *world, ecs_entity_t type, ecs_type_kind_t kind, ecs_size_t size, ecs_size_t alignment); void flecs_meta_detect_cycles( ecs_world_t *world, ecs_entity_t type, ecs_entity_t target); #define flecs_init_type_t(world, type, kind, T) \ flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) int flecs_compare_string( const void *str_a, const void *str_b, const ecs_type_info_t *ti); bool flecs_equals_string( const void *str_a, const void *str_b, const ecs_type_info_t *ti); void flecs_meta_primitives_init( ecs_world_t *world); void flecs_meta_enum_init( ecs_world_t *world); void flecs_meta_struct_init( ecs_world_t *world); void flecs_meta_array_init( ecs_world_t *world); void flecs_meta_map_init( ecs_world_t *world); void flecs_meta_opaque_init( ecs_world_t *world); void flecs_meta_units_init( ecs_world_t *world); #endif #endif #ifdef FLECS_META /* Opaque type serializer for addon vector */ static int flecs_addon_vec_serialize(const ecs_serializer_t *ser, const void *ptr) { char ***data = ECS_CONST_CAST(char***, ptr); char **addons = data[0]; do { ser->value(ser, ecs_id(ecs_string_t), addons); } while((++ addons)[0]); return 0; } static size_t flecs_addon_vec_count(const void *ptr) { int32_t count = 0; char ***data = ECS_CONST_CAST(char***, ptr); char **addons = data[0]; do { ++ count; } while(addons[count]); return flecs_ito(size_t, count); } static int flecs_const_str_serialize(const ecs_serializer_t *ser, const void *ptr) { char **data = ECS_CONST_CAST(char**, ptr); ser->value(ser, ecs_id(ecs_string_t), data); return 0; } /* Initialize reflection data for core components */ static void flecs_meta_import_core_definitions( ecs_world_t *world) { ecs_struct(world, { .entity = ecs_id(EcsComponent), .members = { { .name = "size", .type = ecs_id(ecs_i32_t) }, { .name = "alignment", .type = ecs_id(ecs_i32_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsIdentifier), .members = { { .name = "value", .type = ecs_id(ecs_string_t), .offset = offsetof(EcsIdentifier, value) } } }); ecs_struct(world, { .entity = ecs_id(EcsParent), .members = { { .name = "value", .type = ecs_id(ecs_entity_t) } } }); /* Define const string as an opaque type that maps to string. This enables reflection for strings that are in .rodata (read-only), so that the meta addon does not try to free them. This opaque type defines how to serialize (read) the string, but won't let users assign a new value. */ ecs_entity_t const_string = ecs_opaque(world, { .entity = ecs_component(world, { .entity = ecs_entity(world, { .name = "flecs.core.const_string_t", .root_sep = "" }), .type = { .size = ECS_SIZEOF(const char*), .alignment = ECS_ALIGNOF(const char*), .hooks = { .cmp = flecs_compare_string, .equals = flecs_equals_string } } }), .type = { .as_type = ecs_id(ecs_string_t), .serialize = flecs_const_str_serialize, } }); ecs_entity_t string_vec = ecs_vector(world, { .entity = ecs_entity(world, { .name = "flecs.core.string_vec_t", .root_sep = "" }), .type = ecs_id(ecs_string_t) }); ecs_entity_t addon_vec = ecs_opaque(world, { .entity = ecs_component(world, { .entity = ecs_entity(world, { .name = "flecs.core.addon_vec_t", .root_sep = "" }), .type = { .size = ECS_SIZEOF(char**), .alignment = ECS_ALIGNOF(char**) } }), .type = { .as_type = string_vec, .serialize = flecs_addon_vec_serialize, .count = flecs_addon_vec_count, } }); ecs_struct(world, { .entity = ecs_entity(world, { .name = "flecs.core.BuildInfo", .root_sep = "" }), .members = { { .name = "compiler", .type = const_string }, { .name = "addons", .type = addon_vec }, { .name = "flags", .type = addon_vec }, { .name = "version", .type = const_string }, { .name = "version_major", .type = ecs_id(ecs_i16_t) }, { .name = "version_minor", .type = ecs_id(ecs_i16_t) }, { .name = "version_patch", .type = ecs_id(ecs_i16_t) }, { .name = "debug", .type = ecs_id(ecs_bool_t) }, { .name = "sanitize", .type = ecs_id(ecs_bool_t) }, { .name = "perf_trace", .type = ecs_id(ecs_bool_t) } } }); } /* Initialize reflection data for meta components */ static void flecs_meta_import_meta_definitions( ecs_world_t *world) { ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "TypeKind" }), .constants = { { .name = "PrimitiveType" }, { .name = "BitmaskType" }, { .name = "EnumType" }, { .name = "StructType" }, { .name = "ArrayType" }, { .name = "VectorType" }, { .name = "OpaqueType" }, { .name = "MapType" }, { .name = "ValueType" } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsType), .members = { { .name = "kind", .type = type_kind } } }); ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "PrimitiveKind" }), .constants = { { .name = "Bool", 1 }, { .name = "Char" }, { .name = "Byte" }, { .name = "U8" }, { .name = "U16" }, { .name = "U32" }, { .name = "U64 "}, { .name = "I8" }, { .name = "I16" }, { .name = "I32" }, { .name = "I64" }, { .name = "F32" }, { .name = "F64" }, { .name = "UPtr "}, { .name = "IPtr" }, { .name = "String" }, { .name = "Entity" }, { .name = "Id" } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsPrimitive), .members = { { .name = "kind", .type = primitive_kind } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMember), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "unit", .type = ecs_id(ecs_entity_t) }, { .name = "offset", .type = ecs_id(ecs_i32_t) }, { .name = "use_offset", .type = ecs_id(ecs_bool_t) } } }); ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "value_range" }), .members = { { .name = "min", .type = ecs_id(ecs_f64_t) }, { .name = "max", .type = ecs_id(ecs_f64_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMemberRanges), .members = { { .name = "value", .type = vr }, { .name = "warning", .type = vr }, { .name = "error", .type = vr } } }); ecs_entity_t member_t = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "member_t" }), .members = { { .name = "name", .type = ecs_id(ecs_string_t) }, { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "offset", .type = ecs_id(ecs_i32_t) }, { .name = "unit", .type = ecs_id(ecs_entity_t) }, { .name = "use_offset", .type = ecs_id(ecs_bool_t) }, { .name = "range", .type = vr }, { .name = "error_range", .type = vr }, { .name = "warning_range", .type = vr }, { .name = "size", .type = ecs_id(ecs_i32_t) }, { .name = "member", .type = ecs_id(ecs_entity_t) } } }); ecs_entity_t member_vec = ecs_vector(world, { .entity = ecs_entity(world, { .name = "member_vec_t" }), .type = member_t }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsStruct), .members = { { .name = "members", .type = member_vec } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsEnum), .members = { { .name = "underlying_type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsVector), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMap), .members = { { .name = "key_type", .type = ecs_id(ecs_entity_t) }, { .name = "type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsOpaque), .members = { { .name = "as_type", .type = ecs_id(ecs_entity_t) } } }); ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "unit_translation" }), .members = { { .name = "factor", .type = ecs_id(ecs_i32_t) }, { .name = "power", .type = ecs_id(ecs_i32_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnit), .members = { { .name = "symbol", .type = ecs_id(ecs_string_t) }, { .name = "prefix", .type = ecs_id(ecs_entity_t) }, { .name = "base", .type = ecs_id(ecs_entity_t) }, { .name = "over", .type = ecs_id(ecs_entity_t) }, { .name = "translation", .type = ut } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnitPrefix), .members = { { .name = "symbol", .type = ecs_id(ecs_string_t) }, { .name = "translation", .type = ut } } }); } void flecs_meta_import_definitions( ecs_world_t *world) { flecs_meta_import_core_definitions(world); flecs_meta_import_meta_definitions(world); } #endif #ifdef FLECS_META ecs_entity_t FlecsMeta = 0; void flecs_type_serializer_dtor( EcsTypeSerializer *ptr) { int32_t i, count = ecs_vec_count(&ptr->ops); ecs_meta_op_t *ops = ecs_vec_first(&ptr->ops); for (i = 0; i < count; i ++) { ecs_meta_op_t *op = &ops[i]; ecs_os_free(ECS_CONST_CAST(char*, op->name)); if (op->kind == EcsOpPushStruct) { if (op->is.members) { flecs_name_index_free(op->is.members); } } } ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_op_t); } static ECS_COPY(EcsTypeSerializer, dst, src, { flecs_type_serializer_dtor(dst); dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_op_t); int32_t o, count = ecs_vec_count(&dst->ops); ecs_meta_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_op_t); for (o = 0; o < count; o ++) { ecs_meta_op_t *op = &ops[o]; if (op->name) { op->name = ecs_os_strdup(op->name); } } for (o = 0; o < count; o ++) { ecs_meta_op_t *op = &ops[o]; if (op->kind == EcsOpPushStruct) { if (op->is.members) { op->is.members = flecs_name_index_copy(op->is.members); /* Repoint the name index keys to the copied name strings, as * the index doesn't own its keys. The index maps member names * to the offset of the member op relative to the op after the * push op. */ flecs_hashmap_iter_t it = flecs_hashmap_iter(op->is.members); ecs_hashed_string_t *key; uint64_t *op_index; while ((op_index = flecs_hashmap_next_w_key( &it, ecs_hashed_string_t, &key, uint64_t))) { ecs_meta_op_t *mbr_op = &ops[o + 1 + flecs_uto( int32_t, *op_index)]; key->value = ECS_CONST_CAST(char*, mbr_op->name); } } } } }) static ECS_MOVE(EcsTypeSerializer, dst, src, { flecs_type_serializer_dtor(dst); dst->ops = src->ops; src->ops = (ecs_vec_t){0}; }) static ECS_DTOR(EcsTypeSerializer, ptr, { flecs_type_serializer_dtor(ptr); }) static const char* flecs_type_kind_str( ecs_type_kind_t kind) { switch(kind) { case EcsPrimitiveType: return "Primitive"; case EcsBitmaskType: return "Bitmask"; case EcsEnumType: return "Enum"; case EcsStructType: return "Struct"; case EcsArrayType: return "Array"; case EcsVectorType: return "Vector"; case EcsMapType: return "Map"; case EcsValueType: return "Value"; case EcsOpaqueType: return "Opaque"; default: return "unknown"; } } #ifdef FLECS_DEBUG static bool flecs_meta_detect_cycles_w_stack( ecs_world_t *world, ecs_entity_t type, ecs_entity_t target, ecs_vec_t *visited) { if (!type) { return false; } if (type == target) { return true; } ecs_entity_t *visited_types = ecs_vec_first_t(visited, ecs_entity_t); int32_t i, count = ecs_vec_count(visited); for (i = 0; i < count; i ++) { if (visited_types[i] == type) { return false; } } ecs_vec_append_t(NULL, visited, ecs_entity_t)[0] = type; const EcsStruct *struct_info = ecs_get(world, type, EcsStruct); if (struct_info) { ecs_member_t *members = ecs_vec_first_t(&struct_info->members, ecs_member_t); count = ecs_vec_count(&struct_info->members); for (i = 0; i < count; i ++) { if (flecs_meta_detect_cycles_w_stack( world, members[i].type, target, visited)) { return true; } } } const EcsArray *array_info = ecs_get(world, type, EcsArray); if (array_info) { if (flecs_meta_detect_cycles_w_stack( world, array_info->type, target, visited)) { return true; } } return false; } #endif void flecs_meta_detect_cycles( ecs_world_t *world, ecs_entity_t type, ecs_entity_t target) { (void)world; (void)type; (void)target; #ifdef FLECS_DEBUG ecs_vec_t visited; ecs_vec_init_t(NULL, &visited, ecs_entity_t, 0); bool cycles_detected = flecs_meta_detect_cycles_w_stack( world, type, target, &visited); ecs_vec_fini_t(NULL, &visited, ecs_entity_t); ecs_assert( !cycles_detected, ECS_CYCLE_DETECTED, "cyclic type definition: '%s' depends on '%s'", flecs_errstr(ecs_get_path(world, target)), flecs_errstr_1(ecs_get_path(world, type))); #endif } int flecs_init_type( ecs_world_t *world, ecs_entity_t type, ecs_type_kind_t kind, ecs_size_t size, ecs_size_t alignment) { ecs_world_t *stage = world; world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_DEBUG { const ecs_type_info_t *ti = ecs_get_type_info(world, type); if (ti && (ti->hooks.flags & ECS_TYPE_HOOK_IN_USE)) { const EcsType *t = ecs_get(world, type, EcsType); ecs_assert(!t || t->existing, ECS_INVALID_OPERATION, "cannot modify runtime type '%s' after it is in use", flecs_errstr_1(ecs_get_path(world, type))); } } #endif EcsType *meta_type = ecs_ensure(stage, type, EcsType); if (meta_type->kind == 0) { meta_type->kind = kind; const EcsComponent *cptr = ecs_get(world, type, EcsComponent); /* Determine if this is an existing type or a reflection-defined type * (runtime type) */ meta_type->existing = (cptr != NULL) && (cptr->size != 0); /* For existing types, ensure that component has a default constructor, * to prevent crashing serializers on uninitialized values. For runtime * types (rtt), the default hooks are set by * flecs_meta_rtt_init_default_hooks */ ecs_type_info_t *ti = flecs_type_info_ensure(world, type); if (meta_type->existing) { if(!ti->hooks.ctor) { ti->hooks.ctor = flecs_default_ctor; ti->hooks.flags |= ECS_TYPE_HOOK_CTOR; } if(kind == EcsEnumType) { /* Generate compare/equals hooks for enums, copying the underlying type's hooks, which should be any of the default primitive integral compare hooks, i.e. ecs_compare_i8, _i16, _i32... */ const EcsEnum* enum_info = ecs_get(world, type, EcsEnum); ecs_assert(enum_info != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_hooks_t *enum_hooks = ecs_get_hooks_id( world, enum_info->underlying_type); ecs_assert(!(enum_hooks->flags & (ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL)), ECS_INTERNAL_ERROR, NULL); ti->hooks.cmp = enum_hooks->cmp; ti->hooks.equals = enum_hooks->equals; ti->hooks.flags &= ~(ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL); ti->hooks.flags |= ECS_TYPE_HOOK_CMP|ECS_TYPE_HOOK_EQUALS; } } } else { if (meta_type->kind != kind) { ecs_err("type '%s' reregistered as '%s' (was '%s')", ecs_get_name(world, type), flecs_type_kind_str(kind), flecs_type_kind_str(meta_type->kind)); return -1; } } if (!meta_type->existing) { EcsComponent *comp = ecs_ensure(stage, type, EcsComponent); comp->size = size; comp->alignment = alignment; ecs_modified(stage, type, EcsComponent); } else { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (comp->size) { if (comp->size < size) { ecs_err( "computed size (%d) for '%s' is larger than actual type (%d)", size, ecs_get_name(world, type), comp->size); return -1; } if (comp->alignment < alignment) { ecs_err( "computed alignment (%d) for '%s' is larger than actual type (%d)", alignment, ecs_get_name(world, type), comp->alignment); return -1; } if (comp->size == size && comp->alignment != alignment) { if (comp->alignment < alignment) { ecs_err("computed size for '%s' matches with actual type but " "alignment is different (%d vs. %d)", ecs_get_name(world, type), alignment, comp->alignment); } } meta_type->partial = comp->size != size; } } ecs_modified(stage, type, EcsType); return 0; } void FlecsMetaImport( ecs_world_t *world) { ECS_MODULE(world, FlecsMeta); FlecsMeta = ecs_id(FlecsMeta); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTypeSerializer); ecs_add_pair(world, ecs_id(EcsTypeSerializer), EcsOnInstantiate, EcsDontInherit); ecs_entity_t type_component = ecs_entity(world, { .id = ecs_id(EcsType), .name = "type", .symbol = "EcsType" }); ecs_add_pair(world, type_component, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = type_component, .type.size = sizeof(EcsType), .type.alignment = ECS_ALIGNOF(EcsType), }); ecs_set_hooks(world, EcsType, { .ctor = flecs_default_ctor }); ecs_set_hooks(world, EcsTypeSerializer, { .ctor = flecs_default_ctor, .move = ecs_move(EcsTypeSerializer), .copy = ecs_copy(EcsTypeSerializer), .dtor = ecs_dtor(EcsTypeSerializer) }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsType) }, .events = {EcsOnSet}, .callback = flecs_meta_type_serializer_init, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsType) }, .events = {EcsOnSet}, .callback = flecs_rtt_init_default_hooks, .global_observer = true }); /* Import type support for different type kinds */ flecs_meta_primitives_init(world); flecs_meta_enum_init(world); flecs_meta_struct_init(world); flecs_meta_array_init(world); flecs_meta_map_init(world); flecs_meta_value_init(world); flecs_meta_opaque_init(world); flecs_meta_units_init(world); /* Import reflection definitions for builtin types */ flecs_meta_import_definitions(world); } #endif #ifdef FLECS_META static int flecs_meta_ser_scalar( const ecs_world_t *world, ecs_meta_op_kind_t op_kind, const void *ptr, ecs_strbuf_t *str, bool is_expr) { switch(op_kind) { case EcsOpBool: if (*(const bool*)ptr) { ecs_strbuf_appendlit(str, "true"); } else { ecs_strbuf_appendlit(str, "false"); } break; case EcsOpChar: { char chbuf[3]; char ch = *(const char*)ptr; if (ch) { flecs_chresc(chbuf, *(const char*)ptr, '"'); if (is_expr) ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, chbuf); if (is_expr) ecs_strbuf_appendch(str, '"'); } else { ecs_strbuf_appendch(str, '0'); } break; } case EcsOpByte: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)ptr)); break; case EcsOpU8: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)ptr)); break; case EcsOpU16: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)ptr)); break; case EcsOpU32: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)ptr)); break; case EcsOpU64: ecs_strbuf_append(str, "%llu", *(const uint64_t*)ptr); break; case EcsOpI8: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)ptr)); break; case EcsOpI16: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)ptr)); break; case EcsOpI32: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)ptr)); break; case EcsOpI64: ecs_strbuf_appendint(str, *(const int64_t*)ptr); break; case EcsOpF32: ecs_strbuf_appendflt(str, (double)*(const float*)ptr, 0); break; case EcsOpF64: ecs_strbuf_appendflt(str, *(const double*)ptr, 0); break; case EcsOpIPtr: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)ptr)); break; case EcsOpUPtr: ecs_strbuf_append(str, "%llu", (unsigned long long)*(const uintptr_t*)ptr); break; case EcsOpString: { const char *value = *ECS_CONST_CAST(const char**, ptr); if (value) { if (!is_expr) { ecs_strbuf_appendstr(str, value); } else { ecs_size_t length = flecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstrn(str, value, length); ecs_strbuf_appendch(str, '"'); } else { char *out = ecs_os_malloc(length + 3); flecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr(str, out); ecs_os_free(out); } } } else { ecs_strbuf_appendlit(str, "null"); } break; } case EcsOpEntity: { ecs_entity_t e = *(const ecs_entity_t*)ptr; if (!e) { ecs_strbuf_appendlit(str, "#0"); } else { ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str, false); } break; } case EcsOpId: { ecs_id_t id = *(const ecs_id_t*)ptr; if (!id) { ecs_strbuf_appendlit(str, "#0"); } else { ecs_id_str_buf(world, id, str); } break; } case EcsOpEnum: case EcsOpBitmask: case EcsOpForward: case EcsOpPushStruct: case EcsOpPushArray: case EcsOpPushVector: case EcsOpPushMap: case EcsOpPushValue: case EcsOpOpaqueValue: case EcsOpOpaqueStruct: case EcsOpOpaqueArray: case EcsOpOpaqueVector: case EcsOpPop: case EcsOpPrimitive: case EcsOpScope: ecs_err("invalid primitive kind"); return -1; } return 0; } ecs_entity_t flecs_meta_primitive_type( ecs_primitive_kind_t kind) { static const ecs_entity_t *types[] = { [EcsBool] = &ecs_id(ecs_bool_t), [EcsChar] = &ecs_id(ecs_char_t), [EcsByte] = &ecs_id(ecs_byte_t), [EcsU8] = &ecs_id(ecs_u8_t), [EcsU16] = &ecs_id(ecs_u16_t), [EcsU32] = &ecs_id(ecs_u32_t), [EcsU64] = &ecs_id(ecs_u64_t), [EcsI8] = &ecs_id(ecs_i8_t), [EcsI16] = &ecs_id(ecs_i16_t), [EcsI32] = &ecs_id(ecs_i32_t), [EcsI64] = &ecs_id(ecs_i64_t), [EcsF32] = &ecs_id(ecs_f32_t), [EcsF64] = &ecs_id(ecs_f64_t), [EcsUPtr] = &ecs_id(ecs_uptr_t), [EcsIPtr] = &ecs_id(ecs_iptr_t), [EcsString] = &ecs_id(ecs_string_t), [EcsEntity] = &ecs_id(ecs_entity_t), [EcsId] = &ecs_id(ecs_id_t), }; if (kind < EcsBool || kind > EcsPrimitiveKindLast) { return 0; } return *types[kind]; } ecs_meta_op_kind_t flecs_meta_primitive_to_op_kind( ecs_primitive_kind_t kind) { if (kind < EcsBool || kind > EcsPrimitiveKindLast) { ecs_abort(ECS_INTERNAL_ERROR, NULL); } return (ecs_meta_op_kind_t)(EcsOpPrimitive + (int)kind); } ecs_entity_t flecs_meta_op_kind_to_type( ecs_meta_op_kind_t kind) { if (kind == EcsOpI32) { kind = EcsOpU32; } else if (kind == EcsOpI64) { kind = EcsOpU64; } ecs_entity_t type = flecs_meta_primitive_type( (ecs_primitive_kind_t)(kind - EcsOpPrimitive)); if (!type) { ecs_err("cannot convert kind to type"); } return type; } const char* flecs_meta_op_kind_str( ecs_meta_op_kind_t kind) { switch(kind) { case EcsOpEnum: return "Enum"; case EcsOpBitmask: return "Bitmask"; case EcsOpForward: return "Forward"; case EcsOpPushStruct: return "Push"; case EcsOpPushArray: return "PushArray"; case EcsOpPushVector: return "PushVector"; case EcsOpPushMap: return "PushMap"; case EcsOpPushValue: return "PushValue"; case EcsOpOpaqueValue: return "OpaqueValue"; case EcsOpOpaqueStruct: return "OpaqueStruct"; case EcsOpOpaqueArray: return "OpaqueArray"; case EcsOpOpaqueVector: return "OpaqueVector"; case EcsOpPop: return "Pop"; case EcsOpPrimitive: return "Primitive"; case EcsOpBool: return "Bool"; case EcsOpChar: return "Char"; case EcsOpByte: return "Byte"; case EcsOpU8: return "U8"; case EcsOpU16: return "U16"; case EcsOpU32: return "U32"; case EcsOpU64: return "U64"; case EcsOpI8: return "I8"; case EcsOpI16: return "I16"; case EcsOpI32: return "I32"; case EcsOpI64: return "I64"; case EcsOpF32: return "F32"; case EcsOpF64: return "F64"; case EcsOpUPtr: return "UPtr"; case EcsOpIPtr: return "IPtr"; case EcsOpString: return "String"; case EcsOpEntity: return "Entity"; case EcsOpId: return "Id"; case EcsOpScope: return "Scope"; default: return "<< invalid kind >>"; } } bool flecs_meta_valid_digit( const char *str) { return str[0] == '-' || isdigit(str[0]); } bool flecs_meta_type_is_integer( const ecs_world_t *world, ecs_entity_t type) { const EcsPrimitive *prim = ecs_get(world, type, EcsPrimitive); if (!prim) { return false; } switch(prim->kind) { case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsUPtr: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsIPtr: return true; case EcsBool: case EcsChar: case EcsByte: case EcsF32: case EcsF64: case EcsString: case EcsEntity: case EcsId: return false; } return false; } int flecs_value_blit_u64( const ecs_world_t *world, const ecs_value_t *value, ecs_map_key_t *out) { const EcsComponent *comp = ecs_get(world, value->type, EcsComponent); if (!comp) { ecs_err("entity '%s' is not a type", flecs_errstr(ecs_get_path(world, value->type))); return -1; } if (comp->size > ECS_SIZEOF(*out)) { ecs_err("type '%s' is too large for a u64", flecs_errstr(ecs_get_path(world, value->type))); return -1; } *out = 0; ecs_os_memcpy(out, value->ptr, comp->size); return 0; } int flecs_meta_value_type_str( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); const char *symbol = ecs_get_symbol(world, type); if (symbol) { ecs_strbuf_appendstr(str, symbol); return 0; } char *path = ecs_get_path(world, type); if (!path) { ecs_err("cannot serialize value type without name"); return -1; } ecs_strbuf_appendstr(str, path); ecs_os_free(path); return 0; } int flecs_meta_ser_enum( const ecs_world_t *world, ecs_entity_t enum_type, ecs_meta_op_kind_t op_kind, ecs_map_t *constants, const void *ptr, ecs_strbuf_t *str) { ecs_map_key_t value; if (op_kind == EcsOpU8) { value = *(const uint8_t*)ptr; } else if (op_kind == EcsOpI8) { value = (ecs_map_key_t)(int64_t)*(const int8_t*)ptr; } else if (op_kind == EcsOpU16) { value = *(const uint16_t*)ptr; } else if (op_kind == EcsOpI16) { value = (ecs_map_key_t)(int64_t)*(const int16_t*)ptr; } else if (op_kind == EcsOpU32) { value = *(const uint32_t*)ptr; } else if (op_kind == EcsOpI32) { value = (ecs_map_key_t)(int64_t)*(const int32_t*)ptr; } else if (op_kind == EcsOpUPtr) { value = *(const uintptr_t*)ptr; } else if (op_kind == EcsOpIPtr) { value = (ecs_map_key_t)(int64_t)*(const intptr_t*)ptr; } else if (op_kind == EcsOpU64 || op_kind == EcsOpI64) { value = *(const uint64_t*)ptr; } else { ecs_abort(ECS_INTERNAL_ERROR, "invalid underlying type"); } ecs_enum_constant_t *constant = ecs_map_get_deref(constants, ecs_enum_constant_t, value); if (!constant) { char *name = enum_type ? ecs_get_path(world, enum_type) : NULL; ecs_err("enumeration value '%d' of type '%s' is not a valid constant", value, name ? name : "unknown"); ecs_os_free(name); return -1; } ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); return 0; } int flecs_meta_ser_bitmask( const ecs_world_t *world, ecs_entity_t bitmask_type, ecs_map_t *constants, const void *ptr, const char *delim, ecs_strbuf_t *str) { uint32_t value = *(const uint32_t*)ptr; if (!value) { ecs_strbuf_appendstr(str, "0"); return 0; } ecs_strbuf_list_push(str, delim, "|"); ecs_map_iter_t it = ecs_map_iter(constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); value -= (uint32_t)key; } } if (value != 0) { char *name = bitmask_type ? ecs_get_path(world, bitmask_type) : NULL; ecs_err("value for bitmask %s contains bits (%u) that cannot be mapped" " to a constant", name ? name : "unknown", value); ecs_os_free(name); goto error; } ecs_strbuf_list_pop(str, delim); return 0; error: return -1; } int flecs_meta_ser_map_key( const ecs_world_t *world, const ecs_meta_op_t *op, ecs_map_key_t key, ecs_strbuf_t *str) { if (op->underlying_kind == EcsOpEnum) { if (flecs_meta_ser_enum( world, 0, EcsOpU64, op->is.constants, &key, str)) { return -1; } } else if (op->underlying_kind == EcsOpBitmask) { uint32_t value = (uint32_t)key; if (!value) { ecs_strbuf_appendch(str, '0'); return 0; } ecs_strbuf_list_push(str, "", "|"); ecs_map_iter_t it = ecs_map_iter(op->is.constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); ecs_map_key_t constant_key = ecs_map_key(&it); if ((value & constant_key) == constant_key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, constant->constant)); value -= (uint32_t)constant_key; } } ecs_strbuf_list_pop(str, ""); if (value != 0) { char *name = ecs_get_path(world, op->type); ecs_err("map key '%llu' of type '%s' contains invalid/unknown " "bits", (unsigned long long)key, name); ecs_os_free(name); return -1; } } else { return flecs_meta_ser_scalar( world, op->underlying_kind, &key, str, false); } return 0; } int flecs_meta_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *ptr, ecs_strbuf_t *str, bool is_expr) { return flecs_meta_ser_scalar( world, flecs_meta_primitive_to_op_kind(kind), ptr, str, is_expr); } int flecs_meta_find_constant( const ecs_world_t *world, const ecs_map_t *constants, const char *name, uint64_t *value_out) { ecs_map_iter_t it = ecs_map_iter(constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *constant = ecs_map_ptr(&it); if (!ecs_os_strcmp( ecs_get_name(world, constant->constant), name)) { *value_out = ecs_map_key(&it); return 0; } } return -1; } static int flecs_meta_add_bitmask_constant( const ecs_world_t *world, ecs_entity_t type, ecs_map_t *constants, void *out, const char *value) { if (!ecs_os_strcmp(value, "0")) { return 0; } uint32_t v; if (type) { ecs_entity_t c = ecs_lookup_child(world, type, value); if (!c) { char *path = ecs_get_path(world, type); ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); ecs_os_free(path); return -1; } const uint32_t *vptr = ecs_get_pair_second( world, c, EcsConstant, ecs_u32_t); if (vptr == NULL) { char *path = ecs_get_path(world, type); ecs_err("'%s' is not a bitmask constant for type '%s'", value, path); ecs_os_free(path); return -1; } v = *vptr; } else { uint64_t constant = 0; flecs_meta_find_constant(world, constants, value, &constant); v = (uint32_t)constant; } *(ecs_u32_t*)out |= v; return 0; } int flecs_meta_parse_bitmask( const ecs_world_t *world, ecs_entity_t type, ecs_map_t *constants, void *out, const char *value) { char token[ECS_MAX_TOKEN_SIZE]; const char *prev = value, *ptr = value; *(ecs_u32_t*)out = 0; while ((ptr = strchr(ptr, '|'))) { ecs_os_memcpy(token, prev, ptr - prev); token[ptr - prev] = '\0'; if (flecs_meta_add_bitmask_constant(world, type, constants, out, token)) { goto error; } ptr ++; prev = ptr; } if (flecs_meta_add_bitmask_constant(world, type, constants, out, prev)) { goto error; } return 0; error: return -1; } int flecs_meta_parse_enum( const ecs_world_t *world, ecs_entity_t type, ecs_map_t *constants, void *out, const char *value) { ecs_value_t v; if (!type) { ecs_assert(constants != NULL, ECS_INTERNAL_ERROR, NULL); uint64_t constant = 0; if (flecs_meta_find_constant(world, constants, value, &constant)) { goto error; } *(uint64_t*)out = constant; return 0; } { ecs_entity_t c = ecs_lookup_child(world, type, value); if (!c) { char *path = ecs_get_path(world, type); ecs_err("unresolved enum constant '%s' for type '%s'", value, path); ecs_os_free(path); goto error; } ecs_entity_t underlying = ecs_get_target(world, c, EcsConstant, 0); if (!underlying) { char *path = ecs_get_path(world, type); ecs_err("'%s' is not an enum constant for type '%s'", value, path); ecs_os_free(path); goto error; } const void *cptr = ecs_get_id(world, c, ecs_pair(EcsConstant, underlying)); if (!cptr) { char *path = ecs_get_path(world, type); ecs_err("constant '%s' enum '%s' is of an invalid underlying type", value, path); ecs_os_free(path); goto error; } v = (ecs_value_t) { .ptr = ECS_CONST_CAST(void*, cptr), .type = underlying }; } ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, out); ecs_meta_set_value(&cur, &v); return 0; error: return -1; } #endif #ifdef FLECS_META int ecs_ptr_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_ptr_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_ptr_init_w_type_info(world, ti, ptr); error: return -1; } void* ecs_ptr_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_ptr_init_w_type_info(world, ti, result) != 0) { flecs_free(&world->allocator, ti->size, result); goto error; } return result; error: return NULL; } void* ecs_ptr_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_ptr_new_w_type_info(world, ti); error: return NULL; } int ecs_ptr_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_ptr_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_ptr_fini_w_type_info(world, ti, ptr); error: return -1; } int ecs_ptr_free_w_type_info( 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); if (ecs_ptr_fini_w_type_info(world, ti, ptr) != 0) { goto error; } flecs_free(&world->allocator, ti->size, ptr); return 0; error: return -1; } int ecs_ptr_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"); return ecs_ptr_free_w_type_info(world, ti, ptr); error: return -1; } int ecs_ptr_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_ptr_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_ptr_copy_w_type_info(world, ti, dst, src); error: return -1; } int ecs_ptr_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_ptr_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_ptr_move_w_type_info(world, ti, dst, src); error: return -1; } int ecs_ptr_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_ptr_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_ptr_move_ctor_w_type_info(world, ti, dst, src); error: return -1; } #endif #ifdef FLECS_META /* Stores all the information necessary to forward a hook call to a * struct member's type */ typedef struct ecs_rtt_call_data_t { const ecs_type_info_t *type_info; int32_t offset; int32_t count; } ecs_rtt_call_data_t; typedef struct ecs_rtt_struct_ctx_t { ecs_vec_t members; ecs_vec_t dtors; } ecs_rtt_struct_ctx_t; /* Generic copy assign hook */ static void flecs_rtt_default_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_os_memcpy(dst_ptr, src_ptr, count * type_info->size); } /* Generic move assign hook */ static void flecs_rtt_default_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { flecs_rtt_default_copy(dst_ptr, src_ptr, count, type_info); } typedef enum flecs_rtt_action_t { FlecsRttCtor, FlecsRttDtor, FlecsRttCopy, FlecsRttMove, FlecsRttCompare, FlecsRttEquals } flecs_rtt_action_t; static FLECS_ALWAYS_INLINE int flecs_rtt_struct_action( void *ptr, const void *src, int32_t count, const ecs_type_info_t *ti, flecs_rtt_action_t action) { if (action >= FlecsRttCompare && ptr == src) { return 0; } ecs_rtt_struct_ctx_t *ctx = ti->hooks.lifecycle_ctx; ecs_vec_t *vec = action == FlecsRttDtor ? &ctx->dtors : &ctx->members; const ecs_rtt_call_data_t *members = ecs_vec_first(vec); int32_t i, j, member_count = ecs_vec_count(vec); for (j = 0; j < count; j ++) { for (i = 0; i < member_count; i ++) { const ecs_rtt_call_data_t *m = &members[i]; const ecs_type_info_t *mt = m->type_info; void *dst = ECS_OFFSET(ptr, ti->size * j + m->offset); const void *from = src ? ECS_OFFSET(src, ti->size * j + m->offset) : NULL; switch (action) { case FlecsRttCtor: (mt->hooks.ctor ? mt->hooks.ctor : flecs_default_ctor)( dst, m->count, mt); break; case FlecsRttDtor: mt->hooks.dtor(dst, m->count, mt); break; case FlecsRttCopy: (mt->hooks.copy ? mt->hooks.copy : flecs_rtt_default_copy)( dst, from, m->count, mt); break; case FlecsRttMove: (mt->hooks.move ? mt->hooks.move : flecs_rtt_default_move)( dst, ECS_CONST_CAST(void*, from), m->count, mt); break; case FlecsRttCompare: case FlecsRttEquals: for (int32_t e = 0; e < m->count; e ++) { const void *a = ECS_ELEM(dst, mt->size, e); const void *b = ECS_ELEM(from, mt->size, e); int result = action == FlecsRttEquals ? !mt->hooks.equals(a, b, mt) : mt->hooks.cmp(a, b, mt); if (result) { return result; } } break; } } } return 0; } static void flecs_rtt_struct_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { flecs_rtt_struct_action(ptr, NULL, count, ti, FlecsRttCtor); } static void flecs_rtt_struct_dtor( void *ptr, int32_t count, const ecs_type_info_t *ti) { flecs_rtt_struct_action(ptr, NULL, count, ti, FlecsRttDtor); } static void flecs_rtt_struct_copy( void *ptr, const void *src, int32_t count, const ecs_type_info_t *ti) { flecs_rtt_struct_action(ptr, src, count, ti, FlecsRttCopy); } static void flecs_rtt_struct_move( void *ptr, void *src, int32_t count, const ecs_type_info_t *ti) { flecs_rtt_struct_action(ptr, src, count, ti, FlecsRttMove); } static int flecs_rtt_struct_cmp( const void *ptr, const void *src, const ecs_type_info_t *ti) { return flecs_rtt_struct_action( ECS_CONST_CAST(void*, ptr), src, 1, ti, FlecsRttCompare); } static bool flecs_rtt_struct_equals( const void *ptr, const void *src, const ecs_type_info_t *ti) { return !flecs_rtt_struct_action( ECS_CONST_CAST(void*, ptr), src, 1, ti, FlecsRttEquals); } static void flecs_rtt_free_lifecycle_nop( void *ctx) { (void)ctx; } static void flecs_rtt_free_lifecycle_struct_ctx( void *ctx) { if (!ctx) { return; } ecs_rtt_struct_ctx_t *data = ctx; ecs_vec_fini_t(NULL, &data->members, ecs_rtt_call_data_t); ecs_vec_fini_t(NULL, &data->dtors, ecs_rtt_call_data_t); ecs_os_free(ctx); } static ecs_rtt_struct_ctx_t* flecs_rtt_struct_members( ecs_world_t *world, const ecs_type_info_t *ti, const ecs_member_t *members, int32_t member_count, ecs_type_hooks_t *hooks) { ecs_rtt_struct_ctx_t *result = ecs_os_malloc_t(ecs_rtt_struct_ctx_t); ecs_vec_init_t(NULL, &result->members, ecs_rtt_call_data_t, member_count); ecs_vec_init_t(NULL, &result->dtors, ecs_rtt_call_data_t, 0); ecs_type_hooks_t available = { .cmp = flecs_rtt_struct_cmp, .equals = flecs_rtt_struct_equals }; for (int32_t i = 0; i < member_count; i ++) { const ecs_member_t *member = &members[i]; const ecs_type_info_t *mt = ecs_get_type_info(world, member->type); if (!mt || mt == ti) { continue; } ecs_rtt_call_data_t *data = ecs_vec_append_t(NULL, &result->members, ecs_rtt_call_data_t); *data = (ecs_rtt_call_data_t){ mt, member->offset, member->count ? member->count : 1 }; if (mt->hooks.dtor) { *ecs_vec_append_t(NULL, &result->dtors, ecs_rtt_call_data_t) = *data; available.dtor = flecs_rtt_struct_dtor; } if (mt->hooks.ctor && mt->hooks.ctor != flecs_default_ctor) { available.ctor = flecs_rtt_struct_ctor; } if (mt->hooks.copy) available.copy = flecs_rtt_struct_copy; if (mt->hooks.move) available.move = flecs_rtt_struct_move; if (!mt->hooks.cmp) available.cmp = NULL; if (!mt->hooks.equals) available.equals = NULL; available.flags |= mt->hooks.flags & ECS_TYPE_HOOKS_ILLEGAL; } if (hooks) { ecs_flags32_t flags = available.flags; hooks->ctor = flags & ECS_TYPE_HOOK_CTOR_ILLEGAL ? NULL : available.ctor; hooks->dtor = flags & ECS_TYPE_HOOK_DTOR_ILLEGAL ? NULL : available.dtor; hooks->copy = flags & ECS_TYPE_HOOK_COPY_ILLEGAL ? NULL : available.copy; hooks->move = flags & ECS_TYPE_HOOK_MOVE_ILLEGAL ? NULL : available.move; hooks->cmp = flags & ECS_TYPE_HOOK_CMP_ILLEGAL ? NULL : available.cmp; hooks->equals = flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL ? NULL : available.equals; hooks->flags = flags; } return result; } static void flecs_rtt_struct_hooks( ecs_world_t *world, const ecs_type_info_t *ti, const ecs_member_t *members, int32_t member_count, ecs_type_hooks_t *hooks) { if (hooks->lifecycle_ctx_free) { hooks->lifecycle_ctx_free(hooks->lifecycle_ctx); } ecs_rtt_struct_ctx_t *ctx = flecs_rtt_struct_members( world, ti, members, member_count, hooks); if (hooks->ctor || hooks->dtor || hooks->move || hooks->copy || hooks->cmp || hooks->equals) { hooks->lifecycle_ctx = ctx; hooks->lifecycle_ctx_free = flecs_rtt_free_lifecycle_struct_ctx; } else { flecs_rtt_free_lifecycle_struct_ctx(ctx); hooks->lifecycle_ctx = NULL; hooks->lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; } } /* * * RTT vector support * */ /* Generic vector constructor. Makes sure the vector structure is initialized to * 0 elements */ static void flecs_rtt_vector_ctor( void *ptr, int32_t count, const ecs_type_info_t *type_info) { const ecs_type_info_t *element_ti = type_info->hooks.lifecycle_ctx; int i; for (i = 0; i < count; i++) { ecs_vec_t *vec = ECS_ELEM(ptr, type_info->size, i); ecs_vec_init(NULL, vec, element_ti->size, 0); } } static FLECS_ALWAYS_INLINE void flecs_rtt_vector_clear( void *ptr, int32_t count, const ecs_type_info_t *type_info, bool release) { const ecs_type_info_t *element_ti = type_info->hooks.lifecycle_ctx; int i; for (i = 0; i < count; i++) { ecs_vec_t *vec = ECS_ELEM(ptr, type_info->size, i); int32_t num_elements = ecs_vec_count(vec); if (element_ti->hooks.dtor && num_elements) { flecs_type_info_dtor( ecs_vec_first(vec), num_elements, element_ti); } if (release) { ecs_vec_fini(NULL, vec, element_ti->size); } else { ecs_vec_clear(vec); } } } static void flecs_rtt_vector_dtor( void *ptr, int32_t count, const ecs_type_info_t *ti) { flecs_rtt_vector_clear(ptr, count, ti, true); } /* Generic vector move hook. */ static void flecs_rtt_vector_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { flecs_rtt_vector_dtor(dst_ptr, count, type_info); int i; for (i = 0; i < count; i++) { ecs_vec_t *src_vec = ECS_ELEM(src_ptr, type_info->size, i); ecs_vec_t *dst_vec = ECS_ELEM(dst_ptr, type_info->size, i); *dst_vec = *src_vec; src_vec->array = NULL; src_vec->count = 0; } } /* Generic vector copy hook. It makes a deep copy of vector contents */ static void flecs_rtt_vector_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { const ecs_type_info_t *ti = type_info->hooks.lifecycle_ctx; flecs_rtt_vector_clear(dst_ptr, count, type_info, false); for (int32_t i = 0; i < count; i ++) { const ecs_vec_t *src = ECS_ELEM(src_ptr, type_info->size, i); ecs_vec_t *dst = ECS_ELEM(dst_ptr, type_info->size, i); ecs_vec_init_if(dst, ti->size); int32_t src_count = ecs_vec_count(src); ecs_vec_set_count(NULL, dst, ti->size, src_count); if (!src_count) { continue; } if (ti->hooks.ctor) { flecs_type_info_ctor(ecs_vec_first(dst), src_count, ti); } else { flecs_default_ctor(ecs_vec_first(dst), src_count, ti); } flecs_type_info_copy(ecs_vec_first(dst), ecs_vec_first(src), src_count, ti); } } /* Generic vector compare hook. */ static int flecs_rtt_vector_cmp( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *type_info) { if(a_ptr == b_ptr) { return 0; } const ecs_vec_t *vec_a = a_ptr; const ecs_vec_t *vec_b = b_ptr; ecs_size_t count_a = ecs_vec_count(vec_a); ecs_size_t count_b = ecs_vec_count(vec_b); { int c = count_a - count_b; if(c != 0) { return c; } } const ecs_type_info_t *element_ti = type_info->hooks.lifecycle_ctx; ecs_size_t element_size = element_ti->size; const void *a = ecs_vec_first(vec_a); const void *b = ecs_vec_first(vec_b); int i; for (i = 0; i < count_a; i++) { const void *a_element = ECS_ELEM(a, element_size, i); const void *b_element = ECS_ELEM(b, element_size, i); int c = flecs_type_info_cmp(a_element, b_element, element_ti); if(c != 0) { return c; } } return 0; } /* Generic vector equals hook. */ static bool flecs_rtt_vector_equals( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *type_info) { if(a_ptr == b_ptr) { return true; } const ecs_vec_t *vec_a = a_ptr; const ecs_vec_t *vec_b = b_ptr; ecs_size_t count_a = ecs_vec_count(vec_a); ecs_size_t count_b = ecs_vec_count(vec_b); if (count_a != count_b) { return false; } const ecs_type_info_t *element_ti = type_info->hooks.lifecycle_ctx; ecs_size_t element_size = element_ti->size; const void *a = ecs_vec_first(vec_a); const void *b = ecs_vec_first(vec_b); int i; for (i = 0; i < count_a; i++) { const void *a_element = ECS_ELEM(a, element_size, i); const void *b_element = ECS_ELEM(b, element_size, i); bool eq = flecs_type_info_equals( a_element, b_element, element_ti); if(!eq) { return false; } } return true; } static void* flecs_rtt_map_value_ptr( const ecs_type_info_t *value_ti, ecs_map_val_t *val) { if (value_ti->size > ECS_SIZEOF(ecs_map_val_t)) { return (void*)(uintptr_t)val[0]; } else { return val; } } static void flecs_rtt_map_dtor( void *ptr, int32_t count, const ecs_type_info_t *type_info) { const ecs_type_info_t *element_ti = type_info->hooks.lifecycle_ctx; int i; for (i = 0; i < count; i++) { ecs_map_t *map = ECS_ELEM(ptr, type_info->size, i); flecs_meta_map_clear(map, element_ti); ecs_map_fini(map); } } static void flecs_rtt_map_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { flecs_rtt_map_dtor(dst_ptr, count, type_info); int i; for (i = 0; i < count; i++) { ecs_map_t *src_map = ECS_ELEM(src_ptr, type_info->size, i); ecs_map_t *dst_map = ECS_ELEM(dst_ptr, type_info->size, i); *dst_map = *src_map; ecs_os_zeromem(src_map); } } static void flecs_rtt_map_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { const ecs_type_info_t *value_ti = type_info->hooks.lifecycle_ctx; bool is_alloc = value_ti->size > ECS_SIZEOF(ecs_map_val_t); int i; for (i = 0; i < count; i++) { const ecs_map_t *src_map = ECS_ELEM(src_ptr, type_info->size, i); ecs_map_t *dst_map = ECS_ELEM(dst_ptr, type_info->size, i); flecs_meta_map_clear(dst_map, value_ti); ecs_map_init_if(dst_map, NULL); if (!ecs_map_is_init(src_map)) { continue; } ecs_map_iter_t it = ecs_map_iter(src_map); while (ecs_map_next(&it)) { ecs_map_val_t *val = ecs_map_ensure(dst_map, ecs_map_key(&it)); void *dst_value; const void *src_value; if (is_alloc) { val[0] = (ecs_map_val_t)(uintptr_t) ecs_os_calloc(value_ti->size); dst_value = (void*)(uintptr_t)val[0]; src_value = ecs_map_ptr(&it); } else { dst_value = val; src_value = &it.res[1]; } if (value_ti->hooks.ctor) { value_ti->hooks.ctor(dst_value, 1, value_ti); } if (value_ti->hooks.copy) { value_ti->hooks.copy(dst_value, src_value, 1, value_ti); } else { flecs_rtt_default_copy(dst_value, src_value, 1, value_ti); } } } } static int flecs_rtt_map_cmp( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *type_info) { if (a_ptr == b_ptr) { return 0; } const ecs_map_t *a = a_ptr, *b = b_ptr; int32_t count_diff = ecs_map_count(a) - ecs_map_count(b); if (count_diff) { return count_diff; } bool different_keys = false; ecs_map_key_t first_key = UINT64_MAX; ecs_map_iter_t it = ecs_map_iter(a); while (ecs_map_next(&it)) { ecs_map_key_t key = ecs_map_key(&it); if (key <= first_key && !ecs_map_get(b, key)) { first_key = key; different_keys = true; } } if (different_keys) { it = ecs_map_iter(b); while (ecs_map_next(&it)) { ecs_map_key_t key = ecs_map_key(&it); if (key < first_key && !ecs_map_get(a, key)) { return 1; } } return -1; } const ecs_type_info_t *ti = type_info->hooks.lifecycle_ctx; int result = 0; it = ecs_map_iter(a); while (ecs_map_next(&it)) { ecs_map_key_t key = ecs_map_key(&it); if (key <= first_key) { int cmp = flecs_type_info_cmp( flecs_rtt_map_value_ptr(ti, &it.res[1]), flecs_rtt_map_value_ptr(ti, ecs_map_get(b, key)), ti); if (cmp) { first_key = key; result = cmp; } } } return result; } static bool flecs_rtt_map_equals( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *type_info) { if (a_ptr == b_ptr) { return true; } const ecs_map_t *map_a = a_ptr; const ecs_map_t *map_b = b_ptr; int32_t count_a = ecs_map_count(map_a); int32_t count_b = ecs_map_count(map_b); if (count_a != count_b) { return false; } if (!count_a) { return true; } const ecs_type_info_t *value_ti = type_info->hooks.lifecycle_ctx; ecs_map_iter_t it = ecs_map_iter(map_a); while (ecs_map_next(&it)) { ecs_map_val_t *val_a = &it.res[1]; ecs_map_val_t *val_b = ecs_map_get(map_b, ecs_map_key(&it)); if (!val_b) { return false; } bool eq = flecs_type_info_equals( flecs_rtt_map_value_ptr(value_ti, val_a), flecs_rtt_map_value_ptr(value_ti, val_b), value_ti); if (!eq) { return false; } } return true; } static void flecs_rtt_collection_hooks( ecs_world_t *world, ecs_entity_t component, ecs_entity_t element, bool map, ecs_type_hooks_t *hooks) { const ecs_type_info_t *ti = ecs_is_alive(world, element) ? ecs_get_type_info(world, element) : NULL; if (!ti) { ecs_err("collection '%s' has invalid element type", ecs_get_name(world, component)); return; } if (hooks->lifecycle_ctx_free) { hooks->lifecycle_ctx_free(hooks->lifecycle_ctx); } hooks->lifecycle_ctx = ECS_CONST_CAST(ecs_type_info_t*, ti); hooks->lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; hooks->ctor = map ? flecs_default_ctor : flecs_rtt_vector_ctor; hooks->dtor = map ? flecs_rtt_map_dtor : flecs_rtt_vector_dtor; hooks->move = map ? flecs_rtt_map_move : flecs_rtt_vector_move; hooks->copy = map ? flecs_rtt_map_copy : flecs_rtt_vector_copy; hooks->cmp = ti->hooks.cmp && !(ti->hooks.flags & ECS_TYPE_HOOK_CMP_ILLEGAL) ? (map ? flecs_rtt_map_cmp : flecs_rtt_vector_cmp) : NULL; hooks->equals = ti->hooks.equals && !(ti->hooks.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ? (map ? flecs_rtt_map_equals : flecs_rtt_vector_equals) : NULL; hooks->flags |= ti->hooks.flags & (ECS_TYPE_HOOK_CMP_ILLEGAL | ECS_TYPE_HOOK_EQUALS_ILLEGAL); hooks->flags &= ECS_TYPE_HOOKS_ILLEGAL; } static int flecs_rtt_ensure_hook( ecs_world_t *world, ecs_entity_t type, bool equals); static bool flecs_rtt_has_hook( const ecs_type_info_t *ti, bool equals) { if (equals) { return ti->hooks.equals != NULL && !(ti->hooks.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL); } else { return ti->hooks.cmp != NULL && !(ti->hooks.flags & ECS_TYPE_HOOK_CMP_ILLEGAL); } } static void flecs_rtt_set_hook( ecs_type_hooks_t *hooks, bool equals, ecs_cmp_t cmp, ecs_equals_t eq) { if (equals) { hooks->equals = eq; hooks->flags &= ECS_TYPE_HOOKS_ILLEGAL; hooks->flags &= ~ECS_TYPE_HOOK_EQUALS_ILLEGAL; } else { hooks->cmp = cmp; hooks->flags &= ECS_TYPE_HOOKS_ILLEGAL; hooks->flags &= ~ECS_TYPE_HOOK_CMP_ILLEGAL; } } static int flecs_rtt_gen_struct_hook( ecs_world_t *world, ecs_entity_t component, bool equals, const ecs_member_t *members, int32_t member_count) { int i; for (i = 0; i < member_count; i++) { const ecs_member_t *m = &members[i]; if (m->type == component) { continue; } if (flecs_rtt_ensure_hook(world, m->type, equals)) { return -1; } } const ecs_type_info_t *ti = ecs_get_type_info(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_hooks_t hooks = ti->hooks; if (hooks.lifecycle_ctx_free != flecs_rtt_free_lifecycle_struct_ctx) { if (hooks.lifecycle_ctx) { return -1; } hooks.lifecycle_ctx = flecs_rtt_struct_members( world, ti, members, member_count, NULL); hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_struct_ctx; } flecs_rtt_set_hook(&hooks, equals, flecs_rtt_struct_cmp, flecs_rtt_struct_equals); ecs_set_hooks_id(world, component, &hooks); return 0; } static int flecs_rtt_gen_collection_hook( ecs_world_t *world, ecs_entity_t component, ecs_entity_t element, bool equals, ecs_cmp_t cmp, ecs_equals_t eq) { if (!element || element == component || flecs_rtt_ensure_hook(world, element, equals)) { return -1; } const ecs_type_info_t *element_ti = ecs_get_type_info(world, element); const ecs_type_info_t *ti = ecs_get_type_info(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_hooks_t hooks = ti->hooks; if (hooks.lifecycle_ctx && hooks.lifecycle_ctx != element_ti) { return -1; } hooks.lifecycle_ctx = ECS_CONST_CAST(ecs_type_info_t*, element_ti); hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; flecs_rtt_set_hook(&hooks, equals, cmp, eq); ecs_set_hooks_id(world, component, &hooks); return 0; } static int flecs_rtt_ensure_hook( ecs_world_t *world, ecs_entity_t type, bool equals) { const ecs_type_info_t *ti = ecs_get_type_info(world, type); if (!ti) { return -1; } if (flecs_rtt_has_hook(ti, equals)) { return 0; } const EcsType *type_ptr = ecs_get(world, type, EcsType); if (!type_ptr) { return -1; } switch (type_ptr->kind) { case EcsStructType: { const EcsStruct *st = ecs_get(world, type, EcsStruct); return st ? flecs_rtt_gen_struct_hook(world, type, equals, st->members.array, st->members.count) : -1; } case EcsArrayType: { const EcsArray *arr = ecs_get(world, type, EcsArray); if (!arr || arr->type == type) { return -1; } ecs_member_t member = { .type = arr->type, .count = arr->count }; return flecs_rtt_gen_struct_hook(world, type, equals, &member, 1); } case EcsVectorType: { const EcsVector *v = ecs_get(world, type, EcsVector); return flecs_rtt_gen_collection_hook(world, type, v ? v->type : 0, equals, flecs_rtt_vector_cmp, flecs_rtt_vector_equals); } case EcsMapType: { const EcsMap *m = ecs_get(world, type, EcsMap); return flecs_rtt_gen_collection_hook(world, type, m ? m->type : 0, equals, flecs_rtt_map_cmp, flecs_rtt_map_equals); } case EcsPrimitiveType: case EcsBitmaskType: case EcsEnumType: case EcsOpaqueType: case EcsValueType: break; } return -1; } int ecs_set_rtt_compare( ecs_world_t *world, ecs_entity_t type) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); return flecs_rtt_ensure_hook(world, type, false); } int ecs_set_rtt_equals( ecs_world_t *world, ecs_entity_t type) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); return flecs_rtt_ensure_hook(world, type, true); } void flecs_rtt_init_default_hooks( ecs_iter_t *it) { ecs_world_t *world = it->world; EcsType *type_field = ecs_field(it, EcsType, 0); int i; for (i = 0; i < it->count; i++) { EcsType *type = &type_field[i]; if (type->existing) { continue; /* non-rtt type. Ignore. */ } /* If a component is defined from reflection data, configure appropriate * default hooks. * - For trivial types, at least set a default constructor so memory is * zero-initialized. * - For struct types, configure a hook that in turn calls hooks of * member types, if those member types have hooks defined themselves. * - For array types, configure a hook that in turn calls hooks for the * underlying type, for each element in the array. * - For vector types, configure hooks to manage the vector structure * itself, move the vector and deep-copy vector elements. */ ecs_entity_t component = it->entities[i]; /* Skip configuring hooks for ids already in use */ const ecs_world_t* w = ecs_get_world(world); if(ecs_id_in_use(w, component) || ecs_id_in_use(w, ecs_pair(component, EcsWildcard))) { continue; } const ecs_type_info_t *ti = ecs_get_type_info(world, component); ecs_assert(ti,ECS_INTERNAL_ERROR,NULL); ecs_type_hooks_t hooks = ti->hooks; if (type->kind == EcsStructType) { const EcsStruct *st = ecs_get(world, component, EcsStruct); flecs_rtt_struct_hooks( world, ti, st->members.array, st->members.count, &hooks); } else if (type->kind == EcsArrayType) { const EcsArray *arr = ecs_get(world, component, EcsArray); ecs_member_t member = { .type = arr->type, .count = arr->count }; flecs_rtt_struct_hooks(world, ti, &member, 1, &hooks); } else if (type->kind == EcsVectorType) { const EcsVector *vector = ecs_get(world, component, EcsVector); flecs_rtt_collection_hooks(world, component, vector->type, false, &hooks); } else if (type->kind == EcsMapType) { const EcsMap *map = ecs_get(world, component, EcsMap); flecs_rtt_collection_hooks(world, component, map->type, true, &hooks); } /* Make sure there is at least a default constructor. This ensures that * a new component value does not contain uninitialized memory, which * could cause serializers to crash when for example inspecting string * fields. */ if(!hooks.ctor && !(hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL)) { hooks.ctor = flecs_default_ctor; } hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id( world, component, &hooks); } } #endif #ifdef FLECS_META #if defined(FLECS_JSON) || defined(FLECS_SCRIPT) #ifdef FLECS_JSON #endif static int flecs_meta_write_type_ops( const ecs_world_t *world, ecs_meta_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format); static int flecs_meta_write_forward( const ecs_world_t *world, ecs_entity_t type, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format); #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif static void flecs_meta_flt_to_str( char *buf, int32_t size, double value, bool is_f32) { if (value == 0) { ecs_os_strcpy(buf, "0"); return; } int32_t precision = is_f32 ? 6 : 15; int32_t max_precision = is_f32 ? 9 : 17; for (; precision < max_precision; precision ++) { ecs_os_snprintf(buf, size, "%.*g", precision, value); if (is_f32) { if ((float)strtod(buf, NULL) == (float)value) { return; } } else { if (strtod(buf, NULL) == value) { return; } } } ecs_os_snprintf(buf, size, "%.*g", max_precision, value); } #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #pragma GCC diagnostic pop #endif static void flecs_meta_write_member( ecs_strbuf_t *str, const char *name, flecs_meta_format_t format) { #ifdef FLECS_JSON if (format == EcsMetaJson) { flecs_json_member(str, name); return; } #endif ecs_strbuf_list_next(str); if (format != EcsMetaExprPositional && format != EcsMetaExprPrecisePositional) { ecs_strbuf_append(str, "%s: ", name); } } static int flecs_meta_write_scope( const ecs_world_t *world, ecs_meta_op_t *ops, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { if (flecs_meta_write_type_ops( world, ops + 1, ops->op_count - 2, base, str, format)) { return -1; } return 0; } static int flecs_meta_write_array( const ecs_world_t *world, ecs_meta_op_t *ops, const void *array, int32_t count, ecs_strbuf_t *str, flecs_meta_format_t format) { ecs_strbuf_list_push(str, "[", ", "); int32_t i; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(str); void *ptr = ECS_ELEM(array, ops->elem_size, i); if (flecs_meta_write_scope(world, ops, ptr, str, format)) { goto error; } } ecs_strbuf_list_pop(str, "]"); return 0; error: return -1; } static int flecs_meta_write_map( const ecs_world_t *world, ecs_meta_op_t *ops, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { const ecs_map_t *map = base; bool json = format == EcsMetaJson; ecs_strbuf_list_push(str, json ? "{" : "[", ", "); ecs_map_iter_t it = ecs_map_iter(map); while (ecs_map_next(&it)) { ecs_strbuf_t key_buf = ECS_STRBUF_INIT; if (!json) { ecs_strbuf_list_next(str); } if (flecs_meta_ser_map_key( world, ops, ecs_map_key(&it), json ? &key_buf : str)) { ecs_strbuf_reset(&key_buf); return -1; } if (json) { char *key = ecs_strbuf_get(&key_buf); flecs_meta_write_member(str, key, format); ecs_os_free(key); } else { ecs_strbuf_appendlit(str, ": "); } const void *ptr; if (ops->elem_size > ECS_SIZEOF(ecs_map_val_t)) { ptr = ecs_map_ptr(&it); } else { ptr = &it.res[1]; } if (flecs_meta_write_scope(world, ops, ptr, str, format)) { goto error; } } ecs_strbuf_list_pop(str, json ? "}" : "]"); return 0; error: return -1; } static int flecs_meta_write_value( const ecs_world_t *world, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { const ecs_value_t *value = base; if (!value->type || !value->ptr) { ecs_assert(false, ECS_INVALID_PARAMETER, "cannot serialize value without value"); ecs_err("cannot serialize value without value"); return -1; } bool json = format == EcsMetaJson; ecs_strbuf_t type_buf = ECS_STRBUF_INIT; ecs_strbuf_list_push(str, "{", ", "); if (!json) { ecs_strbuf_list_next(str); } if (flecs_meta_value_type_str(world, value->type, json ? &type_buf : str)) { ecs_strbuf_reset(&type_buf); return -1; } if (json) { char *name = ecs_strbuf_get(&type_buf); flecs_meta_write_member(str, name, format); ecs_os_free(name); } else { ecs_strbuf_appendlit(str, ": "); } if (flecs_meta_write_forward( world, value->type, value->ptr, str, format)) { return -1; } ecs_strbuf_list_pop(str, "}"); return 0; } static int flecs_meta_write_struct( const ecs_world_t *world, ecs_meta_op_t *ops, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { ecs_strbuf_list_push(str, "{", ", "); if (flecs_meta_write_scope(world, ops, base, str, format)) { return -1; } ecs_strbuf_list_pop(str, "}"); return 0; } static int flecs_meta_write_forward( const ecs_world_t *world, ecs_entity_t type, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { const EcsTypeSerializer *ts = ecs_get(world, type, EcsTypeSerializer); if (!ts) { ecs_err("missing type serializer for '%s'", flecs_errstr(ecs_get_path(world, type))); return -1; } return flecs_meta_write_type_ops(world, ecs_vec_first(&ts->ops), ecs_vec_count(&ts->ops), base, str, format); } typedef struct flecs_meta_writer_t { ecs_strbuf_t *str; bool is_collection; flecs_meta_format_t format; } flecs_meta_writer_t; static int flecs_meta_write_opaque_value( const ecs_serializer_t *ser, ecs_entity_t type, const void *value) { flecs_meta_writer_t *writer = ser->ctx; if (writer->is_collection) { ecs_strbuf_list_next(writer->str); } return flecs_meta_write_forward( ser->world, type, value, writer->str, writer->format); } static int flecs_meta_write_opaque_member( const ecs_serializer_t *ser, const char *name) { flecs_meta_writer_t *writer = ser->ctx; flecs_meta_write_member(writer->str, name, writer->format); return 0; } static int flecs_meta_write_opaque( const ecs_world_t *world, ecs_meta_op_t *op, const void *base, ecs_strbuf_t *str, ecs_meta_op_kind_t kind, flecs_meta_format_t format) { bool is_struct = kind == EcsOpOpaqueStruct; bool is_collection = kind == EcsOpOpaqueVector || kind == EcsOpOpaqueArray; if (is_struct) { ecs_strbuf_list_push(str, "{", ", "); } else if (is_collection) { ecs_strbuf_list_push(str, "[", ", "); } flecs_meta_writer_t writer = { .str = str, .is_collection = is_collection, .format = format == EcsMetaJson ? EcsMetaJson : format >= EcsMetaExprPositional ? EcsMetaExprPositional : EcsMetaExpr }; ecs_serializer_t ser = { .world = world, .value = flecs_meta_write_opaque_value, .member = is_struct ? flecs_meta_write_opaque_member : NULL, .ctx = &writer }; ecs_assert(op->is.opaque != NULL, ECS_INTERNAL_ERROR, NULL); if (op->is.opaque(&ser, base)) { return -1; } if (is_struct) { ecs_strbuf_list_pop(str, "}"); } else if (is_collection) { ecs_strbuf_list_pop(str, "]"); } return 0; } static int flecs_meta_write_primitive( const ecs_world_t *world, const ecs_meta_op_t *op, const void *ptr, ecs_strbuf_t *str, flecs_meta_format_t format) { ecs_meta_op_kind_t kind = op->kind; #ifdef FLECS_JSON if (format == EcsMetaJson) { switch (kind) { case EcsOpEntity: case EcsOpId: { ecs_id_t id = *(const ecs_id_t*)ptr; if (!id) { ecs_strbuf_appendlit(str, "\"#0\""); } else if (kind == EcsOpEntity) { flecs_json_path(str, world, id); } else { flecs_json_id(str, world, id); } return 0; } case EcsOpString: flecs_json_string_escape_ctrl(str, *(char* const*)ptr); return 0; case EcsOpF32: case EcsOpF64: ecs_strbuf_appendflt(str, kind == EcsOpF32 ? (double)*(const float*)ptr : *(const double*)ptr, '"'); return 0; default: break; } } #endif if ((format == EcsMetaExprPrecise || format == EcsMetaExprPrecisePositional) && (kind == EcsOpF32 || kind == EcsOpF64)) { char buf[32]; bool is_f32 = kind == EcsOpF32; flecs_meta_flt_to_str(buf, 32, is_f32 ? (double)*(const float*)ptr : *(const double*)ptr, is_f32); ecs_strbuf_appendstr(str, buf); return 0; } if (kind <= EcsOpPrimitive || kind > EcsMetaTypeOpKindLast) { ecs_throw(ECS_INVALID_PARAMETER, "invalid serializer operation"); } bool quote = format == EcsMetaJson && ((kind == EcsOpI64 && *(const int64_t*)ptr >= 2147483648) || (kind == EcsOpU64 && *(const uint64_t*)ptr >= 2147483648)); if (quote) { ecs_strbuf_appendch(str, '"'); } if (flecs_meta_ser_primitive(world, kind - EcsOpPrimitive, ptr, str, format != EcsMetaStr)) { return -1; } if (quote) { ecs_strbuf_appendch(str, '"'); } return 0; error: return -1; } static int flecs_meta_write_type_ops( const ecs_world_t *world, ecs_meta_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { for (int i = 0; i < op_count; i ++) { ecs_meta_op_t *op = &ops[i]; const void *ptr = ECS_OFFSET(base, op->offset); if (op->name) { flecs_meta_write_member(str, op->name, format); } switch(op->kind) { case EcsOpPushStruct: { if (flecs_meta_write_struct(world, op, ptr, str, format)) { goto error; } break; } case EcsOpPushArray: { if (flecs_meta_write_array(world, op, ptr, ecs_meta_op_get_elem_count(op, ptr), str, format)) { goto error; } break; } case EcsOpPushVector: { ecs_vec_t *vec = ECS_OFFSET(base, op->offset); if (flecs_meta_write_array(world, op, vec->array, vec->count, str, format)) { goto error; } break; } case EcsOpPushMap: { if (flecs_meta_write_map(world, op, ptr, str, format)) { goto error; } break; } case EcsOpPushValue: { if (flecs_meta_write_value(world, ptr, str, format)) { goto error; } break; } case EcsOpForward: { if (flecs_meta_write_forward(world, op->type, ptr, str, format)) { goto error; } break; } case EcsOpOpaqueStruct: case EcsOpOpaqueArray: case EcsOpOpaqueVector: case EcsOpOpaqueValue: if (flecs_meta_write_opaque(world, op, ptr, str, op->kind, format)) { goto error; } break; case EcsOpEnum: if (format == EcsMetaJson) { ecs_strbuf_appendch(str, '"'); } if (flecs_meta_ser_enum(world, op->type, op->underlying_kind, op->is.constants, ptr, str)) { return -1; } if (format == EcsMetaJson) { ecs_strbuf_appendch(str, '"'); } break; case EcsOpBitmask: if (flecs_meta_ser_bitmask(world, op->type, op->is.constants, ptr, format == EcsMetaJson ? "\"" : "", str)) { return -1; } break; default: if (flecs_meta_write_primitive(world, op, ptr, str, format)) { return -1; } break; } i += op->op_count - 1; } return 0; error: return -1; } int flecs_meta_serialize( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str, flecs_meta_format_t format) { ecs_meta_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_op_t); int32_t count = ecs_vec_count(v_ops); return flecs_meta_write_type_ops( world, ops, count, base, str, format); } #endif #endif #ifdef FLECS_META static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops, bool root); static int flecs_meta_serialize_scope( ecs_world_t *world, ecs_meta_op_t op, ecs_entity_t element, int32_t array_count, ecs_vec_t *ops) { int32_t first = ecs_vec_count(ops); *ecs_vec_append_t(NULL, ops, ecs_meta_op_t) = op; if (op.kind == EcsOpPushStruct) { const EcsStruct *st = ecs_get(world, op.type, EcsStruct); ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); ecs_member_t *members = ecs_vec_first(&st->members); int32_t count = ecs_vec_count(&st->members); ecs_hashmap_t *index = flecs_name_index_new(&world->allocator); ecs_vec_get_t(ops, ecs_meta_op_t, first)->is.members = index; for (int32_t i = 0; i < count; i ++) { ecs_member_t *m = &members[i]; int32_t cur = ecs_vec_count(ops); int ret; if (m->count >= 1) { ecs_meta_op_t array = { .kind = EcsOpPushArray, .type = m->type, .offset = m->offset, .elem_size = flecs_type_size(world, m->type) }; ret = flecs_meta_serialize_scope( world, array, m->type, m->count, ops); } else { ret = flecs_meta_serialize_type( world, m->type, m->offset, ops, false); } if (ret) { continue; } ecs_meta_op_t *member_op = ecs_vec_get_t(ops, ecs_meta_op_t, cur); if (!member_op->type) { member_op->type = m->type; } member_op->op_count = flecs_ito(int16_t, ecs_vec_count(ops) - cur); member_op->name = ecs_os_strdup(m->name); member_op->member_index = flecs_ito(int16_t, i); flecs_name_index_ensure(index, flecs_ito(uint64_t, cur - first - 1), member_op->name, 0, 0); } } else if (element && flecs_meta_serialize_type( world, element, 0, ops, false)) { return -1; } ecs_meta_op_t *pop = ecs_vec_append_t(NULL, ops, ecs_meta_op_t); *pop = (ecs_meta_op_t){ .kind = EcsOpPop, .op_count = 1, .type = op.type, .type_info = op.kind == EcsOpPushStruct ? NULL : op.type_info, .elem_size = array_count }; ecs_vec_get_t(ops, ecs_meta_op_t, first)->op_count = flecs_ito(int16_t, ecs_vec_count(ops) - first); return 0; } static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops, bool root) { const EcsType *t = ecs_get(world, type, EcsType); if (!t) { ecs_err("missing reflection data for type '%s'", flecs_errstr(ecs_get_path(world, type))); return -1; } ecs_meta_op_t op = { .kind = EcsOpForward, .op_count = 1, .type = type, .type_info = ecs_get_type_info(world, type), .offset = offset }; ecs_assert(op.type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t element = 0; int32_t array_count = 0; switch (t->kind) { case EcsPrimitiveType: { const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); if (!p) { ecs_err("entity '%s' is not a primitive type", flecs_errstr(ecs_get_path(world, type))); return -1; } op.kind = flecs_meta_primitive_to_op_kind(p->kind); break; } case EcsEnumType: case EcsBitmaskType: { const EcsConstants *c = ecs_get(world, type, EcsConstants); ecs_assert(c != NULL, ECS_INVALID_PARAMETER, NULL); op.is.constants = c->constants; ecs_assert(op.is.constants != NULL, ECS_INTERNAL_ERROR, NULL); op.kind = EcsOpBitmask; if (t->kind == EcsEnumType) { const EcsEnum *e = ecs_get(world, type, EcsEnum); ecs_assert(e != NULL, ECS_INVALID_PARAMETER, NULL); const EcsPrimitive *p = ecs_get( world, e->underlying_type, EcsPrimitive); ecs_assert(p != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((p->kind >= EcsU8 && p->kind <= EcsI64) || p->kind == EcsUPtr || p->kind == EcsIPtr, ECS_INTERNAL_ERROR, "invalid underlying enum type"); op.kind = EcsOpEnum; op.underlying_kind = flecs_meta_primitive_to_op_kind(p->kind); } break; } case EcsStructType: op.kind = EcsOpPushStruct; break; case EcsArrayType: if (root) { const EcsArray *a = ecs_get(world, type, EcsArray); if (!a) { return -1; } op.kind = EcsOpPushArray; op.type = element = a->type; op.type_info = NULL; array_count = a->count; } break; case EcsVectorType: if (root) { const EcsVector *v = ecs_get(world, type, EcsVector); if (!v) { return -1; } op.kind = EcsOpPushVector; element = v->type; } break; case EcsMapType: if (root) { const EcsMap *m = ecs_get(world, type, EcsMap); if (!m) { return -1; } const EcsTypeSerializer *key = ecs_get( world, m->key_type, EcsTypeSerializer); if (!key || !ecs_vec_count(&key->ops)) { return -1; } const ecs_meta_op_t *key_op = ecs_vec_first(&key->ops); op.kind = EcsOpPushMap; op.underlying_kind = key_op->kind; op.is.constants = key_op->is.constants; element = m->type; } break; case EcsValueType: if (root) { op.kind = EcsOpPushValue; } break; case EcsOpaqueType: { const EcsOpaque *o = ecs_get(world, type, EcsOpaque); ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); const EcsType *as = ecs_get(world, o->as_type, EcsType); ecs_assert(as != NULL, ECS_INTERNAL_ERROR, "missing reflection for Opaque::as_type"); op.kind = EcsOpOpaqueValue; if (as->kind == EcsArrayType) { op.kind = EcsOpOpaqueArray; } else if (as->kind == EcsVectorType) { op.kind = EcsOpOpaqueVector; } else if (as->kind == EcsStructType) { op.kind = EcsOpOpaqueStruct; } op.is.opaque = o->serialize; break; } } if (op.kind == EcsOpPushStruct || op.kind == EcsOpPushArray || op.kind == EcsOpPushVector || op.kind == EcsOpPushMap || op.kind == EcsOpPushValue) { if (element) { op.elem_size = flecs_type_size(world, element); } return flecs_meta_serialize_scope(world, op, element, array_count, ops); } *ecs_vec_append_t(NULL, ops, ecs_meta_op_t) = op; return 0; } void flecs_meta_type_serializer_init( ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t type = it->entities[i]; ecs_vec_t ops; ecs_vec_init_t(NULL, &ops, ecs_meta_op_t, 0); const EcsType *type_ptr = ecs_get(world, type, EcsType); if (!type_ptr) { char *path = ecs_get_path(world, type); ecs_err("missing reflection data for type '%s'", path); ecs_os_free(path); continue; } int ret = flecs_meta_serialize_type(world, type, 0, &ops, true); if (ret != 0) { ecs_vec_fini_t(NULL, &ops, ecs_meta_op_t); continue; } EcsTypeSerializer *ptr = ecs_ensure(world, type, EcsTypeSerializer); if (ptr->ops.array) { flecs_type_serializer_dtor(ptr); } ptr->kind = type_ptr->kind; ptr->ops = ops; ecs_modified(world, type, EcsTypeSerializer); } } char* ecs_meta_serializer_to_str( ecs_world_t *world, ecs_entity_t type) { const EcsTypeSerializer *ptr = ecs_get( world, type, EcsTypeSerializer); if (!ptr) { return NULL; } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_meta_op_t *ops = ecs_vec_first(&ptr->ops); int32_t i, count = ecs_vec_count(&ptr->ops); for (i = 0; i < count; i ++) { ecs_meta_op_t *op = &ops[i]; const char *kind_str = flecs_meta_op_kind_str(op->kind); ecs_strbuf_append(&buf, "%s", kind_str); ecs_strbuf_append(&buf, "%*s", 10 - ecs_os_strlen(kind_str), " "); if (op->type) { char *type_str = ecs_id_str(world, op->type); ecs_strbuf_append(&buf, "%s ", type_str); ecs_os_free(type_str); } if (op->name) { ecs_strbuf_append(&buf, "%s: ", op->name); } ecs_strbuf_appendstr(&buf, "{ "); ecs_strbuf_append(&buf, "offset: %d, size: %d ", op->offset, op->type_info ? op->type_info->size : 0); ecs_strbuf_append(&buf, ", op_count = %d ", op->op_count); ecs_strbuf_appendstr(&buf, "}"); ecs_strbuf_appendlit(&buf, "\n"); } return ecs_strbuf_get(&buf); } #endif #ifdef FLECS_META static const ecs_type_info_t* flecs_value_type_info( const ecs_world_t *world, ecs_entity_t type) { const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "entity '%s' is not a type", flecs_errstr(ecs_get_path(world, type))); return ti; } static void* flecs_value_alloc( const ecs_type_info_t *ti) { void *result = ecs_os_calloc(ti->size); flecs_type_info_ctor(result, 1, ti); return result; } int flecs_value_ensure_type( const ecs_world_t *world, ecs_value_t *v, ecs_entity_t type) { ecs_assert(v != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); if (v->type == type && v->ptr) { return 0; } const ecs_type_info_t *ti = flecs_value_type_info(world, type); if (!ti) { return -1; } ecs_value_fini(world, v); v->type = type; v->ptr = flecs_value_alloc(ti); return 0; } void ecs_value_set( const ecs_world_t *world, ecs_value_t *v, ecs_entity_t type, const void *ptr) { ecs_assert(v != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(type != 0, ECS_INVALID_PARAMETER, "value must be assigned a valid type"); ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, "value must be assigned a valid value"); const ecs_type_info_t *ti = flecs_value_type_info(world, type); if (!ti) { return; } if (v->type != type || !v->ptr) { ecs_value_fini(world, v); v->type = type; v->ptr = flecs_value_alloc(ti); } flecs_type_info_copy(v->ptr, ptr, 1, ti); } ecs_value_t ecs_value_new( const ecs_world_t *world, ecs_entity_t type) { ecs_assert(type != 0, ECS_INVALID_PARAMETER, "value must be assigned a valid type"); ecs_value_t result = {0}; const ecs_type_info_t *ti = flecs_value_type_info(world, type); if (!ti) { return result; } result.type = type; result.ptr = flecs_value_alloc(ti); return result; } ecs_value_t ecs_value_init( const ecs_world_t *world, ecs_entity_t type, const void *ptr) { ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, "value must be assigned a valid value"); ecs_value_t result = ecs_value_new(world, type); if (!result.ptr) { return result; } const ecs_type_info_t *ti = flecs_value_type_info(world, type); flecs_type_info_copy(result.ptr, ptr, 1, ti); return result; } void ecs_value_fini( const ecs_world_t *world, ecs_value_t *v) { ecs_assert(v != NULL, ECS_INVALID_PARAMETER, NULL); if (!v->ptr) { ecs_assert(v->type == 0, ECS_INVALID_PARAMETER, "value has a type but no value"); v->type = 0; return; } ecs_assert(v->type != 0, ECS_INVALID_PARAMETER, "value has a value but no type"); const ecs_type_info_t *ti = flecs_value_type_info(world, v->type); if (ti) { flecs_type_info_dtor(v->ptr, 1, ti); } ecs_os_free(v->ptr); v->type = 0; v->ptr = NULL; } void ecs_value_copy( const ecs_world_t *world, ecs_value_t *dst, const ecs_value_t *src) { ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(src != NULL, ECS_INVALID_PARAMETER, NULL); if (!src->ptr) { ecs_value_fini(world, dst); return; } ecs_value_set(world, dst, src->type, src->ptr); } void ecs_value_copy_ctor( const ecs_world_t *world, ecs_value_t *dst, const ecs_value_t *src) { ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(src != NULL, ECS_INVALID_PARAMETER, NULL); dst->type = 0; dst->ptr = NULL; ecs_value_copy(world, dst, src); } void ecs_value_move( const ecs_world_t *world, ecs_value_t *dst, ecs_value_t *src) { ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(src != NULL, ECS_INVALID_PARAMETER, NULL); ecs_value_fini(world, dst); *dst = *src; src->type = 0; src->ptr = NULL; } void ecs_value_move_ctor( const ecs_world_t *world, ecs_value_t *dst, ecs_value_t *src) { (void)world; ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(src != NULL, ECS_INVALID_PARAMETER, NULL); *dst = *src; src->type = 0; src->ptr = NULL; } int ecs_value_compare( const ecs_world_t *world, const ecs_value_t *a, const ecs_value_t *b) { ecs_assert(a != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (a->type != b->type) { return (a->type > b->type) ? 1 : -1; } if (a->ptr == b->ptr) { return 0; } if (!a->ptr || !b->ptr) { return a->ptr ? 1 : -1; } const ecs_type_info_t *ti = flecs_value_type_info(world, a->type); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_type_info_cmp(a->ptr, b->ptr, ti); } bool ecs_value_equals( const ecs_world_t *world, const ecs_value_t *a, const ecs_value_t *b) { ecs_assert(a != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (a->type != b->type) { return false; } if (a->ptr == b->ptr) { return true; } if (!a->ptr || !b->ptr) { return false; } const ecs_type_info_t *ti = flecs_value_type_info(world, a->type); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_type_info_equals(a->ptr, b->ptr, ti); } static ecs_world_t* flecs_value_world( const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.lifecycle_ctx; ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); return world; } static void flecs_value_dtor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = flecs_value_world(ti); ecs_value_t *v = ptr; int32_t i; for (i = 0; i < count; i ++) { ecs_value_fini(world, &v[i]); } } static void flecs_value_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = flecs_value_world(ti); ecs_value_t *dst = dst_ptr; ecs_value_t *src = src_ptr; int32_t i; for (i = 0; i < count; i ++) { ecs_value_move(world, &dst[i], &src[i]); } } static void flecs_value_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = flecs_value_world(ti); ecs_value_t *dst = dst_ptr; const ecs_value_t *src = src_ptr; int32_t i; for (i = 0; i < count; i ++) { ecs_value_copy(world, &dst[i], &src[i]); } } static int flecs_value_cmp( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { ecs_world_t *world = flecs_value_world(ti); const ecs_value_t *a = a_ptr; const ecs_value_t *b = b_ptr; if (a->type != b->type) { return (a->type > b->type) ? 1 : -1; } if (a->ptr == b->ptr) { return 0; } if (!a->ptr || !b->ptr) { return a->ptr ? 1 : -1; } const ecs_type_info_t *value_ti = ecs_get_type_info(world, a->type); ecs_assert(value_ti != NULL, ECS_INTERNAL_ERROR, NULL); if (value_ti->hooks.cmp && !(value_ti->hooks.flags & ECS_TYPE_HOOK_CMP_ILLEGAL)) { return value_ti->hooks.cmp(a->ptr, b->ptr, value_ti); } return ecs_os_memcmp(a->ptr, b->ptr, value_ti->size); } static bool flecs_value_equals( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { return flecs_value_cmp(a_ptr, b_ptr, ti) == 0; } static void flecs_value_fini_lifecycle_ctx( void *ctx) { (void)ctx; } void flecs_meta_value_init( ecs_world_t *world) { ecs_component_init(world, &(ecs_component_desc_t){ .entity = ecs_entity(world, { .id = ecs_id(ecs_value_t), .name = "value", .symbol = "ecs_value_t" }), .type.size = ECS_SIZEOF(ecs_value_t), .type.alignment = ECS_ALIGNOF(ecs_value_t) }); ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, ecs_id(ecs_value_t)); hooks.flags = 0; hooks.ctor = flecs_default_ctor; hooks.dtor = flecs_value_dtor; hooks.move = flecs_value_move; hooks.copy = flecs_value_copy; hooks.cmp = flecs_value_cmp; hooks.equals = flecs_value_equals; hooks.lifecycle_ctx = world; hooks.lifecycle_ctx_free = flecs_value_fini_lifecycle_ctx; hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, ecs_id(ecs_value_t), &hooks); flecs_init_type_t(world, ecs_id(ecs_value_t), EcsValueType, ecs_value_t); } #endif #ifdef FLECS_OS_API_IMPL #ifdef ECS_TARGET_WINDOWS 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 #ifdef FLECS_PARSER #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 #define EcsTokKeyword\ case EcsTokKeywordWith: case EcsTokKeywordUsing: case EcsTokKeywordProp:\ case EcsTokKeywordMut: case EcsTokKeywordConst: case EcsTokKeywordMatch:\ case EcsTokKeywordNew: case EcsTokKeywordExport: case EcsTokKeywordIf:\ case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn:\ case EcsTokKeywordTemplate: case EcsTokKeywordModule:\ case EcsTokKeywordInclude: case EcsTokKeywordFn: case EcsTokKeywordAwait:\ case EcsTokKeywordTry: case EcsTokKeywordCatch: case EcsTokKeywordAsync:\ case EcsTokKeywordContinue: case EcsTokKeywordWhile: case EcsTokKeywordScript /* Definitions for parser functions */ #define ParserBegin\ ecs_tokenizer_t _tokenizer;\ ecs_os_zeromem(&_tokenizer);\ _tokenizer.tokens = _tokenizer.stack;\ 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() \ int32_t _token_frame = tokenizer->count;\ ecs_token_t *_token_frame_base = tokenizer->tokens;\ tokenizer->tokens = &tokenizer->stack[_token_frame]; #define TokenFramePop() \ tokenizer->count = _token_frame;\ tokenizer->tokens = _token_frame_base; /* 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(...)\ {\ ecs_expr_node_t *EXPR = NULL;\ bool expr_significant_newline = parser->significant_newline;\ parser->significant_newline = false;\ parser->expr_pos = pos;\ if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ goto error;\ }\ parser->significant_newline = expr_significant_newline;\ parser->expr_end = pos;\ __VA_ARGS__\ } /* Parse initializer */ #define Initializer(until, ...)\ {\ ecs_expr_node_t *INITIALIZER = NULL;\ bool init_significant_newline = parser->significant_newline;\ if (until != '\n') {\ parser->significant_newline = false;\ }\ parser->expr_pos = (until == '\n') ? pos : pos - 1;\ if (!(pos = flecs_script_parse_initializer(\ parser, pos, until, &INITIALIZER))) \ {\ goto error;\ }\ parser->significant_newline = init_significant_newline;\ parser->expr_end = (until == '\n')\ ? flecs_parser_stmt_end(parser, pos)\ : pos;\ if (INITIALIZER && !INITIALIZER->end) {\ INITIALIZER->end = parser->expr_end;\ }\ __VA_ARGS__\ } /* Parse next token */ #define Parse(...)\ {\ ecs_assert(tokenizer->count < ECS_PARSER_MAX_TOKENS,\ ECS_INTERNAL_ERROR, NULL);\ ecs_token_t *t = &tokenizer->stack[tokenizer->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__\ }\ break;\ ) #define Parse_2(tok1, tok2, ...)\ Parse_1(tok1, Parse_1(tok2, __VA_ARGS__)) #define Parse_3(tok1, tok2, tok3, ...)\ Parse_2(tok1, tok2, Parse_1(tok3, __VA_ARGS__)) #define Consume() \ 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;\ const char *lookahead_pos = pos;\ int32_t lookahead_count = tokenizer->count;\ ecs_token_t lookahead_token;\ const char *old_lh_token_cur = parser->token_cur;\ if ((lookahead = flecs_token(parser, pos, &lookahead_token, true))) {\ ecs_assert(tokenizer->count < ECS_PARSER_MAX_TOKENS,\ ECS_INTERNAL_ERROR, NULL);\ tokenizer->stack[tokenizer->count ++] = lookahead_token;\ switch(lookahead_token.kind) {\ __VA_ARGS__\ default:\ break;\ }\ if (pos <= lookahead_pos) {\ tokenizer->count = lookahead_count;\ }\ flecs_token_restore(parser, old_lh_token_cur);\ } else {\ goto error;\ }\ } /* Lookahead N consecutive tokens */ #define LookAhead_1(tok, ...)\ LookAhead(\ case tok: {\ __VA_ARGS__\ }\ ) #define LookAheadAny_2(cases1, cases2, ...)\ LookAhead(cases1 {\ const char *lookahead_start = pos;\ pos = lookahead;\ LookAhead(cases2 {\ pos = lookahead_start;\ __VA_ARGS__\ })\ if (pos == lookahead) {\ pos = lookahead_start;\ }\ }) #define LookAhead_2(tok1, tok2, ...)\ LookAheadAny_2(case tok1:, case tok2:, __VA_ARGS__) #define LookAheadSkipNewlines_1(tok, ...)\ {\ const char *stmt_end = pos;\ pos = flecs_parser_skip_newlines(parser, pos);\ LookAhead_1(tok, __VA_ARGS__)\ pos = stmt_end;\ } /* 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->count;\ do {\ tokenizer->count = token_stack_count;\ __VA_ARGS__\ } while (true); #define EndOfRule return pos #endif 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 '`': 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((unsigned char)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((unsigned char)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((unsigned char)*ptr)) { ptr ++; } return ptr; } #endif #ifdef FLECS_PARSER int64_t flecs_parser_errpos( const ecs_parser_t *parser, const char *pos) { if (parser->fixed_pos) { return parser->fixed_pos - parser->code; } return pos - parser->code; } static bool flecs_is_comment( const char *pos) { return pos[0] == '/' && (pos[1] == '/' || pos[1] == '*'); } static const struct { const char *spelling; const char *description; } flecs_tokens[EcsTokLast] = { [EcsTokUnknown] = {"unknown token", "unknown token "}, [EcsTokColon] = {":", ""}, [EcsTokScopeOpen] = {"{", ""}, [EcsTokScopeClose] = {"}", ""}, [EcsTokParenOpen] = {"(", ""}, [EcsTokParenClose] = {")", ""}, [EcsTokBracketOpen] = {"[", ""}, [EcsTokBracketClose] = {"]", ""}, [EcsTokAnnotation] = {"@", ""}, [EcsTokComma] = {",", ""}, [EcsTokSemiColon] = {";", ""}, [EcsTokAssign] = {"=", ""}, [EcsTokAdd] = {"+", ""}, [EcsTokSub] = {"-", ""}, [EcsTokMul] = {"*", ""}, [EcsTokDiv] = {"/", ""}, [EcsTokMod] = {"%", ""}, [EcsTokBitwiseOr] = {"|", ""}, [EcsTokBitwiseAnd] = {"&", ""}, [EcsTokNot] = {"!", ""}, [EcsTokOptional] = {"?", ""}, [EcsTokEq] = {"==", ""}, [EcsTokNeq] = {"!=", ""}, [EcsTokGt] = {">", ""}, [EcsTokGtEq] = {">=", ""}, [EcsTokLt] = {"<", ""}, [EcsTokLtEq] = {"<=", ""}, [EcsTokAnd] = {"&&", ""}, [EcsTokOr] = {"||", ""}, [EcsTokMatch] = {"~=", ""}, [EcsTokRange] = {"..", ""}, [EcsTokShiftLeft] = {"<<", ""}, [EcsTokShiftRight] = {">>", ""}, [EcsTokAddAssign] = {"+=", ""}, [EcsTokMulAssign] = {"*=", ""}, [EcsTokHasBracketOpen] = {"?[", ""}, [EcsTokKeywordWith] = {"with", "keyword "}, [EcsTokKeywordUsing] = {"using", "keyword "}, [EcsTokKeywordProp] = {"prop", "keyword "}, [EcsTokKeywordMut] = {"mut", "keyword "}, [EcsTokKeywordConst] = {"const", "keyword "}, [EcsTokKeywordMatch] = {"match", "keyword "}, [EcsTokKeywordNew] = {"new", "keyword "}, [EcsTokKeywordExport] = {"export", "keyword "}, [EcsTokKeywordIf] = {"if", "keyword "}, [EcsTokKeywordElse] = {"else", "keyword "}, [EcsTokKeywordFor] = {"for", "keyword "}, [EcsTokKeywordIn] = {"in", "keyword "}, [EcsTokKeywordTemplate] = {"template", "keyword "}, [EcsTokKeywordModule] = {"module", "keyword "}, [EcsTokKeywordInclude] = {"include", "keyword "}, [EcsTokKeywordFn] = {"fn", "keyword "}, [EcsTokKeywordAwait] = {"await", "keyword "}, [EcsTokKeywordTry] = {"try", "keyword "}, [EcsTokKeywordCatch] = {"catch", "keyword "}, [EcsTokKeywordContinue] = {"continue", "keyword "}, [EcsTokKeywordAsync] = {"async", "keyword "}, [EcsTokKeywordWhile] = {"while", "keyword "}, [EcsTokKeywordScript] = {"script", "keyword "}, [EcsTokArrow] = {"->", ""}, [EcsTokIdentifier] = {"identifier", "identifier "}, [EcsTokString] = {"string", "string "}, [EcsTokChar] = {"char", "char "}, [EcsTokNumber] = {"number", "number "}, [EcsTokNewline] = {"newline", "newline"}, [EcsTokMember] = {".", ""}, [EcsTokEnd] = {"end of script", "end of script"}, }; static const ecs_token_kind_t flecs_token_pairs[128][2] = { ['+'] = {EcsTokAddAssign}, ['*'] = {EcsTokMulAssign}, ['-'] = {EcsTokArrow}, ['?'] = {EcsTokHasBracketOpen}, ['.'] = {EcsTokRange}, ['='] = {EcsTokEq}, ['!'] = {EcsTokNeq}, ['<'] = {EcsTokLtEq, EcsTokShiftLeft}, ['>'] = {EcsTokGtEq, EcsTokShiftRight}, ['&'] = {EcsTokAnd}, ['|'] = {EcsTokOr}, ['~'] = {EcsTokMatch} }; static char* flecs_tokenizer_write( ecs_parser_t *parser, char *dst, char ch) { (void)parser; ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!parser->token_end || dst < parser->token_end, ECS_INVALID_OPERATION, "out of parser token storage"); dst[0] = ch; return dst + 1; } const char* flecs_token_kind_str( ecs_token_kind_t kind) { if ((unsigned)kind < EcsTokLast && flecs_tokens[kind].description) { return flecs_tokens[kind].description; } return ""; } const char* flecs_token_str( ecs_token_kind_t kind) { if ((unsigned)kind < EcsTokLast && flecs_tokens[kind].spelling) { return flecs_tokens[kind].spelling; } return ""; } const char* flecs_scan_whitespace( ecs_parser_t *parser, const char *pos) { ecs_assert(pos != NULL, ECS_INTERNAL_ERROR, NULL); while (isspace((unsigned char)pos[0]) && (pos[0] != '\n' || !parser->significant_newline)) { pos ++; } return pos; } static const char* flecs_scan_line_comment( const char *pos) { ecs_assert(pos[0] == '/' && pos[1] == '/', ECS_INTERNAL_ERROR, NULL); for (pos = pos + 2; pos[0] && pos[0] != '\n'; pos ++) { } return pos; } const char* flecs_scan_whitespace_and_comment( ecs_parser_t *parser, const char *pos, bool *newline) { for (;;) { const char *start = pos; pos = flecs_scan_whitespace(parser, pos); if (newline && memchr(start, '\n', flecs_ito(size_t, pos - start))) { *newline = true; } if (!flecs_is_comment(pos)) { return pos; } if (pos[1] == '/') { pos = flecs_scan_line_comment(pos); continue; } const char *end = strstr(pos + 2, "*/"); if (!end) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, pos), "missing */ for multiline comment"); return NULL; } pos = end + 2; } } const char* flecs_parser_skip_newlines( ecs_parser_t *parser, const char *pos) { bool significant_newline = parser->significant_newline; parser->significant_newline = false; pos = flecs_scan_whitespace_and_comment(parser, pos, NULL); parser->significant_newline = significant_newline; return pos; } static bool flecs_script_is_identifier( char c) { return isalpha((unsigned char)c) || (c == '_') || (c == '$') || (c == '#'); } const char* flecs_tokenizer_identifier( ecs_parser_t *parser, const char *pos, ecs_token_t *out) { bool is_var = pos[0] == '$'; char *outpos = NULL; const char *start = pos; if (parser) { outpos = parser->token_cur; if (parser->merge_variable_members) { is_var = false; } } if (out) { out->kind = EcsTokIdentifier; out->value = outpos; } const char *name = parser ? parser->name : NULL; const char *code = parser ? parser->code : pos; if (!flecs_script_is_identifier(pos[0])) { ecs_parser_error(name, code, pos - code, "invalid start of identifier '%c'", pos[0]); return NULL; } do { char c = pos[0]; if ((c == '.') && (pos[1] == '.')) { break; } bool is_ident = flecs_script_is_identifier(c) || isdigit((unsigned char)c); if (!is_var) { is_ident = is_ident || (c == '.'); } /* Retain \. for name lookup operation */ if (!is_ident && c == '\\' && pos[1] == '.') { is_ident = true; } /* Retain .* for using wildcard expressions */ if (!is_ident && c == '*') { if (pos != start && pos[-1] == '.') { is_ident = true; } } if (!is_ident) { if (c == '\\') { pos ++; if (!pos[0]) { goto done; } } else if (c == '<') { int32_t indent = 0; do { c = *pos; if (c == '<') { indent ++; } else if (c == '>') { indent --; } else if (!c) { ecs_parser_error(name, code, pos - code, "< without > in identifier"); return NULL; } if (outpos) { outpos = flecs_tokenizer_write(parser, outpos, c); } pos ++; if (!indent) { break; } } while (true); goto done; } else if (c == '>') { ecs_parser_error(name, code, pos - code, "> without < in identifier"); return NULL; } else { goto done; } } if (outpos) { outpos = flecs_tokenizer_write(parser, outpos, *pos); } pos ++; } while (true); done: if (outpos) { parser->token_cur = flecs_tokenizer_write(parser, outpos, '\0'); } return pos; } static char* flecs_tokenizer_copy( ecs_parser_t *parser, const char *start, const char *end) { ecs_size_t length = flecs_ito(ecs_size_t, end - start); ecs_assert(!parser->token_end || parser->token_cur + length < parser->token_end, ECS_INVALID_OPERATION, "out of parser token storage"); char *result = parser->token_cur; ecs_os_memcpy(result, start, length); result[length] = '\0'; parser->token_cur += length + 1; return result; } static const char* flecs_script_digits( const char *pos, int base) { while (base == 16 ? isxdigit((unsigned char)*pos) : base == 2 ? (*pos == '0' || *pos == '1') : isdigit((unsigned char)*pos)) { pos ++; } return pos; } static const char* flecs_script_number( ecs_parser_t *parser, const char *pos, ecs_token_t *out) { const char *start = pos; int base = 10; if (pos[0] == '0') { if (pos[1] == 'x' || pos[1] == 'X') { base = 16; } else if (pos[1] == 'b' || pos[1] == 'B') { base = 2; } pos += base != 10 ? 2 : 0; } const char *digits = pos; pos = flecs_script_digits(pos, base); bool has_digits = pos != digits; if (base != 10 && *pos == '.' && isxdigit((unsigned char)pos[1])) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, start), "fractional part not allowed in hexadecimal or binary literal"); return NULL; } if (*pos == '.' && isdigit((unsigned char)pos[1])) { pos = flecs_script_digits(pos + 1, 10); } if (base == 10 && (*pos == 'e' || *pos == 'E')) { digits = pos + 1; digits += *digits == '+' || *digits == '-'; if (isdigit((unsigned char)*digits)) { pos = flecs_script_digits(digits, 10); } } if ((!has_digits && base != 10) || flecs_script_is_identifier(*pos) || isdigit((unsigned char)*pos)) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, start), "invalid number literal"); return NULL; } out->kind = EcsTokNumber; out->value = flecs_tokenizer_copy(parser, start, pos); return pos; } static const char* flecs_script_skip_string( ecs_parser_t *parser, const char *pos, char delim) { char ch; const char *start = pos; for (; (ch = pos[0]) && pos[0] != delim; pos ++) { if (ch == '\\' && pos[1]) { pos ++; } } if (!pos[0]) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, start), "unterminated string"); return NULL; } return pos; } static const char* flecs_script_string( ecs_parser_t *parser, const char *pos, ecs_token_t *out) { const char *end = flecs_script_skip_string(parser, pos + 1, pos[0]); if (!end) { return NULL; } ecs_assert(end[0] == pos[0], ECS_INTERNAL_ERROR, NULL); bool is_char = pos[0] == '\''; int32_t len = flecs_ito(int32_t, end - (pos + 1)); if (is_char && len == 0) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, pos), "Empty char"); return NULL; } else if (is_char && (len > 1) && !((len == 2) && (pos[1] == '\\'))) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, pos), "only one char allowed"); return NULL; } out->kind = is_char ? EcsTokChar : EcsTokString; out->value = flecs_tokenizer_copy(parser, pos + 1, end); return end + 1; } static void flecs_tokenizer_set_pos( ecs_parser_t *parser, const char *pos) { parser->pos = parser->fixed_pos ? parser->fixed_pos : pos; if (!parser->stmt_pos) { parser->stmt_pos = parser->pos; } } const char* flecs_tokenizer_line( ecs_parser_t *parser, const char *pos, ecs_token_t *out, bool stmt_end) { const char *start = pos = flecs_scan_whitespace(parser, pos); flecs_tokenizer_set_pos(parser, pos); for (; pos[0] && pos[0] != '\n'; pos ++) { if (stmt_end && (pos[0] == ';' || flecs_is_comment(pos))) { break; } } const char *end = pos; while (end != start && isspace((unsigned char)end[-1])) { end --; } out->kind = EcsTokString; out->value = flecs_tokenizer_copy(parser, start, end); parser->token_keep = parser->token_cur; return pos; } static const char* flecs_token_scan( ecs_parser_t *parser, const char *pos, ecs_token_t *out, bool is_lookahead) { if (!pos) { return NULL; } pos = flecs_scan_whitespace_and_comment(parser, pos, NULL); if (!pos) { return NULL; } if (!is_lookahead) { flecs_tokenizer_set_pos(parser, pos); } out->kind = EcsTokUnknown; out->value = NULL; if (pos[0] == '\0') { out->kind = EcsTokEnd; return pos; } else if (pos[0] == '\n') { out->kind = EcsTokNewline; do { pos = flecs_scan_whitespace_and_comment(parser, pos + 1, NULL); } while (pos && pos[0] == '\n'); return pos; } else if (isdigit((unsigned char)pos[0])) { return flecs_script_number(parser, pos, out); } unsigned char ch = (unsigned char)pos[0]; if (ch < 128) { for (int32_t i = 0; i < 2; i ++) { ecs_token_kind_t kind = flecs_token_pairs[ch][i]; if (kind && pos[1] == flecs_tokens[kind].spelling[1]) { out->kind = kind; out->value = flecs_tokens[kind].spelling; return pos + 2; } } if (flecs_tokens[ch].spelling && flecs_tokens[ch].spelling[0] == ch) { out->kind = (ecs_token_kind_t)ch; out->value = flecs_tokens[ch].spelling; return pos + 1; } } if (pos[0] == '\'' || pos[0] == '"' || pos[0] == '`') { return flecs_script_string(parser, pos, out); } else if (flecs_script_is_identifier(pos[0])) { pos = flecs_tokenizer_identifier(parser, pos, out); for (ecs_token_kind_t k = EcsTokKeywordWith; pos && out->value && k < EcsTokLast; k ++) { if (!ecs_os_strcmp(out->value, flecs_tokens[k].spelling)) { parser->token_cur = ECS_CONST_CAST(char*, out->value); out->kind = k; out->value = flecs_tokens[k].spelling; } } return pos; } ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, pos), "unknown token '%c'", pos[0]); return NULL; } const char* flecs_token( ecs_parser_t *parser, const char *pos, ecs_token_t *out, bool is_lookahead) { const char *result = flecs_token_scan(parser, pos, out, is_lookahead); if (result && !is_lookahead) { parser->token_keep = parser->token_cur; } if (result && out->kind != EcsTokNewline && out->kind != EcsTokEnd) { parser->token_ends[parser->token_ends_i & 7] = result; parser->token_ends_i ++; } return result; } void flecs_token_restore( ecs_parser_t *parser, const char *token_cur) { if (token_cur > parser->token_keep) { parser->token_cur = ECS_CONST_CAST(char*, token_cur); } else { parser->token_cur = parser->token_keep; } } const char* flecs_parser_stmt_end( const ecs_parser_t *parser, const char *pos) { const char *end = NULL; int32_t i; for (i = 0; i < 8; i ++) { const char *cur = parser->token_ends[i]; if (cur && cur <= pos && (!end || cur > end)) { end = cur; } } if (!end) { end = pos; } return end; } #endif #ifdef FLECS_PIPELINE /* Free state-owned resources without touching the query. Used when the * query is owned by something else (e.g. ecs_query_update has replaced it). */ static void flecs_pipeline_state_free( ecs_world_t *world, ecs_pipeline_state_t *p) { if (p) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); ecs_vec_fini_t(a, &p->systems, ecs_system_t*); ecs_os_free(p); } } static void flecs_pipeline_free( ecs_pipeline_state_t *p) { if (p) { ecs_world_t *world = p->query->world; if (p->query->entity) { ecs_delete(ecs_get_stage(world, 0), p->query->entity); } else { ecs_query_fini(p->query); } flecs_pipeline_state_free(world, p); } } static ECS_MOVE(EcsPipeline, dst, src, { flecs_pipeline_free(dst->state); dst->state = src->state; src->state = NULL; }) static ECS_DTOR(EcsPipeline, ptr, { flecs_pipeline_free(ptr->state); }) typedef enum ecs_write_kind_t { WriteStateNone = 0, WriteStateToStage, } ecs_write_kind_t; typedef struct ecs_write_state_t { bool write_barrier; ecs_map_t ids; ecs_map_t wildcard_ids; } ecs_write_state_t; static ecs_write_kind_t flecs_pipeline_get_write_state( ecs_write_state_t *write_state, ecs_id_t id) { ecs_write_kind_t result = WriteStateNone; if (write_state->write_barrier) { /* Any component could have been written */ return WriteStateToStage; } if (id == EcsWildcard) { /* Using a wildcard for id indicates read barrier. Return true if any * components could have been staged */ if (ecs_map_count(&write_state->ids) || ecs_map_count(&write_state->wildcard_ids)) { return WriteStateToStage; } } if (!ecs_id_is_wildcard(id)) { if (ecs_map_get(&write_state->ids, id)) { result = WriteStateToStage; } } else { ecs_map_iter_t it = ecs_map_iter(&write_state->ids); while (ecs_map_next(&it)) { if (ecs_id_match(ecs_map_key(&it), id)) { return WriteStateToStage; } } } if (ecs_map_count(&write_state->wildcard_ids)) { ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); while (ecs_map_next(&it)) { if (ecs_id_match(id, ecs_map_key(&it))) { return WriteStateToStage; } } } return result; } static void flecs_pipeline_set_write_state( ecs_write_state_t *write_state, ecs_id_t id) { if (id == EcsWildcard) { /* If writing to wildcard, flag all components as written */ write_state->write_barrier = true; return; } ecs_map_t *ids; if (ecs_id_is_wildcard(id)) { ids = &write_state->wildcard_ids; } else { ids = &write_state->ids; } ecs_map_ensure(ids, id)[0] = true; } static void flecs_pipeline_reset_write_state( ecs_write_state_t *write_state) { ecs_map_clear(&write_state->ids); ecs_map_clear(&write_state->wildcard_ids); write_state->write_barrier = false; } static bool flecs_pipeline_check_term( ecs_world_t *world, ecs_term_t *term, bool is_active, ecs_write_state_t *write_state) { (void)world; ecs_term_ref_t *src = &term->src; if (term->inout == EcsInOutFilter) { return false; } ecs_id_t id = term->id; int16_t oper = term->oper; int16_t inout = term->inout; bool from_any = ecs_term_match_0(term); bool from_this = ecs_term_match_this(term); bool is_shared = !from_any && (!from_this || !(src->id & EcsSelf)); bool is_fixed_src = !from_any && !from_this; ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); if (from_this && ws >= WriteStateToStage) { /* A staged write could have happened for an id that's matched on the * main storage. Even if the id isn't read, still insert a merge so that * a write to the main storage after the staged write doesn't get * overwritten. */ return true; } if (inout == EcsInOutDefault) { if (from_any) { /* If no inout kind is specified for terms without a source, this is * not interpreted as a read/write annotation but just a (component) * id that's passed to a system. */ return false; } else if (is_shared) { inout = EcsIn; } else { /* Default for owned terms is InOut */ inout = EcsInOut; } } if (oper == EcsNot && inout == EcsOut) { /* If a Not term is combined with Out, it signals that the system * intends to add a component that the entity doesn't yet have */ from_any = true; } if (from_any || is_fixed_src) { switch(inout) { case EcsOut: case EcsInOut: if (is_active) { /* Only flag component as written if system is active */ flecs_pipeline_set_write_state(write_state, id); } break; case EcsInOutDefault: case EcsInOutNone: case EcsInOutFilter: case EcsIn: break; } switch(inout) { case EcsIn: case EcsInOut: if (ws == WriteStateToStage) { /* If a system does a get/ensure, the component is fetched from * the main store so it must be merged first */ return true; } /* fall through */ case EcsInOutDefault: case EcsInOutNone: case EcsInOutFilter: case EcsOut: break; } } return false; } static bool flecs_pipeline_check_terms( ecs_world_t *world, ecs_query_t *query, bool is_active, ecs_write_state_t *ws) { bool needs_merge = false; ecs_term_t *terms = query->terms; int32_t t, term_count = query->term_count; /* Check This terms first. This way if a term indicating writing to a stage * was added before a This term, it won't cause merging. */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } /* Now check non-$this terms */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (!ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } return needs_merge; } static EcsPoly* flecs_pipeline_term_system( ecs_iter_t *it) { int32_t index = ecs_table_get_column_index( it->world, it->table, flecs_poly_id(EcsSystem)); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); return poly; } static bool flecs_pipeline_build( ecs_world_t *world, ecs_pipeline_state_t *pq) { ecs_iter_t it = ecs_query_iter(world, pq->query); int32_t new_match_count = ecs_query_match_count(pq->query); if (pq->match_count == new_match_count) { /* No need to rebuild the pipeline */ ecs_iter_fini(&it); return false; } world->info.pipeline_build_count_total ++; pq->rebuild_count ++; ecs_allocator_t *a = &world->allocator; ecs_pipeline_op_t *op = NULL; ecs_write_state_t ws = {0}; ecs_map_init(&ws.ids, a); ecs_map_init(&ws.wildcard_ids, a); ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); ecs_vec_reset_t(a, &pq->systems, ecs_system_t*); bool multi_threaded = false; bool immediate = false; bool first = true; /* Iterate systems in pipeline, add ops for running / merging */ while (ecs_query_next(&it)) { EcsPoly *poly = flecs_pipeline_term_system(&it); bool is_active = ecs_table_get_type_index( world, it.table, EcsEmpty) == -1; int32_t i; for (i = 0; i < it.count; i ++) { flecs_poly_assert(poly[i].poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly[i].poly; ecs_query_t *q = sys->query; bool needs_merge = false; needs_merge = flecs_pipeline_check_terms( world, q, is_active, &ws); if (is_active) { if (first) { multi_threaded = sys->multi_threaded; immediate = sys->immediate; first = false; } if (sys->multi_threaded != multi_threaded) { needs_merge = true; multi_threaded = sys->multi_threaded; } if (sys->immediate != immediate) { needs_merge = true; immediate = sys->immediate; } } if (immediate) { needs_merge = true; } if (needs_merge) { /* After merge all components will be merged, so reset state */ flecs_pipeline_reset_write_state(&ws); /* An inactive system can insert a merge if one of its * components got written, which could make the system * active. If this is the only system in the pipeline operation, * it results in an empty operation when we get here. If that's * the case, reuse the empty operation for the next op. */ if (op && op->count) { op = NULL; } /* Re-evaluate columns to set write flags if system is active. * If system is inactive, it can't write anything and so it * should not insert unnecessary merges. */ needs_merge = false; if (is_active) { needs_merge = flecs_pipeline_check_terms( world, q, true, &ws); } /* The component states were just reset, so if we conclude that * another merge is needed something is wrong. */ ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); } if (!op) { op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); op->offset = ecs_vec_count(&pq->systems); op->count = 0; op->multi_threaded = false; op->immediate = false; op->time_spent = 0; op->commands_enqueued = 0; } /* Don't increase count for inactive systems, as they are ignored by * the query used to run the pipeline. */ if (is_active) { ecs_vec_append_t(a, &pq->systems, ecs_system_t*)[0] = sys; if (!op->count) { op->multi_threaded = multi_threaded; op->immediate = immediate; } op->count ++; } } } if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { ecs_vec_remove_last(&pq->ops); } ecs_map_fini(&ws.ids); ecs_map_fini(&ws.wildcard_ids); op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); if (!op) { ecs_dbg("#[green]pipeline#[reset] is empty"); return true; } else { /* Add schedule to debug tracing */ ecs_dbg("#[bold]pipeline rebuild"); ecs_log_push_1(); ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", op->multi_threaded, !op->immediate); ecs_log_push_1(); int32_t i, count = ecs_vec_count(&pq->systems); int32_t op_index = 0, ran_since_merge = 0; ecs_system_t **systems = ecs_vec_first_t(&pq->systems, ecs_system_t*); for (i = 0; i < count; i ++) { ecs_system_t *sys = systems[i]; ecs_entity_t system = sys->query->entity; ecs_assert(system != 0, ECS_INTERNAL_ERROR, NULL); (void)system; #ifdef FLECS_LOG_1 char *path = ecs_get_path(world, system); const char *doc_name = NULL; #ifdef FLECS_DOC const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, EcsDocDescription, EcsName); if (doc_name_id) { doc_name = doc_name_id->value; } #endif if (doc_name) { ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); } else { ecs_dbg("#[green]system#[reset] %s", path); } ecs_os_free(path); #endif ecs_assert(op[op_index].offset + ran_since_merge == i, ECS_INTERNAL_ERROR, NULL); ran_since_merge ++; if (ran_since_merge == op[op_index].count) { ecs_dbg("#[magenta]merge#[reset]"); ecs_log_pop_1(); ran_since_merge = 0; op_index ++; if (op_index < ecs_vec_count(&pq->ops)) { ecs_dbg( "#[green]schedule#[reset]: " "threading: %d, staging: %d:", op[op_index].multi_threaded, !op[op_index].immediate); } ecs_log_push_1(); } if (sys->last_frame == (world->info.frame_count_total + 1)) { if (op_index < ecs_vec_count(&pq->ops)) { pq->cur_op = &op[op_index]; pq->cur_i = i; } else { pq->cur_op = NULL; pq->cur_i = 0; } } } ecs_log_pop_1(); ecs_log_pop_1(); } pq->match_count = new_match_count; if (ecs_vec_count(&pq->ops)) { ecs_assert((pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)), ECS_INTERNAL_ERROR, NULL); } return true; } static void flecs_pipeline_next_system( ecs_pipeline_state_t *pq) { if (!pq->cur_op) { return; } pq->cur_i ++; if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { pq->cur_op ++; if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { pq->cur_op = NULL; } } } static bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot update pipeline while world is in readonly mode"); /* If any entity mutations happened that could have affected query matching, * notify appropriate queries so caches are up to date. This includes the * pipeline query. */ if (start_of_frame) { ecs_run_aperiodic(world, 0); } ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); bool rebuilt = flecs_pipeline_build(world, pq); if (start_of_frame) { pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); pq->cur_i = 0; } else { flecs_pipeline_next_system(pq); } return rebuilt; } void ecs_run_pipeline( ecs_world_t *world, ecs_entity_t pipeline, ecs_ftime_t delta_time) { if (!pipeline) { pipeline = world->pipeline; } /* Create any worker task threads requested */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } EcsPipeline *p = ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); flecs_workers_progress(world, p->state, delta_time); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } } int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time) { ecs_pipeline_state_t* pq = world->pq; ecs_pipeline_op_t* op = pq->cur_op; int32_t i = pq->cur_i; ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); ecs_system_t **systems = ecs_vec_first_t(&pq->systems, ecs_system_t*); int32_t end = op->offset + op->count; ecs_stage_t *s = op->immediate ? NULL : stage; for (; i < end; i++) { ecs_system_t* sys = systems[i]; /* Keep track of the last frame for which the system has run, so we * know from where to resume the schedule in case the schedule * changes during a merge. */ if (stage_index == 0) { sys->last_frame = world->info.frame_count_total + 1; } flecs_run_system(world, s, sys->query->entity, sys, stage_index, stage_count, delta_time, NULL); ecs_os_linc(&world->info.systems_ran_total); } return i - 1; } void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); int32_t stage_index = ecs_stage_get_id(stage->thread_ctx); int32_t stage_count = ecs_get_stage_count(world); bool multi_threaded = world->worker_cond != 0; ecs_assert(!stage_index, ECS_INVALID_OPERATION, "cannot run pipeline on stage"); // Update the pipeline the workers will execute world->pq = pq; // Update the pipeline before waking the workers. flecs_pipeline_update(world, pq, true); // If there are no operations to execute in the pipeline bail early, // no need to wake the workers since they have nothing to do. while (pq->cur_op != NULL) { if (pq->cur_i == ecs_vec_count(&pq->systems)) { flecs_pipeline_update(world, pq, false); continue; } bool immediate = pq->cur_op->immediate; bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; pq->immediate = immediate; if (!immediate) { ecs_readonly_begin(world, multi_threaded); } else { flecs_commands_begin(world, stage); } ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); if (op_multi_threaded) { flecs_signal_workers(world); } ecs_time_t st = { 0 }; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&st); } const int32_t i = flecs_run_pipeline_ops( world, stage, stage_index, stage_count, delta_time); if (measure_time) { /* Don't include merge time in system time */ world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } if (op_multi_threaded) { flecs_wait_for_sync(world); } if (!immediate) { ecs_time_t mt = { 0 }; if (measure_time) { ecs_time_measure(&mt); } int32_t si; for (si = 0; si < stage_count; si ++) { ecs_stage_t *s = world->stages[si]; pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); } ecs_readonly_end(world); if (measure_time) { pq->cur_op->time_spent += ecs_time_measure(&mt); } } else { flecs_commands_end(world, stage); } /* Store the current state of the schedule after we synchronized the * threads, to avoid race conditions. */ pq->cur_i = i; flecs_pipeline_update(world, pq, false); } } static void flecs_run_startup_systems( ecs_world_t *world) { ecs_component_record_t *cr = flecs_components_get(world, ecs_dependson(EcsOnStart)); if (!cr || !flecs_table_cache_count(&cr->cache)) { /* Don't bother creating startup pipeline if no systems exist */ return; } ecs_dbg_2("#[bold]startup#[reset]"); ecs_log_push_2(); int32_t stage_count = world->stage_count; world->stage_count = 1; /* Prevents running startup systems on workers */ /* Creating a pipeline is relatively expensive, but this only happens * for the first frame. The startup pipeline is deleted afterwards, which * eliminates the overhead of keeping its query cache in sync. */ ecs_dbg_2("#[bold]create startup pipeline#[reset]"); ecs_log_push_2(); ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ .query = { .terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.id = EcsCascade, .trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.id = EcsSelf|EcsUp, .trav = EcsDependsOn }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsChildOf, .oper = EcsNot } }, .order_by_callback = flecs_entity_compare } }); ecs_log_pop_2(); /* Run & delete pipeline */ ecs_dbg_2("#[bold]run startup systems#[reset]"); ecs_log_push_2(); ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, "pipeline entity is missing flecs.pipeline.Pipeline component"); flecs_workers_progress(world, p->state, 0); ecs_log_pop_2(); ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); ecs_log_push_2(); ecs_delete(world, start_pip); ecs_log_pop_2(); world->stage_count = stage_count; ecs_log_pop_2(); error: return; } bool ecs_progress( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); /* If this is the first frame, run startup systems */ if (world->info.frame_count_total == 0) { flecs_run_startup_systems(world); } /* Create any worker task threads requested */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); ecs_log_push_3(); const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, "pipeline entity is missing flecs.pipeline.Pipeline component"); flecs_workers_progress(world, p->state, delta_time); ecs_log_pop_3(); ecs_frame_end(world); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return false; } void ecs_set_pipeline( ecs_world_t *world, ecs_entity_t pipeline) { flecs_poly_assert(world, ecs_world_t); ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, ECS_INVALID_PARAMETER, "not a pipeline"); world->pipeline = pipeline; error: return; } ecs_entity_t ecs_get_pipeline( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->pipeline; error: return 0; } static ecs_entity_t flecs_pipeline_init( ecs_world_t *world, ecs_entity_t entity, const ecs_pipeline_desc_t *desc, ecs_query_t* (*query_init)( ecs_world_t*, ecs_entity_t, const ecs_query_desc_t*)) { ecs_query_desc_t qd = desc->query; if (!qd.order_by_callback) { qd.order_by_callback = flecs_entity_compare; } qd.entity = entity; ecs_query_t *query = query_init(world, entity, &qd); if (!query) { return 0; } ecs_check(query->terms != NULL, ECS_INVALID_PARAMETER, "pipeline query cannot be empty"); ecs_check(query->terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, "pipeline must start with System term"); ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); pq->query = query; pq->match_count = -1; ecs_set(world, entity, EcsPipeline, { pq }); return entity; error: ecs_query_fini(query); return 0; } /* Trampolines so flecs_pipeline_init can dispatch through a function pointer * without exposing the entity argument to ecs_query_init. */ static ecs_query_t* flecs_pipeline_query_init( ecs_world_t *world, ecs_entity_t entity, const ecs_query_desc_t *desc) { (void)entity; return ecs_query_init(world, desc); } static ecs_query_t* flecs_pipeline_query_update( ecs_world_t *world, ecs_entity_t entity, const ecs_query_desc_t *desc) { return ecs_query_update(world, entity, desc); } ecs_entity_t ecs_pipeline_init( ecs_world_t *world, const ecs_pipeline_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); bool entity_created = false; ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); entity_created = true; } else { ecs_check(!ecs_has(world, result, EcsPipeline), ECS_INVALID_OPERATION, "entity %s already is a pipeline, use ecs_pipeline_update() " "to modify", flecs_errstr(ecs_get_path(world, result))); } ecs_entity_t r = flecs_pipeline_init( world, result, desc, flecs_pipeline_query_init); if (!r && entity_created) { ecs_delete(world, result); } return r; error: return 0; } ecs_entity_t ecs_pipeline_update( ecs_world_t *world, ecs_entity_t pipeline, const ecs_pipeline_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!desc->entity || desc->entity == pipeline, ECS_INVALID_PARAMETER, "ecs_pipeline_desc_t::entity does not match pipeline entity"); /* Free the existing state struct. The query it points to is owned by * EcsPoly and will be replaced by ecs_query_update inside the helper; * detach it here so the dtor doesn't double-free. */ EcsPipeline *p = ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); if (p && p->state) { flecs_pipeline_state_free(world, p->state); p->state = NULL; } return flecs_pipeline_init( world, pipeline, desc, flecs_pipeline_query_update); error: return 0; } /* -- Module implementation -- */ static void FlecsPipelineFini( ecs_world_t *world, void *ctx) { (void)ctx; if (ecs_get_stage_count(world)) { ecs_set_threads(world, 0); } ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } #define flecs_bootstrap_phase(world, phase, depends_on)\ flecs_bootstrap_tag(world, phase);\ flecs_bootstrap_phase_(world, phase, depends_on) static void flecs_bootstrap_phase_( ecs_world_t *world, ecs_entity_t phase, ecs_entity_t depends_on) { ecs_add_id(world, phase, EcsPhase); if (depends_on) { ecs_add_pair(world, phase, EcsDependsOn, depends_on); } } void FlecsPipelineImport( ecs_world_t *world) { ECS_MODULE(world, FlecsPipeline); ECS_IMPORT(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsPipeline); flecs_bootstrap_tag(world, EcsPhase); /* Create anonymous phases to which the builtin phases will have DependsOn * relationships. This ensures that, for example, EcsOnUpdate doesn't have a * direct DependsOn relationship on EcsPreUpdate, which ensures that when * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ ecs_entity_t phase_0 = ecs_entity(world, {0}); ecs_entity_t phase_1 = ecs_entity(world, {0}); ecs_entity_t phase_2 = ecs_entity(world, {0}); ecs_entity_t phase_3 = ecs_entity(world, {0}); ecs_entity_t phase_4 = ecs_entity(world, {0}); ecs_entity_t phase_5 = ecs_entity(world, {0}); ecs_entity_t phase_6 = ecs_entity(world, {0}); ecs_entity_t phase_7 = ecs_entity(world, {0}); ecs_entity_t phase_8 = ecs_entity(world, {0}); ecs_add_pair(world, phase_1, EcsDependsOn, phase_0); ecs_add_pair(world, phase_2, EcsDependsOn, phase_1); ecs_add_pair(world, phase_3, EcsDependsOn, phase_2); ecs_add_pair(world, phase_4, EcsDependsOn, phase_3); ecs_add_pair(world, phase_5, EcsDependsOn, phase_4); ecs_add_pair(world, phase_6, EcsDependsOn, phase_5); ecs_add_pair(world, phase_7, EcsDependsOn, phase_6); ecs_add_pair(world, phase_8, EcsDependsOn, phase_7); flecs_bootstrap_phase(world, EcsOnStart, 0); flecs_bootstrap_phase(world, EcsPreFrame, 0); flecs_bootstrap_phase(world, EcsOnLoad, phase_0); flecs_bootstrap_phase(world, EcsPostLoad, phase_1); flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); flecs_bootstrap_phase(world, EcsOnValidate, phase_4); flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); flecs_bootstrap_phase(world, EcsPreStore, phase_6); flecs_bootstrap_phase(world, EcsOnStore, phase_7); flecs_bootstrap_phase(world, EcsPostFrame, phase_8); ecs_set_hooks(world, EcsPipeline, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsPipeline), .move = ecs_move(EcsPipeline) }); world->pipeline = ecs_pipeline(world, { .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), .query = { .terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.id = EcsCascade, .trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.id = EcsSelf|EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsChildOf, .oper = EcsNot } }, .order_by_callback = flecs_entity_compare } }); /* Cleanup thread administration when world is destroyed */ ecs_atfini(world, FlecsPipelineFini, NULL); } #endif #ifdef FLECS_PIPELINE /* Synchronize workers */ static void flecs_sync_worker( ecs_world_t* world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } /* Signal that thread is waiting */ ecs_os_mutex_lock(world->sync_mutex); if (++world->workers_waiting == (stage_count - 1)) { /* Only signal main thread when all threads are waiting */ ecs_os_cond_signal(world->sync_cond); } /* Wait until main thread signals that thread can continue */ ecs_os_cond_wait(world->worker_cond, world->sync_mutex); ecs_os_mutex_unlock(world->sync_mutex); } /* Worker thread */ static void* flecs_worker(void *arg) { ecs_stage_t *stage = arg; ecs_world_t *world = stage->world; flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); ecs_dbg_2("worker %d: start", stage->id); /* Start worker, increase counter so main thread knows how many * workers are ready */ ecs_os_mutex_lock(world->sync_mutex); world->workers_running ++; if (!(world->flags & EcsWorldQuitWorkers)) { ecs_os_cond_wait(world->worker_cond, world->sync_mutex); } ecs_os_mutex_unlock(world->sync_mutex); while (!(world->flags & EcsWorldQuitWorkers)) { ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); ecs_dbg_3("worker %d: run", stage->id); flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count, world->info.delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); flecs_sync_worker(world); } ecs_dbg_2("worker %d: finalizing", stage->id); ecs_os_mutex_lock(world->sync_mutex); world->workers_running --; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_2("worker %d: stop", stage->id); return NULL; } /* Start threads */ void flecs_create_worker_threads( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); int32_t stages = ecs_get_stage_count(world); for (int32_t i = 1; i < stages; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(stage, ecs_stage_t); ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); if (ecs_using_task_threads(world)) { /* workers are using tasks in an external task manager provided via * the OS API */ stage->thread = ecs_os_task_new(flecs_worker, stage); } else { /* workers are using long-running os threads */ stage->thread = ecs_os_thread_new(flecs_worker, stage); } ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, "failed to create thread"); } } static void flecs_start_workers( ecs_world_t *world, int32_t threads) { ecs_set_stage_count(world, threads); ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); if (!ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } } /* Wait until all workers are running */ static void flecs_wait_for_workers( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } bool wait = true; do { ecs_os_mutex_lock(world->sync_mutex); if (world->workers_running == (stage_count - 1)) { wait = false; } ecs_os_mutex_unlock(world->sync_mutex); } while (wait); } /* Wait until all threads are waiting on sync point */ void flecs_wait_for_sync( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); ecs_os_mutex_lock(world->sync_mutex); if (world->workers_waiting != (stage_count - 1)) { ecs_os_cond_wait(world->sync_cond, world->sync_mutex); } /* We shouldn't have been signalled unless all workers are waiting on sync */ ecs_assert(world->workers_waiting == (stage_count - 1), ECS_INTERNAL_ERROR, NULL); world->workers_waiting = 0; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_3("#[bold]pipeline: workers synced"); } /* Signal workers that they can start/resume work */ void flecs_signal_workers( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: signal workers"); ecs_os_mutex_lock(world->sync_mutex); ecs_os_cond_broadcast(world->worker_cond); ecs_os_mutex_unlock(world->sync_mutex); } void flecs_join_worker_threads( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); bool threads_active = false; /* Test if threads are created. Cannot use workers_running, since this is * a potential race if threads haven't spun up yet. */ int i, count = world->stage_count; for (i = 1; i < count; i ++) { ecs_stage_t *stage = world->stages[i]; if (stage->thread) { threads_active = true; break; } }; /* If no threads are active, just return */ if (!threads_active) { return; } /* Make sure all threads are running, to ensure they catch the signal */ flecs_wait_for_workers(world); /* Signal threads should quit */ world->flags |= EcsWorldQuitWorkers; flecs_signal_workers(world); /* Join all threads with main */ for (i = 1; i < count; i ++) { ecs_stage_t *stage = world->stages[i]; if (ecs_using_task_threads(world)) { ecs_os_task_join(stage->thread); } else { ecs_os_thread_join(stage->thread); } stage->thread = 0; } world->flags &= ~EcsWorldQuitWorkers; ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } /* -- Private functions -- */ void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!ecs_is_stage(world), ECS_INVALID_OPERATION, "cannot call progress while world is deferred"); /* Make sure workers are running and ready */ flecs_wait_for_workers(world); /* Run pipeline on main thread */ ecs_world_t *stage = ecs_get_stage(world, 0); ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); flecs_run_pipeline(stage, pq, delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } static void flecs_set_threads_internal( ecs_world_t *world, int32_t threads, bool use_task_api) { ecs_assert(threads <= 1 || (use_task_api ? ecs_os_has_task_support() : ecs_os_has_threading()), ECS_MISSING_OS_API, NULL); int32_t stage_count = ecs_get_stage_count(world); bool worker_method_changed = (use_task_api != world->workers_use_task_api); if ((stage_count != threads) || worker_method_changed) { /* Stop existing threads */ if (stage_count > 1) { flecs_join_worker_threads(world); ecs_set_stage_count(world, 1); if (world->worker_cond) { ecs_os_cond_free(world->worker_cond); } if (world->sync_cond) { ecs_os_cond_free(world->sync_cond); } if (world->sync_mutex) { ecs_os_mutex_free(world->sync_mutex); } } world->workers_use_task_api = use_task_api; /* Start threads if number of threads > 1 */ if (threads > 1) { world->worker_cond = ecs_os_cond_new(); world->sync_cond = ecs_os_cond_new(); world->sync_mutex = ecs_os_mutex_new(); flecs_start_workers(world, threads); } } } /* -- Public functions -- */ void ecs_set_threads( ecs_world_t *world, int32_t threads) { flecs_set_threads_internal(world, threads, false /* use thread API */); } void ecs_set_task_threads( ecs_world_t *world, int32_t task_threads) { flecs_set_threads_internal(world, task_threads, true /* use task API */); } bool ecs_using_task_threads( ecs_world_t *world) { return world->workers_use_task_api; } #endif #ifdef FLECS_PREFAB 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]; void *dst_ptr = NULL; /* 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); 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_w_cr( world, instance_table, row_offset + j, id, cr, true, dst_ptr); } } } 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 *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; } } /* 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 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; ecs_record_t *instance_r = flecs_entities_get(world, instance); bool has_isa = ecs_table_get_type_index( world, instance_r->table, ecs_isa(base)) != -1; while (cur) { ecs_assert(cur->flags & EcsIdSparse, ECS_INTERNAL_ERROR, NULL); bool inherit = (cur->flags & EcsIdOnInstantiateInherit) != 0; if (cur->sparse && (!has_isa || !inherit) && !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; bool is_new = 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 (is_new) { ecs_type_t added = { .array = &cur->id, .count = 1 }; flecs_emit(world, world, &(ecs_event_desc_t){ .event = EcsOnAdd, .ids = &added, .table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1, .observable = world, .flags = EcsEventNoOnSet }); } if (ti) { flecs_notify_on_set( world, r->table, ECS_RECORD_TO_ROW(r->row), cur->id, true, ptr); } } } 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_iter((ecs_table_cache_t*)cr, &it, EcsTableEmpty|EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_table_range_t range = { elem->table, 0, ecs_table_count(elem->table) }; flecs_instantiate_children( world, base, instance, range, ctx, depth); } } } ecs_os_perf_trace_pop("flecs.instantiate"); } error: return; } 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; } #else 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) { (void)world; (void)table; (void)component; (void)cr; (void)recur_depth; return NULL; } 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)world; (void)base; (void)instance; (void)ctx; (void)depth; } #endif #ifdef FLECS_PREFAB 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); } } 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 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_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); } 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; } void flecs_bootstrap_prefab( ecs_world_t *world) { flecs_bootstrap_tag(world, EcsPrefab); flecs_bootstrap_tag(world, EcsOverride); flecs_bootstrap_tag(world, EcsInherit); flecs_bootstrap_trait(world, EcsOnInstantiate); #ifdef FLECS_CONSTRAINT_TRAITS ecs_add_id(world, EcsOnInstantiate, EcsRelationship); ecs_add_id(world, EcsOverride, EcsTarget); ecs_add_id(world, EcsInherit, EcsTarget); #endif ecs_add_pair(world, EcsPrefab, EcsOnInstantiate, EcsDontInherit); flecs_type_info_init(world, EcsTreeSpawner, { .ctor = flecs_default_ctor, .copy = ecs_copy(EcsTreeSpawner), .move = ecs_move(EcsTreeSpawner), .dtor = ecs_dtor(EcsTreeSpawner) }); /* 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 }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnInstantiate, EcsAny) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_on_instantiate, .global_observer = true }); /* OnInstantiate */ ecs_add_id(world, EcsOnInstantiate, EcsExclusive); /* DontInherit components */ 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); ecs_add_pair(world, EcsChildOf, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, ecs_id(EcsIdentifier), EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, ecs_id(EcsTreeSpawner), EcsOnInstantiate, EcsDontInherit); /* Inherited components */ ecs_add_pair(world, EcsIsA, EcsOnInstantiate, EcsInherit); ecs_add_pair(world, EcsDependsOn, EcsOnInstantiate, EcsInherit); /* IsA */ ecs_add_id(world, EcsIsA, EcsTransitive); ecs_add_id(world, EcsIsA, EcsReflexive); } #else void flecs_bootstrap_prefab( ecs_world_t *world) { (void)world; #ifdef FLECS_REST /* If the REST addon is enabled, just create an entity called Prefab. * Without this the explorer tree query (which checks if an entity is a * Prefab) will fail to create. */ ecs_entity(world, { .name = "flecs.core.Prefab" }); #endif } void flecs_fini_prefab( ecs_world_t *world) { (void)world; } ecs_entity_t flecs_get_prefab_instance_child( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t prefab_child) { (void)world; (void)entity; (void)prefab_child; return 0; } #endif #ifdef FLECS_PREFAB 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 static bool flecs_tree_spawner_is_empty( const EcsTreeSpawner *ts) { int32_t i; for (i = 0; i < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; i ++) { if (ecs_vec_count(&ts->data[i].children)) { return false; } } return true; } 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; } const EcsTreeSpawner *ts = ecs_get(world, cur, EcsTreeSpawner); if (ts != NULL && !flecs_tree_spawner_is_empty(ts)) { 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_prefab( 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_tree_spawner_fini( ecs_world_t *world, ecs_entity_t parent) { ecs_record_t *r = flecs_entities_get_any(world, parent); if (!r->table) { return; } ecs_entity_t id = ecs_id(EcsTreeSpawner); if (!world->non_trivial_lookup[id] && r->table->component_map[id] <= 0) { return; } EcsTreeSpawner* ts = flecs_get_mut( world, parent, ecs_id(EcsTreeSpawner), r, sizeof(EcsTreeSpawner)).ptr; if (ts) { EcsTreeSpawner_free(ts); ecs_os_zeromem(ts); } } #else #ifdef FLECS_DEBUG void flecs_tree_spawner_assert_not_instantiated( ecs_world_t *world, ecs_entity_t parent) { (void)world; (void)parent; } #endif #endif #ifdef FLECS_DEBUG static bool flecs_prefab_diff_has_override( ecs_world_t *world, const ecs_type_t *type) { int32_t i; for (i = 0; i < type->count; i ++) { ecs_id_t id = type->array[i]; if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { return true; } ecs_component_record_t *cr = flecs_components_get(world, id); if (cr && !(cr->flags & (EcsIdOnInstantiateInherit|EcsIdOnInstantiateDontInherit))) { return true; } } return false; } void flecs_prefab_assert_not_instantiated( ecs_world_t *world, ecs_entity_t prefab, const ecs_table_diff_t *diff) { if (world->flags & EcsWorldQuit) { return; } ecs_component_record_t *cr = flecs_components_get( world, ecs_pair(EcsIsA, prefab)); if (!cr || !flecs_table_cache_count(&cr->cache)) { return; } if (!flecs_prefab_diff_has_override(world, &diff->added) && !flecs_prefab_diff_has_override(world, &diff->removed)) { return; } char *path = ecs_get_path(world, prefab); ecs_abort(ECS_ALREADY_IN_USE, "cannot add or remove auto-overridden components of prefab '%s' " "after it has been instantiated", path); ecs_os_free(path); } #endif #ifdef FLECS_QUERY_DSL typedef struct flecs_term_parser_t { ecs_parser_t *parser; const char *pos; ecs_token_t token; } flecs_term_parser_t; static bool flecs_term_next(flecs_term_parser_t *p) { p->pos = flecs_token(p->parser, p->pos, &p->token, false); if (p->pos && p->token.kind >= EcsTokKeywordWith) { p->token.kind = EcsTokIdentifier; } return p->pos != NULL; } static bool flecs_term_identifier(flecs_term_parser_t *p) { return p->token.kind == EcsTokIdentifier || p->token.kind == EcsTokNumber || p->token.kind == EcsTokMul; } static bool flecs_term_error(flecs_term_parser_t *p, const char *message) { ecs_parser_t *parser = p->parser; ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, p->pos ? p->pos - 1 : parser->pos), "%s", message); return false; } static ecs_entity_t flecs_query_parse_trav_flags(const char *tok) { if (!ecs_os_strcmp(tok, "self")) return EcsSelf; if (!ecs_os_strcmp(tok, "up")) return EcsUp; if (!ecs_os_strcmp(tok, "cascade")) return EcsCascade; if (!ecs_os_strcmp(tok, "desc")) return EcsDesc; return 0; } static bool flecs_term_trav(flecs_term_parser_t *p, ecs_term_ref_t *ref) { for (;;) { if (p->token.kind != EcsTokIdentifier) { return flecs_term_error(p, "expected traversal flag"); } ref->id |= flecs_query_parse_trav_flags(p->token.value); if (!flecs_term_next(p)) return false; if (p->token.kind == '|') { if (!flecs_term_next(p)) return false; continue; } if (p->token.kind == EcsTokIdentifier) { p->parser->term->trav = ecs_lookup(p->parser->world, p->token.value); if (!p->parser->term->trav) { return flecs_term_error(p, "unresolved traversal relationship"); } if (!flecs_term_next(p)) return false; } return true; } } static bool flecs_term_ref(flecs_term_parser_t *p, ecs_term_ref_t *ref) { if (p->token.kind == EcsTokIdentifier && flecs_query_parse_trav_flags(p->token.value)) { return flecs_term_trav(p, ref); } if (p->token.kind == '@') { p->parser->term->id = ECS_VALUE_PAIR; if (!flecs_term_next(p)) return false; if (p->token.kind == EcsTokMul) { ref->id = EcsWildcard; } else if (p->token.kind == EcsTokNumber) { ref->id = strtoul(p->token.value, NULL, 10); } else if (p->token.kind == EcsTokIdentifier) { ref->name = p->token.value; } else { return flecs_term_error(p, "expected value pair target"); } return flecs_term_next(p); } if (!flecs_term_identifier(p)) { return flecs_term_error(p, "expected term argument"); } ref->name = p->token.value; if (!flecs_term_next(p)) return false; if (p->token.kind == '|') { if (!flecs_term_next(p) || !flecs_term_trav(p, ref)) return false; if (p->token.kind == EcsTokIdentifier) { p->parser->term->trav = ecs_lookup(p->parser->world, p->token.value); if (!p->parser->term->trav) { return flecs_term_error(p, "unresolved traversal relationship"); } return flecs_term_next(p); } } return true; } static bool flecs_term_args(flecs_term_parser_t *p, int32_t arg) { ecs_parser_t *parser = p->parser; for (;;) { ecs_term_ref_t *ref; if (!arg) { ref = &parser->term->src; } else if (arg == 1) { ref = &parser->term->second; } else { if (arg > FLECS_TERM_ARG_COUNT_MAX || !parser->extra_args) { return flecs_term_error(p, "too many arguments in term"); } ref = &parser->extra_args[arg - 2]; } if (!flecs_term_ref(p, ref)) return false; if (p->token.kind == ')') return flecs_term_next(p); if (p->token.kind != ',' && p->token.kind != EcsTokOr) { return flecs_term_error(p, "expected argument separator or ')'"); } ecs_oper_kind_t oper = p->token.kind == ',' ? EcsAnd : EcsOr; if (arg > 1 && parser->extra_oper != oper) { return flecs_term_error(p, "cannot mix operators in extra term arguments"); } parser->extra_oper = oper; arg ++; if (!flecs_term_next(p)) return false; } } static bool flecs_term_pair(flecs_term_parser_t *p) { if (!flecs_term_next(p) || !flecs_term_identifier(p)) { return flecs_term_error(p, "expected pair relationship"); } p->parser->term->first.name = p->token.value; if (!flecs_term_next(p)) return false; if (p->token.kind == '|') { if (!flecs_term_next(p) || !flecs_term_trav(p, &p->parser->term->first)) { return false; } } if (p->token.kind != ',') return flecs_term_error(p, "expected ',' in pair"); return flecs_term_next(p) && flecs_term_args(p, 1); } static const char* flecs_query_term_parse( ecs_parser_t *parser, const char *pos) { flecs_term_parser_t p = { .parser = parser, .pos = pos }; ecs_term_t *term = parser->term; bool inout = false, unary = false, neq = false; if (!flecs_term_next(&p)) return NULL; if (p.token.kind == '[') { inout = true; if (!flecs_term_next(&p) || p.token.kind != EcsTokIdentifier) goto unexpected; static const char *names[] = {"default", "none", "filter", "inout", "in", "out"}; static const ecs_inout_kind_t kinds[] = { EcsInOutDefault, EcsInOutNone, EcsInOutFilter, EcsInOut, EcsIn, EcsOut }; for (int32_t i = 0; i < 6; i ++) { if (!ecs_os_strcmp(p.token.value, names[i])) { term->inout = flecs_ito(int16_t, kinds[i]); break; } } if (!flecs_term_next(&p) || p.token.kind != ']' || !flecs_term_next(&p)) { goto unexpected; } } if (p.token.kind == '!' || p.token.kind == '?') { term->oper = p.token.kind == '!' ? EcsNot : EcsOptional; unary = true; if (!flecs_term_next(&p)) return NULL; } if (p.token.kind == '{' && (!inout || unary)) { term->first.id = EcsScopeOpen; term->src.id = EcsIsEntity; term->inout = EcsInOutNone; return p.pos; } if (!inout && !unary) { if (p.token.kind == EcsTokEnd || p.token.kind == EcsTokNewline) return p.pos; if (p.token.kind == '}') { term->first.id = EcsScopeClose; term->src.id = EcsIsEntity; term->inout = EcsInOutNone; const char *end = p.pos; if (!flecs_term_next(&p)) return NULL; return p.token.kind == ',' ? p.pos : end; } } if (flecs_term_identifier(&p) && !unary) { int16_t oper = 0; ecs_id_t flag = 0; const char *name = p.token.value; if (!ecs_os_strcmp(name, "and")) oper = EcsAndFrom; else if (!ecs_os_strcmp(name, "or")) oper = EcsOrFrom; else if (!ecs_os_strcmp(name, "not")) oper = EcsNotFrom; else if (!ecs_os_strcmp(name, "auto_override")) flag = ECS_AUTO_OVERRIDE; else if (!ecs_os_strcmp(name, "toggle")) flag = ECS_TOGGLE; if (oper || flag) { if (!flecs_term_next(&p) || p.token.kind != '|' || !flecs_term_next(&p)) { goto unexpected; } if (flecs_term_identifier(&p)) { if (oper) term->oper = oper; if (flag) term->id = flag; } else if (p.token.kind != '(') { goto unexpected; } } } if (p.token.kind == '(') { if (!flecs_term_pair(&p)) return NULL; } else if (flecs_term_identifier(&p)) { term->first.name = p.token.value; if (!flecs_term_next(&p)) return NULL; if (p.token.kind == EcsTokEq || p.token.kind == EcsTokNeq || p.token.kind == EcsTokMatch) { if (term->oper != EcsAnd) { flecs_term_error(&p, "cannot mix operator with equality expression"); return NULL; } neq = p.token.kind == EcsTokNeq; ecs_entity_t pred = p.token.kind == EcsTokMatch ? EcsPredMatch : EcsPredEq; term->src = term->first; term->first = (ecs_term_ref_t){ .id = pred }; if (!flecs_term_next(&p)) return NULL; if (!flecs_term_identifier(&p) && p.token.kind != EcsTokString) goto unexpected; term->second.name = p.token.value; if (p.token.kind == EcsTokString) { term->second.id = EcsIsName; if (pred == EcsPredMatch && term->second.name[0] == '!') { term->second.name ++; term->oper = EcsNot; } } if (!flecs_term_next(&p)) return NULL; } else { bool trav = p.token.kind == '|'; if (trav && (!flecs_term_next(&p) || !flecs_term_trav(&p, &term->first))) return NULL; if (p.token.kind == '(') { if (!flecs_term_next(&p)) return NULL; if (!trav && p.token.kind == ')') { term->src.id = EcsIsEntity; if (!flecs_term_next(&p)) return NULL; } else if (!flecs_term_args(&p, 0)) { return NULL; } } } } else { goto unexpected; } switch (p.token.kind) { case '}': p.pos --; break; case EcsTokOr: if (term->oper != EcsAnd || neq) { flecs_term_error(&p, "cannot mix operators in || expression"); return NULL; } term->oper = EcsOr; break; case ',': case EcsTokNewline: case EcsTokEnd: break; default: goto unexpected; } if (neq) term->oper = EcsNot; return p.pos; unexpected: flecs_term_error(&p, "unexpected token in term"); return NULL; } int flecs_terms_parse( ecs_world_t *world, const char *name, const char *code, char *token_buffer, ecs_term_t *terms, int32_t *term_count_out) { if (!ecs_os_strcmp(code, "0")) { *term_count_out = 0; return 0; } ecs_parser_t parser = { .name = name, .code = code, .world = world, .pos = code, .merge_variable_members = true }; parser.token_cur = token_buffer; int32_t term_count = 0; const char *ptr = code; ecs_term_ref_t extra_args[FLECS_TERM_ARG_COUNT_MAX]; parser.extra_args = extra_args; parser.extra_oper = 0; do { if (term_count == FLECS_TERM_COUNT_MAX) { ecs_err("max number of terms (%d) reached, increase " "FLECS_TERM_COUNT_MAX to support more", FLECS_TERM_COUNT_MAX); goto error; } /* Parse next term */ ecs_term_t *term = &terms[term_count]; parser.term = term; ecs_os_memset_t(term, 0, ecs_term_t); ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); parser.extra_oper = 0; ptr = flecs_query_term_parse(&parser, ptr); if (!ptr) { /* Parser error */ goto error; } if (!ecs_term_is_initialized(term)) { /* Last term parsed */ break; } term_count ++; /* Unpack terms with more than two args into multiple terms so that: * Rel(X, Y, Z) * becomes: * Rel(X, Y), Rel(Y, Z) */ int32_t arg = 0; while (ecs_term_ref_is_set(&extra_args[arg ++])) { ecs_assert(arg <= FLECS_TERM_ARG_COUNT_MAX, ECS_INTERNAL_ERROR, NULL); if (term_count == FLECS_TERM_COUNT_MAX) { ecs_err("max number of terms (%d) reached, increase " "FLECS_TERM_COUNT_MAX to support more", FLECS_TERM_COUNT_MAX); goto error; } term = &terms[term_count ++]; *term = term[-1]; if (parser.extra_oper == EcsAnd) { term->src = term[-1].second; term->second = extra_args[arg - 1]; } else if (parser.extra_oper == EcsOr) { term->src = term[-1].src; term->second = extra_args[arg - 1]; term[-1].oper = EcsOr; } } } while (ptr[0]); (*term_count_out) += term_count; return 0; error: return -1; } const char* flecs_term_parse( ecs_world_t *world, const char *name, const char *expr, char *token_buffer, ecs_term_t *term) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, "%s", name); ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); ecs_parser_t parser = { .name = name, .code = expr, .world = world, .token_cur = token_buffer }; parser.term = term; return flecs_query_term_parse(&parser, expr); } const char* flecs_id_parse( const ecs_world_t *world, const char *name, const char *expr, ecs_id_t *id) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, "%s", name); ecs_assert(id != NULL, ECS_INVALID_PARAMETER, NULL); ecs_size_t token_buffer_size = ecs_os_strlen(expr) * 2 + 1; char *token_buffer = ecs_os_malloc(token_buffer_size); const char *ret = NULL; ecs_parser_t parser = { .name = name, .code = expr, .world = ECS_CONST_CAST(ecs_world_t*, world), /* Safe, won't modify */ .token_cur = token_buffer, .token_end = &token_buffer[token_buffer_size] }; ecs_term_t term = {0}; parser.term = &term; expr = flecs_scan_whitespace(&parser, expr); if (!ecs_os_strcmp(expr, "#0")) { *id = 0; ret = &expr[1]; goto done; } const char *result = flecs_query_term_parse(&parser, expr); if (!result) { goto done; } ecs_query_validator_ctx_t ctx = {0}; ctx.world = world; ctx.term = &term; if (flecs_term_finalize(world, &term, &ctx)) { goto done; } if (term.oper != EcsAnd) { ecs_parser_error(name, expr, (result - expr), "invalid operator for add expression"); goto done; } if ((term.src.id & ~EcsTraverseFlags) != (EcsThis|EcsIsVariable)) { ecs_parser_error(name, expr, (result - expr), "invalid source for add expression (must be $this)"); goto done; } *id = term.id; ret = result; done: ecs_os_free(token_buffer); return ret; } static const char* flecs_query_arg_parse( ecs_parser_t *parser, ecs_query_t *q, ecs_iter_t *it, const char *pos) { ParserBegin; Parse_3(EcsTokIdentifier, ':', EcsTokIdentifier, { int var = ecs_query_find_var(q, Token(0)); if (var == -1) { Error("unknown variable '%s'", Token(0)); } ecs_entity_t val = ecs_lookup(q->stage, Token(2)); if (!val) { Error("unresolved entity '%s'", Token(2)); } ecs_iter_set_var(it, var, val); EndOfRule; }) ParserEnd; } static const char* flecs_query_args_parse( ecs_parser_t *parser, ecs_query_t *q, ecs_iter_t *it, const char *pos) { ParserBegin; bool has_paren = false; LookAhead( case '\0': Consume(); EndOfRule; case '(': { Consume(); has_paren = true; LookAhead_1(')', Consume(); EndOfRule; ) } ) Loop( pos = flecs_query_arg_parse(parser, q, it, pos); if (!pos) { goto error; } Parse( case ',': continue; case '\0': EndOfRule; case ')': if (!has_paren) { Error("unexpected ')' without opening '(')"); } EndOfRule; ) ) ParserEnd; } const char* ecs_query_args_parse( ecs_query_t *q, ecs_iter_t *it, const char *expr) { flecs_poly_assert(q, ecs_query_t); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL); const char *q_name = q->entity ? ecs_get_name(q->stage, q->entity) : NULL; if (ecs_os_strlen(expr) > 512) { ecs_parser_error(q_name, expr, 0, "query argument expression too long"); return NULL; } char token_buffer[1024]; ecs_parser_t parser = { .name = q_name, .code = expr, .world = q->world, .token_cur = token_buffer }; return flecs_query_args_parse(&parser, q, it, expr); error: return NULL; } #endif #ifdef FLECS_SCRIPT #define flecs_ast_new(parser, T, kind)\ (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) #define flecs_ast_vec(parser, vec, T) \ ecs_vec_init_t(&parser->script->allocator, &vec, T*, 0) #define flecs_ast_insert(parser, T, kind)\ (T*)flecs_ast_insert_(parser, ECS_SIZEOF(T), kind) static void* flecs_ast_new_( ecs_parser_t *parser, ecs_size_t size, ecs_script_node_kind_t kind) { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; ecs_script_node_t *result = flecs_calloc_w_dbg_info( a, size, "ecs_script_node_t"); result->kind = kind; result->alloc_size = size; result->pos = parser->stmt_pos ? parser->stmt_pos : parser->pos; return result; } static void* flecs_ast_insert_( ecs_parser_t *parser, ecs_size_t size, ecs_script_node_kind_t kind) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_node_t *result = flecs_ast_new_(parser, size, kind); ecs_vec_append_t(&parser->script->allocator, &scope->stmts, ecs_script_node_t*)[0] = result; return result; } ecs_script_scope_t* flecs_script_scope_new( ecs_parser_t *parser) { ecs_script_scope_t *result = flecs_ast_new( parser, ecs_script_scope_t, EcsAstScope); flecs_ast_vec(parser, result->stmts, ecs_script_node_t); ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); ecs_vec_init_t(NULL, &result->set_components, ecs_id_t, 0); result->parent = parser->scope; result->scope_slot = -1; return result; } bool flecs_scope_is_empty( ecs_script_scope_t *scope) { return ecs_vec_count(&scope->stmts) == 0; } static void flecs_script_name_to_expr( ecs_parser_t *parser, const char *name, ecs_expr_node_t** out) { if (name && flecs_string_is_interpolated(name)) { *out = (ecs_expr_node_t*)flecs_expr_interpolated_string(parser, name); } } /* Entity names cannot be paths (a.b). Interpolated expressions in a name can * contain any valid expression, which may include '.' characters. */ static bool flecs_script_name_is_path( const char *name) { if (!name) { return false; } int32_t depth = 0; const char *ptr; for (ptr = name; ptr[0]; ptr ++) { if (ptr[0] == '\\') { if (ptr[1]) { ptr ++; } continue; } if (ptr[0] == '{') { depth ++; } else if (ptr[0] == '}') { if (depth) { depth --; } } else if (!depth && ptr[0] == '.') { return true; } } return false; } ecs_script_entity_t* flecs_script_insert_entity( ecs_parser_t *parser, const char *name) { if (flecs_script_name_is_path(name)) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, parser->pos), "invalid path '%s' in entity name", name); return NULL; } ecs_script_entity_t *result = flecs_ast_insert( parser, ecs_script_entity_t, EcsAstEntity); result->symbol = -1; result->kind_symbol = -1; result->kind_sp = -1; if (name && !ecs_os_strcmp(name, "_")) { name = NULL; } result->name = name; flecs_script_name_to_expr(parser, name, &result->name_expr); result->scope = flecs_script_scope_new(parser); return result; } static void flecs_script_set_id( ecs_parser_t *parser, ecs_script_id_t *id, const char *first, const char *second) { ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); id->first = first; id->second = second; id->first_sp = -1; id->second_sp = -1; id->index_sp = -1; id->first_symbol = -1; id->second_symbol = -1; flecs_script_name_to_expr(parser, first, &id->first_expr); flecs_script_name_to_expr(parser, second, &id->second_expr); id->dynamic = id->first_expr || id->second_expr; } ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_parser_t *parser, const char *first, const char *second) { ecs_script_pair_scope_t *result = flecs_ast_insert( parser, ecs_script_pair_scope_t, EcsAstPairScope); flecs_script_set_id(parser, &result->id, first, second); result->scope = flecs_script_scope_new(parser); return result; } ecs_script_tag_t* flecs_script_insert_pair_tag( ecs_parser_t *parser, const char *first, const char *second) { ecs_script_tag_t *result = flecs_ast_insert( parser, ecs_script_tag_t, EcsAstTag); result->component_slot = -1; flecs_script_set_id(parser, &result->id, first, second); return result; } ecs_script_tag_t* flecs_script_insert_tag( ecs_parser_t *parser, const char *name) { return flecs_script_insert_pair_tag(parser, name, NULL); } ecs_script_component_t* flecs_script_insert_pair_component( ecs_parser_t *parser, const char *first, const char *second) { ecs_script_component_t *result = flecs_ast_insert( parser, ecs_script_component_t, EcsAstComponent); result->component_slot = -1; result->value_pos = parser->expr_pos; result->value_end = parser->expr_end; flecs_script_set_id(parser, &result->id, first, second); return result; } ecs_script_component_t* flecs_script_insert_component( ecs_parser_t *parser, const char *name) { return flecs_script_insert_pair_component(parser, name, NULL); } ecs_script_with_t* flecs_script_insert_with( ecs_parser_t *parser) { ecs_script_with_t *result = flecs_ast_insert( parser, ecs_script_with_t, EcsAstWith); result->expressions = flecs_script_scope_new(parser); result->scope = flecs_script_scope_new(parser); return result; } ecs_script_using_t* flecs_script_insert_using( ecs_parser_t *parser, const char *name) { ecs_script_using_t *result = flecs_ast_insert( parser, ecs_script_using_t, EcsAstUsing); result->name = name; return result; } ecs_script_module_t* flecs_script_insert_module( ecs_parser_t *parser, const char *name) { ecs_script_module_t *result = flecs_ast_insert( parser, ecs_script_module_t, EcsAstModule); result->name = name; result->symbol = -1; return result; } ecs_script_annot_t* flecs_script_insert_annot( ecs_parser_t *parser, const char *name, const char *expr) { ecs_script_annot_t *result = flecs_ast_insert( parser, ecs_script_annot_t, EcsAstAnnotation); result->name = name; result->expr = expr; return result; } ecs_script_template_node_t* flecs_script_insert_template( ecs_parser_t *parser, const char *name) { ecs_script_template_node_t *result = flecs_ast_insert( parser, ecs_script_template_node_t, EcsAstTemplate); result->symbol = -1; result->symbol_offset = -1; result->symbol_count = -1; result->root_symbol = -1; result->name = name; result->scope = flecs_script_scope_new(parser); return result; } ecs_script_var_node_t* flecs_script_insert_var( ecs_parser_t *parser, const char *name) { ecs_script_var_node_t *result = flecs_ast_insert( parser, ecs_script_var_node_t, EcsAstConst); result->sp = -1; result->symbol = -1; result->name = name; return result; } ecs_script_await_t* flecs_script_insert_await( ecs_parser_t *parser) { return flecs_ast_insert(parser, ecs_script_await_t, EcsAstAwait); } ecs_script_async_t* flecs_script_insert_async( ecs_parser_t *parser) { ecs_script_async_t *result = flecs_ast_insert( parser, ecs_script_async_t, EcsAstAsync); result->scope = flecs_script_scope_new(parser); return result; } ecs_script_while_t* flecs_script_insert_while( ecs_parser_t *parser) { ecs_script_while_t *result = flecs_ast_insert( parser, ecs_script_while_t, EcsAstWhile); result->scope = flecs_script_scope_new(parser); return result; } ecs_script_assign_t* flecs_script_insert_assign( ecs_parser_t *parser, const char *name) { ecs_script_assign_t *result = flecs_ast_insert( parser, ecs_script_assign_t, EcsAstAssign); result->name = name; result->sp = -1; return result; } ecs_script_continue_t* flecs_script_insert_continue( ecs_parser_t *parser) { return flecs_ast_insert(parser, ecs_script_continue_t, EcsAstContinue); } ecs_script_try_t* flecs_script_insert_try( ecs_parser_t *parser) { ecs_script_try_t *result = flecs_ast_insert( parser, ecs_script_try_t, EcsAstTry); result->try_scope = flecs_script_scope_new(parser); ecs_vec_init_t(&parser->script->allocator, &result->catches, ecs_script_catch_t, 0); return result; } ecs_script_catch_t* flecs_script_try_add_catch( ecs_parser_t *parser, ecs_script_try_t *stmt) { ecs_script_catch_t *result = ecs_vec_append_t( &parser->script->allocator, &stmt->catches, ecs_script_catch_t); ecs_os_zeromem(result); result->error_symbol = -1; result->scope = flecs_script_scope_new(parser); return result; } ecs_script_if_t* flecs_script_insert_if( ecs_parser_t *parser) { ecs_script_if_t *result = flecs_ast_insert( parser, ecs_script_if_t, EcsAstIf); result->if_true = flecs_script_scope_new(parser); result->if_false = flecs_script_scope_new(parser); return result; } ecs_script_for_t* flecs_script_insert_for( ecs_parser_t *parser) { ecs_script_for_t *result = flecs_ast_insert( parser, ecs_script_for_t, EcsAstFor); result->loop_var_sp[0] = -1; result->loop_var_sp[1] = -1; result->loop_var_sp[2] = -1; result->for_slot = -1; result->scope = flecs_script_scope_new(parser); return result; } ecs_script_include_t* flecs_script_insert_include( ecs_parser_t *parser, const char *filename) { ecs_script_include_t *result = flecs_ast_insert( parser, ecs_script_include_t, EcsAstInclude); result->filename = filename; return result; } ecs_script_function_node_t* flecs_script_insert_function( ecs_parser_t *parser, const char *name) { ecs_script_function_node_t *result = flecs_ast_insert( parser, ecs_script_function_node_t, EcsAstFunction); result->symbol = -1; result->name = name; result->body = flecs_script_scope_new(parser); ecs_vec_init_t(&parser->script->allocator, &result->params, ecs_script_fn_param_t, 0); return result; } int flecs_script_visit_scopes( ecs_script_node_t *node, flecs_script_scope_action_t action, void *ctx) { ecs_script_scope_t *scopes[2] = {0}; switch (node->kind) { case EcsAstEntity: scopes[0] = ((ecs_script_entity_t*)node)->scope; break; case EcsAstWith: scopes[0] = ((ecs_script_with_t*)node)->expressions; scopes[1] = ((ecs_script_with_t*)node)->scope; break; case EcsAstTemplate: scopes[0] = ((ecs_script_template_node_t*)node)->scope; break; case EcsAstPairScope: scopes[0] = ((ecs_script_pair_scope_t*)node)->scope; break; case EcsAstIf: scopes[0] = ((ecs_script_if_t*)node)->if_true; scopes[1] = ((ecs_script_if_t*)node)->if_false; break; case EcsAstFor: scopes[0] = ((ecs_script_for_t*)node)->scope; break; case EcsAstFunction: scopes[0] = ((ecs_script_function_node_t*)node)->body; break; case EcsAstAsync: scopes[0] = ((ecs_script_async_t*)node)->scope; break; case EcsAstWhile: scopes[0] = ((ecs_script_while_t*)node)->scope; break; case EcsAstTry: { ecs_script_try_t *stmt = (ecs_script_try_t*)node; int result = stmt->try_scope ? action(stmt->try_scope, ctx) : 0; if (result) { return result; } ecs_script_catch_t *catches = ecs_vec_first(&stmt->catches); for (int32_t i = 0; i < ecs_vec_count(&stmt->catches); i ++) { result = catches[i].scope ? action(catches[i].scope, ctx) : 0; if (result) { return result; } } return 0; } default: break; } for (int32_t i = 0; i < 2; i ++) { int result = scopes[i] ? action(scopes[i], ctx) : 0; if (result) { return result; } } return 0; } #endif #ifdef FLECS_SCRIPT typedef enum flecs_script_edit_kind_t { FlecsScriptEditValue, FlecsScriptEditRemove, FlecsScriptEditDelete } flecs_script_edit_kind_t; typedef struct flecs_script_edit_t { ecs_entity_t entity; ecs_id_t component; flecs_script_edit_kind_t kind; int32_t offset; int32_t length; int32_t seq; int32_t open_len; /* Length of scope open text that inserts can share */ int32_t close_len; /* Length of scope close text that inserts can share */ char *text; } flecs_script_edit_t; /* Key that identifies the single edit recorded for a component of an entity. */ typedef struct flecs_script_edit_key_t { ecs_entity_t entity; ecs_id_t component; } flecs_script_edit_key_t; struct ecs_script_edits_t { ecs_script_t *script; ecs_vec_t edits; ecs_hashmap_t index; /* hashmap with the * index of the edit for a key, incremented by one */ ecs_vec_t scopes; /* vec with the scopes of the using * statements, resolved once per edit session */ ecs_map_t id_strs; /* map with the identifier written for * a component, resolved once per edit session */ bool scopes_init; int32_t seq; }; static uint64_t flecs_script_edit_key_hash( const void *ptr) { return flecs_hash(ptr, ECS_SIZEOF(flecs_script_edit_key_t)); } static int flecs_script_edit_key_compare( const void *first, const void *second) { return ecs_os_memcmp(first, second, ECS_SIZEOF(flecs_script_edit_key_t)); } typedef enum flecs_script_value_style_t { FlecsScriptValueOther, FlecsScriptValueNamed, FlecsScriptValuePositional, FlecsScriptValueEmpty } flecs_script_value_style_t; static bool flecs_script_edit_is_ident( char c) { return isalpha((unsigned char)c) || isdigit((unsigned char)c) || c == '_' || c == '.'; } static const char* flecs_script_edit_skip_ws( const char *pos, const char *end) { while (pos < end) { if (isspace((unsigned char)pos[0])) { pos ++; } else if (pos[0] == '/' && (pos + 1) < end && pos[1] == '/') { while (pos < end && pos[0] != '\n') { pos ++; } } else if (pos[0] == '/' && (pos + 1) < end && pos[1] == '*') { pos += 2; while (pos < end && !(pos[0] == '*' && (pos + 1) < end && pos[1] == '/')) { pos ++; } if (pos < end) { pos += 2; } } else { break; } } return pos; } typedef struct flecs_script_edit_walk_t { int (*action)(ecs_script_entity_t *node, void *ctx); void *ctx; } flecs_script_edit_walk_t; static int flecs_script_edit_walk( ecs_script_scope_t *scope, void *ctx) { flecs_script_edit_walk_t *walk = ctx; ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); for (int32_t i = 0; i < ecs_vec_count(&scope->stmts); i ++) { ecs_script_node_t *node = stmts[i]; if (node->kind == EcsAstTemplate || node->kind == EcsAstFor || node->kind == EcsAstFunction || node->kind == EcsAstAsync) { continue; } if (node->kind == EcsAstEntity) { int result = walk->action((ecs_script_entity_t*)node, walk->ctx); if (result) { return result; } } int result = flecs_script_visit_scopes(node, flecs_script_edit_walk, ctx); if (result) { return result; } } return 0; } static int flecs_script_edit_index_entity( ecs_script_entity_t *entity, void *ctx) { ecs_script_impl_t *impl = ctx; if (entity->symbol >= 0 && entity->symbol < ecs_vec_count(&impl->state.symbol_slots)) { ecs_script_symbol_slot_t *slot = ecs_vec_get_t( &impl->state.symbol_slots, ecs_script_symbol_slot_t, entity->symbol); if (slot->entity && !ecs_map_get(&impl->entity_index, slot->entity)) { ecs_map_insert_ptr(&impl->entity_index, slot->entity, entity); } } return 0; } static bool flecs_script_edit_is_template_entity( const ecs_world_t *world, ecs_entity_t entity) { if (!world || !ecs_is_alive(world, entity)) { return false; } return ecs_get_target(world, entity, EcsScriptTemplate, 0) != 0; } static ecs_script_entity_t* flecs_script_edit_find( ecs_script_impl_t *impl, ecs_entity_t entity) { if (!entity) { return NULL; } if (flecs_script_edit_is_template_entity(impl->pub.world, entity)) { return NULL; } if (!impl->entity_index_valid || impl->entity_index_visit != impl->state.visit) { ecs_map_fini(&impl->entity_index); ecs_map_init(&impl->entity_index, NULL); flecs_script_edit_walk_t walk = {flecs_script_edit_index_entity, impl}; flecs_script_edit_walk(impl->root, &walk); impl->entity_index_valid = true; impl->entity_index_visit = impl->state.visit; } return ecs_map_get_ptr(&impl->entity_index, entity); } void flecs_script_edit_cache_fini( ecs_script_impl_t *impl) { if (impl->entity_index_valid) { ecs_map_fini(&impl->entity_index); impl->entity_index_valid = false; } if (impl->line_index_code) { ecs_vec_fini_t(NULL, &impl->line_index, int32_t); impl->line_index_code = NULL; } } /* Index the start offset of every line of the script source, so the line and * column of a position can be found without scanning the source again. */ static void flecs_script_edit_line_index( ecs_script_impl_t *impl) { const char *code = impl->pub.code; if (impl->line_index_code == code) { return; } if (impl->line_index_code) { ecs_vec_fini_t(NULL, &impl->line_index, int32_t); } ecs_vec_init_t(NULL, &impl->line_index, int32_t, 0); ecs_vec_append_t(NULL, &impl->line_index, int32_t)[0] = 0; const char *ptr; for (ptr = code; ptr[0]; ptr ++) { if (ptr[0] == '\n') { ecs_vec_append_t(NULL, &impl->line_index, int32_t)[0] = flecs_ito(int32_t, (ptr + 1) - code); } } impl->line_index_code = code; impl->line_index_len = flecs_ito(int32_t, ptr - code); } static void flecs_script_edit_line_col( ecs_script_impl_t *impl, const char *pos, int32_t *line, int32_t *column) { const char *code = impl->pub.code; line[0] = 0; column[0] = 0; if (!code || !pos || pos < code) { return; } flecs_script_edit_line_index(impl); int32_t offset = flecs_ito(int32_t, pos - code); if (offset > impl->line_index_len) { return; } int32_t *starts = ecs_vec_first(&impl->line_index); int32_t low = 0, high = ecs_vec_count(&impl->line_index) - 1; while (low < high) { int32_t mid = low + ((high - low + 1) / 2); if (starts[mid] <= offset) { low = mid; } else { high = mid - 1; } } line[0] = low + 1; column[0] = (offset - starts[low]) + 1; } typedef struct flecs_script_edit_symbol_t { int32_t symbol; ecs_script_entity_t *node; } flecs_script_edit_symbol_t; static int flecs_script_edit_find_symbol( ecs_script_entity_t *node, void *ctx) { flecs_script_edit_symbol_t *symbol = ctx; if (node->symbol != symbol->symbol) { return 0; } symbol->node = node; return 1; } static ecs_script_entity_t* flecs_script_edit_find_in_instance( ecs_script_impl_t *impl, ecs_entity_t entity, ecs_entity_t instance, ecs_entity_t template_entity) { ecs_world_t *world = impl->pub.world; const EcsScript *sc = ecs_get(world, template_entity, EcsScript); if (!sc || !sc->script || !sc->template_) { return NULL; } if (sc->script != &impl->pub) { return NULL; } const EcsScriptTemplateRoot *root = ecs_get_pair( world, instance, EcsScriptTemplateRoot, template_entity); if (!root) { return NULL; } ecs_script_template_t *template = sc->template_; const ecs_script_symbol_slot_t *slots = ecs_vec_first(&root->state.symbol_slots); int32_t i, count = ecs_vec_count(&root->state.symbol_slots); int32_t slot = -1; for (i = 0; i < count; i ++) { if (slots[i].entity == entity) { slot = i; break; } } if (slot == -1) { return NULL; } int32_t symbol = template->symbol_offset + slot; if (symbol == template->root_symbol) { return NULL; } if (!template->node || !template->node->scope) { return NULL; } flecs_script_edit_symbol_t find = {symbol, NULL}; flecs_script_edit_walk_t walk = {flecs_script_edit_find_symbol, &find}; flecs_script_edit_walk(template->node->scope, &walk); ecs_script_entity_t *node = find.node; if (!node || node->name_expr || !node->node.pos || !node->node.end) { return NULL; } return node; } static ecs_script_entity_t* flecs_script_edit_find_in_template( ecs_script_impl_t *impl, ecs_entity_t entity, ecs_entity_t *template_out) { ecs_world_t *world = impl->pub.world; if (!world || !entity || !ecs_is_alive(world, entity)) { return NULL; } ecs_entity_t instance = ecs_get_target(world, entity, EcsChildOf, 0); while (instance) { int32_t i = 0; ecs_entity_t template_entity; while ((template_entity = ecs_get_target( world, instance, ecs_id(EcsScriptTemplateRoot), i ++))) { ecs_script_entity_t *node = flecs_script_edit_find_in_instance( impl, entity, instance, template_entity); if (node) { if (template_out) { template_out[0] = template_entity; } return node; } } instance = ecs_get_target(world, instance, EcsChildOf, 0); } return NULL; } static ecs_script_entity_t* flecs_script_edit_find_w_template( ecs_script_impl_t *impl, ecs_entity_t entity, ecs_entity_t *template_out) { if (template_out) { template_out[0] = 0; } ecs_script_entity_t *node = flecs_script_edit_find(impl, entity); if (!node) { node = flecs_script_edit_find_in_template(impl, entity, template_out); } if (!node || !node->node.pos || !node->node.end) { return NULL; } return node; } bool ecs_script_entity_source( const ecs_script_t *script, ecs_entity_t entity, ecs_script_source_t *source) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_impl_t *impl = flecs_script_impl( ECS_CONST_CAST(ecs_script_t*, script)); ecs_entity_t template_entity = 0; ecs_script_entity_t *node = flecs_script_edit_find_w_template( impl, entity, &template_entity); if (!node) { return false; } if (source) { source->offset = flecs_ito(int32_t, node->node.pos - script->code); source->length = flecs_ito(int32_t, node->node.end - node->node.pos); source->has_scope = node->scope != NULL && node->scope->open != NULL; source->template_ = template_entity; flecs_script_edit_line_col(impl, node->node.pos, &source->line, &source->column); } return true; error: return false; } static ecs_entity_t flecs_script_edit_template_owner( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t src, ecs_entity_t rel, bool *has_template) { ecs_entity_t template_entity; int32_t i = 0; while ((template_entity = ecs_get_target(world, src, rel, i ++))) { *has_template = true; const EcsScript *sc = ecs_get(world, template_entity, EcsScript); if (!sc || !sc->script) { continue; } ecs_entity_t owner = flecs_script_impl(sc->script)->entity; if (owner && ecs_script_entity_source(sc->script, entity, NULL)) { return owner; } } return 0; } ecs_entity_t ecs_script_entity_owner( const ecs_world_t *stage, ecs_entity_t entity) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); if (!entity || !ecs_is_alive(world, entity)) { return 0; } bool in_template_body = false; ecs_entity_t owner = flecs_script_edit_template_owner( world, entity, entity, EcsScriptTemplate, &in_template_body); if (owner || in_template_body) { return owner; } ecs_entity_t script_entity = ecs_get_target( world, entity, ecs_id(EcsScript), 0); if (script_entity) { const EcsScript *sc = ecs_get(world, script_entity, EcsScript); if (sc && sc->script && !sc->template_ && ecs_script_entity_source(sc->script, entity, NULL)) { return script_entity; } } ecs_entity_t instance = entity; while ((instance = ecs_get_target(world, instance, EcsChildOf, 0))) { owner = flecs_script_edit_template_owner(world, entity, instance, ecs_id(EcsScriptTemplateRoot), &in_template_body); if (owner) { return owner; } } return 0; error: return 0; } static void flecs_script_edit_using_scopes( ecs_script_impl_t *impl, ecs_vec_t *scopes) { ecs_world_t *world = impl->pub.world; ecs_script_node_t **stmts = ecs_vec_first(&impl->root->stmts); int32_t i, count = ecs_vec_count(&impl->root->stmts); for (i = 0; i < count; i ++) { if (stmts[i]->kind == EcsAstModule) { ecs_script_module_t *m = (ecs_script_module_t*)stmts[i]; if (m->eval) { ecs_vec_append_t(NULL, scopes, ecs_entity_t)[0] = m->eval; } } else if (stmts[i]->kind == EcsAstUsing) { ecs_script_using_t *u = (ecs_script_using_t*)stmts[i]; if (!u->eval) { continue; } int32_t len = ecs_os_strlen(u->name); bool wildcard = len > 2 && !ecs_os_strcmp(&u->name[len - 2], ".*"); ecs_vec_append_t(NULL, scopes, ecs_entity_t)[0] = u->eval; if (wildcard) { ecs_iter_t it = ecs_children(world, u->eval); while (ecs_children_next(&it)) { int32_t e; for (e = 0; e < it.count; e ++) { ecs_vec_append_t(NULL, scopes, ecs_entity_t)[0] = it.entities[e]; } } } } } } static ecs_entity_t flecs_script_edit_lookup( ecs_world_t *world, const ecs_vec_t *scopes, const char *name) { ecs_entity_t result = ecs_lookup_path_w_sep( world, 0, name, ".", NULL, false); if (result) { return result; } ecs_entity_t *elems = ecs_vec_first(scopes); int32_t i, count = ecs_vec_count(scopes); for (i = 0; i < count; i ++) { result = ecs_lookup_path_w_sep( world, elems[i], name, ".", NULL, false); if (result) { return result; } } return 0; } static char* flecs_script_edit_entity_name( ecs_script_impl_t *impl, const ecs_vec_t *scopes, ecs_entity_t entity) { ecs_world_t *world = impl->pub.world; char *best = ecs_get_path_w_sep(world, 0, entity, ".", NULL); if (!best) { return NULL; } ecs_size_t best_len = ecs_os_strlen(best); ecs_entity_t *elems = ecs_vec_first(scopes); int32_t i, count = ecs_vec_count(scopes); for (i = 0; i < count; i ++) { if (!elems[i] || elems[i] == entity) { continue; } char *rel = ecs_get_path_w_sep(world, elems[i], entity, ".", NULL); if (!rel) { continue; } ecs_size_t rel_len = ecs_os_strlen(rel); if (!rel_len || rel_len >= best_len || flecs_script_edit_lookup(world, scopes, rel) != entity) { ecs_os_free(rel); continue; } ecs_os_free(best); best = rel; best_len = rel_len; } return best; } static char* flecs_script_edit_id_str( ecs_script_impl_t *impl, const ecs_vec_t *scopes, ecs_id_t id) { ecs_world_t *world = impl->pub.world; if (ECS_IS_PAIR(id)) { char *first = flecs_script_edit_entity_name( impl, scopes, ecs_pair_first(world, id)); char *second = flecs_script_edit_entity_name( impl, scopes, ecs_pair_second(world, id)); if (!first || !second) { ecs_os_free(first); ecs_os_free(second); return NULL; } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_strbuf_appendlit(&buf, "("); ecs_strbuf_appendstr(&buf, first); ecs_strbuf_appendlit(&buf, ", "); ecs_strbuf_appendstr(&buf, second); ecs_strbuf_appendlit(&buf, ")"); ecs_os_free(first); ecs_os_free(second); return ecs_strbuf_get(&buf); } if (id & ECS_ID_FLAGS_MASK) { return NULL; } return flecs_script_edit_entity_name(impl, scopes, id); } static flecs_script_value_style_t flecs_script_edit_value_style( const char *pos, const char *end) { if (pos >= end) { return FlecsScriptValueOther; } char open = pos[0]; if (open != '{' && open != '(') { return FlecsScriptValueOther; } char close = (open == '{') ? '}' : ')'; const char *cur = flecs_script_edit_skip_ws(pos + 1, end); if (cur >= end || cur[0] == close) { return FlecsScriptValueEmpty; } if (!isalpha((unsigned char)cur[0]) && cur[0] != '_') { return FlecsScriptValuePositional; } while (cur < end && flecs_script_edit_is_ident(cur[0])) { cur ++; } cur = flecs_script_edit_skip_ws(cur, end); if (cur < end && cur[0] == ':' && ((cur + 1) >= end || cur[1] != ':')) { return FlecsScriptValueNamed; } return FlecsScriptValuePositional; } static char* flecs_script_edit_value_str( ecs_world_t *world, ecs_id_t component, const void *value, const char *str, flecs_script_value_style_t style, bool parens) { if (str) { return ecs_os_strdup(str); } ecs_entity_t type = ecs_get_typeid(world, component); if (!type) { char *idstr = ecs_id_str(world, component); ecs_err("cannot serialize value for '%s': not a component", idstr); ecs_os_free(idstr); return NULL; } bool positional = style == FlecsScriptValuePositional || style == FlecsScriptValueEmpty; char *expr = flecs_script_ptr_to_expr_precise(world, type, value, positional); if (!expr) { return NULL; } if (parens) { ecs_size_t len = ecs_os_strlen(expr); if (len >= 2 && expr[0] == '{' && expr[len - 1] == '}') { expr[0] = '('; expr[len - 1] = ')'; } } return expr; } static ecs_script_node_t* flecs_script_edit_find_stmt( ecs_script_entity_t *entity, ecs_id_t component) { ecs_script_scope_t *scope = entity->scope; if (!scope) { return NULL; } ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); int32_t i, count = ecs_vec_count(&scope->stmts); ecs_script_node_t *result = NULL; for (i = 0; i < count; i ++) { ecs_script_node_t *node = stmts[i]; if (node->kind == EcsAstComponent) { if (((ecs_script_component_t*)node)->id.eval == component) { result = node; } } else if (node->kind == EcsAstTag) { if (((ecs_script_tag_t*)node)->id.eval == component) { result = node; } } } return result; } static const char* flecs_script_edit_line_start( const char *code, const char *pos) { while (pos > code && pos[-1] != '\n') { pos --; } return pos; } static char* flecs_script_edit_indent_str( const char *code, const char *pos) { const char *start = flecs_script_edit_line_start(code, pos); const char *cur = start; while (cur < pos && (cur[0] == ' ' || cur[0] == '\t')) { cur ++; } int32_t len = flecs_ito(int32_t, cur - start); char *result = ecs_os_malloc_n(char, len + 1); ecs_os_memcpy(result, start, len); result[len] = '\0'; return result; } static char* flecs_script_edit_child_indent( const char *indent) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_strbuf_appendstr(&buf, indent); if (strchr(indent, 0x09)) { ecs_strbuf_appendlit(&buf, "\t"); } else { ecs_strbuf_appendlit(&buf, " "); } return ecs_strbuf_get(&buf); } static void flecs_script_edit_line_span( const char *code, const char *start, const char *end, int32_t *offset_out, int32_t *length_out) { const char *begin = flecs_script_edit_line_start(code, start); const char *cur = begin; while (cur < start && (cur[0] == ' ' || cur[0] == '\t')) { cur ++; } if (cur != start) { begin = start; } const char *finish = end; while (finish[0] == ' ' || finish[0] == '\t' || finish[0] == '\r') { finish ++; } if (finish[0] == ';') { finish ++; while (finish[0] == ' ' || finish[0] == '\t' || finish[0] == '\r') { finish ++; } } if (finish[0] == '/' && finish[1] == '/') { while (finish[0] && finish[0] != '\n') { finish ++; } } if (finish[0] == '\n') { finish ++; } else if (finish[0]) { finish = end; } *offset_out = flecs_ito(int32_t, begin - code); *length_out = flecs_ito(int32_t, finish - begin); } static void flecs_script_edit_collapse_blank( const char *code, int32_t *offset_out, int32_t *length_out) { const char *begin = &code[*offset_out]; const char *finish = begin + *length_out; bool blank_before = false; { const char *cur = begin; int32_t newlines = 0; while (cur > code) { char c = cur[-1]; if (c == '\n') { newlines ++; if (newlines >= 2) { break; } } else if (c != ' ' && c != '\t' && c != '\r') { break; } cur --; } if (cur == code || newlines >= 2 || cur[-1] == '{') { blank_before = true; } } if (!blank_before) { return; } bool blank_after = false; int32_t newlines_after = 0; { const char *cur = finish; while (cur[0]) { char c = cur[0]; if (c == '\n') { newlines_after ++; } else if (c != ' ' && c != '\t' && c != '\r') { break; } cur ++; } if (!cur[0] || newlines_after >= 1 || cur[0] == '}') { blank_after = true; } } if (!blank_after) { return; } if (newlines_after >= 1) { const char *cur = finish; while (cur[0] == ' ' || cur[0] == '\t' || cur[0] == '\r') { cur ++; } if (cur[0] == '\n') { *length_out += flecs_ito(int32_t, (cur + 1) - finish); } return; } if (begin > code && begin[-1] == '\n') { const char *cur = begin - 1; while (cur > code && (cur[-1] == ' ' || cur[-1] == '\t' || cur[-1] == '\r')) { cur --; } if (cur == code || cur[-1] == '\n') { int32_t delta = flecs_ito(int32_t, begin - cur); *offset_out -= delta; *length_out += delta; } } } ecs_script_edits_t* ecs_script_edits_new( ecs_script_t *script) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_edits_t *result = ecs_os_calloc_t(ecs_script_edits_t); result->script = script; ecs_vec_init_t(NULL, &result->edits, flecs_script_edit_t, 0); ecs_vec_init_t(NULL, &result->scopes, ecs_entity_t, 0); ecs_map_init(&result->id_strs, NULL); flecs_hashmap_init(&result->index, flecs_script_edit_key_t, int32_t, flecs_script_edit_key_hash, flecs_script_edit_key_compare, NULL); return result; error: return NULL; } void ecs_script_edits_free( ecs_script_edits_t *edits) { if (!edits) { return; } flecs_script_edit_t *elems = ecs_vec_first(&edits->edits); int32_t i, count = ecs_vec_count(&edits->edits); for (i = 0; i < count; i ++) { ecs_os_free(elems[i].text); } ecs_map_iter_t mit = ecs_map_iter(&edits->id_strs); while (ecs_map_next(&mit)) { ecs_os_free(ecs_map_ptr(&mit)); } ecs_map_fini(&edits->id_strs); flecs_hashmap_fini(&edits->index); ecs_vec_fini_t(NULL, &edits->scopes, ecs_entity_t); ecs_vec_fini_t(NULL, &edits->edits, flecs_script_edit_t); ecs_os_free(edits); } static flecs_script_edit_t* flecs_script_edit_add( ecs_script_edits_t *edits, ecs_entity_t entity, ecs_id_t component, flecs_script_edit_kind_t kind, int32_t offset, int32_t length, char *text) { flecs_script_edit_key_t key = { entity, component }; flecs_hashmap_result_t slot = flecs_hashmap_ensure( &edits->index, &key, int32_t); int32_t *index = slot.value; if (index[0]) { flecs_script_edit_t *elem = ecs_vec_get_t( &edits->edits, flecs_script_edit_t, index[0] - 1); ecs_os_free(elem->text); elem->kind = kind; elem->offset = offset; elem->length = length; elem->open_len = 0; elem->close_len = 0; elem->text = text; return elem; } flecs_script_edit_t *elem = ecs_vec_append_t( NULL, &edits->edits, flecs_script_edit_t); elem->entity = entity; elem->component = component; elem->kind = kind; elem->offset = offset; elem->length = length; elem->seq = edits->seq ++; elem->open_len = 0; elem->close_len = 0; elem->text = text; index[0] = ecs_vec_count(&edits->edits); return elem; } static ecs_script_entity_t* flecs_script_edit_entity( ecs_script_edits_t *edits, ecs_entity_t entity) { ecs_script_impl_t *impl = flecs_script_impl(edits->script); ecs_script_entity_t *node = flecs_script_edit_find_w_template( impl, entity, NULL); if (!node) { char *path = ecs_get_path(edits->script->world, entity); ecs_err("entity '%s' is not declared by an editable statement in " "script '%s'", path ? path : "?", edits->script->name); ecs_os_free(path); return NULL; } return node; } /* Resolve the identifier that is written for a component. The identifier and * the scopes it is resolved against are cached for the edit session. */ static const char* flecs_script_edit_component_str( ecs_script_edits_t *edits, ecs_id_t component) { ecs_map_val_t *cached = ecs_map_get(&edits->id_strs, component); if (cached) { return (const char*)(uintptr_t)cached[0]; } ecs_script_impl_t *impl = flecs_script_impl(edits->script); if (!edits->scopes_init) { flecs_script_edit_using_scopes(impl, &edits->scopes); edits->scopes_init = true; } char *result = flecs_script_edit_id_str(impl, &edits->scopes, component); ecs_map_insert_ptr(&edits->id_strs, component, result); return result; } static int flecs_script_edit_set_text( ecs_script_edits_t *edits, ecs_entity_t entity, ecs_id_t component, const void *value, const char *expr) { ecs_check(edits != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); ecs_script_t *script = edits->script; const char *code = script->code; ecs_script_entity_t *node = flecs_script_edit_entity(edits, entity); if (!node) { return -1; } ecs_script_node_t *stmt = flecs_script_edit_find_stmt(node, component); if (stmt && stmt->kind == EcsAstComponent) { ecs_script_component_t *comp = (ecs_script_component_t*)stmt; if (comp->value_pos && comp->value_end && comp->value_pos >= code && comp->value_end > comp->value_pos) { const char *value_pos = comp->value_pos; const char *value_end = comp->value_end; while (value_pos < value_end && isspace((unsigned char)value_pos[0])) { value_pos ++; } flecs_script_value_style_t style = flecs_script_edit_value_style(value_pos, value_end); char *text = flecs_script_edit_value_str(script->world, component, value, expr, style, value_pos[0] == '('); if (!text) { return -1; } flecs_script_edit_add(edits, entity, component, FlecsScriptEditValue, flecs_ito(int32_t, value_pos - code), flecs_ito(int32_t, value_end - value_pos), text); return 0; } } const char *id_str = flecs_script_edit_component_str(edits, component); if (!id_str) { char *idstr = ecs_id_str(script->world, component); ecs_err("cannot write component '%s' to script '%s'", idstr ? idstr : "?", script->name); ecs_os_free(idstr); return -1; } char *value_str = flecs_script_edit_value_str(script->world, component, value, expr, FlecsScriptValueNamed, false); if (!value_str) { return -1; } ecs_strbuf_t stmt_buf = ECS_STRBUF_INIT; ecs_strbuf_appendstr(&stmt_buf, id_str); ecs_strbuf_appendlit(&stmt_buf, ": "); ecs_strbuf_appendstr(&stmt_buf, value_str); ecs_os_free(value_str); char *stmt_str = ecs_strbuf_get(&stmt_buf); if (stmt && stmt->kind == EcsAstTag && stmt->pos && stmt->end) { flecs_script_edit_add(edits, entity, component, FlecsScriptEditValue, flecs_ito(int32_t, stmt->pos - code), flecs_ito(int32_t, stmt->end - stmt->pos), stmt_str); return 0; } char *indent = flecs_script_edit_indent_str(code, node->node.pos); char *child_indent = flecs_script_edit_child_indent(indent); ecs_script_scope_t *scope = node->scope; ecs_strbuf_t buf = ECS_STRBUF_INIT; int32_t offset, open_len = 0, close_len = 0; if (scope && scope->open && scope->node.end) { const char *close = scope->node.end - 1; const char *anchor = scope->open + 1; const char *last_indent = child_indent; char *stmt_indent = NULL; { ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { if (stmts[i]->end && stmts[i]->end > anchor && stmts[i]->end <= close) { anchor = stmts[i]->end; stmt_indent = flecs_script_edit_indent_str( code, stmts[i]->pos); } } } if (stmt_indent) { last_indent = stmt_indent; } const char *newline = NULL; const char *cur; for (cur = anchor; cur < close; cur ++) { if (cur[0] == '\n') { newline = cur; } } if (!newline) { ecs_strbuf_appendlit(&buf, "\n"); } ecs_strbuf_appendstr(&buf, last_indent); ecs_strbuf_appendstr(&buf, stmt_str); ecs_strbuf_appendlit(&buf, "\n"); if (newline) { offset = flecs_ito(int32_t, (newline + 1) - code); } else { ecs_strbuf_appendstr(&buf, indent); offset = flecs_ito(int32_t, close - code); open_len = 1; close_len = ecs_os_strlen(indent); } ecs_os_free(stmt_indent); } else { ecs_strbuf_appendlit(&buf, " {\n"); ecs_strbuf_appendstr(&buf, child_indent); ecs_strbuf_appendstr(&buf, stmt_str); ecs_strbuf_appendlit(&buf, "\n"); ecs_strbuf_appendstr(&buf, indent); ecs_strbuf_appendlit(&buf, "}"); offset = flecs_ito(int32_t, node->node.end - code); open_len = 3; close_len = ecs_os_strlen(indent) + 1; } ecs_os_free(stmt_str); ecs_os_free(indent); ecs_os_free(child_indent); flecs_script_edit_t *edit = flecs_script_edit_add(edits, entity, component, FlecsScriptEditValue, offset, 0, ecs_strbuf_get(&buf)); edit->open_len = open_len; edit->close_len = close_len; return 0; error: return -1; } int ecs_script_edits_set( ecs_script_edits_t *edits, ecs_entity_t entity, ecs_id_t component, const void *value) { ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_edit_set_text(edits, entity, component, value, NULL); error: return -1; } int ecs_script_edits_set_expr( ecs_script_edits_t *edits, ecs_entity_t entity, ecs_id_t component, const char *expr) { ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_edit_set_text(edits, entity, component, NULL, expr); error: return -1; } int ecs_script_edits_remove( ecs_script_edits_t *edits, ecs_entity_t entity, ecs_id_t component) { ecs_check(edits != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(component != 0, ECS_INVALID_PARAMETER, NULL); ecs_script_entity_t *node = flecs_script_edit_entity(edits, entity); if (!node) { return -1; } ecs_script_node_t *stmt = flecs_script_edit_find_stmt(node, component); if (!stmt) { return 0; } if (!stmt->pos || !stmt->end) { char *idstr = ecs_id_str(edits->script->world, component); ecs_err("cannot remove '%s': component is part of the entity statement", idstr ? idstr : "?"); ecs_os_free(idstr); return -1; } int32_t offset, length; flecs_script_edit_line_span(edits->script->code, stmt->pos, stmt->end, &offset, &length); flecs_script_edit_add(edits, entity, component, FlecsScriptEditRemove, offset, length, ecs_os_strdup("")); return 0; error: return -1; } int ecs_script_edits_delete( ecs_script_edits_t *edits, ecs_entity_t entity) { ecs_check(edits != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_entity_t *node = flecs_script_edit_entity(edits, entity); if (!node) { return -1; } int32_t offset, length; flecs_script_edit_line_span(edits->script->code, node->node.pos, node->node.end, &offset, &length); flecs_script_edit_collapse_blank(edits->script->code, &offset, &length); flecs_script_edit_add(edits, entity, 0, FlecsScriptEditDelete, offset, length, ecs_os_strdup("")); return 0; error: return -1; } int ecs_script_edits_clear( ecs_script_edits_t *edits, ecs_entity_t entity, ecs_id_t component) { ecs_check(edits != NULL, ECS_INVALID_PARAMETER, NULL); flecs_script_edit_key_t key = { entity, component }; int32_t *index = flecs_hashmap_get(&edits->index, &key, int32_t); if (!index || !index[0]) { return -1; } int32_t i = index[0] - 1; flecs_script_edit_t *elems = ecs_vec_first(&edits->edits); ecs_os_free(elems[i].text); flecs_hashmap_remove_w_hash(&edits->index, &key, int32_t, flecs_script_edit_key_hash(&key)); ecs_vec_remove_t(&edits->edits, flecs_script_edit_t, i); if (i < ecs_vec_count(&edits->edits)) { flecs_script_edit_key_t moved = { elems[i].entity, elems[i].component }; int32_t *moved_index = flecs_hashmap_get( &edits->index, &moved, int32_t); ecs_assert(moved_index != NULL, ECS_INTERNAL_ERROR, NULL); moved_index[0] = i + 1; } return 0; error: return -1; } int32_t ecs_script_edits_count( const ecs_script_edits_t *edits) { ecs_check(edits != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_vec_count(&edits->edits); error: return 0; } static int flecs_script_edit_compare( const void *a_ptr, const void *b_ptr) { const flecs_script_edit_t *a = a_ptr; const flecs_script_edit_t *b = b_ptr; if (a->offset != b->offset) { return a->offset < b->offset ? -1 : 1; } if (a->kind != b->kind) { if (a->kind == FlecsScriptEditDelete) { return -1; } if (b->kind == FlecsScriptEditDelete) { return 1; } } if (a->length != b->length) { return a->length > b->length ? -1 : 1; } return a->seq < b->seq ? -1 : (a->seq > b->seq); } char* ecs_script_edits_apply( const ecs_script_edits_t *edits) { ecs_check(edits != NULL, ECS_INVALID_PARAMETER, NULL); const char *code = edits->script->code; if (!code) { code = ""; } int32_t count = ecs_vec_count(&edits->edits); if (!count) { return ecs_os_strdup(code); } flecs_script_edit_t *sorted = ecs_os_malloc_n(flecs_script_edit_t, count); ecs_os_memcpy_n(sorted, ecs_vec_first(&edits->edits), flecs_script_edit_t, count); qsort(sorted, flecs_itosize(count), sizeof(flecs_script_edit_t), flecs_script_edit_compare); ecs_strbuf_t buf = ECS_STRBUF_INIT; int32_t i, cursor = 0, delete_start = 0, delete_end = 0; for (i = 0; i < count; i ++) { flecs_script_edit_t *edit = &sorted[i]; if (edit->kind == FlecsScriptEditDelete && edit->offset >= cursor) { delete_start = edit->offset; delete_end = edit->offset + edit->length; } if (edit->offset < cursor) { if (edit->offset >= delete_start && (edit->offset + edit->length) <= delete_end) { continue; } ecs_err("conflicting edits for script '%s'", edits->script->name); ecs_os_free(sorted); ecs_strbuf_reset(&buf); return NULL; } ecs_strbuf_appendstrn(&buf, &code[cursor], edit->offset - cursor); /* Inserts that open a scope for the same entity at the same offset * share the scope open and close text of the first insert. */ int32_t last = i; if (!edit->length && (edit->open_len || edit->close_len)) { while ((last + 1) < count) { flecs_script_edit_t *next = &sorted[last + 1]; if (next->offset != edit->offset || next->length || next->entity != edit->entity || (!next->open_len && !next->close_len)) { break; } last ++; } } if (last != i) { ecs_strbuf_appendstrn(&buf, edit->text, edit->open_len); int32_t j; for (j = i; j <= last; j ++) { flecs_script_edit_t *e = &sorted[j]; ecs_strbuf_appendstrn(&buf, &e->text[e->open_len], ecs_os_strlen(e->text) - e->open_len - e->close_len); } ecs_strbuf_appendstr(&buf, &edit->text[ecs_os_strlen(edit->text) - edit->close_len]); i = last; } else if (edit->text && edit->text[0]) { ecs_strbuf_appendstr(&buf, edit->text); } cursor = edit->offset + edit->length; } ecs_strbuf_appendstr(&buf, &code[cursor]); ecs_os_free(sorted); char *result = ecs_strbuf_get(&buf); if (!result) { result = ecs_os_strdup(""); } return result; error: return NULL; } static bool flecs_script_edit_expr_is_const( ecs_expr_node_t *node) { if (!node) { return false; } switch(node->kind) { case EcsExprValue: case EcsExprEmptyInitializer: return true; case EcsExprInitializer: { ecs_expr_initializer_t *init = (ecs_expr_initializer_t*)node; ecs_expr_initializer_element_t *elems = ecs_vec_first(&init->elements); int32_t i, count = ecs_vec_count(&init->elements); for (i = 0; i < count; i ++) { if (elems[i].key && !flecs_script_edit_expr_is_const(elems[i].key)) { return false; } if (!flecs_script_edit_expr_is_const(elems[i].value)) { return false; } } return true; } case EcsExprIdentifier: return flecs_script_edit_expr_is_const( ((ecs_expr_identifier_t*)node)->expr); case EcsExprCast: case EcsExprCastNumber: return flecs_script_edit_expr_is_const(((ecs_expr_cast_t*)node)->expr); case EcsExprUnary: return flecs_script_edit_expr_is_const(((ecs_expr_unary_t*)node)->expr); case EcsExprBinary: return flecs_script_edit_expr_is_const( ((ecs_expr_binary_t*)node)->left) && flecs_script_edit_expr_is_const( ((ecs_expr_binary_t*)node)->right); case EcsExprInterpolatedString: case EcsExprVariable: case EcsExprGlobalVariable: case EcsExprFunction: case EcsExprMethod: case EcsExprTemplate: case EcsExprMember: case EcsExprSwizzle: case EcsExprElement: case EcsExprComponent: case EcsExprHas: case EcsExprMatch: case EcsExprRange: case EcsExprNew: case EcsExprScript: default: return false; } } static bool flecs_script_edit_text_has_var( const char *pos, const char *end) { while (pos < end) { char c = pos[0]; if (c == '"' || c == '`') { pos ++; while (pos < end && pos[0] != c) { if (pos[0] == '\\' && (pos + 1) < end) { pos ++; } pos ++; } } else if (c == '$') { return true; } pos ++; } return false; } static int flecs_script_edit_stmt_changed( ecs_world_t *world, ecs_script_t *script, ecs_entity_t entity, ecs_script_component_t *stmt, const void **current_out) { ecs_id_t id = stmt->id.eval; *current_out = NULL; if (!id || !stmt->expr || stmt->id.interface) { return 0; } if (!ECS_IS_PAIR(id) && ecs_has(world, id, EcsScriptVisitor)) { return 0; } if (!ecs_owns_id(world, entity, id)) { return 0; } const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (!ti || !stmt->expr->type_info) { return 0; } if (!flecs_script_edit_expr_is_const(stmt->expr)) { return 0; } if (!stmt->value_pos || !stmt->value_end || flecs_script_edit_text_has_var(stmt->value_pos, stmt->value_end)) { return 0; } const void *current = ecs_get_id(world, entity, id); if (!current) { return 0; } int result = 0; char *current_str = NULL, *script_str = NULL; ecs_value_t value = { .ptr = ecs_os_malloc(ti->size), .type = ti->component }; if (!ti->hooks.ctor) { ecs_os_memset(value.ptr, 0, ti->size); } else { flecs_type_info_ctor(value.ptr, 1, ti); } ecs_expr_eval_desc_t desc = { .name = script->name, .type = ti->component }; if (flecs_expr_visit_eval(script, stmt->expr, &desc, &value)) { result = -1; goto done; } current_str = flecs_script_ptr_to_expr_precise( world, ti->component, current, true); script_str = flecs_script_ptr_to_expr_precise( world, ti->component, value.ptr, true); if (!current_str || !script_str) { result = -1; goto done; } if (ecs_os_strcmp(current_str, script_str)) { *current_out = current; result = 1; } done: ecs_os_free(current_str); ecs_os_free(script_str); flecs_type_info_dtor(value.ptr, 1, ti); ecs_os_free(value.ptr); return result; } static int flecs_script_edit_entity_from_scene( ecs_world_t *world, ecs_script_edits_t *edits, ecs_entity_t entity) { ecs_script_impl_t *impl = flecs_script_impl(edits->script); ecs_script_entity_t *node = flecs_script_edit_find(impl, entity); if (!node || !node->node.pos || !node->node.end || !node->scope) { return 0; } ecs_script_node_t **stmts = ecs_vec_first(&node->scope->stmts); int32_t i, count = ecs_vec_count(&node->scope->stmts); for (i = 0; i < count; i ++) { if (stmts[i]->kind != EcsAstComponent) { continue; } ecs_script_component_t *stmt = (ecs_script_component_t*)stmts[i]; if (flecs_script_edit_find_stmt(node, stmt->id.eval) != stmts[i]) { continue; } const void *current = NULL; int changed = flecs_script_edit_stmt_changed( world, edits->script, entity, stmt, ¤t); if (changed == -1) { return -1; } if (!changed) { continue; } if (ecs_script_edits_set(edits, entity, stmt->id.eval, current)) { return -1; } } return 0; } int ecs_script_from_scene( ecs_world_t *world, ecs_entity_t script) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(script != 0, ECS_INVALID_PARAMETER, NULL); const EcsScript *s = ecs_get(world, script, EcsScript); if (!s || !s->script || s->template_) { char *path = ecs_get_path(world, script); ecs_err("entity '%s' is not a managed script", path); ecs_os_free(path); return -1; } int result = 0; char *code = NULL; ecs_script_edits_t *edits = ecs_script_edits_new(s->script); if (!edits) { return -1; } ecs_iter_t it = ecs_each_pair_t(world, EcsScript, script); while (ecs_each_next(&it)) { int32_t i; for (i = 0; i < it.count; i ++) { if (flecs_script_edit_entity_from_scene( world, edits, it.entities[i])) { ecs_iter_fini(&it); result = -1; goto done; } } } if (!ecs_script_edits_count(edits)) { goto done; } code = ecs_script_edits_apply(edits); if (!code) { result = -1; goto done; } if (s->code && !ecs_os_strcmp(code, s->code)) { goto done; } ecs_script_edits_free(edits); edits = NULL; if (ecs_script_reload(world, script, code)) { result = -1; goto done; } result = 1; done: ecs_script_edits_free(edits); ecs_os_free(code); return result; error: return -1; } int ecs_script_save( ecs_world_t *world, ecs_entity_t script) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(script != 0, ECS_INVALID_PARAMETER, NULL); const EcsScript *s = ecs_get(world, script, EcsScript); if (!s) { char *path = ecs_get_path(world, script); ecs_err("entity '%s' is not a script", path); ecs_os_free(path); return -1; } if (!s->filename) { return 0; } FILE *file = ecs_os_fopen(s->filename, "w"); if (!file) { ecs_err("cannot open file '%s' for writing", s->filename); return -1; } const char *code = s->code ? s->code : ""; size_t len = (size_t)ecs_os_strlen(code); int result = 0; if (len && fwrite(code, len, 1, file) != 1) { ecs_err("failed to write file '%s'", s->filename); result = -1; } ecs_os_fclose(file); return result; error: return -1; } #endif #ifdef FLECS_SCRIPT #ifdef FLECS_SCRIPT_ASYNC #define flecs_script_copy_async_callbacks(dst, src)\ dst->async_callback = src->async_callback;\ dst->async_cancel = src->async_cancel; #else #define flecs_script_copy_async_callbacks(dst, src) #endif static void ecs_script_params_free(ecs_vec_t *params) { int32_t i, count = ecs_vec_count(params); ecs_script_parameter_t *array = ecs_vec_first(params); for (i = 0; i < count; i ++) { /* Safe, component owns string */ ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); } ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); ecs_os_zeromem(params); } static void ecs_script_params_copy( ecs_vec_t *dst, const ecs_vec_t *src) { int32_t i, count = ecs_vec_count(src); if (!count) { ecs_os_zeromem(dst); return; } const ecs_script_parameter_t *src_array = ecs_vec_first(src); ecs_vec_init_t(NULL, dst, ecs_script_parameter_t, count); for (i = 0; i < count; i ++) { ecs_script_parameter_t *p = ecs_vec_append_t( NULL, dst, ecs_script_parameter_t); p->type = src_array[i].type; p->name = src_array[i].name ? ecs_os_strdup(src_array[i].name) : NULL; } } static void ecs_script_const_var_fini( EcsScriptConstVar *ptr) { if (!ptr->value.ptr) { return; } ecs_assert(ptr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_info_dtor(ptr->value.ptr, 1, ptr->type_info); ecs_os_free(ptr->value.ptr); ptr->value.ptr = NULL; ptr->value.type = 0; ptr->type_info = NULL; } static ECS_COPY(EcsScriptConstVar, dst, src, { ecs_script_const_var_fini(dst); dst->value.type = src->value.type; dst->type_info = src->type_info; if (src->value.ptr) { ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); dst->value.ptr = ecs_os_malloc(src->type_info->size); flecs_type_info_copy( dst->value.ptr, src->value.ptr, 1, src->type_info); } }) static ECS_MOVE(EcsScriptConstVar, dst, src, { ecs_script_const_var_fini(dst); *dst = *src; src->value.ptr = NULL; src->value.type = 0; src->type_info = NULL; }) static ECS_DTOR(EcsScriptConstVar, ptr, { ecs_script_const_var_fini(ptr); }) static void flecs_script_function_fini( EcsScriptFunction *ptr) { ecs_script_params_free(&ptr->params); if (ptr->binding_ctx && ptr->binding_ctx_free) { ptr->binding_ctx_free(ptr->binding_ctx); } ptr->binding_ctx = NULL; ptr->binding_ctx_free = NULL; } static ECS_COPY(EcsScriptFunction, dst, src, { flecs_script_function_fini(dst); dst->return_type = src->return_type; dst->callback = src->callback; flecs_script_copy_async_callbacks(dst, src) ecs_os_memcpy_n(dst->vector_callbacks, src->vector_callbacks, ecs_vector_function_callback_t, FLECS_SCRIPT_VECTOR_FUNCTION_COUNT); dst->ctx = src->ctx; ecs_script_params_copy(&dst->params, &src->params); }) static ECS_MOVE(EcsScriptFunction, dst, src, { flecs_script_function_fini(dst); *dst = *src; ecs_os_zeromem(src); }) static ECS_DTOR(EcsScriptFunction, ptr, { flecs_script_function_fini(ptr); }) static ecs_entity_t flecs_script_var_init( ecs_world_t *world, const char *name, ecs_entity_t parent, ecs_entity_t type, const void *value, ecs_entity_t component, const char *kind) { flecs_poly_assert(world, ecs_world_t); ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); if (ecs_lookup_child(world, parent, name) != 0) { ecs_err("cannot redeclare %s variable entity '%s' in parent '%s'", kind, name, flecs_errstr(ecs_get_path(world, parent))); return 0; } const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "ecs_%s_var_desc_t::type is not a valid type", kind); ecs_entity_t result = ecs_entity(world, { .name = name, .parent = parent }); if (!result) { goto error; } EcsScriptConstVar *v = ecs_ensure_id(world, result, component, ECS_SIZEOF(EcsScriptConstVar)); v->value.ptr = ecs_os_malloc(ti->size); v->value.type = type; v->type_info = ti; ecs_ptr_init(world, type, v->value.ptr); ecs_ptr_copy(world, type, v->value.ptr, value); ecs_modified_id(world, result, component); return result; error: return 0; } static ecs_value_t flecs_script_var_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t component) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const EcsScriptConstVar *v = ecs_get_id(world, entity, component); if (!v) { goto error; } return v->value; error: return (ecs_value_t){0}; } static void* flecs_script_var_get_w_type( const ecs_world_t *world, const char *name, ecs_entity_t type, ecs_size_t size, void *out, ecs_entity_t component, const char *kind) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_entity_t var = ecs_lookup(world, name); const EcsScriptConstVar *v = NULL; if (var) { v = ecs_get_id(world, var, component); } if (!v) { ecs_err("%s variable '%s' not found", kind, name); goto error; } const ecs_type_info_t *ti = ecs_get_type_info(world, type); if (!ti) { ecs_err("requested type for %s variable '%s' is not a valid type", kind, name); goto error; } if (ti->size != size) { ecs_err("size of requested type for %s variable '%s' does not " "match size of provided type", kind, name); goto error; } ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, out); if (ecs_meta_set_value(&cur, &v->value)) { ecs_err("value of %s variable '%s' cannot be converted to " "requested type", kind, name); goto error; } error: return out; } ecs_entity_t ecs_const_var_init( ecs_world_t *world, ecs_const_var_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_var_init(world, desc->name, desc->parent, desc->type, desc->value, ecs_id(EcsScriptConstVar), "const"); error: return 0; } ecs_value_t ecs_const_var_get( const ecs_world_t *world, ecs_entity_t entity) { return flecs_script_var_get(world, entity, ecs_id(EcsScriptConstVar)); } void* ecs_const_var_get_w_type( const ecs_world_t *world, const char *name, ecs_entity_t type, ecs_size_t size, void *out) { return flecs_script_var_get_w_type(world, name, type, size, out, ecs_id(EcsScriptConstVar), "const"); } ecs_value_t flecs_script_global_var_get( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t *component) { const EcsScriptConstVar *cv = ecs_get(world, entity, EcsScriptConstVar); if (cv) { if (component) { component[0] = ecs_id(EcsScriptConstVar); } return cv->value; } const EcsScriptMutVar *mv = ecs_get(world, entity, EcsScriptMutVar); if (mv) { if (component) { component[0] = ecs_id(EcsScriptMutVar); } return mv->value; } if (component) { component[0] = 0; } return (ecs_value_t){0}; } ecs_entity_t ecs_mut_var_init( ecs_world_t *world, ecs_mut_var_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_var_init(world, desc->name, desc->parent, desc->type, desc->value, ecs_id(EcsScriptMutVar), "mut"); error: return 0; } ecs_value_t ecs_mut_var_get( const ecs_world_t *world, ecs_entity_t entity) { return flecs_script_var_get(world, entity, ecs_id(EcsScriptMutVar)); } void* ecs_mut_var_get_w_type( const ecs_world_t *world, const char *name, ecs_entity_t type, ecs_size_t size, void *out) { return flecs_script_var_get_w_type(world, name, type, size, out, ecs_id(EcsScriptMutVar), "mut"); } int ecs_mut_var_set_w_type( ecs_world_t *world, const char *name, ecs_entity_t type, ecs_size_t size, const void *value) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t var = ecs_lookup(world, name); EcsScriptMutVar *v = NULL; if (var) { v = ecs_get_mut(world, var, EcsScriptMutVar); } if (!v) { ecs_err("mut variable '%s' not found", name); goto error; } const ecs_type_info_t *ti = ecs_get_type_info(world, type); if (!ti) { ecs_err("provided type for mut variable '%s' is not a valid type", name); goto error; } if (ti->size != size) { ecs_err("size of provided type for mut variable '%s' does not " "match size of provided value", name); goto error; } ecs_meta_cursor_t cur = ecs_meta_cursor( ecs_get_world(world), v->value.type, v->value.ptr); ecs_value_t from = { .type = type, .ptr = ECS_CONST_CAST(void*, value) }; if (ecs_meta_set_value(&cur, &from)) { ecs_err("provided value cannot be converted to type of mut " "variable '%s'", name); goto error; } ecs_modified(world, var, EcsScriptMutVar); return 0; error: return -1; } void ecs_mut_var_modified( ecs_world_t *world, ecs_entity_t entity) { ecs_modified(world, entity, EcsScriptMutVar); } #ifdef FLECS_DEBUG static bool flecs_script_function_has_vector_args( const ecs_function_desc_t *desc) { int32_t i; for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { if (!desc->params[i].name) { break; } if (desc->params[i].type == EcsScriptVectorType) { return true; } } return false; } static int flecs_script_function_validate_desc( const ecs_function_desc_t *desc, bool async) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); if (!flecs_script_function_has_vector_args(desc)) { ecs_check(async || desc->callback != NULL, ECS_INVALID_PARAMETER, "%s", desc->name); ecs_check(desc->return_type != EcsScriptVectorType, ECS_INVALID_PARAMETER, "function '%s' cannot have flecs.script.vector return type unless " "at least one argument is of type flecs.script.vector", desc->name); } else { int32_t i; for (i = 0; i < EcsPrimitiveKindLast; i ++) { if (desc->vector_callbacks[i]) { break; } } if (i == EcsPrimitiveKindLast) { ecs_throw(ECS_INVALID_PARAMETER, "function '%s' has vector " "arguments, must implement at least one element of " ".vector_callbacks", desc->name); } } return 0; error: return -1; } #endif static void flecs_script_function_parse_args( const ecs_function_desc_t *desc, ecs_vec_t *params) { int32_t i; for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { if (!desc->params[i].name) { break; } if (!i) { ecs_vec_init_t(NULL, params, ecs_script_parameter_t, 0); } ecs_script_parameter_t *p = ecs_vec_append_t( NULL, params, ecs_script_parameter_t); p->type = desc->params[i].type; p->name = ecs_os_strdup(desc->params[i].name); } } static ecs_entity_t flecs_script_function_init( ecs_world_t *world, const ecs_function_desc_t *desc, ecs_entity_t component, bool async) { flecs_poly_assert(world, ecs_world_t); ecs_dbg_assert(!flecs_script_function_validate_desc(desc, async), ECS_INVALID_PARAMETER, NULL); (void)async; ecs_check(component != ecs_id(EcsScriptMethod) || desc->parent != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); if (!result) { goto error; } EcsScriptFunction *f = ecs_ensure_id( world, result, component, sizeof(EcsScriptFunction)); ecs_script_params_free(&f->params); f->return_type = desc->return_type; f->callback = desc->callback; #ifdef FLECS_SCRIPT_ASYNC f->async_callback = NULL; f->async_cancel = NULL; #endif ecs_os_memcpy_n(f->vector_callbacks, desc->vector_callbacks, ecs_vector_function_callback_t, FLECS_SCRIPT_VECTOR_FUNCTION_COUNT); f->ctx = desc->ctx; flecs_script_function_parse_args(desc, &f->params); return result; error: return 0; } ecs_entity_t ecs_function_init( ecs_world_t *world, const ecs_function_desc_t *desc) { ecs_entity_t result = flecs_script_function_init( world, desc, ecs_id(EcsScriptFunction), false); if (result) { ecs_modified(world, result, EcsScriptFunction); } return result; } #ifdef FLECS_SCRIPT_ASYNC ecs_entity_t ecs_async_function_init( ecs_world_t *world, const ecs_async_function_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); ecs_function_desc_t fn_desc = { .name = desc->name, .parent = desc->parent, .return_type = desc->return_type, .ctx = desc->ctx }; ecs_os_memcpy_n(fn_desc.params, desc->params, ecs_script_parameter_t, FLECS_SCRIPT_FUNCTION_ARGS_MAX); ecs_entity_t result = flecs_script_function_init( world, &fn_desc, ecs_id(EcsScriptFunction), true); if (!result) { goto error; } EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); f->async_callback = desc->callback; f->async_cancel = desc->cancel; ecs_modified(world, result, EcsScriptFunction); return result; error: return 0; } #endif ecs_entity_t ecs_method_init( ecs_world_t *world, const ecs_function_desc_t *desc) { ecs_entity_t result = flecs_script_function_init( world, desc, ecs_id(EcsScriptMethod), false); if (result) { ecs_modified(world, result, EcsScriptMethod); } return result; } static int flecs_script_function_call( ecs_world_t *world, ecs_entity_t function, const struct ecs_script_function_t *f, const ecs_value_t *instance, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { ecs_check(f != NULL, ECS_INVALID_PARAMETER, "entity is not a script function or method"); ecs_check(f->callback != NULL, ECS_UNSUPPORTED, "vector functions cannot be called directly"); ecs_check(argc >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(argc == ecs_vec_count(&f->params), ECS_INVALID_PARAMETER, "expected %d arguments, got %d", ecs_vec_count(&f->params), argc); ecs_check(!argc || argv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_script_parameter_t *params = ecs_vec_first(&f->params); (void)params; int32_t i; for (i = 0; i < argc; i ++) { ecs_check(argv[i].type == params[i].type, ECS_INVALID_PARAMETER, "type of argument %d does not match parameter type", i); ecs_check(argv[i].ptr != NULL, ECS_INVALID_PARAMETER, NULL); } if (instance) { ecs_entity_t method_type = ecs_get_parent(world, function); if (instance->type != method_type) { const EcsType *type = ecs_get(world, instance->type, EcsType); (void)type; ecs_check(type && (type->kind == EcsArrayType || type->kind == EcsVectorType || type->kind == EcsMapType) && method_type == ecs_lookup(world, "flecs.script.collection"), ECS_INVALID_PARAMETER, "instance type does not match method type"); } ecs_check(instance->ptr != NULL, ECS_INVALID_PARAMETER, NULL); } ecs_check(!result->type || result->type == f->return_type, ECS_INVALID_PARAMETER, "result type does not match function return type"); bool result_allocated = false; if (!result->ptr) { *result = ecs_value_new(world, f->return_type); ecs_check(result->ptr != NULL, ECS_INVALID_PARAMETER, "function return type is not a valid value type"); result_allocated = true; } else if (!result->type) { result->type = f->return_type; } const ecs_value_t *call_argv = argv; if (instance) { ecs_value_t *method_argv = ecs_os_alloca_n(ecs_value_t, argc + 1); method_argv[0] = *instance; if (argc) { ecs_os_memcpy_n(&method_argv[1], argv, ecs_value_t, argc); } call_argv = method_argv; } ecs_function_calldata_t calldata = { .function = function, .is.callback = f->callback, .ctx = f->ctx }; flecs_script_runtime_error_reset(flecs_script_runtime_get(world)); if (flecs_function_calldata_invoke( world, &calldata, argc, call_argv, result)) { if (result_allocated) { ecs_value_fini(world, result); } goto error; } return 0; error: return -1; } int ecs_function_call( ecs_world_t *world, ecs_entity_t function, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { ecs_world_t *real_world = world; flecs_stage_from_world(&real_world); ecs_check(function != 0, ECS_INVALID_PARAMETER, NULL); const EcsScriptFunction *f = ecs_get( real_world, function, EcsScriptFunction); return flecs_script_function_call( world, function, f, NULL, argc, argv, result); error: return -1; } int ecs_method_call( ecs_world_t *world, ecs_entity_t method, const ecs_value_t *instance, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { ecs_world_t *real_world = world; flecs_stage_from_world(&real_world); ecs_check(method != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(instance != NULL, ECS_INVALID_PARAMETER, NULL); const EcsScriptMethod *f = ecs_get( real_world, method, EcsScriptMethod); return flecs_script_function_call( world, method, f, instance, argc, argv, result); error: return -1; } void flecs_function_import( ecs_world_t *world) { ecs_set_name_prefix(world, "EcsScript"); ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); ECS_COMPONENT_DEFINE(world, EcsScriptMutVar); ECS_COMPONENT_DEFINE(world, EcsScriptFunction); ECS_COMPONENT_DEFINE(world, EcsScriptMethod); ecs_struct(world, { .entity = ecs_id(EcsScriptConstVar), .members = { { .name = "value", .type = ecs_id(ecs_value_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsScriptMutVar), .members = { { .name = "value", .type = ecs_id(ecs_value_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsScriptFunction), .members = { { .name = "return_type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsScriptMethod), .members = { { .name = "return_type", .type = ecs_id(ecs_entity_t) } } }); ecs_set_hooks(world, EcsScriptConstVar, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptConstVar), .copy = ecs_copy(EcsScriptConstVar), .move = ecs_move(EcsScriptConstVar), }); ecs_set_hooks(world, EcsScriptMutVar, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptConstVar), .copy = ecs_copy(EcsScriptConstVar), .move = ecs_move(EcsScriptConstVar), }); ecs_set_hooks(world, EcsScriptFunction, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptFunction), .copy = ecs_copy(EcsScriptFunction), .move = ecs_move(EcsScriptFunction), }); ecs_set_hooks(world, EcsScriptMethod, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptFunction), .copy = ecs_copy(EcsScriptFunction), .move = ecs_move(EcsScriptFunction), }); flecs_script_register_builtin_functions(world); } #endif #ifdef FLECS_SCRIPT static void flecs_meta_collection_count( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; const EcsType *type = ecs_get(ctx->world, argv[0].type, EcsType); int32_t count = 0; switch (type->kind) { case EcsArrayType: count = ecs_get(ctx->world, argv[0].type, EcsArray)->count; break; case EcsVectorType: count = ecs_vec_count(argv[0].ptr); break; case EcsMapType: count = ecs_map_count((ecs_map_t*)argv[0].ptr); break; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } *(ecs_i32_t*)result->ptr = count; } static bool flecs_meta_entity_valid( const ecs_function_ctx_t *ctx, ecs_entity_t entity) { if (entity && ecs_is_alive(ctx->world, entity)) { return true; } ecs_err("invalid entity %u passed to method", (uint32_t)entity); flecs_script_runtime_get(ctx->world)->error = true; return false; } static void flecs_meta_entity_name( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; if (!flecs_meta_entity_valid(ctx, entity)) { return; } *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } static void flecs_meta_entity_path( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; if (!flecs_meta_entity_valid(ctx, entity)) { return; } *(char**)result->ptr = ecs_get_path(ctx->world, entity); } static void flecs_meta_entity_parent( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; if (!flecs_meta_entity_valid(ctx, entity)) { return; } *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); } static void flecs_meta_entity_has( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; if (!flecs_meta_entity_valid(ctx, entity)) { return; } ecs_id_t id = *(ecs_id_t*)argv[1].ptr; *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); } static void flecs_meta_entity_lookup( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; (void)ctx; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; const char *path = *(char**)argv[1].ptr; if (!path) { *(ecs_entity_t*)result->ptr = 0; } else if (entity) { *(ecs_entity_t*)result->ptr = ecs_lookup_child(ctx->world, entity, path); } else { *(ecs_entity_t*)result->ptr = ecs_lookup(ctx->world, path); } } static void flecs_meta_core_pair( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; (void)ctx; ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; *(ecs_id_t*)result->ptr = ecs_pair(first, second); } #ifdef FLECS_DOC #define FLECS_DOC_FUNC(name)\ static\ void flecs_meta_entity_doc_##name(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result)\ {\ (void)argc;\ ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ if (!flecs_meta_entity_valid(ctx, entity)) {\ return;\ }\ *(char**)result->ptr = \ ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ } FLECS_DOC_FUNC(name) FLECS_DOC_FUNC(uuid) FLECS_DOC_FUNC(brief) FLECS_DOC_FUNC(detail) FLECS_DOC_FUNC(link) FLECS_DOC_FUNC(color) #undef FLECS_DOC_FUNC static const struct { const char *name; ecs_function_callback_t callback; } flecs_script_doc_methods[] = { { "doc_name", flecs_meta_entity_doc_name }, { "doc_uuid", flecs_meta_entity_doc_uuid }, { "doc_brief", flecs_meta_entity_doc_brief }, { "doc_detail", flecs_meta_entity_doc_detail }, { "doc_link", flecs_meta_entity_doc_link }, { "doc_color", flecs_meta_entity_doc_color } }; #endif void flecs_script_register_builtin_functions( ecs_world_t *world) { ecs_method(world, { .name = "count", .parent = ecs_entity(world, { .name = "collection" }), .return_type = ecs_id(ecs_i32_t), .callback = flecs_meta_collection_count }); ecs_function_desc_t entity_methods[] = {{ .name = "name", .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_name }, { .name = "path", .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_path }, { .name = "parent", .return_type = ecs_id(ecs_entity_t), .callback = flecs_meta_entity_parent }, { .name = "has", .params = {{ .name = "component", .type = ecs_id(ecs_id_t) }}, .return_type = ecs_id(ecs_bool_t), .callback = flecs_meta_entity_has }, { .name = "lookup", .params = {{ .name = "path", .type = ecs_id(ecs_string_t) }}, .return_type = ecs_id(ecs_entity_t), .callback = flecs_meta_entity_lookup }}; int32_t i, count = (int32_t)( sizeof(entity_methods) / sizeof(entity_methods[0])); for (i = 0; i < count; i ++) { entity_methods[i].parent = ecs_id(ecs_entity_t); ecs_method_init(world, &entity_methods[i]); } ecs_function(world, { .name = "pair", .parent = ecs_entity(world, { .name = "core" }), .params = { { .name = "first", .type = ecs_id(ecs_entity_t) }, { .name = "second", .type = ecs_id(ecs_entity_t) } }, .return_type = ecs_id(ecs_id_t), .callback = flecs_meta_core_pair }); #ifdef FLECS_DOC count = (int32_t)(sizeof(flecs_script_doc_methods) / sizeof(flecs_script_doc_methods[0])); for (i = 0; i < count; i ++) { ecs_method(world, { .name = flecs_script_doc_methods[i].name, .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_script_doc_methods[i].callback }); } #endif } #endif #ifdef FLECS_SCRIPT #define EcsTokEndOfStatement\ case ';':\ case '\n':\ case '\0' static void flecs_script_initializer_set_full( ecs_expr_node_t *node) { if (node->kind == EcsExprInitializer) { ((ecs_expr_initializer_t*)node)->is_partial = false; } } /* Parse scope (statements inside {}) */ static const char* flecs_script_scope( ecs_parser_t *parser, ecs_script_scope_t *scope, const char *pos) { ParserBegin; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pos[-1] == '{', ECS_INTERNAL_ERROR, NULL); if (parser->scope_depth >= ECS_PARSER_MAX_RECURSION_DEPTH) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, pos), "maximum scope nesting depth exceeded"); return NULL; } parser->scope_depth ++; scope->open = pos - 1; ecs_script_scope_t *prev = parser->scope; parser->scope = scope; Loop( LookAhead( case EcsTokScopeClose: Consume(); goto scope_close; case EcsTokEnd: Error("unexpected end of script"); ) pos = flecs_script_stmt_w_separator(parser, pos); if (!pos) { goto error; } ) scope_close: parser->scope = prev; parser->scope_depth --; ecs_assert(pos[-1] == '}', ECS_INTERNAL_ERROR, NULL); scope->node.end = pos; return pos; error: parser->scope = prev; parser->scope_depth --; return NULL; } /* Parse scope of entity */ static const char* flecs_script_entity_scope( ecs_parser_t *parser, ecs_script_entity_t *entity, const char *pos) { if (!entity) { return NULL; } return flecs_script_scope(parser, entity->scope, pos); } /* Parse base list (bases separated by ',') */ static const char* flecs_script_base_list( ecs_parser_t *parser, ecs_script_entity_t *entity, const char *base, const char *pos) { ParserBegin; ecs_script_scope_t *prev = parser->scope; parser->scope = entity->scope; flecs_script_insert_pair_tag(parser, "IsA", base); Loop( LookAhead_1(',', Consume(); pos = flecs_parser_skip_newlines(parser, pos); LookAhead_1(EcsTokIdentifier, Consume(); flecs_script_insert_pair_tag(parser, "IsA", Token(1)); continue; ) ) break; ) parser->scope = prev; return pos; error: parser->scope = prev; return NULL; } static const char* flecs_script_pair( ecs_parser_t *parser, const char *pos, const char **first, const char **second) { ParserBegin; Parse( case EcsTokIdentifier: case EcsTokString: *first = Token(0); Parse_1(',', Parse( case EcsTokIdentifier: case EcsTokString: *second = Token(2); Parse_1(')', EndOfRule; ) ) ) ) ParserEnd; } /* Parse with expression (expression after 'with' keyword) */ static const char* flecs_script_with_expr( ecs_parser_t *parser, const char *pos) { ParserBegin; const char *first = NULL, *second = NULL; Parse( // Position case EcsTokIdentifier: { first = Token(0); goto with_id; } // (Eats, Apples) case '(': { pos = flecs_script_pair(parser, pos, &first, &second); if (!pos) { goto error; } goto with_id; } ) with_id: // Position ( // (Eats, Apples) ( LookAhead_1('(', Consume(); // Position ( expr ) // (Eats, Apples) ( expr ) Initializer(')', ecs_script_component_t *component = flecs_script_insert_pair_component(parser, first, second); component->node.kind = EcsAstWithComponent; component->expr = INITIALIZER; flecs_script_initializer_set_full(component->expr); EndOfRule; ) ) ecs_script_tag_t *tag = flecs_script_insert_pair_tag(parser, first, second); tag->node.kind = EcsAstWithTag; EndOfRule; ParserEnd; } /* Parse with expression list (expression list after 'with' keyword) */ static const char* flecs_script_with( ecs_parser_t *parser, ecs_script_with_t *with, const char *pos) { ParserBegin; Loop( Scope(with->expressions, pos = flecs_script_with_expr(parser, pos); ) if (!pos) { goto error; } pos = flecs_parser_skip_newlines(parser, pos); Parse( case ',': { pos = flecs_parser_skip_newlines(parser, pos); continue; } case '{': { return flecs_script_scope(parser, with->scope, pos); } ) ) ParserEnd; } /* Parenthesis expression */ static const char* flecs_script_paren_expr( ecs_parser_t *parser, const char *kind, ecs_script_entity_t *entity, const char *pos) { ParserBegin; Initializer(')', entity->kind_w_expr = true; Scope(entity->scope, ecs_script_component_t *component = flecs_script_insert_component(parser, kind); component->expr = INITIALIZER; flecs_script_initializer_set_full(component->expr); ) // Position spaceship (expr) { // Position spaceship (expr)\n{ LookAheadSkipNewlines_1('{', Consume(); return flecs_script_entity_scope(parser, entity, pos); ) EndOfRule; ) ParserEnd; } /* Parse a single statement */ static const char* flecs_script_if_stmt( ecs_parser_t *parser, const char *pos) { ParserBegin; // if expr Expr( pos = flecs_parser_skip_newlines(parser, pos); // if expr { Parse_1('{', { ecs_script_if_t *stmt = flecs_script_insert_if(parser); stmt->expr = EXPR; pos = flecs_script_scope(parser, stmt->if_true, pos); if (!pos) { goto error; } // if expr { }\n else LookAheadSkipNewlines_1(EcsTokKeywordElse, // if expr { } else\n Consume(); pos = flecs_parser_skip_newlines(parser, pos); Parse( // if expr { } else if case EcsTokKeywordIf: { parser->scope_depth ++; Scope(stmt->if_false, pos = flecs_script_if_stmt(parser, pos); ) parser->scope_depth --; if (!pos) { goto error; } return pos; } // if expr { } else { case '{': { return flecs_script_scope( parser, stmt->if_false, pos); } ) ) EndOfRule; }); ) ParserEnd; } static const char* flecs_script_for_in( ecs_parser_t *parser, const char *pos, ecs_script_for_t *stmt) { ParserBegin; Expr({ stmt->expr = EXPR; pos = flecs_parser_skip_newlines(parser, pos); Parse( case EcsTokRange: { stmt->from = EXPR; stmt->expr = NULL; Expr({ stmt->to = EXPR; pos = flecs_parser_skip_newlines(parser, pos); Parse_1('{', { return flecs_script_scope(parser, stmt->scope, pos); }) }) } case '{': { if (EXPR->kind == EcsExprRange) { ecs_expr_range_t *range = (ecs_expr_range_t*)EXPR; stmt->from = range->from; stmt->to = range->to; range->from = NULL; range->to = NULL; stmt->expr = NULL; flecs_expr_visit_free(&parser->script->pub, EXPR); } return flecs_script_scope(parser, stmt->scope, pos); } ) }) ParserEnd; } static const char* flecs_script_parse_var( ecs_parser_t *parser, const char *pos, ecs_script_node_kind_t kind) { ParserBegin; Parse_1(EcsTokIdentifier, ecs_script_var_node_t *var = flecs_script_insert_var( parser, Token(0)); var->node.kind = kind; bool is_prop = kind == EcsAstProp; bool is_mut = kind == EcsAstMut || kind == EcsAstExportMut; const char *kind_str = is_prop ? "prop" : (is_mut ? "mut" : "const"); Parse( // const color = case '=': { LookAhead_1(EcsTokKeywordAwait, Consume(); Expr( var->is_await = true; var->expr = EXPR; EndOfRule; ) ) // const color = Color : LookAhead_2(EcsTokIdentifier, ':', Error("'%s %s = %s: ...' is invalid, did you mean " "'%s %s: %s = ...' instead?", kind_str, var->name, Token(2), kind_str, var->name, Token(2)); ) // const PI = expr\n Expr( var->expr = EXPR; EndOfRule; ) } case ':': { LookAhead_1(EcsTokKeywordTemplate, if (!is_prop) { Error("'%s %s: template ...' is invalid, template " "types are only allowed for prop variables", kind_str, var->name); } Consume(); var->type_is_template = true; LookAhead_1(EcsTokIdentifier, var->type = Token(3); Consume(); ) LookAhead_2('[', ']', Consume(); var->type_is_vector = true; ) goto var_type; ) LookAhead_1(EcsTokIdentifier, Consume(); var->type = Token(2); goto var_type; ) goto var_type_error; } ) var_type: { LookAhead( case '=': { Consume(); LookAhead_1(EcsTokKeywordAwait, Error("'%s %s: %s = await ...' is invalid, " "await variables derive their type from the " "awaited expression, use '%s %s = await ...' " "instead", kind_str, var->name, var->type, kind_str, var->name); ) LookAhead( case '{': case '[': case EcsTokKeywordMatch: Expr( var->expr = EXPR; EndOfRule; ) ) LookAhead_2(EcsTokIdentifier, '(', Expr( var->expr = EXPR; EndOfRule; ) ) Initializer('\n', var->expr = INITIALIZER; EndOfRule; ) } // prop color: Color\n EcsTokEndOfStatement: case EcsTokScopeClose: { if (is_prop) { EndOfRule; } break; } ) var_type_error: Error("expected type name followed by '=', did you mean " "'%s %s = ...' or '%s %s: type = ...' instead?", kind_str, var->name, kind_str, var->name); } ) error: return NULL; } static const char* flecs_script_fn_params( ecs_parser_t *parser, ecs_script_function_node_t *fn, const char *pos) { ParserBegin; Loop( LookAhead( case EcsTokParenClose: { Consume(); EndOfRule; } case EcsTokIdentifier: { Consume(); Parse_2(EcsTokColon, EcsTokIdentifier, { ecs_script_fn_param_t *p = ecs_vec_append_t( &parser->script->allocator, &fn->params, ecs_script_fn_param_t); ecs_os_zeromem(p); p->node = (ecs_script_node_t){ .pos = parser->pos }; p->sp = -1; p->name = Token(0); p->type = Token(2); LookAhead_1(EcsTokComma, Consume(); continue; ) LookAhead_1(EcsTokParenClose, Consume(); EndOfRule; ) Error("expected ',' or ')' after parameter"); }) } ) Error("expected parameter or ')'"); ) ParserEnd; } static const char* flecs_script_fn_body( ecs_parser_t *parser, ecs_script_function_node_t *fn, const char *pos) { ParserBegin; Loop( pos = flecs_parser_skip_newlines(parser, pos); LookAhead( case EcsTokScopeClose: { Error("function body must end with an expression"); } case EcsTokSemiColon: { Consume(); continue; } case EcsTokKeywordConst: { parser->stmt_pos = pos; Consume(); Scope(fn->body, pos = flecs_script_parse_var(parser, pos, EcsAstConst); ) if (!pos) { goto error; } continue; } ) break; ) Expr( fn->return_expr = EXPR; ) pos = flecs_parser_skip_newlines(parser, pos); LookAhead_1(EcsTokScopeClose, Consume(); return pos; ) Error("expected '}' after function body"); error: return NULL; } static int32_t flecs_script_last_stmt_kind( ecs_script_scope_t *scope) { int32_t count = ecs_vec_count(&scope->stmts); if (!count) { return -1; } return ecs_vec_last_t(&scope->stmts, ecs_script_node_t*)[0]->kind; } /* Parse a single statement */ static const char* flecs_script_stmt_parse( ecs_parser_t *parser, const char *pos) { ParserBegin; const char *component_first = NULL, *component_second = NULL; bool component_collection = false; ecs_expr_node_t *component_index = NULL; parser->stmt_pos = NULL; LookAhead( EcsTokEndOfStatement: EndOfRule; ) Parse( case EcsTokIdentifier: goto identifier; case EcsTokString: goto identifier; case '{': goto anonymous_entity; case '(': goto paren; case '@': goto annotation; case EcsTokKeywordWith: goto with_stmt; case EcsTokKeywordModule: goto module_stmt; case EcsTokKeywordUsing: goto using_stmt; case EcsTokKeywordTemplate: goto template_stmt; case EcsTokKeywordProp: goto prop_var; case EcsTokKeywordMut: goto mut_var; case EcsTokKeywordConst: goto const_var; case EcsTokKeywordExport: goto export_var; case EcsTokKeywordIf: goto if_stmt; case EcsTokKeywordFor: goto for_stmt; case EcsTokKeywordInclude: goto include_stmt; case EcsTokKeywordFn: goto fn_stmt; case EcsTokKeywordAwait: goto await_stmt; case EcsTokKeywordTry: goto try_stmt; case EcsTokKeywordContinue: goto continue_stmt; case EcsTokKeywordAsync: goto async_stmt; case EcsTokKeywordWhile: goto while_stmt; ); anonymous_entity: { return flecs_script_entity_scope(parser, flecs_script_insert_entity(parser, "_"), pos); } identifier: { LookAhead( // auto_override | case '|': { Consume(); goto identifier_flag; } // Position: case ':': { Consume(); goto identifier_colon; } // facade[ case '[': { Consume(); goto identifier_index; } // SpaceShip( case '(': { Consume(); goto identifier_paren; } // value = case '=': { Consume(); goto identifier_assign; } // Spaceship enterprise // Spaceship "enterprise" case EcsTokIdentifier: case EcsTokString: { Consume(); goto identifier_identifier; } ) // enterprise { // Npc\n{ LookAheadSkipNewlines_1('{', Consume(); return flecs_script_entity_scope(parser, flecs_script_insert_entity(parser, Token(0)), pos); ) goto insert_tag; } // Npc // facade[0] insert_tag: { ecs_script_tag_t *tag = flecs_script_insert_tag(parser, Token(0)); tag->id.index_expr = component_index; EndOfRule; } // @ annotation: { // @brief Parse_1(EcsTokIdentifier, { // @brief value ecs_token_t value; pos = flecs_tokenizer_line(parser, pos, &value, false); flecs_script_insert_annot(parser, Token(1), value.value); EndOfRule; }) } // with with_stmt: { ecs_script_with_t *with = flecs_script_insert_with(parser); pos = flecs_script_with(parser, with, pos); EndOfRule; } // using using_stmt: { if (parser->scope != parser->script->root) { Error("'using' must be declared in the root scope of a script"); } { int32_t last = flecs_script_last_stmt_kind(parser->scope); if (last != -1 && last != EcsAstModule && last != EcsAstInclude && last != EcsAstUsing) { Error("'using' must be declared before other statements"); } } // using flecs.meta\n Parse_1(EcsTokIdentifier, flecs_script_insert_using(parser, Token(1)); EndOfRule; ) } // module module_stmt: { if (parser->scope != parser->script->root) { Error("'module' must be declared in the root scope of a script"); } if (ecs_vec_count(&parser->scope->stmts)) { Error("'module' must be the first statement of a script"); } // module flecs.meta\n Parse_1(EcsTokIdentifier, flecs_script_insert_module(parser, Token(1)); EndOfRule; ) } // template template_stmt: { // template SpaceShip Parse_1(EcsTokIdentifier, ecs_script_template_node_t *template = flecs_script_insert_template( parser, Token(1)); LookAhead_1(':', Consume(); Loop( pos = flecs_parser_skip_newlines(parser, pos); Parse_1(EcsTokIdentifier, if (!ecs_os_strcmp(Token(3), "parent")) { if (template->parent) { Error("duplicate parent constraint"); } Parse_1(EcsTokIdentifier, template->parent = Token(4); ) } else { if (template->base) { Error("multiple template bases are not supported"); } template->base = Token(3); } LookAhead_1(',', Consume(); continue; ) ) goto template_scope; ) ) template_scope: pos = flecs_parser_skip_newlines(parser, pos); // template SpaceShip { Parse_1('{', return flecs_script_scope(parser, template->scope, pos); ) ) } // prop prop_var: { // prop color = Color: return flecs_script_parse_var(parser, pos, EcsAstProp); } // mut mut_var: { return flecs_script_parse_var(parser, pos, EcsAstMut); } // export export_var: { Parse( // export const case EcsTokKeywordConst: return flecs_script_parse_var(parser, pos, EcsAstExportConst); // export mut case EcsTokKeywordMut: return flecs_script_parse_var(parser, pos, EcsAstExportMut); ) } // const const_var: { // const color return flecs_script_parse_var(parser, pos, EcsAstConst); } // if if_stmt: { return flecs_script_if_stmt(parser, pos); } // for for_stmt: { ecs_script_for_t *stmt = flecs_script_insert_for(parser); Parse( case EcsTokIdentifier: { stmt->loop_vars[0] = Token(1); stmt->loop_var_count = 1; goto for_in_stmt; } case '(': { Loop( Parse_1(EcsTokIdentifier, stmt->loop_vars[stmt->loop_var_count ++] = Token(2); ) if (stmt->loop_var_count == 1) { Parse_1(',', continue;) } else if (stmt->loop_var_count == 2) { LookAhead_1(',', Consume(); continue; ) } Parse_1(')', goto for_in_stmt;) ) } ) for_in_stmt: Parse_1(EcsTokKeywordIn, return flecs_script_for_in(parser, pos, stmt); ) } fn_stmt: { Parse_2(EcsTokIdentifier, '(', { ecs_script_function_node_t *fn = flecs_script_insert_function( parser, Token(1)); bool significant_newline = parser->significant_newline; parser->significant_newline = false; pos = flecs_script_fn_params(parser, fn, pos); parser->significant_newline = significant_newline; if (!pos) { goto error; } pos = flecs_parser_skip_newlines(parser, pos); Parse_1(EcsTokArrow, pos = flecs_parser_skip_newlines(parser, pos); Parse_1(EcsTokIdentifier, { fn->return_type = Token(4); fn->return_type_node.pos = parser->pos; pos = flecs_parser_skip_newlines(parser, pos); Parse_1('{', { pos = flecs_script_fn_body(parser, fn, pos); if (!pos) { goto error; } EndOfRule; }) }) ) }) } continue_stmt: { flecs_script_insert_continue(parser); EndOfRule; } await_stmt: { Expr({ ecs_script_await_t *await = flecs_script_insert_await(parser); await->expr = EXPR; EndOfRule; }) } // async async_stmt: { // async { Parse_1('{', { ecs_script_async_t *stmt = flecs_script_insert_async(parser); return flecs_script_scope(parser, stmt->scope, pos); }) } // while while_stmt: { // while expr Expr( ecs_script_while_t *stmt = flecs_script_insert_while(parser); stmt->expr = EXPR; pos = flecs_parser_skip_newlines(parser, pos); // while expr { Parse_1('{', { return flecs_script_scope(parser, stmt->scope, pos); }) ) } // value = identifier_assign: { ecs_script_assign_t *stmt = flecs_script_insert_assign(parser, Token(0)); // value = expr\n Expr({ stmt->expr = EXPR; EndOfRule; }) } // try try_stmt: { // try { Parse_1('{', { ecs_script_try_t *stmt = flecs_script_insert_try(parser); pos = flecs_script_scope(parser, stmt->try_scope, pos); if (!pos) { goto error; } Loop( // try { } catch LookAheadSkipNewlines_1(EcsTokKeywordCatch, Consume(); ecs_script_catch_t *clause = flecs_script_try_add_catch( parser, stmt); Parse( // try { } catch { case '{': { pos = flecs_script_scope(parser, clause->scope, pos); if (!pos) { goto error; } continue; } // try { } catch( case '(': { // try { } catch(Error) Parse_2(EcsTokIdentifier, ')', { if (Token(4)[0] == '$') { Error("variable not allowed as catch error"); } clause->error = Token(4); // try { } catch(Error) { Parse_1('{', { pos = flecs_script_scope( parser, clause->scope, pos); if (!pos) { goto error; } continue; }) }) } ) ) if (!ecs_vec_count(&stmt->catches)) { Error("expected 'catch' after 'try' block"); } EndOfRule; ) }) } // include foo.flecs include_stmt: { if (parser->scope != parser->script->root) { Error("'include' must be declared in the root scope of a script"); } { int32_t last = flecs_script_last_stmt_kind(parser->scope); if (last != -1 && last != EcsAstModule && last != EcsAstInclude) { Error( "'include' must be declared before statements other than " "'module'"); } } { ecs_token_t filename; pos = flecs_tokenizer_line(parser, pos, &filename, true); if (!filename.value[0]) { Error("expected filename after 'include'"); } flecs_script_insert_include(parser, filename.value); EndOfRule; } } // ( paren: { pos = flecs_script_pair( parser, pos, &component_first, &component_second); if (!pos) { goto error; } // (Likes, Apples): LookAhead_1(':', Consume(); goto component_expr; ) // (IsA, Machine) { LookAheadSkipNewlines_1('{', Consume(); ecs_script_pair_scope_t *ps = flecs_script_insert_pair_scope( parser, component_first, component_second); return flecs_script_scope(parser, ps->scope, pos); ) // (Likes, Apples) flecs_script_insert_pair_tag(parser, component_first, component_second); EndOfRule; } identifier_flag: { if (ecs_os_strcmp(Token(0), "auto_override")) { Error("invalid flag '%s'", Token(0)); } Parse( case '(': pos = flecs_script_pair( parser, pos, &component_first, &component_second); if (!pos) { goto error; } goto flagged_id; case EcsTokIdentifier: component_first = Token(2); goto flagged_id; ) } flagged_id: { ecs_script_tag_t *tag = flecs_script_insert_pair_tag( parser, component_first, component_second); tag->id.flag = ECS_AUTO_OVERRIDE; // auto_override | Position: { LookAhead_1(':', Consume(); LookAhead_1('{', goto component_expr; ) Error("expected '{' after ':'"); ) EndOfRule; } // facade[0] identifier_index: { component_first = Token(0); LookAhead( case '}': case ']': EcsTokEndOfStatement: { Error("expected index expression after '%s['", component_first); } ) Expr( component_index = EXPR; ) Parse_1(']', // facade[0]: LookAhead_1(':', Consume(); goto component_expr; ) goto insert_tag; ) } // Position: identifier_colon: { component_first = Token(0); LookAhead( case '[': component_collection = true; goto component_expr; case '{': case EcsTokKeywordMatch: goto component_expr; ) bool is_inherit = tokenizer->tokens[0].kind == EcsTokString; if (!is_inherit) { LookAheadAny_2(case EcsTokIdentifier:, case '{': case ',':, is_inherit = Token(2)[0] != '$'; ) } if (!is_inherit) { goto component_expr; } // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(0)); if (!entity) { goto error; } if (!ecs_os_strcmp(Token(2), "Prefab")) { Warning("'%s : Prefab' creates a live instance, not a " "prefab; use 'prefab %s' instead", Token(0), Token(0)); } pos = flecs_script_base_list(parser, entity, Token(2), pos); if (!pos) { goto error; } Parse( // enterprise : SpaceShip { case '{': return flecs_script_entity_scope(parser, entity, pos); ) }) } // Spaceship enterprise identifier_identifier: { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(1)); if (!entity) { goto error; } entity->kind = Token(0); // Spaceship enterprise : LookAhead_1(':', Consume(); Parse_1(EcsTokIdentifier, { pos = flecs_script_base_list(parser, entity, Token(3), pos); if (!pos) { goto error; } goto identifier_identifier_x; }) ) identifier_identifier_x: // Spaceship enterprise( LookAhead_1('(', Consume(); return flecs_script_paren_expr(parser, Token(0), entity, pos); ) // Spaceship enterprise { // Spaceship enterprise\n{ LookAheadSkipNewlines_1('{', Consume(); return flecs_script_entity_scope(parser, entity, pos); ) EndOfRule; } // SpaceShip( identifier_paren: { return flecs_script_paren_expr(parser, Token(0), flecs_script_insert_entity(parser, NULL), pos); } component_expr: { Expr({ ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, component_first, component_second); comp->expr = EXPR; comp->is_collection = component_collection; comp->id.index_expr = component_index; EndOfRule; }) } ParserEnd; } const char* flecs_script_stmt( ecs_parser_t *parser, const char *pos) { ecs_script_scope_t *scope = parser->scope; int32_t first = scope ? ecs_vec_count(&scope->stmts) : 0; pos = flecs_script_stmt_parse(parser, pos); if (pos && scope) { const char *end = flecs_parser_stmt_end(parser, pos); ecs_script_node_t **stmts = ecs_vec_first_t( &scope->stmts, ecs_script_node_t*); int32_t i, count = ecs_vec_count(&scope->stmts); for (i = first; i < count; i ++) { if (!stmts[i]->end) { stmts[i]->end = end; } } } return pos; } /* Parse a single statement, and verify that it is followed by a statement * separator (';', newline, end of scope or end of script). */ const char* flecs_script_stmt_w_separator( ecs_parser_t *parser, const char *pos) { pos = flecs_script_stmt(parser, pos); if (!pos) { return NULL; } ecs_token_t token; const char *old_token_cur = parser->token_cur; const char *lookahead = flecs_token(parser, pos, &token, true); flecs_token_restore(parser, old_token_cur); if (!lookahead) { return NULL; } switch(token.kind) { EcsTokEndOfStatement: return lookahead; case EcsTokScopeClose: return pos; default: break; } ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, lookahead - 1), "missing ; or newline"); return NULL; } ecs_script_t* flecs_script_parse_init( ecs_world_t *world, const char *name, const char *code, ecs_parser_t *parser) { if (!code) { code = ""; } ecs_script_t *script = flecs_script_new(world); script->name = ecs_os_strdup(name); script->code = ecs_os_strdup(code); ecs_script_impl_t *impl = flecs_script_impl(script); *parser = (ecs_parser_t){ .name = script->name, .code = script->code, .pos = script->code, .script = impl, .scope = impl->root, .significant_newline = true }; /* Allocate a buffer that is able to store all parsed tokens. Multiply the * size of the script by two so that there is enough space to add \0 * terminators and expression delimiters ('""') * The token buffer will exist for as long as the script object exists, and * ensures that AST nodes don't need to do separate allocations for the data * they contain. */ impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; impl->token_buffer = flecs_alloc_w_dbg_info( &impl->allocator, impl->token_buffer_size, "token buffer"); parser->token_cur = impl->token_buffer; parser->token_end = &impl->token_buffer[impl->token_buffer_size]; return script; } static void flecs_script_append_using( ecs_script_scope_t *scope, ecs_strbuf_t *buf) { if (scope->parent) { flecs_script_append_using(scope->parent, buf); } int32_t i, count = ecs_vec_count(&scope->stmts); ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); for (i = 0; i < count; i ++) { if (stmts[i]->kind == EcsAstUsing) { ecs_script_using_t *using_node = (ecs_script_using_t*)stmts[i]; ecs_strbuf_appendlit(buf, "using "); ecs_strbuf_appendstr(buf, using_node->name); ecs_strbuf_appendch(buf, ';'); } } } ecs_script_t* flecs_script_parse_nested( const ecs_parser_t *outer, const char *code, const char **next) { ecs_parser_t scan = *outer; scan.significant_newline = false; ecs_token_t token = { .kind = EcsTokUnknown }; const char *end = code; for (int32_t depth = 1; depth && token.kind != EcsTokEnd;) { if (!(end = flecs_token(&scan, end, &token, true))) { return NULL; } depth += (token.kind == '{') - (token.kind == '}'); scan.token_cur = outer->token_cur; } ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_script_append_using(outer->scope, &buf); const char *outer_code = outer->code; const char *outer_open = code - 1; const char *line_start = outer_code; const char *ptr; for (ptr = outer_code; ptr < outer_open; ptr ++) { if (ptr[0] == '\n') { ecs_strbuf_appendch(&buf, '\n'); line_start = ptr + 1; } } int32_t column = flecs_ito(int32_t, outer_open - line_start); if (line_start == outer_code) { column -= ecs_strbuf_written(&buf); } for (; column > 0; column --) { ecs_strbuf_appendch(&buf, ' '); } int32_t open_offset = ecs_strbuf_written(&buf); ecs_strbuf_appendch(&buf, '{'); ecs_strbuf_appendstrn(&buf, code, flecs_ito(int32_t, end - code)); char *nested_code = ecs_strbuf_get(&buf); ecs_parser_t parser; ecs_script_t *script = flecs_script_parse_init( outer->script->pub.world, outer->name, nested_code, &parser); ecs_os_free(nested_code); ecs_script_impl_t *impl = flecs_script_impl(script); const char *open = &script->code[open_offset]; const char *pos = flecs_parser_skip_newlines(&parser, script->code); while (pos < open) { pos = flecs_script_stmt_w_separator(&parser, pos); if (!pos) { goto error; } pos = flecs_parser_skip_newlines(&parser, pos); } ecs_assert(pos == open, ECS_INTERNAL_ERROR, NULL); pos = flecs_script_scope(&parser, impl->root, open + 1); if (!pos) { goto error; } *next = code + (pos - (open + 1)); ECS_CONST_CAST(char*, open)[0] = ' '; ECS_CONST_CAST(char*, pos)[-1] = '\0'; impl->token_remaining = parser.token_cur; return script; error: ecs_script_free(script); return NULL; } ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, const char *code, const ecs_script_eval_desc_t *desc, ecs_script_eval_result_t *result) { if (result) { flecs_log_capture_push(true); } ecs_parser_t parser; ecs_script_t *script = flecs_script_parse_init( world, name, code, &parser); ecs_script_impl_t *impl = flecs_script_impl(script); if (desc && desc->skip_unknown) { impl->skip_unknown = true; } if (desc && desc->ir) { impl->ir_enabled = true; } /* Start parsing code */ const char *pos = script->code; do { pos = flecs_script_stmt_w_separator(&parser, pos); if (!pos) { /* NULL means error */ goto error; } if (!pos[0]) { /* \0 means end of input */ break; } } while (true); impl->token_remaining = parser.token_cur; if (result) { ecs_os_free(flecs_log_capture_pop()); } return script; error: if (result) { result->error = flecs_log_capture_pop(); flecs_log_get_captured_error_pos(&result->line, &result->column); } ecs_script_free(script); return NULL; } #endif #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScript); ECS_COMPONENT_DECLARE(EcsScriptVisitor); ECS_COMPONENT_DECLARE(EcsScriptConstVar); ECS_COMPONENT_DECLARE(EcsScriptMutVar); ECS_COMPONENT_DECLARE(EcsScriptFunction); ECS_COMPONENT_DECLARE(EcsScriptMethod); ECS_DECLARE(EcsScriptVectorType); static void flecs_script_component_fini( EcsScript *ptr) { if (ptr->template_) { flecs_script_template_fini( flecs_script_impl(ptr->script), ptr->template_); } if (ptr->script) { #ifdef FLECS_SCRIPT_ASYNC if (!ptr->template_) { flecs_script_async_tasks_free( &flecs_script_impl(ptr->script)->async_tasks); } #endif ecs_script_free(ptr->script); } ecs_os_free(ptr->filename); ecs_os_free(ptr->code); ecs_os_free(ptr->error); flecs_script_ref_observers_fini(&ptr->observers); flecs_script_ref_observers_fini(&ptr->dyn_observers); } static ECS_MOVE(EcsScript, dst, src, { flecs_script_component_fini(dst); *dst = *src; ecs_os_zeromem(src); }) static ECS_DTOR(EcsScript, ptr, { flecs_script_component_fini(ptr); }) static ecs_id_t flecs_script_tag( ecs_entity_t script, ecs_entity_t instance) { if (!instance) { return ecs_pair_t(EcsScript, script); } else { return ecs_pair(EcsChildOf, instance); } } ecs_script_t* flecs_script_new( ecs_world_t *world) { ecs_script_impl_t *result = ecs_os_calloc_t(ecs_script_impl_t); flecs_allocator_init(&result->allocator); ecs_parser_t parser = { .script = result }; result->root = flecs_script_scope_new(&parser); result->pub.world = world; result->refcount = 1; ecs_vec_init_t(NULL, &result->async_tasks, void*, 0); ecs_vec_init_t(NULL, &result->refs, ecs_script_ref_t, 0); ecs_vec_init_t(NULL, &result->run_refs, ecs_script_ref_t, 0); flecs_script_state_init(&result->state); ecs_vec_init_t(NULL, &result->regions, ecs_script_region_t, 0); ecs_vec_init_t(NULL, &result->unresolved_refs, ecs_script_unresolved_ref_t, 0); ecs_vec_init_t(NULL, &result->unresolved_component_refs, ecs_script_unresolved_component_ref_t, 0); ecs_vec_init_t(NULL, &result->skip_unknown_warned, char*, 0); return &result->pub; } char* ecs_script_ir_to_str( const ecs_script_t *script) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_impl_t *impl = flecs_script_impl( ECS_CONST_CAST(ecs_script_t*, script)); int r = 0; if (!impl->compiled) { ecs_entity_t prev_scope = ecs_set_scope(script->world, 0); r = flecs_script_eval(script, NULL, 0, 0, NULL); ecs_set_scope(script->world, prev_scope); } ecs_script_ir_t *ir = r ? NULL : flecs_script_ir_ensure(impl); if (!ir) { goto error; } ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_script_ir_to_buf(impl, ir, &buf); return ecs_strbuf_get(&buf); error: return NULL; } void flecs_script_skip_unknown_warn( ecs_script_t *script, const char *name, const char *msg) { ecs_script_impl_t *impl = flecs_script_impl(script); char **names = ecs_vec_first(&impl->skip_unknown_warned); int32_t i, count = ecs_vec_count(&impl->skip_unknown_warned); for (i = 0; i < count; i ++) { if (!ecs_os_strcmp(names[i], name)) { return; } } ecs_vec_append_t(NULL, &impl->skip_unknown_warned, char*)[0] = ecs_os_strdup(name); ecs_warn("%s: %s '%s'", impl->pub.name ? impl->pub.name : "script", msg, name); } void flecs_script_pos_to_line_col( const char *code, const char *pos, int32_t *line, int32_t *column) { line[0] = 0; column[0] = 0; if (!code || !pos || (pos < code) || (pos > &code[ecs_os_strlen(code)])) { return; } const char *ptr, *line_start = code; line[0] = 1; for (ptr = code; ptr < pos; ptr ++) { if (ptr[0] == '\n') { line[0] ++; line_start = ptr + 1; } } column[0] = flecs_ito(int32_t, pos - line_start) + 1; } void flecs_script_unresolved_ref_add( ecs_script_impl_t *impl, const char *pos, const char *name, flecs_script_unresolved_kind_t kind) { ecs_script_unresolved_ref_t *ref = ecs_vec_append_t( NULL, &impl->unresolved_refs, ecs_script_unresolved_ref_t); ref->name = name; ref->kind = kind; flecs_script_pos_to_line_col(impl->pub.code, pos, &ref->line, &ref->column); ref->offset = ref->line ? (int32_t)(pos - impl->pub.code) : -1; } void ecs_script_clear( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance) { if (!instance) { flecs_delete_with(world, ecs_pair_t(EcsScript, script), true); } else { ecs_assert(ecs_is_alive(world, instance), ECS_INTERNAL_ERROR, NULL); ecs_vec_t to_delete = {0}; ecs_vec_init_t(&world->allocator, &to_delete, ecs_entity_t, 0); ecs_iter_t it = ecs_children(world, instance); while (ecs_children_next(&it)) { if (it.table) { if (ecs_table_has_id(world, it.table, ecs_pair(EcsScriptTemplate, script))) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_vec_append_t( &world->allocator, &to_delete, ecs_entity_t)[0] = it.entities[i]; } } } else { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_entity_t e = it.entities[i]; if (ecs_has_pair(world, e, EcsScriptTemplate, script)) { ecs_vec_append_t( &world->allocator, &to_delete, ecs_entity_t)[0] = e; } } } } ecs_entity_t *to_delete_ids = ecs_vec_first(&to_delete); int32_t i, count = ecs_vec_count(&to_delete); for (i = 0; i < count; i ++) { ecs_delete(world, to_delete_ids[i]); } ecs_vec_fini_t(&world->allocator, &to_delete, ecs_entity_t); } } int ecs_script_run_w_desc( ecs_world_t *world, const char *name, const char *code, const ecs_script_eval_desc_t *desc, ecs_script_eval_result_t *result) { ecs_script_t *script = ecs_script_parse(world, name, code, desc, result); if (!script) { return -1; } int r = ecs_script_eval(script, desc, result); ecs_script_free(script); return r; } int ecs_script_run( ecs_world_t *world, const char *name, const char *code, ecs_script_eval_result_t *result) { return ecs_script_run_w_desc(world, name, code, NULL, result); } int ecs_script_run_file_w_desc( ecs_world_t *world, const char *filename, const ecs_script_eval_desc_t *desc) { char *script = flecs_load_from_file(filename); if (!script) { return -1; } int result = ecs_script_run_w_desc(world, filename, script, desc, NULL); ecs_os_free(script); return result; } int ecs_script_run_file( ecs_world_t *world, const char *filename) { return ecs_script_run_file_w_desc(world, filename, NULL); } void ecs_script_free( ecs_script_t *script) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_impl_t *impl = flecs_script_impl(script); ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); if (!--impl->refcount) { #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_tasks_free(&impl->async_tasks); #endif ecs_vec_fini_t(NULL, &impl->async_tasks, void*); ecs_assert(impl->task_refcount == 0, ECS_INVALID_OPERATION, "script freed while tasks are still alive"); flecs_script_ir_free(impl->ir); impl->ir = NULL; flecs_script_edit_cache_fini(impl); flecs_script_visit_free(script); flecs_expr_visit_free(script, impl->expr); ecs_vec_fini_t(NULL, &impl->refs, ecs_script_ref_t); ecs_vec_fini_t(NULL, &impl->run_refs, ecs_script_ref_t); flecs_script_state_fini(&impl->state); ecs_vec_fini_t(NULL, &impl->regions, ecs_script_region_t); ecs_vec_fini_t(NULL, &impl->unresolved_refs, ecs_script_unresolved_ref_t); ecs_vec_fini_t(NULL, &impl->unresolved_component_refs, ecs_script_unresolved_component_ref_t); { char **warned = ecs_vec_first(&impl->skip_unknown_warned); int32_t wi, wcount = ecs_vec_count(&impl->skip_unknown_warned); for (wi = 0; wi < wcount; wi ++) { ecs_os_free(warned[wi]); } ecs_vec_fini_t(NULL, &impl->skip_unknown_warned, char*); } flecs_free(&impl->allocator, impl->token_buffer_size, impl->token_buffer); flecs_allocator_fini(&impl->allocator); ecs_os_free(ECS_CONST_CAST(char*, impl->pub.name)); /* safe, owned value */ ecs_os_free(ECS_CONST_CAST(char*, impl->pub.code)); /* safe, owned value */ ecs_os_free(impl); } error: return; } static EcsScript* flecs_script_set_error( ecs_world_t *world, ecs_entity_t e, char *error, const char *fallback) { EcsScript *s = ecs_ensure(world, e, EcsScript); ecs_os_free(s->error); s->error = error ? error : ecs_os_strdup(fallback); return s; } static ecs_script_t* flecs_script_update_parse_code( ecs_world_t *world, ecs_entity_t e, const char *code) { const char *name = ecs_get_name(world, e); const EcsScript *s = ecs_get(world, e, EcsScript); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_eval_result_t parse_result = {0}; ecs_script_eval_desc_t parse_desc = { .skip_unknown = s->skip_unknown, .ir = s->ir }; ecs_script_t *parsed = ecs_script_parse( world, name, code, &parse_desc, &parse_result); if (!parsed) { s = flecs_script_set_error( world, e, parse_result.error, "failed to parse script"); ecs_log_(-3, NULL, 0, "%s: %s", name ? name : "script", s->error); } return parsed; } static int flecs_script_update_parse( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *code, ecs_script_t *parsed) { EcsScript *s = ecs_ensure(world, e, EcsScript); if (s->template_) { char *template_name = ecs_get_path(world, s->template_->props.type); flecs_script_set_error(world, e, flecs_asprintf( "cannot update scripts for individual templates, " "update parent script instead (tried to update '%s')", template_name), NULL); ecs_err("%s", s->error); ecs_os_free(template_name); if (parsed) { ecs_script_free(parsed); } return -1; } ecs_os_free(s->code); s->code = ecs_os_strdup(code); ecs_os_free(s->error); s->error = NULL; if (s->script) { #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_tasks_free( &flecs_script_impl(s->script)->async_tasks); #endif ecs_script_free(s->script); } flecs_script_runtime_error_reset(flecs_script_runtime_get(world)); if (!parsed) { parsed = flecs_script_update_parse_code(world, e, code); } s->script = parsed; if (!parsed) { if (!instance) { flecs_script_ref_observers_clear(world, &s->observers); flecs_script_ref_observers_clear(world, &s->dyn_observers); } return -1; } flecs_script_impl(parsed)->entity = e; return 0; } static void flecs_script_update_error( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, ecs_script_t *parsed, const char *name, char *error) { ecs_script_runtime_t *runtime = flecs_script_runtime_get(world); EcsScript *s = flecs_script_set_error( world, e, error, "failed to evaluate script"); if (runtime->error_name && runtime->include_depth) { ecs_log_(-3, NULL, 0, "%s: %s: %s", name ? name : "script", runtime->error_name, s->error); } else { ecs_log_(-3, NULL, 0, "%s: %s", runtime->error_name ? runtime->error_name : (name ? name : "script"), s->error); } flecs_script_runtime_error_reset(runtime); if (!instance) { flecs_script_update_resolve_observers(world, e, flecs_script_impl(parsed), &s->observers); s = ecs_ensure(world, e, EcsScript); flecs_script_ref_observers_clear(world, &s->dyn_observers); s = ecs_ensure(world, e, EcsScript); } ecs_script_free(parsed); s->script = NULL; ecs_delete_with(world, ecs_pair_t(EcsScript, e)); } static void flecs_script_update_observers( ecs_world_t *world, ecs_entity_t e, ecs_script_t *parsed) { EcsScript *s = ecs_ensure(world, e, EcsScript); ecs_vec_t *script_refs = &flecs_script_impl(s->script)->refs; ecs_script_ref_t *refs = ecs_vec_first(script_refs); int32_t i; for (i = ecs_vec_count(script_refs) - 1; i >= 0; i --) { if (refs[i].component == ecs_id(EcsScriptMutVar)) { continue; } if (refs[i].entity && ecs_has_pair( world, refs[i].entity, ecs_id(EcsScript), e)) { ecs_vec_remove_t(script_refs, ecs_script_ref_t, i); } } flecs_script_ref_observers_clear(world, &s->observers); flecs_script_update_ref_observers(world, e, 0, script_refs, &s->observers, flecs_script_ref_on_set); ecs_vec_clear(script_refs); s = ecs_ensure(world, e, EcsScript); flecs_script_update_dyn_observers(world, e, flecs_script_impl(parsed), &s->dyn_observers, UINT64_MAX); ecs_vec_clear(&flecs_script_impl(parsed)->run_refs); } static int flecs_script_update_impl( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *code, ecs_script_runtime_t *eval_runtime, bool preparse) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(code != NULL, ECS_INTERNAL_ERROR, NULL); int result = 0; bool is_defer = ecs_is_stage(world) || ecs_stage_is_readonly(world); ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); world = real_world; } /* Parse the new version before anything in the world is deleted, so a * script that does not parse leaves the previous version in place. */ ecs_script_t *preparsed = NULL; if (preparse) { preparsed = flecs_script_update_parse_code(world, e, code); if (!preparsed) { result = -1; goto done; } } const char *name = ecs_get_name(world, e); if (flecs_script_update_parse(world, e, instance, code, preparsed)) { result = -1; goto done; } ecs_script_clear(world, e, instance); #ifdef FLECS_DEBUG { ecs_iter_t it = ecs_each_pair_t(world, EcsScript, e); ecs_assert(!ecs_iter_is_true(&it), ECS_INTERNAL_ERROR, NULL); } #endif EcsScript *s = ecs_ensure(world, e, EcsScript); ecs_script_t *parsed = s->script; ecs_script_impl_t *parsed_impl = flecs_script_impl(parsed); parsed_impl->evaluating = true; parsed_impl->refcount ++; ecs_script_eval_result_t eval_result = {0}; ecs_script_eval_desc_t eval_desc = { .runtime = eval_runtime, .ir = s->ir }; int eval_result_code = flecs_script_eval(parsed, &eval_desc, flecs_script_tag(e, instance), UINT64_MAX, &eval_result); if (!ecs_is_alive(world, e)) { parsed_impl->evaluating = false; flecs_script_runtime_error_reset(flecs_script_runtime_get(world)); ecs_os_free(eval_result.error); result = -1; goto done_free; } if (eval_result_code) { flecs_script_update_error(world, e, instance, parsed, name, eval_result.error); result = -1; } else { parsed_impl->evaluating = false; if (!instance) { flecs_script_pending_resolve_remove(world, e); flecs_script_update_observers(world, e, parsed); } } done_free: ecs_script_free(parsed); done: if (is_defer) { flecs_resume_readonly(real_world, &srs); } return result; } int flecs_script_update( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *code, ecs_script_runtime_t *eval_runtime) { return flecs_script_update_impl( world, e, instance, code, eval_runtime, false); } int ecs_script_update( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *code) { return flecs_script_update(world, e, instance, code, NULL); } int ecs_script_reload( ecs_world_t *world, ecs_entity_t e, const char *code) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(e != 0, ECS_INVALID_PARAMETER, NULL); char *file_code = NULL; const EcsScript *s = ecs_get(world, e, EcsScript); if (!s) { ecs_err("entity is not a script, cannot reload"); goto error; } if (!code) { if (!s->filename) { ecs_err("script was not loaded from a file, cannot reload"); goto error; } file_code = flecs_load_from_file(s->filename); if (!file_code) { goto error; } code = file_code; } int result = flecs_script_update_impl(world, e, 0, code, NULL, true); ecs_os_free(file_code); return result; error: return -1; } ecs_entity_t ecs_script_init( ecs_world_t *world, const ecs_script_desc_t *desc) { ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = desc->entity; if (!e) { if (desc->filename) { e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", "/"); } else { e = ecs_new(world); } } if (desc->skip_unknown || desc->ir) { EcsScript *comp = ecs_ensure(world, e, EcsScript); comp->skip_unknown = desc->skip_unknown; comp->ir = desc->ir; } const char *script = desc->code; if (!script && desc->filename) { script = flecs_load_from_file(desc->filename); if (!script) { if (!desc->entity) { ecs_delete(world, e); } return 0; } EcsScript *comp = ecs_ensure(world, e, EcsScript); ecs_os_strset(&comp->filename, desc->filename); } ecs_script_update(world, e, 0, script); if (script != desc->code) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free(ECS_CONST_CAST(char*, script)); } return e; error: return 0; } static int EcsScript_serialize( const ecs_serializer_t *ser, const void *ptr) { const EcsScript *data = ptr; ser->member(ser, "filename"); ser->value(ser, ecs_id(ecs_string_t), &data->filename); ser->member(ser, "code"); ser->value(ser, ecs_id(ecs_string_t), &data->code); ser->member(ser, "error"); ser->value(ser, ecs_id(ecs_string_t), &data->error); if (data->script) { char *ast = ecs_script_ast_to_str(data->script, true); ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &ast); ecs_os_free(ast); } else { char *nullString = NULL; ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &nullString); } return 0; } void FlecsScriptImport( ecs_world_t *world) { ecs_assert(FLECS_SCRIPT_VECTOR_FUNCTION_COUNT == EcsPrimitiveKindLast, ECS_INTERNAL_ERROR, NULL); ECS_MODULE(world, FlecsScript); ECS_IMPORT(world, FlecsMeta); ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); ECS_COMPONENT_DEFINE(world, EcsScriptVisitor); ECS_TAG_DEFINE(world, EcsScriptVectorType); ecs_add_pair(world, ecs_id(EcsScriptVisitor), EcsOnInstantiate, EcsDontInherit); ecs_set(world, ecs_id(EcsStruct), EcsScriptVisitor, { .visit = flecs_script_struct_visit }); ecs_set(world, ecs_id(EcsEnum), EcsScriptVisitor, { .visit = flecs_script_enum_visit }); ecs_set(world, ecs_id(EcsBitmask), EcsScriptVisitor, { .visit = flecs_script_bitmask_visit }); ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), .dtor = ecs_dtor(EcsScript), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ECS_COMPONENT(world, ecs_script_t); ecs_entity_t opaque_view = ecs_struct(world, { .entity = ecs_entity(world, { .name = "ecs_script_view_t" }), .members = { { .name = "filename", .type = ecs_id(ecs_string_t) }, { .name = "code", .type = ecs_id(ecs_string_t) }, { .name = "error", .type = ecs_id(ecs_string_t) }, { .name = "ast", .type = ecs_id(ecs_string_t) } } }); ecs_opaque(world, { .entity = ecs_id(EcsScript), .type.as_type = opaque_view, .type.serialize = EcsScript_serialize }); ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); flecs_script_refs_import(world); flecs_script_template_import(world); flecs_function_import(world); #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_import(world); #endif } #endif #ifdef FLECS_SCRIPT static int flecs_ptr_to_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out, flecs_meta_format_t format) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (ser == NULL) { char *path = ecs_get_path(world, type); ecs_err("cannot serialize value for type '%s'", path); ecs_os_free(path); return -1; } return flecs_meta_serialize(world, &ser->ops, ptr, buf_out, format); } static char* flecs_ptr_to_str( const ecs_world_t *world, ecs_entity_t type, const void *ptr, flecs_meta_format_t format) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (flecs_ptr_to_buf(world, type, ptr, &str, format)) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_expr_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { return flecs_ptr_to_buf(world, type, ptr, buf_out, EcsMetaExpr); } char* flecs_script_ptr_to_expr_precise( const ecs_world_t *world, ecs_entity_t type, const void *ptr, bool positional) { return flecs_ptr_to_str(world, type, ptr, positional ? EcsMetaExprPrecisePositional : EcsMetaExprPrecise); } char* ecs_ptr_to_expr( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { return flecs_ptr_to_str(world, type, ptr, EcsMetaExpr); } int ecs_ptr_to_str_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { return flecs_ptr_to_buf(world, type, ptr, buf_out, EcsMetaStr); } char* ecs_ptr_to_str( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { return flecs_ptr_to_str(world, type, ptr, EcsMetaStr); } #endif #ifdef FLECS_SCRIPT ecs_script_vars_t* flecs_script_vars_push( ecs_script_vars_t *parent, ecs_stack_t *stack, ecs_allocator_t *allocator) { ecs_check(stack || parent, ECS_INVALID_PARAMETER, "must provide either parent scope or stack allocator"); ecs_check(allocator || parent, ECS_INVALID_PARAMETER, "must provide either parent scope or allocator"); if (!stack) { stack = parent->stack; } else if (parent) { ecs_check(stack == parent->stack, ECS_INVALID_PARAMETER, "provided stack allocator is different from parent scope"); } if (!allocator) { allocator = parent->allocator; } else if (parent) { ecs_check(allocator == parent->allocator, ECS_INVALID_PARAMETER, "provided allocator is different from parent scope"); } ecs_stack_cursor_t *cursor = flecs_stack_get_cursor(stack); ecs_script_vars_t *result = flecs_stack_calloc_t(stack, ecs_script_vars_t); ecs_vec_init_t(allocator, &result->vars, ecs_script_var_t, 0); result->parent = parent; if (parent) { result->world = parent->world; result->sp = parent->sp + ecs_vec_count(&parent->vars); } else { result->sp = 0; } result->stack = stack; result->allocator = allocator; result->cursor = cursor; return result; error: return NULL; } ecs_script_vars_t* ecs_script_vars_init( ecs_world_t *world) { ecs_script_vars_t *result = flecs_script_vars_push(NULL, flecs_stage_get_stack_allocator(world), flecs_stage_get_allocator(world)); result->world = ecs_get_world(world); /* Provided world can be stage */ return result; } void ecs_script_vars_fini( ecs_script_vars_t *vars) { ecs_check(vars->parent == NULL, ECS_INVALID_PARAMETER, "ecs_script_vars_fini can only be called on the root scope"); ecs_script_vars_pop(vars); error: return; } ecs_script_vars_t* ecs_script_vars_push( ecs_script_vars_t *parent) { ecs_check(parent != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_vars_push(parent, NULL, NULL); error: return NULL; } ecs_script_vars_t* ecs_script_vars_pop( ecs_script_vars_t *vars) { ecs_script_vars_t *parent = vars->parent; ecs_stack_cursor_t *cursor = vars->cursor; int32_t i, count = ecs_vec_count(&vars->vars); if (count) { ecs_script_var_t *var_array = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *var = &var_array[i]; if (!var->value.ptr || !var->owned) { continue; } ecs_assert(var->type_info != NULL, ECS_INTERNAL_ERROR, NULL); flecs_type_info_dtor(var->value.ptr, 1, var->type_info); flecs_stack_free(var->value.ptr, var->type_info->size); } flecs_name_index_fini(&vars->var_index); } ecs_vec_fini_t(vars->allocator, &vars->vars, ecs_script_var_t); flecs_stack_restore_cursor(vars->stack, cursor); return parent; } ecs_script_var_t* ecs_script_vars_declare( ecs_script_vars_t *vars, const char *name) { if (name) { if (flecs_name_index_is_init(&vars->var_index)) { if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { goto error; } } else { flecs_name_index_init(&vars->var_index, vars->allocator); } } ecs_script_var_t *var = ecs_vec_append_t( vars->allocator, &vars->vars, ecs_script_var_t); var->name = name; var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; var->sp = ecs_vec_count(&vars->vars) + vars->sp - 1; var->is_const = false; var->owned = false; if (name) { flecs_name_index_ensure(&vars->var_index, flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); } return var; error: return NULL; } void ecs_script_vars_set_size( ecs_script_vars_t *vars, int32_t count) { ecs_assert(!ecs_vec_count(&vars->vars), ECS_INVALID_OPERATION, "variable scope must be empty for resize operation"); ecs_vec_set_size_t(vars->allocator, &vars->vars, ecs_script_var_t, count); } ecs_script_var_t* ecs_script_vars_define_id( ecs_script_vars_t *vars, const char *name, ecs_entity_t type) { ecs_check(vars->world != NULL, ECS_INVALID_OPERATION, "variable scope is " "not associated with world, create scope with ecs_script_vars_init"); const ecs_type_info_t *ti = ecs_get_type_info(vars->world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "the entity provided for the type parameter is not a type"); ecs_script_var_t *result = ecs_script_vars_declare(vars, name); if (!result) { return NULL; } result->value.type = type; result->value.ptr = flecs_stack_alloc(vars->stack, ti->size, ti->alignment); result->type_info = ti; result->owned = true; flecs_type_info_ctor(result->value.ptr, 1, ti); return result; error: return NULL; } ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name) { if (!vars) { return NULL; } uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { if (flecs_name_index_is_init(&vars->var_index)) { var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); } } if (!var_id) { if (vars->parent) { return ecs_script_vars_lookup(vars->parent, name); } return NULL; } return ecs_vec_get_t(&vars->vars, ecs_script_var_t, flecs_uto(int32_t, var_id - 1)); } ecs_script_var_t* ecs_script_vars_from_sp( const ecs_script_vars_t *vars, int32_t sp) { ecs_check(sp >= 0, ECS_INVALID_PARAMETER, NULL); if (sp < vars->sp) { ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_script_vars_from_sp(vars->parent, sp); } sp -= vars->sp; ecs_check(sp < ecs_vec_count(&vars->vars), ECS_INVALID_PARAMETER, NULL); return ecs_vec_get_t(&vars->vars, ecs_script_var_t, sp); error: return NULL; } void ecs_script_vars_print( const ecs_script_vars_t *vars) { if (vars->parent) { ecs_script_vars_print(vars->parent); } int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *array = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *var = &array[i]; if (!i) { printf("FRAME "); } else { printf(" "); } printf("%2d: %s\n", var->sp, var->name); } } /* Static names for iterator fields */ static const char* flecs_script_iter_field_names[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31" }; static int flecs_script_vars_set_ref( ecs_script_vars_t *vars, const char *name, ecs_entity_t type, void *ptr) { ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); if (!var) { var = ecs_script_vars_declare(vars, name); var->value.type = type; } else { ecs_check(var->value.type == type, ECS_INVALID_PARAMETER, NULL); } var->value.ptr = ptr; var->owned = false; return 0; error: return -1; } void ecs_script_vars_from_iter( const ecs_iter_t *it, ecs_script_vars_t *vars, int offset) { ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); /* Set variable for $this */ if (it->count) { /* Safe, variable value will never be written */ if (flecs_script_vars_set_ref(vars, "this", ecs_id(ecs_entity_t), ECS_CONST_CAST(ecs_entity_t*, &it->entities[offset]))) { goto error; } } /* Set variables for fields */ { int8_t i, field_count = it->field_count; for (i = 0; i < field_count; i ++) { ecs_size_t size = it->sizes[i]; if (!size) { continue; } void *ptr = ecs_base_field_w_size(it, flecs_itosize(size), i); if (!ptr) { continue; } if (!it->sources[i]) { ptr = ECS_OFFSET(ptr, offset * flecs_uto(ecs_size_t, ecs_field_stride(it, i))); } if (flecs_script_vars_set_ref(vars, flecs_script_iter_field_names[i], ecs_get_typeid(it->world, it->ids[i]), ptr)) { goto error; } } } #ifdef FLECS_QUERY_PLANS /* Set variables for query variables */ { if (it->query) { char **variable_names = it->query->vars; int32_t i, var_count = ecs_iter_get_var_count(it); for (i = 1 /* skip this variable */ ; i < var_count; i ++) { const ecs_entity_t *e_ptr = NULL; ecs_var_t *query_var = &ecs_iter_get_vars(it)[i]; if (query_var->entity) { e_ptr = &query_var->entity; } else { ecs_table_range_t *range = &query_var->range; if (range->count == 1) { const ecs_entity_t *entities = ecs_table_entities(range->table); e_ptr = &entities[range->offset]; } } if (!e_ptr) { continue; } /* Safe, variable value will never be written */ if (flecs_script_vars_set_ref(vars, variable_names[i], ecs_id(ecs_entity_t), ECS_CONST_CAST(ecs_entity_t*, e_ptr))) { goto error; } } } } #endif error: return; } #endif #ifdef FLECS_SCRIPT static int flecs_script_scope_free( ecs_script_scope_t *scope, void *ctx) { return flecs_script_visit_free_node(ctx, (ecs_script_node_t*)scope); } static void flecs_script_id_free( ecs_script_t *script, ecs_script_id_t *id) { flecs_expr_visit_free(script, id->first_expr); flecs_expr_visit_free(script, id->second_expr); flecs_expr_visit_free(script, id->index_expr); } int flecs_script_visit_free_node( ecs_script_t *script, ecs_script_node_t *node) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); if (!node) { return 0; } ecs_allocator_t *a = &flecs_script_impl(script)->allocator; flecs_script_visit_scopes(node, flecs_script_scope_free, script); switch (node->kind) { case EcsAstScope: { ecs_script_scope_t *scope = (ecs_script_scope_t*)node; ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); for (int32_t i = 0; i < ecs_vec_count(&scope->stmts); i ++) { flecs_script_visit_free_node(script, stmts[i]); } ecs_vec_fini_t(a, &scope->stmts, ecs_script_node_t*); ecs_vec_fini_t(a, &scope->components, ecs_id_t); ecs_vec_fini_t(a, &scope->set_components, ecs_id_t); break; } case EcsAstEntity: flecs_expr_visit_free(script, ((ecs_script_entity_t*)node)->name_expr); break; case EcsAstPairScope: flecs_script_id_free(script, &((ecs_script_pair_scope_t*)node)->id); break; case EcsAstIf: flecs_expr_visit_free(script, ((ecs_script_if_t*)node)->expr); break; case EcsAstFor: { ecs_script_for_t *stmt = (ecs_script_for_t*)node; flecs_expr_visit_free(script, stmt->from); flecs_expr_visit_free(script, stmt->to); flecs_expr_visit_free(script, stmt->expr); break; } case EcsAstTag: case EcsAstWithTag: flecs_script_id_free(script, &((ecs_script_tag_t*)node)->id); break; case EcsAstComponent: case EcsAstWithComponent: { ecs_script_component_t *stmt = (ecs_script_component_t*)node; flecs_expr_visit_free(script, stmt->expr); flecs_script_id_free(script, &stmt->id); break; } case EcsAstProp: case EcsAstMut: case EcsAstConst: case EcsAstExportConst: case EcsAstExportMut: flecs_expr_visit_free(script, ((ecs_script_var_node_t*)node)->expr); break; case EcsAstAwait: flecs_expr_visit_free(script, ((ecs_script_await_t*)node)->expr); break; case EcsAstWhile: flecs_expr_visit_free(script, ((ecs_script_while_t*)node)->expr); break; case EcsAstAssign: flecs_expr_visit_free(script, ((ecs_script_assign_t*)node)->expr); break; case EcsAstTry: ecs_vec_fini_t(a, &((ecs_script_try_t*)node)->catches, ecs_script_catch_t); break; case EcsAstFunction: { ecs_script_function_node_t *fn = (ecs_script_function_node_t*)node; flecs_expr_visit_free(script, fn->return_expr); ecs_vec_fini_t(a, &fn->params, ecs_script_fn_param_t); break; } default: break; } flecs_free(a, node->alloc_size, node); return 0; error: return -1; } int flecs_script_visit_free( ecs_script_t *script) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_visit_free_node(script, (ecs_script_node_t*)flecs_script_impl(script)->root); error: return -1; } #endif #ifdef FLECS_SCRIPT typedef struct ecs_script_str_visitor_t { ecs_script_impl_t *script; ecs_strbuf_t *buf; int32_t depth; bool newline; bool colors; } ecs_script_str_visitor_t; static void flecs_script_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_scope_t *scope); static void flecs_script_stmt_to_str( ecs_script_str_visitor_t *v, ecs_script_node_t *node); static void flecs_script_color_to_str( ecs_script_str_visitor_t *v, const char *color) { if (v->colors) ecs_strbuf_appendstr(v->buf, color); } static void flecs_scriptbuf_append( ecs_script_str_visitor_t *v, const char *fmt, ...) { if (v->newline) { ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); v->newline = false; } va_list args; va_start(args, fmt); ecs_strbuf_vappend(v->buf, fmt, args); va_end(args); if (fmt[strlen(fmt) - 1] == '\n') { v->newline = true; } } static void flecs_scriptbuf_appendstr( ecs_script_str_visitor_t *v, const char *str) { if (v->newline) { ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); v->newline = false; } ecs_strbuf_appendstr(v->buf, str); if (str[strlen(str) - 1] == '\n') { v->newline = true; } } static void flecs_script_id_to_str( ecs_script_str_visitor_t *v, ecs_script_id_t *id) { if (id->flag) { if (id->flag == ECS_AUTO_OVERRIDE) { flecs_scriptbuf_appendstr(v, "auto_override | "); } else { flecs_scriptbuf_appendstr(v, "??? | "); } } if (id->second) { flecs_scriptbuf_append(v, "(%s, %s)", id->first, id->second); } else { flecs_scriptbuf_appendstr(v, id->first); } if (id->index_expr) { flecs_scriptbuf_appendstr(v, "["); flecs_expr_to_str_buf( &v->script->pub, id->index_expr, v->buf, v->colors); flecs_scriptbuf_appendstr(v, "]"); } } static void flecs_expr_to_str( ecs_script_str_visitor_t *v, const ecs_expr_node_t *expr) { if (expr) { flecs_expr_to_str_buf( &v->script->pub, expr, v->buf, v->colors); } else { flecs_scriptbuf_appendstr(v, "{}"); } } const char* flecs_script_node_kind_str( const ecs_script_node_t *node) { switch(node->kind) { case EcsAstScope: return "scope"; case EcsAstWithTag: case EcsAstTag: return "tag"; case EcsAstWithComponent: case EcsAstComponent: return "component"; case EcsAstWith: return "with"; case EcsAstUsing: return "using"; case EcsAstModule: return "module"; case EcsAstAnnotation: return "annot"; case EcsAstTemplate: return "template"; case EcsAstProp: return "prop"; case EcsAstMut: return "mut"; case EcsAstConst: return "const"; case EcsAstExportConst: return "export const"; case EcsAstExportMut: return "export mut"; case EcsAstEntity: return "entity"; case EcsAstPairScope: return "pair_scope"; case EcsAstIf: return "if"; case EcsAstFor: return "for"; case EcsAstInclude: return "include"; case EcsAstFunction: return "fn"; case EcsAstAwait: return "await"; case EcsAstTry: return "try"; case EcsAstContinue: return "continue"; case EcsAstAsync: return "async"; case EcsAstWhile: return "while"; case EcsAstAssign: return "assign"; } return "???"; } static void flecs_script_named_scope_to_str( ecs_script_str_visitor_t *v, const char *name, ecs_script_scope_t *scope) { flecs_script_color_to_str(v, ECS_CYAN); flecs_scriptbuf_append(v, "%s: ", name); flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, scope); } static void flecs_script_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_scope_t *scope) { ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); if (!ecs_vec_count(&scope->stmts)) { flecs_scriptbuf_appendstr(v, "{}\n"); return; } flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; int32_t i, count = ecs_vec_count(&scope->stmts); ecs_script_node_t **nodes = ecs_vec_first(&scope->stmts); for (i = 0; i < count; i ++) { flecs_script_stmt_to_str(v, nodes[i]); } v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); } static void flecs_script_stmt_to_str( ecs_script_str_visitor_t *v, ecs_script_node_t *node) { if (node->kind != EcsAstScope) { flecs_script_color_to_str(v, ECS_BLUE); flecs_scriptbuf_append(v, "%s: ", flecs_script_node_kind_str(node)); flecs_script_color_to_str(v, ECS_NORMAL); } ecs_script_scope_t *scope = NULL; bool wrap = false; switch(node->kind) { case EcsAstScope: flecs_script_scope_to_str(v, (ecs_script_scope_t*)node); return; case EcsAstTag: case EcsAstWithTag: { ecs_script_tag_t *stmt = (ecs_script_tag_t*)node; flecs_script_id_to_str(v, &stmt->id); break; } case EcsAstComponent: case EcsAstWithComponent: { ecs_script_component_t *stmt = (ecs_script_component_t*)node; flecs_script_id_to_str(v, &stmt->id); if (stmt->expr) { flecs_scriptbuf_appendstr(v, ": "); flecs_expr_to_str(v, stmt->expr); } break; } case EcsAstWith: { ecs_script_with_t *stmt = (ecs_script_with_t*)node; flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; flecs_script_named_scope_to_str(v, "expressions", stmt->expressions); flecs_script_named_scope_to_str(v, "scope", stmt->scope); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); return; } case EcsAstUsing: { ecs_script_using_t *stmt = (ecs_script_using_t*)node; flecs_scriptbuf_append(v, "%s", stmt->name); break; } case EcsAstModule: { ecs_script_module_t *stmt = (ecs_script_module_t*)node; flecs_scriptbuf_append(v, "%s", stmt->name); break; } case EcsAstAnnotation: { ecs_script_annot_t *stmt = (ecs_script_annot_t*)node; flecs_script_color_to_str(v, ECS_GREEN); flecs_scriptbuf_append(v, "%s = \"%s\"", stmt->name, stmt->expr); flecs_script_color_to_str(v, ECS_NORMAL); break; } case EcsAstTemplate: { ecs_script_template_node_t *stmt = (ecs_script_template_node_t*)node; flecs_scriptbuf_append(v, "%s ", stmt->name); if (stmt->base) { flecs_scriptbuf_append(v, ": %s%s", stmt->base, stmt->parent ? "" : " "); } if (stmt->parent) { flecs_scriptbuf_append(v, "%sparent %s ", stmt->base ? ", " : ": ", stmt->parent); } scope = stmt->scope; break; } case EcsAstConst: case EcsAstExportConst: case EcsAstExportMut: case EcsAstProp: case EcsAstMut: { ecs_script_var_node_t *stmt = (ecs_script_var_node_t*)node; if (stmt->type_is_template && !stmt->type) { flecs_scriptbuf_append(v, "%s : template%s = ", stmt->name, stmt->type_is_vector ? "[]" : ""); } else if (stmt->type) { flecs_scriptbuf_append(v, "%s : %s%s%s = ", stmt->name, stmt->type_is_template ? "template " : "", stmt->type, stmt->type_is_vector ? "[]" : ""); } else { flecs_scriptbuf_append(v, "%s = ", stmt->name); } if (stmt->is_await) { flecs_scriptbuf_appendstr(v, "await "); } flecs_expr_to_str(v, stmt->expr); break; } case EcsAstEntity: { ecs_script_entity_t *stmt = (ecs_script_entity_t*)node; if (stmt->kind) { flecs_scriptbuf_append(v, "%s ", stmt->kind); } if (stmt->name) { flecs_scriptbuf_append(v, "%s ", stmt->name); } else { flecs_scriptbuf_appendstr(v, " "); } if (!flecs_scope_is_empty(stmt->scope)) { scope = stmt->scope; } break; } case EcsAstPairScope: { ecs_script_pair_scope_t *stmt = (ecs_script_pair_scope_t*)node; flecs_script_id_to_str(v, &stmt->id); flecs_scriptbuf_appendstr(v, " "); scope = stmt->scope; break; } case EcsAstIf: { ecs_script_if_t *stmt = (ecs_script_if_t*)node; flecs_expr_to_str(v, stmt->expr); flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; flecs_script_named_scope_to_str(v, "true", stmt->if_true); flecs_script_named_scope_to_str(v, "false", stmt->if_false); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); return; } case EcsAstFor: { ecs_script_for_t *stmt = (ecs_script_for_t*)node; int32_t i; if (stmt->loop_var_count > 1) { flecs_scriptbuf_appendstr(v, "("); } for (i = 0; i < stmt->loop_var_count; i ++) { if (i) { flecs_scriptbuf_appendstr(v, ", "); } flecs_scriptbuf_appendstr(v, stmt->loop_vars[i]); } if (stmt->loop_var_count > 1) { flecs_scriptbuf_appendstr(v, ")"); } flecs_script_color_to_str(v, ECS_BLUE); flecs_scriptbuf_appendstr(v, " in "); flecs_script_color_to_str(v, ECS_NORMAL); if (stmt->expr) { flecs_expr_to_str(v, stmt->expr); } else { flecs_expr_to_str(v, stmt->from); flecs_scriptbuf_appendstr(v, " .. "); flecs_expr_to_str(v, stmt->to); } scope = stmt->scope; wrap = true; break; } case EcsAstInclude: { ecs_script_include_t *stmt = (ecs_script_include_t*)node; flecs_scriptbuf_append(v, "%s", stmt->filename); break; } case EcsAstAwait: { ecs_script_await_t *await = (ecs_script_await_t*)node; flecs_expr_to_str(v, await->expr); break; } case EcsAstContinue: break; case EcsAstAsync: { ecs_script_async_t *stmt = (ecs_script_async_t*)node; scope = stmt->scope; break; } case EcsAstWhile: { ecs_script_while_t *stmt = (ecs_script_while_t*)node; flecs_expr_to_str(v, stmt->expr); scope = stmt->scope; wrap = true; break; } case EcsAstAssign: { ecs_script_assign_t *stmt = (ecs_script_assign_t*)node; flecs_scriptbuf_append(v, "%s = ", stmt->name); flecs_expr_to_str(v, stmt->expr); break; } case EcsAstTry: { ecs_script_try_t *try_stmt = (ecs_script_try_t*)node; flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; flecs_script_scope_to_str(v, try_stmt->try_scope); int32_t i, count = ecs_vec_count(&try_stmt->catches); ecs_script_catch_t *catches = ecs_vec_first(&try_stmt->catches); for (i = 0; i < count; i ++) { flecs_script_color_to_str(v, ECS_CYAN); if (catches[i].error) { flecs_scriptbuf_append(v, "catch(%s): ", catches[i].error); } else { flecs_scriptbuf_appendstr(v, "catch: "); } flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, catches[i].scope); } v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); return; } case EcsAstFunction: { ecs_script_function_node_t *fn = (ecs_script_function_node_t*)node; flecs_scriptbuf_append(v, "%s(", fn->name); int32_t i, count = ecs_vec_count(&fn->params); ecs_script_fn_param_t *params = ecs_vec_first(&fn->params); for (i = 0; i < count; i ++) { if (i) { flecs_scriptbuf_appendstr(v, ", "); } flecs_scriptbuf_append(v, "%s: %s", params[i].name, params[i].type); } flecs_scriptbuf_append(v, ") -> %s ", fn->return_type); flecs_script_scope_to_str(v, fn->body); if (fn->return_expr) { flecs_expr_to_str(v, fn->return_expr); } break; } } if (scope) { if (wrap) { flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; } flecs_script_scope_to_str(v, scope); if (wrap) { v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); } } else { flecs_scriptbuf_appendstr(v, "\n"); } } int ecs_script_ast_node_to_buf( const ecs_script_t *script, ecs_script_node_t *node, ecs_strbuf_t *buf, bool colors, int32_t depth) { ecs_script_str_visitor_t v = { .buf = buf, .colors = colors, .depth = depth }; v.script = (ecs_script_impl_t*)ECS_CONST_CAST(ecs_script_t*, script); flecs_script_stmt_to_str(&v, node); return 0; } int ecs_script_ast_to_buf( ecs_script_t *script, ecs_strbuf_t *buf, bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_script_ast_node_to_buf(script, (ecs_script_node_t*)flecs_script_impl(script)->root, buf, colors, 0); error: ecs_strbuf_reset(buf); return -1; } char* ecs_script_ast_to_str( ecs_script_t *script, bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_script_impl_t *impl = flecs_script_impl(script); if (!impl->expr && !impl->root) { return NULL; } if (flecs_script_impl(script)->expr) { flecs_expr_to_str_buf( script, flecs_script_impl(script)->expr, &buf, colors); } else { if (ecs_script_ast_to_buf(script, &buf, colors)) { goto error; } } return ecs_strbuf_get(&buf); error: return NULL; } #endif #ifdef FLECS_STATS #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 #ifdef FLECS_REST #endif ECS_COMPONENT_DECLARE(ecs_entities_memory_t); ECS_COMPONENT_DECLARE(ecs_component_index_memory_t); ECS_COMPONENT_DECLARE(ecs_query_memory_t); ECS_COMPONENT_DECLARE(ecs_component_memory_t); ECS_COMPONENT_DECLARE(ecs_table_memory_t); ECS_COMPONENT_DECLARE(ecs_misc_memory_t); ECS_COMPONENT_DECLARE(ecs_table_histogram_t); ECS_COMPONENT_DECLARE(ecs_allocator_memory_t); ECS_COMPONENT_DECLARE(EcsWorldMemory); static ecs_size_t flecs_ballocator_memory_get( const ecs_block_allocator_t *allocator) { ecs_size_t result = 0; (void)allocator; #ifndef FLECS_USE_OS_ALLOC ecs_block_allocator_chunk_header_t *chunk = allocator->head; while (chunk) { result += allocator->data_size; chunk = chunk->next; } #endif return result; } static ecs_size_t flecs_map_memory_get( const ecs_map_t *map, ecs_size_t element_size) { ecs_size_t result = 0; if (map && map->bucket_count > 0) { result += map->bucket_count * ECS_SIZEOF(ecs_bucket_t); result += ecs_map_count(map) * ECS_SIZEOF(ecs_bucket_entry_t); result += ecs_map_count(map) * element_size; } return result; } static ecs_size_t flecs_hashmap_memory_get( const ecs_hashmap_t *name_index) { const ecs_map_t *map = &name_index->impl; ecs_size_t result = flecs_map_memory_get(map, 0); ecs_map_iter_t it = ecs_map_iter(map); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); for (; bucket; bucket = bucket->next) { result += name_index->bucket_size; } } return result; } static ecs_size_t flecs_sparse_memory_get( const ecs_sparse_t *sparse, ecs_size_t element_size) { if (!sparse) { return 0; } ecs_size_t result = 0; result += ecs_vec_size(&sparse->dense) * ECS_SIZEOF(uint64_t); result += ecs_vec_size(&sparse->pages) * ECS_SIZEOF(ecs_sparse_page_t); int32_t i, pages_count = ecs_vec_count(&sparse->pages); ecs_sparse_page_t *pages = ecs_vec_first_t( &sparse->pages, ecs_sparse_page_t); int32_t page_count = 0; for (i = 0; i < pages_count; i++) { ecs_sparse_page_t *page = &pages[i]; if (page->sparse) { page_count ++; } } result += page_count * ECS_SIZEOF(int32_t) * FLECS_SPARSE_PAGE_SIZE; result += page_count * element_size * FLECS_SPARSE_PAGE_SIZE; return result; } static ecs_size_t flecs_allocator_memory_get( const ecs_allocator_t *allocator) { ecs_size_t result = 0; (void)allocator; #ifndef FLECS_USE_OS_ALLOC int32_t i, count = flecs_sparse_count(&allocator->sizes); for (i = 0; i < count; i++) { ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( &allocator->sizes, ecs_block_allocator_t, i); result += flecs_ballocator_memory_get(ba); } result += flecs_ballocator_memory_get(&allocator->chunks); result += flecs_sparse_memory_get(&allocator->sizes, ECS_SIZEOF(ecs_block_allocator_t)); #endif return result; } static ecs_size_t flecs_stack_memory_get( const ecs_stack_t *stack) { ecs_size_t result = 0; ecs_stack_page_t *page = stack->first; while (page) { result += FLECS_STACK_PAGE_OFFSET + FLECS_STACK_PAGE_SIZE; page = page->next; } return result; } static ecs_size_t flecs_identifier_memory_get( const ecs_world_t *world, ecs_entity_t tag) { ecs_size_t result = 0; ecs_iter_t it = ecs_each_id(world, ecs_pair_t(EcsIdentifier, tag)); while (ecs_each_next(&it)) { EcsIdentifier *ptr = ecs_field(&it, EcsIdentifier, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { if (ptr[i].value) { result += ecs_os_strlen(ptr[i].value) + 1; } } } return result; } #ifdef FLECS_DOC static ecs_size_t flecs_doc_string_memory_get( const ecs_world_t *world, ecs_entity_t tag) { ecs_size_t result = 0; ecs_iter_t it = ecs_each_id(world, ecs_pair_t(EcsDocDescription, tag)); while (ecs_each_next(&it)) { EcsDocDescription *ptr = ecs_field(&it, EcsDocDescription, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { if (ptr[i].value) { result += ecs_os_strlen(ptr[i].value) + 1; } } } return result; } #endif ecs_entities_memory_t ecs_entity_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_entity_index_t *index = &world->store.entity_index; ecs_entities_memory_t result = {0}; result.alive_count = index->alive_count - 1; result.not_alive_count = ecs_vec_count(&index->dense) - index->alive_count; result.bytes_entity_index = ecs_vec_size(&index->dense) * ECS_SIZEOF(uint64_t); result.bytes_entity_index += ecs_vec_size(&index->pages) * ECS_SIZEOF(ecs_entity_index_page_t*); ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); int32_t i, page_count = 0, pages_count = ecs_vec_count(&index->pages); for (i = 0; i < pages_count; i++) { if (pages[i]) { page_count ++; } } result.bytes_entity_index += page_count * ECS_SIZEOF(ecs_entity_index_page_t); /* Entity names, symbols, aliases */ result.bytes_names += flecs_identifier_memory_get(world, EcsName); result.bytes_names += flecs_identifier_memory_get(world, EcsSymbol); result.bytes_names += flecs_identifier_memory_get(world, EcsAlias); #ifdef FLECS_DOC /* Doc strings */ result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsName); result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocBrief); result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocDetail); result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocUuid); result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocLink); result.bytes_doc_strings += flecs_doc_string_memory_get(world, EcsDocColor); #endif error: return result; } void ecs_component_record_memory_get( const ecs_component_record_t *cr, ecs_component_index_memory_t *result) { result->count++; result->bytes_component_record += ECS_SIZEOF(ecs_component_record_t); const ecs_table_cache_t *cache = &cr->cache; result->bytes_table_cache += ECS_SIZEOF(ecs_table_cache_t); const ecs_map_t *map = &cache->index; result->bytes_table_cache += flecs_map_memory_get(map, 0); #ifdef FLECS_DEBUG_INFO if (cr->str) { result->bytes_component_record += ecs_os_strlen(cr->str); } #endif if (cr->pair) { ecs_pair_record_t *pair = cr->pair; result->bytes_component_record += ECS_SIZEOF(ecs_pair_record_t); if (pair->name_index) { result->bytes_name_index += ECS_SIZEOF(ecs_hashmap_t); result->bytes_name_index += flecs_hashmap_memory_get( pair->name_index); } result->bytes_ordered_children += ecs_vec_size(&pair->ordered_children) * ECS_SIZEOF(ecs_entity_t); result->bytes_children_table_map += flecs_map_memory_get( &pair->children_tables, 0); const ecs_reachable_cache_t *reachable = &pair->reachable; result->bytes_reachable_cache += ecs_vec_size(&reachable->ids) * ECS_SIZEOF(ecs_reachable_elem_t); } } ecs_component_index_memory_t ecs_component_index_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_component_index_memory_t result = {0}; FLECS_EACH_COMPONENT_RECORD(cr, { ecs_component_record_memory_get(cr, &result); }) error: return result; } void ecs_query_memory_get( const ecs_query_t *query, ecs_query_memory_t *result) { const ecs_query_impl_t *impl = flecs_query_impl(query); result->count++; result->bytes_query += ECS_SIZEOF(ecs_query_impl_t); result->bytes_terms += query->term_count * ECS_SIZEOF(ecs_term_t); if (impl->ops) { result->bytes_plan += impl->op_count * ECS_SIZEOF(ecs_query_op_t); } if (impl->vars) { result->bytes_misc += impl->var_size * ECS_SIZEOF(ecs_query_var_t); } if (impl->src_vars) { result->bytes_misc += query->field_count * ECS_SIZEOF(ecs_var_id_t); } if (impl->tokens) { result->bytes_misc += impl->tokens_len; } if (impl->monitor) { result->bytes_misc += query->field_count * ECS_SIZEOF(int32_t); } /* Query cache memory */ #ifdef FLECS_CACHED_QUERIES if (impl->cache) { ecs_query_cache_t *cache = impl->cache; result->cached_count++; result->bytes_cache += ECS_SIZEOF(ecs_query_cache_t); result->bytes_cache += flecs_map_memory_get(&cache->tables, ECS_SIZEOF(ecs_query_cache_table_t)); result->bytes_group_by += flecs_map_memory_get(&cache->groups, ECS_SIZEOF(ecs_query_cache_group_t)); ecs_size_t cache_elem_size = flecs_query_cache_elem_size(cache); ecs_query_cache_group_t *cur = cache->first_group; for (; cur; cur = cur->next) { result->bytes_cache += ecs_vec_size(&cur->tables) * cache_elem_size; if (!(cache->query->flags & EcsQueryTrivialCache)) { int32_t group_table_count = ecs_vec_count(&cur->tables); for (int32_t j = 0; j < group_table_count; j++) { ecs_query_cache_match_t *match = ecs_vec_get(&cur->tables, cache_elem_size, j); ecs_size_t elem_arrays = 0; elem_arrays += query->field_count * ECS_SIZEOF(ecs_id_t); elem_arrays += query->field_count * ECS_SIZEOF(ecs_entity_t); elem_arrays += query->field_count * ECS_SIZEOF(ecs_table_t*); elem_arrays += query->field_count * ECS_SIZEOF(int32_t); result->bytes_cache += elem_arrays; if (match->wildcard_matches) { int32_t wildcard_count = ecs_vec_size(match->wildcard_matches); result->bytes_cache += (cache_elem_size + elem_arrays) * wildcard_count; } } } } result->bytes_order_by += ecs_vec_size(&cache->table_slices) * ECS_SIZEOF(ecs_table_range_t); if (cache->sources) { result->bytes_cache += query->field_count * ECS_SIZEOF(ecs_entity_t); } if (cache->field_map) { result->bytes_cache += query->field_count * ECS_SIZEOF(int8_t); } result->bytes_cache += flecs_ballocator_memory_get(&cache->allocators.ids); result->bytes_cache += flecs_ballocator_memory_get(&cache->allocators.monitors); result->bytes_cache += flecs_ballocator_memory_get(&cache->allocators.pointers); ecs_query_memory_get(cache->query, result); result->count --; /* Don't double count query */ } #endif } static ecs_size_t flecs_query_total_memory_get( const ecs_query_t *query) { ecs_query_memory_t memory = {0}; ecs_query_memory_get(query, &memory); ecs_size_t result = 0; result += memory.bytes_query; result += memory.bytes_cache; result += memory.bytes_group_by; result += memory.bytes_order_by; result += memory.bytes_plan; result += memory.bytes_terms; result += memory.bytes_misc; return result; } ecs_query_memory_t ecs_queries_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_memory_t result = (ecs_query_memory_t){0}; FLECS_EACH_QUERY(query, { ecs_query_memory_get(query, &result); }) error: return result; } static void flecs_component_memory_get_sparse( const ecs_component_record_t *cr, ecs_component_memory_t *result) { ecs_sparse_t *sparse = cr->sparse; if (!sparse) { return; } ecs_size_t component_size = cr->type_info ? cr->type_info->size : 0; result->bytes_sparse_components += flecs_sparse_memory_get( sparse, component_size); if (component_size) { result->instances += flecs_sparse_count(sparse); } } void ecs_table_component_memory_get( const ecs_table_t *table, ecs_component_memory_t *result) { int32_t count = ecs_table_count(table); int32_t size = ecs_table_size(table); ecs_column_t *columns = table->data.columns; int32_t col; for (col = 0; col < table->column_count; col++) { ecs_column_t *column = &columns[col]; ecs_size_t component_size = column->ti->size; ecs_size_t used = count * component_size; ecs_size_t allocated = size * component_size; result->bytes_table_components += used; result->bytes_table_components_unused += allocated - used; } result->instances += count * table->column_count; if (table->_) { result->bytes_toggle_bitsets += table->_->bs_count * ECS_SIZEOF(ecs_bitset_t); result->bytes_toggle_bitsets += (count / 64) * ECS_SIZEOF(uint64_t) * table->_->bs_count; } } ecs_component_memory_t ecs_component_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_component_memory_t result = {0}; /* Iterate through all tables to calculate table component memory */ const ecs_sparse_t *tables = &world->store.tables; int32_t i, table_count = flecs_sparse_count(tables); for (i = 0; i < table_count; i++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); ecs_table_component_memory_get(table, &result); } /* Sparse components */ FLECS_EACH_COMPONENT_RECORD(cr, { flecs_component_memory_get_sparse(cr, &result); }) error: return result; } static void flecs_table_graph_edge_memory_get( const ecs_graph_edge_t *edge, ecs_table_memory_t *result) { result->bytes_edges += ECS_SIZEOF(ecs_graph_edge_t); if (edge->diff) { result->bytes_edges += ECS_SIZEOF(ecs_table_diff_t); result->bytes_edges += edge->diff->added.count * ECS_SIZEOF(ecs_id_t); result->bytes_edges += edge->diff->removed.count * ECS_SIZEOF(ecs_id_t); } } static void flecs_table_graph_edges_memory_get( const ecs_graph_edges_t *edges, ecs_table_memory_t *result) { if (edges->lo) { int32_t i; for (i = 0; i < FLECS_HI_COMPONENT_ID; i++) { ecs_graph_edge_t *edge = &edges->lo[i]; flecs_table_graph_edge_memory_get(edge, result); } } if (edges->hi) { result->bytes_edges += ECS_SIZEOF(ecs_map_t); result->bytes_edges += flecs_map_memory_get(edges->hi, 0); ecs_map_iter_t it = ecs_map_iter(edges->hi); while (ecs_map_next(&it)) { ecs_graph_edge_t *edge = ecs_map_ptr(&it); flecs_table_graph_edge_memory_get(edge, result); } } } void ecs_table_memory_get( const ecs_table_t *table, ecs_table_memory_t *result) { ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); result->bytes_table += ECS_SIZEOF(ecs_table_t); int32_t entity_count = ecs_table_count(table); int32_t entity_size = ecs_table_size(table); int32_t type_count = table->type.count; int32_t column_count = table->column_count; result->column_count += column_count; if (entity_count == 0) { result->empty_count++; } result->bytes_type += type_count * ECS_SIZEOF(ecs_id_t); result->bytes_entities += entity_size * ECS_SIZEOF(ecs_entity_t); if (table->data.overrides) { result->bytes_overrides += ECS_SIZEOF(ecs_table_overrides_t); result->bytes_overrides += column_count * ECS_SIZEOF(ecs_ref_t); } result->bytes_type += column_count * ECS_SIZEOF(ecs_column_t); if (table->_) { result->bytes_table += ECS_SIZEOF(ecs_table__t); result->bytes_type += table->_->record_count * ECS_SIZEOF(ecs_table_record_t); } result->bytes_column_map += (type_count + column_count) * ECS_SIZEOF(int16_t); if (table->component_map && (table->component_map != flecs_table_empty_component_map)) { result->bytes_component_map += FLECS_HI_COMPONENT_ID * ECS_SIZEOF(int16_t); } if (table->dirty_state) { result->bytes_dirty_state += (column_count + 1) * ECS_SIZEOF(int32_t); } flecs_table_graph_edges_memory_get(&table->node.add, result); flecs_table_graph_edges_memory_get(&table->node.remove, result); } ecs_table_memory_t ecs_tables_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_memory_t result = {0}; const ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); result.count = count; result.bytes_table += flecs_sparse_memory_get(tables, ECS_SIZEOF(ecs_table_t)); for (i = 0; i < count; i++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); ecs_table_memory_get(table, &result); /* Already accounted for by flecs_sparse_memory_get */ result.bytes_table -= ECS_SIZEOF(ecs_table_t); } error: return result; } ecs_table_histogram_t ecs_table_histogram_get( const ecs_world_t *world) { ecs_table_histogram_t result = {0}; const 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); ecs_assert(table != NULL, ECS_INVALID_PARAMETER, NULL); int32_t entity_count = ecs_table_count(table); if (entity_count == 0) { result.entity_counts[0]++; /* Bucket 0: 0 entities */ } else if (entity_count == 1) { result.entity_counts[1]++; /* Bucket 1: 1 entity */ } else if (entity_count <= ECS_TABLE_MEMORY_HISTOGRAM_MAX_COUNT) { /* Find the appropriate power-of-2 bucket */ /* Buckets: 2-3, 4-7, 8-15, 16-31, ..., 32768-65535 */ int32_t bucket_index = 2; int32_t bucket_min = 2; int32_t bucket_max = 3; while ((bucket_max < entity_count) && (bucket_index < (ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT - 2))) { bucket_index++; bucket_min = bucket_max + 1; bucket_max = bucket_min * 2 - 1; } result.entity_counts[bucket_index]++; } else { result.entity_counts[ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT - 1]++; } } return result; } static ecs_size_t flecs_event_record_memory_get( const ecs_event_record_t *er) { ecs_size_t result = flecs_map_memory_get(&er->event_ids, ECS_SIZEOF(ecs_event_id_record_t)); ecs_map_iter_t mit = ecs_map_iter(&er->event_ids); while (ecs_map_next(&mit)) { ecs_event_id_record_t *eir = ecs_map_ptr(&mit); result += flecs_map_memory_get(&eir->self, 0); result += flecs_map_memory_get(&eir->self_up, 0); result += flecs_map_memory_get(&eir->up, 0); } return result; } static ecs_size_t flecs_observer_memory_get( const ecs_observer_impl_t *o) { ecs_size_t result = ECS_SIZEOF(ecs_observer_impl_t); result += ecs_vec_size(&o->children) * ECS_SIZEOF(void*); if (o->pub.query) { result += flecs_query_total_memory_get(o->pub.query); } if (o->not_query) { result += flecs_query_total_memory_get(o->not_query); } ecs_observer_impl_t **children = ecs_vec_first(&o->children); int32_t i, count = ecs_vec_count(&o->children); for (i = 0; i < count; i ++) { result += flecs_observer_memory_get(children[i]); } return result; } static ecs_size_t flecs_observers_memory_get( const ecs_world_t *world) { ecs_size_t result = 0; int32_t i, count = ecs_vec_count(&world->observable.global_observers); for (i = 0; i < count; i ++) { ecs_observer_t *o = ecs_vec_get_t( &world->observable.global_observers, ecs_observer_t*, i)[0]; result += flecs_observer_memory_get(flecs_observer_impl(o)); } ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsObserver); while (ecs_each_next(&it)) { EcsPoly *p = ecs_field(&it, EcsPoly, 0); count = it.count; for (i = 0; i < count; i ++) { ecs_observer_impl_t *o = p[i].poly; if (!o) { continue; } result += flecs_observer_memory_get(o); } } return result; } static ecs_size_t flecs_observer_index_memory_get( const ecs_world_t *world) { const ecs_observable_t *o = &world->observable; ecs_size_t result = 0; result += flecs_event_record_memory_get(&o->on_add); result += flecs_event_record_memory_get(&o->on_remove); result += flecs_event_record_memory_get(&o->on_set); result += flecs_event_record_memory_get(&o->on_wildcard); result += flecs_event_record_memory_get(&o->on_table_create); result += flecs_event_record_memory_get(&o->on_table_delete); const ecs_sparse_t *events = &o->events; result += flecs_sparse_memory_get(events, ECS_SIZEOF(ecs_event_record_t)); int32_t i, count = flecs_sparse_count(events); for (i = 0; i < count; i ++) { ecs_event_record_t *er = flecs_sparse_get_dense_t( events, ecs_event_record_t, i); result += flecs_event_record_memory_get(er); } return result; } static void flecs_system_memory_get( const ecs_world_t *world, ecs_misc_memory_t *result) { ecs_iter_t it = ecs_each_pair_t(world, EcsPoly, EcsSystem); while (ecs_each_next(&it)) { EcsPoly *p = ecs_field(&it, EcsPoly, 0); result->bytes_systems += it.count * ECS_SIZEOF(ecs_system_t); int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_system_t *s = p[i].poly; if (!s) { continue; } if (s->name) { result->bytes_systems += ecs_os_strlen(s->name); } } } } static void flecs_pipeline_memory_get( const ecs_world_t *world, ecs_misc_memory_t *result) { ecs_iter_t it = ecs_each(world, EcsPipeline); while (ecs_each_next(&it)) { EcsPipeline *p = ecs_field(&it, EcsPipeline, 0); result->bytes_pipelines += it.count * ECS_SIZEOF(ecs_pipeline_state_t); int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_pipeline_state_t *ps = p[i].state; if (!ps) { continue; } result->bytes_pipelines += ecs_vec_size(&ps->ops) * ECS_SIZEOF(ecs_pipeline_op_t); result->bytes_pipelines += ecs_vec_size(&ps->systems) * ECS_SIZEOF(ecs_entity_t); } } } #ifdef FLECS_META static void flecs_reflection_memory_get( const ecs_world_t *world, ecs_misc_memory_t *result) { { ecs_iter_t it = ecs_each(world, EcsStruct); while (ecs_each_next(&it)) { EcsStruct *s = ecs_field(&it, EcsStruct, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { result->bytes_reflection += ecs_vec_size(&s[i].members) * ECS_SIZEOF(ecs_member_t); int32_t m, mcount = ecs_vec_count(&s[i].members); ecs_member_t *members = ecs_vec_first(&s[i].members); for (m = 0; m < mcount; m ++) { ecs_member_t *member = &members[m]; result->bytes_reflection += ecs_os_strlen(member->name); } } } } { ecs_iter_t it = ecs_each(world, EcsConstants); while (ecs_each_next(&it)) { EcsConstants *c = ecs_field(&it, EcsConstants, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { result->bytes_reflection += flecs_map_memory_get( c[i].constants, ECS_SIZEOF(ecs_enum_constant_t)); result->bytes_reflection += ecs_vec_size(&c[i].ordered_constants) * ECS_SIZEOF(ecs_enum_constant_t); ecs_map_iter_t mit = ecs_map_iter(c[i].constants); while (ecs_map_next(&mit)) { ecs_enum_constant_t *constant = ecs_map_ptr(&mit); result->bytes_reflection += ecs_os_strlen(constant->name); } } } } { ecs_iter_t it = ecs_each(world, EcsTypeSerializer); while (ecs_each_next(&it)) { EcsTypeSerializer *s = ecs_field(&it, EcsTypeSerializer, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { int32_t o, ocount = ecs_vec_count(&s[i].ops); ecs_meta_op_t *ops = ecs_vec_first(&s[i].ops); result->bytes_reflection += ecs_vec_size(&s[i].ops) * ECS_SIZEOF(ecs_meta_op_t); for (o = 0; o < ocount; o ++) { ecs_meta_op_t *op = &ops[o]; if (op->kind == EcsOpPushStruct) { result->bytes_reflection += flecs_hashmap_memory_get( op->is.members); } else if (op->kind == EcsOpEnum || op->kind == EcsOpBitmask) { result->bytes_reflection += flecs_map_memory_get( op->is.constants, 0); } } } } } } #endif static void flecs_stats_memory_get( const ecs_world_t *world, ecs_misc_memory_t *result) { ecs_entity_t periods[] = { EcsPeriod1s, EcsPeriod1m, EcsPeriod1h, EcsPeriod1d, EcsPeriod1w}; int32_t p, period_count = 5; result->bytes_stats += ECS_SIZEOF(ecs_world_stats_t) * ecs_count_id(world, ecs_pair_t(EcsWorldStats, EcsWildcard)); for (p = 0; p < period_count; p ++) { ecs_iter_t it = ecs_each_id(world, ecs_pair_t(EcsSystemStats, periods[p])); while (ecs_each_next(&it)) { EcsSystemStats *ss = ecs_field(&it, EcsSystemStats, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { result->bytes_stats += flecs_map_memory_get(&ss[i].stats, ECS_SIZEOF(ecs_system_stats_t)); } } } for (p = 0; p < period_count; p ++) { ecs_iter_t it = ecs_each_id(world, ecs_pair_t(EcsPipelineStats, periods[p])); while (ecs_each_next(&it)) { EcsPipelineStats *ps = ecs_field(&it, EcsPipelineStats, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { result->bytes_stats += flecs_map_memory_get(&ps[i].stats, ECS_SIZEOF(ecs_pipeline_stats_t)); ecs_map_iter_t pit = ecs_map_iter(&ps[i].stats); while (ecs_map_next(&pit)) { ecs_pipeline_stats_t *stats = ecs_map_ptr(&pit); result->bytes_stats += ecs_vec_size(&stats->systems) * ECS_SIZEOF(ecs_entity_t); result->bytes_stats += ecs_vec_size(&stats->sync_points) * ECS_SIZEOF(ecs_sync_stats_t); } } } } } #ifdef FLECS_REST static void flecs_http_memory_get( ecs_http_server_t *srv, ecs_misc_memory_t *result) { result->bytes_rest += ECS_SIZEOF(ecs_http_server_t); result->bytes_rest += flecs_sparse_memory_get(&srv->connections, ECS_SIZEOF(ecs_http_connection_impl_t)); result->bytes_rest += flecs_hashmap_memory_get(&srv->request_cache); flecs_hashmap_iter_t it = flecs_hashmap_iter(&srv->request_cache); ecs_http_request_key_t *key; ecs_http_request_entry_t *entry; while ((entry = flecs_hashmap_next_w_key( &it, ecs_http_request_key_t, &key, ecs_http_request_entry_t))) { result->bytes_rest += key->count; if (entry->content) { result->bytes_rest += ecs_os_strlen(entry->content); } } } static void flecs_rest_memory_get( const ecs_world_t *world, ecs_misc_memory_t *result) { ecs_iter_t it = ecs_each(world, EcsRest); while (ecs_each_next(&it)) { EcsRest *r = ecs_field(&it, EcsRest, 0); int32_t i, count = it.count; for (i = 0; i < count; i ++) { result->bytes_rest += ECS_SIZEOF(ecs_rest_ctx_t); if (!r[i].impl) { continue; } flecs_http_memory_get(r[i].impl->srv, result); } } } #endif static void flecs_tree_spawner_memory_get( const ecs_world_t *world, ecs_misc_memory_t *result) { ecs_iter_t it = ecs_each(world, EcsTreeSpawner); while (ecs_each_next(&it)) { EcsTreeSpawner *t = ecs_field(&it, EcsTreeSpawner, 0); for (int32_t i = 0; i < it.count; i ++) { for (int32_t depth = 0; depth < FLECS_TREE_SPAWNER_DEPTH_CACHE_SIZE; depth ++) { result->bytes_tree_spawner += ecs_vec_count(&t[i].data[depth].children) * ECS_SIZEOF(ecs_tree_spawner_child_t); } } } result->bytes_tree_spawner += ecs_vec_count(&world->allocators.tree_spawner) * ECS_SIZEOF(ecs_entity_t); } ecs_misc_memory_t ecs_misc_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_misc_memory_t result = {0}; int32_t stage_count = world->stage_count; ecs_stage_t **stages = world->stages; result.bytes_world = ECS_SIZEOF(ecs_world_t) + ECS_SIZEOF(ecs_stage_t) * stage_count + ECS_SIZEOF(ecs_stage_t*) * stage_count; result.bytes_observers = flecs_observers_memory_get(world); result.bytes_observers = flecs_observer_index_memory_get(world); flecs_system_memory_get(world, &result); flecs_pipeline_memory_get(world, &result); #ifdef FLECS_META flecs_reflection_memory_get(world, &result); #endif flecs_stats_memory_get(world, &result); #ifdef FLECS_REST flecs_rest_memory_get(world, &result); #endif flecs_tree_spawner_memory_get(world, &result); result.bytes_prefab_child_indices = flecs_map_memory_get( &world->prefab_child_indices, 0); #ifdef FLECS_MULTI_WORLD result.bytes_component_ids += ecs_vec_size(&world->component_ids) * ECS_SIZEOF(ecs_entity_t); #endif result.bytes_table_lookup += flecs_hashmap_memory_get(&world->store.table_map); result.bytes_component_record_lookup += FLECS_HI_ID_RECORD_ID * ECS_SIZEOF(ecs_component_record_t*); result.bytes_component_record_lookup += flecs_map_memory_get( &world->id_index_hi, 0); result.bytes_type_info += flecs_map_memory_get( &world->type_info, ECS_SIZEOF(ecs_type_info_t)); /* Iterate through all stages to collect command memory usage */ for (int32_t i = 0; i < stage_count; i++) { ecs_stage_t *stage = stages[i]; for (ecs_commands_t *cmd = &stage->cmd_root; cmd; cmd = cmd->next) { if (cmd != &stage->cmd_root) { result.bytes_commands += ECS_SIZEOF(ecs_commands_t); } /* Calculate queue memory (ecs_vec_t) */ result.bytes_commands += ecs_vec_size(&cmd->queue) * ECS_SIZEOF(ecs_cmd_t); /* Calculate entries memory (ecs_sparse_t) */ ecs_sparse_t *entries = &cmd->entries; result.bytes_commands += flecs_sparse_memory_get(entries, ECS_SIZEOF(ecs_cmd_entry_t)); /* Calculate stack memory (ecs_stack_t) */ result.bytes_commands += flecs_stack_memory_get(&cmd->stack); } } #ifdef FLECS_DEBUG result.bytes_locked_components += flecs_map_memory_get( &world->locked_components, 0); result.bytes_locked_components += flecs_map_memory_get( &world->locked_entities, 0); #endif error: return result; } ecs_allocator_memory_t ecs_allocator_memory_get( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_allocator_memory_t result = {0}; result.bytes_graph_edge = flecs_ballocator_memory_get( &world->allocators.graph_edge_lo); result.bytes_graph_edge += flecs_ballocator_memory_get( &world->allocators.graph_edge); result.bytes_component_record = flecs_ballocator_memory_get( &world->allocators.component_record); result.bytes_pair_record = flecs_ballocator_memory_get( &world->allocators.pair_record); result.bytes_sparse_chunk = flecs_ballocator_memory_get( &world->allocators.sparse_chunk); result.bytes_allocator = flecs_allocator_memory_get(&world->allocator); result.bytes_misc += ecs_vec_size(&world->store.records) * ECS_SIZEOF(ecs_table_record_t); result.bytes_misc += ecs_vec_size(&world->store.marked_ids) * ECS_SIZEOF(ecs_marked_id_t); result.bytes_misc += ecs_vec_size(&world->store.deleted_components) * ECS_SIZEOF(ecs_entity_t); int32_t i, stage_count = world->stage_count; ecs_stage_t **stages = world->stages; for (i = 0; i < stage_count; i++) { ecs_stage_t *stage = stages[i]; result.bytes_allocator += flecs_allocator_memory_get(&stage->allocator); result.bytes_cmd_entry_chunk += flecs_ballocator_memory_get( &stage->allocators.cmd_entry_chunk); result.bytes_query_impl += flecs_ballocator_memory_get( &stage->allocators.query_impl); #ifdef FLECS_CACHED_QUERIES result.bytes_query_cache += flecs_ballocator_memory_get( &stage->allocators.query_cache); #endif result.bytes_misc += ecs_vec_size(&stage->variables) * ECS_SIZEOF(ecs_query_var_t); result.bytes_misc += ecs_vec_size(&stage->operations) * ECS_SIZEOF(ecs_query_op_t); result.bytes_stack_allocator += flecs_stack_memory_get(&stage->allocators.iter_stack); } error: return result; } #ifdef FLECS_META static int flecs_world_memory_serialize( const ecs_serializer_t *s, const void *ptr) { (void)ptr; /* Unused, we compute values from world */ const ecs_world_t *world = s->world; flecs_poly_assert(world, ecs_world_t); EcsWorldMemory value; ecs_os_zeromem(&value); ecs_time_t t = {0}; ecs_time_measure(&t); value.entities = ecs_entity_memory_get(world); value.components = ecs_component_memory_get(world); value.component_index = ecs_component_index_memory_get(world); value.queries = ecs_queries_memory_get(world); value.tables = ecs_tables_memory_get(world); value.table_histogram = ecs_table_histogram_get(world); value.misc = ecs_misc_memory_get(world); value.allocators = ecs_allocator_memory_get(world); /* Use component IDs directly */ s->member(s, "entities"); s->value(s, ecs_id(ecs_entities_memory_t), &value.entities); s->member(s, "components"); s->value(s, ecs_id(ecs_component_memory_t), &value.components); s->member(s, "component_index"); s->value(s, ecs_id(ecs_component_index_memory_t), &value.component_index); s->member(s, "queries"); s->value(s, ecs_id(ecs_query_memory_t), &value.queries); s->member(s, "tables"); s->value(s, ecs_id(ecs_table_memory_t), &value.tables); s->member(s, "table_histogram"); s->value(s, ecs_id(ecs_table_histogram_t), &value.table_histogram); s->member(s, "misc"); s->value(s, ecs_id(ecs_misc_memory_t), &value.misc); s->member(s, "allocators"); s->value(s, ecs_id(ecs_allocator_memory_t), &value.allocators); value.collection_time = ecs_time_measure(&t); return 0; } #endif void flecs_stats_memory_register_reflection( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldMemory); #ifdef FLECS_META ecs_entity_t unit = 0; #ifdef FLECS_UNITS unit = EcsBytes; #endif /* Register struct reflection for memory types */ ecs_id(ecs_entities_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "entity_index_memory_t" }), .members = { { .name = "alive_count", .type = ecs_id(ecs_i32_t) }, { .name = "not_alive_count", .type = ecs_id(ecs_i32_t) }, { .name = "bytes_entity_index", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_names", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_doc_strings", .type = ecs_id(ecs_i32_t), .unit = unit } } }); ecs_id(ecs_component_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "component_memory_t" }), .members = { { .name = "instances", .type = ecs_id(ecs_i32_t) }, { .name = "bytes_table_components", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_table_components_unused", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_toggle_bitset", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_sparse_components", .type = ecs_id(ecs_i32_t), .unit = unit } } }); ecs_id(ecs_component_index_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "component_index_memory_t" }), .members = { { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "bytes_component_record", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_table_cache", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_name_index", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_ordered_children", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_children_table_map", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_reachable_cache", .type = ecs_id(ecs_i32_t), .unit = unit } } }); ecs_id(ecs_query_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "query_memory_t" }), .members = { { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "cached_count", .type = ecs_id(ecs_i32_t) }, { .name = "bytes_query", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_cache", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_group_by", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_order_by", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_plan", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_terms", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_misc", .type = ecs_id(ecs_i32_t), .unit = unit } } }); ecs_id(ecs_table_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "table_memory_t" }), .members = { { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "empty_count", .type = ecs_id(ecs_i32_t) }, { .name = "column_count", .type = ecs_id(ecs_i32_t) }, { .name = "bytes_table", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_type", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_entities", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_overrides", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_column_map", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_component_map", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_dirty_state", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_edges", .type = ecs_id(ecs_i32_t), .unit = unit } } }); ecs_id(ecs_table_histogram_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "table_histogram_t" }), .members = { { .name = "entity_counts", .type = ecs_id(ecs_i32_t), .count = ECS_TABLE_MEMORY_HISTOGRAM_BUCKET_COUNT } } }); ecs_id(ecs_misc_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "misc_memory_t" }), .members = { { .name = "bytes_world", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_observers", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_systems", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_pipelines", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_table_lookup", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_component_record_lookup", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_locked_components", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_type_info", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_commands", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_rematch_monitor", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_component_ids", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_reflection", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_tree_spawner", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_prefab_child_indices", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_stats", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_rest", .type = ecs_id(ecs_i32_t), .unit = unit }, } }); ecs_id(ecs_allocator_memory_t) = ecs_struct(world, { .entity = ecs_entity(world, { .name = "allocator_memory_t" }), .members = { { .name = "bytes_graph_edge", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_component_record", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_pair_record", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_table_diff", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_sparse_chunk", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_allocator", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_stack_allocator", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_cmd_entry_chunk", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_query_impl", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_query_cache", .type = ecs_id(ecs_i32_t), .unit = unit }, { .name = "bytes_misc", .type = ecs_id(ecs_i32_t), .unit = unit } } }); /* Register EcsWorldMemory as opaque type that computes on demand */ ecs_entity_t world_memory_struct = ecs_struct(world, { .members = { { .name = "entities", .type = ecs_id(ecs_entities_memory_t) }, { .name = "components", .type = ecs_id(ecs_component_memory_t) }, { .name = "component_index", .type = ecs_id(ecs_component_index_memory_t) }, { .name = "queries", .type = ecs_id(ecs_query_memory_t) }, { .name = "tables", .type = ecs_id(ecs_table_memory_t) }, { .name = "table_histogram", .type = ecs_id(ecs_table_histogram_t) }, { .name = "misc", .type = ecs_id(ecs_misc_memory_t) }, { .name = "allocators", .type = ecs_id(ecs_allocator_memory_t) }, { .name = "collection_time", .type = ecs_id(ecs_f32_t) } } }); ecs_opaque(world, { .entity = ecs_id(EcsWorldMemory), .type.as_type = world_memory_struct, .type.serialize = flecs_world_memory_serialize }); #endif } static ecs_size_t flecs_get_memory_total( const ecs_world_t *world, const void *ptr, ecs_entity_t type) { (void)world; (void)ptr; (void)type; if (!type) { ecs_warn("import flecs.stats module before calling ecs_memory_get"); return 0; } ecs_size_t result = 0; #ifdef FLECS_META const EcsTypeSerializer *s = ecs_get(world, type, EcsTypeSerializer); ecs_meta_op_t *ops = ecs_vec_first(&s->ops); int32_t i, count = ecs_vec_count(&s->ops); for (i = 0; i < count; i ++) { ecs_meta_op_t *op = &ops[i]; if (!op->name) { continue; } if (!ecs_os_strncmp(op->name, "bytes_", 6)) { result += *(ecs_size_t*)ECS_OFFSET(ptr, op->offset); } } #else ecs_warn("FLECS_META addon required for ecs_memory_get"); #endif return result; } ecs_size_t ecs_memory_get( const ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_entities_memory_t entities = {0}; ecs_component_memory_t component = {0}; ecs_component_index_memory_t component_index = {0}; ecs_query_memory_t query = {0}; ecs_table_memory_t table = {0}; ecs_misc_memory_t misc = {0}; ecs_allocator_memory_t allocator = {0}; entities = ecs_entity_memory_get(world); component = ecs_component_memory_get(world); component_index = ecs_component_index_memory_get(world); query = ecs_queries_memory_get(world); table = ecs_tables_memory_get(world); misc = ecs_misc_memory_get(world); allocator = ecs_allocator_memory_get(world); ecs_size_t result = 0; result += flecs_get_memory_total(world, &entities, ecs_id(ecs_entities_memory_t)); result += flecs_get_memory_total(world, &component, ecs_id(ecs_component_memory_t)); result += flecs_get_memory_total(world, &component_index, ecs_id(ecs_component_index_memory_t)); result += flecs_get_memory_total(world, &query, ecs_id(ecs_query_memory_t)); result += flecs_get_memory_total(world, &table, ecs_id(ecs_table_memory_t)); result += flecs_get_memory_total(world, &misc, ecs_id(ecs_misc_memory_t)); result += flecs_get_memory_total(world, &allocator, ecs_id(ecs_allocator_memory_t)); return result; } #endif #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(FlecsStats); ecs_entity_t EcsPeriod1s = 0; ecs_entity_t EcsPeriod1m = 0; ecs_entity_t EcsPeriod1h = 0; ecs_entity_t EcsPeriod1d = 0; ecs_entity_t EcsPeriod1w = 0; typedef struct { ecs_stats_api_t api; ecs_query_t *query; int32_t interval; } ecs_monitor_stats_ctx_t; static void MonitorStats(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_monitor_stats_ctx_t *ctx = it->ctx; EcsStatsHeader *hdr = ecs_field_w_size(it, ecs_field_size(it, 0), 0); ecs_ftime_t elapsed = hdr->elapsed; hdr->elapsed += it->delta_time; int32_t t_last = (int32_t)(elapsed * 60); int32_t t_next = (int32_t)(hdr->elapsed * 60); int32_t i, dif = t_next - t_last; void *stats_storage = ecs_os_alloca(ctx->api.stats_size); void *last = NULL; if (!dif) { hdr->reduce_count ++; } ecs_iter_t qit; int32_t cur = -1, count = 0; void *stats = NULL; ecs_map_t *stats_map = NULL; if (ctx->query) { /* Query results are stored in a map */ qit = ecs_query_iter(it->stage, ctx->query); stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); } else { /* No query, so tracking stats for single element */ stats = ECS_OFFSET_T(hdr, EcsStatsHeader); if (ctx->api.is_pointer) { stats = *((void**)stats); } } do { ecs_entity_t res = 0; if (ctx->query) { /* Query, fetch resource entity & stats pointer */ if (cur == (count - 1)) { if (!ecs_query_next(&qit)) { break; } cur = 0; count = qit.count; if (!count) { cur = -1; continue; } } else { cur ++; } res = qit.entities[cur]; stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); } if (!dif) { /* Copy last value so we can pass it to reduce_last */ last = stats_storage; ecs_os_memset(last, 0, ctx->api.stats_size); ctx->api.copy_last(last, stats); } ctx->api.get(world, res, stats); if (!dif) { /* Still in same interval, combine with last measurement */ ctx->api.reduce_last(stats, last, hdr->reduce_count); } else if (dif > 1) { /* More than one interval has passed, backfill */ for (i = 1; i < dif; i ++) { ctx->api.repeat_last(stats); } } if (last && ctx->api.fini) { ctx->api.fini(last); } if (!ctx->query) { break; } } while (true); if (dif > 1) { hdr->reduce_count = 0; } } static void ReduceStats(ecs_iter_t *it) { ecs_monitor_stats_ctx_t *ctx = it->ctx; int32_t interval = ctx->interval; EcsStatsHeader *dst_hdr = ecs_field_w_size(it, ecs_field_size(it, 0), 0); EcsStatsHeader *src_hdr = ecs_field_w_size(it, ecs_field_size(it, 1), 1); void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); void *dst_map = NULL; void *src_map = NULL; if (ctx->api.query_component_id) { dst_map = dst; src_map = src; dst = NULL; src = NULL; } else { if (ctx->api.is_pointer) { dst = *((void**)dst); src = *((void**)src); } } void *stats_storage = ecs_os_alloca(ctx->api.stats_size); void *last = NULL; ecs_map_iter_t mit; if (src_map) { mit = ecs_map_iter(src_map); } do { if (src_map) { if (!ecs_map_next(&mit)) { break; } src = ecs_map_ptr(&mit); dst = ecs_map_ensure_alloc( dst_map, ctx->api.stats_size, ecs_map_key(&mit)); } if (dst_hdr->reduce_count != 0) { /* Copy last value so we can pass it to reduce_last */ last = stats_storage; ecs_os_memset(last, 0, ctx->api.stats_size); ctx->api.copy_last(last, dst); } /* Reduce stats into the current aggregation interval */ ctx->api.reduce(dst, src); if (dst_hdr->reduce_count != 0) { ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); } if (last && ctx->api.fini != NULL) { ctx->api.fini(last); } if (!src_map) { break; } } while (true); /* Increment reduce count, reset when interval is reached */ dst_hdr->reduce_count ++; if (dst_hdr->reduce_count >= interval) { dst_hdr->reduce_count = 0; } } static void flecs_monitor_ctx_free( void *ptr) { ecs_monitor_stats_ctx_t *ctx = ptr; if (ctx->query) { ecs_query_fini(ctx->query); } ecs_os_free(ctx); } void flecs_stats_api_import( ecs_world_t *world, ecs_stats_api_t *api) { ecs_entity_t kind = api->monitor_component_id; ecs_entity_t prev = ecs_set_scope(world, kind); struct { const char *name; ecs_entity_t period; ecs_entity_t source; int32_t interval; } periods[] = { {"Monitor1s", EcsPeriod1s, 0, 0}, {"Monitor1m", EcsPeriod1m, EcsPeriod1s, 1}, {"Monitor1h", EcsPeriod1h, EcsPeriod1m, 1}, {"Monitor1d", EcsPeriod1d, EcsPeriod1m, 24}, {"Monitor1w", EcsPeriod1w, EcsPeriod1h, 168} }; ecs_entity_t minute = 0; for (int32_t i = 0; i < 5; i ++) { ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); ctx->api = *api; ctx->interval = periods[i].interval; if (!i && api->query_component_id) { ctx->query = ecs_query(world, { .terms = {{ .id = api->query_component_id }}, .cache_kind = EcsQueryCacheNone, .flags = EcsQueryMatchDisabled }); } ecs_system_desc_t desc = { .entity = ecs_entity(world, { .name = periods[i].name }), .phase = EcsPreFrame, .query.terms = {{ .id = ecs_pair(kind, periods[i].period), .src.id = EcsWorld }}, .callback = i ? ReduceStats : MonitorStats, .interval = i == 1 ? (ecs_ftime_t)1 : 0, .rate = i > 1 ? 60 : 0, .tick_source = i > 1 ? minute : 0, .ctx = ctx, .ctx_free = flecs_monitor_ctx_free }; if (i) { desc.query.terms[1].id = ecs_pair(kind, periods[i].source); desc.query.terms[1].src.id = EcsWorld; } ecs_entity_t system = ecs_system_init(world, &desc); if (i == 1) { minute = system; } } ecs_set_scope(world, prev); for (int32_t i = 0; i < 5; i ++) { ecs_add_pair(world, EcsWorld, kind, periods[i].period); } } void FlecsStatsImport( ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsStats); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif ecs_set_name_prefix(world, "Ecs"); EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); FlecsWorldSummaryImport(world); FlecsWorldMonitorImport(world); FlecsSystemMonitorImport(world); FlecsPipelineMonitorImport(world); flecs_stats_memory_register_reflection(world); ecs_add(world, ecs_id(EcsWorldMemory), EcsWorldMemory); if (ecs_os_has_time()) { ecs_measure_frame_time(world, true); ecs_measure_system_time(world, true); } } #endif #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsPipelineStats); static void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { ecs_map_iter_t it = ecs_map_iter(&ptr->stats); while (ecs_map_next(&it)) { ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); ecs_pipeline_stats_fini(stats); ecs_os_free(stats); } ecs_map_fini(&ptr->stats); } static ECS_CTOR(EcsPipelineStats, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->stats, NULL); }) static ECS_COPY(EcsPipelineStats, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); }) static ECS_MOVE(EcsPipelineStats, dst, src, { flecs_pipeline_monitor_dtor(dst); ecs_os_memcpy_t(dst, src, EcsPipelineStats); ecs_os_zeromem(src); }) static ECS_DTOR(EcsPipelineStats, ptr, { flecs_pipeline_monitor_dtor(ptr); }) static void flecs_pipeline_stats_set_t( void *stats, int32_t t) { ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); ((ecs_pipeline_stats_t*)stats)->t = t; } static void flecs_pipeline_stats_copy_last( void *stats, void *src) { ecs_pipeline_stats_copy_last(stats, src); } static void flecs_pipeline_stats_get( ecs_world_t *world, ecs_entity_t res, void *stats) { ecs_pipeline_stats_get(world, res, stats); } static void flecs_pipeline_stats_reduce( void *stats, void *src) { ecs_pipeline_stats_reduce(stats, src); } static void flecs_pipeline_stats_reduce_last( void *stats, void *last, int32_t reduce_count) { ecs_pipeline_stats_reduce_last(stats, last, reduce_count); } static void flecs_pipeline_stats_repeat_last( void* stats) { ecs_pipeline_stats_repeat_last(stats); } static void flecs_pipeline_stats_fini( void *stats) { ecs_pipeline_stats_fini(stats); } void FlecsPipelineMonitorImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsPipelineStats); ecs_set_hooks(world, EcsPipelineStats, { .ctor = ecs_ctor(EcsPipelineStats), .copy = ecs_copy(EcsPipelineStats), .move = ecs_move(EcsPipelineStats), .dtor = ecs_dtor(EcsPipelineStats) }); ecs_stats_api_t api = { .copy_last = flecs_pipeline_stats_copy_last, .get = flecs_pipeline_stats_get, .reduce = flecs_pipeline_stats_reduce, .reduce_last = flecs_pipeline_stats_reduce_last, .repeat_last = flecs_pipeline_stats_repeat_last, .set_t = flecs_pipeline_stats_set_t, .fini = flecs_pipeline_stats_fini, .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), .monitor_component_id = ecs_id(EcsPipelineStats), .query_component_id = ecs_id(EcsPipeline) }; flecs_stats_api_import(world, &api); } #endif #ifdef FLECS_STATS #define ECS_GAUGE_RECORD(m, t, value)\ flecs_gauge_record(m, t, (ecs_float_t)(value)) #define ECS_COUNTER_RECORD(m, t, value)\ flecs_counter_record(m, t, (double)(value)) #define ECS_METRIC_FIRST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) #define ECS_METRIC_LAST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) static int32_t t_next( int32_t t) { return (t + 1) % ECS_STAT_WINDOW; } static int32_t t_prev( int32_t t) { return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } static void flecs_gauge_record( ecs_metric_t *m, int32_t t, ecs_float_t value) { m->gauge.avg[t] = value; m->gauge.min[t] = value; m->gauge.max[t] = value; } static double flecs_counter_record( ecs_metric_t *m, int32_t t, double value) { int32_t tp = t_prev(t); double prev = m->counter.value[tp]; m->counter.value[t] = value; double gauge_value = value - prev; if (gauge_value < 0) { gauge_value = 0; /* Counters are monotonically increasing */ } flecs_gauge_record(m, t, (ecs_float_t)gauge_value); return gauge_value; } static void flecs_metric_print( const char *name, ecs_float_t value) { ecs_size_t len = ecs_os_strlen(name); ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); } static void flecs_gauge_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->gauge.avg[t]); } static void flecs_counter_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->counter.rate.avg[t]); } void ecs_metric_reduce( ecs_metric_t *dst, const ecs_metric_t *src, int32_t t_dst, int32_t t_src) { ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); bool min_set = false; dst->gauge.avg[t_dst] = 0; dst->gauge.min[t_dst] = 0; dst->gauge.max[t_dst] = 0; ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; int32_t i; for (i = 0; i < ECS_STAT_WINDOW; i ++) { int32_t t = (t_src + i) % ECS_STAT_WINDOW; dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { dst->gauge.min[t_dst] = src->gauge.min[t]; min_set = true; } if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { dst->gauge.max[t_dst] = src->gauge.max[t]; } } dst->counter.value[t_dst] = src->counter.value[t_src]; error: return; } void ecs_metric_reduce_last( ecs_metric_t *m, int32_t prev, int32_t count) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); int32_t t = t_next(prev); if (m->gauge.min[t] < m->gauge.min[prev]) { m->gauge.min[prev] = m->gauge.min[t]; } if (m->gauge.max[t] > m->gauge.max[prev]) { m->gauge.max[prev] = m->gauge.max[t]; } ecs_float_t fcount = (ecs_float_t)(count + 1); ecs_float_t cur = m->gauge.avg[prev]; ecs_float_t next = m->gauge.avg[t]; cur *= ((fcount - 1) / fcount); next *= 1 / fcount; m->gauge.avg[prev] = cur + next; m->counter.value[prev] = m->counter.value[t]; error: return; } void ecs_metric_copy( ecs_metric_t *m, int32_t dst, int32_t src) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); m->gauge.avg[dst] = m->gauge.avg[src]; m->gauge.min[dst] = m->gauge.min[src]; m->gauge.max[dst] = m->gauge.max[src]; m->counter.value[dst] = m->counter.value[src]; error: return; } static void flecs_stats_reduce( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } } static void flecs_stats_reduce_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src, int32_t count) { int32_t t_dst_next = t_next(t_dst); for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { /* Reduce into previous value */ ecs_metric_reduce_last(dst_cur, t_dst, count); /* Restore old value */ dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; } } static void flecs_stats_repeat_last( ecs_metric_t *cur, ecs_metric_t *last, int32_t t) { int32_t prev = t_prev(t); for (; cur <= last; cur ++) { ecs_metric_copy(cur, t, prev); } } static void flecs_stats_copy_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; } } void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t t = s->t = t_next(s->t); double delta_frame_count = ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_total); ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_total); ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); double delta_world_time = ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); } else { ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_map_count(&world->type_info)); ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); if (ecs_is_alive(world, EcsSystem)) { ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); int64_t outstanding_allocs = ecs_os_api_malloc_count + ecs_os_api_calloc_count - ecs_os_api_free_count; ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); #ifdef FLECS_HTTP ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif error: return; } void ecs_world_stats_reduce( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_world_stats_reduce_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_world_stats_repeat_last( ecs_world_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_world_stats_copy_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) { ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; int32_t t = s->t = t_next(s->t); ecs_query_count_t counts = ecs_query_count(query); ECS_GAUGE_RECORD(&s->result_count, t, counts.results); ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); error: return; } void ecs_query_cache_stats_reduce( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_query_cache_stats_reduce_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_query_cache_stats_repeat_last( ecs_query_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_query_cache_stats_copy_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } #ifdef FLECS_SYSTEM bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); if (!ptr) { return false; } ecs_query_stats_get(world, ptr->query, &s->query); int32_t t = s->query.t; ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); s->task = !(ptr->query->flags & EcsQueryMatchThis); return true; error: return false; } void ecs_system_stats_reduce( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_cache_stats_reduce(&dst->query, &src->query); dst->task = src->task; flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } void ecs_system_stats_reduce_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src, int32_t count) { ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); dst->task = src->task; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } void ecs_system_stats_repeat_last( ecs_system_stats_t *stats) { ecs_query_cache_stats_repeat_last(&stats->query); flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->query.t)); } void ecs_system_stats_copy_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_cache_stats_copy_last(&dst->query, &src->query); dst->task = src->task; flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } #endif #ifdef FLECS_PIPELINE bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); if (!pqc) { return false; } ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); int32_t sys_count = 0, active_sys_count = 0; ecs_component_record_t *cr_empty = flecs_components_get( world, EcsEmpty); /* Count number of active systems */ ecs_iter_t it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { if (cr_empty && flecs_component_get_table(cr_empty, it.table) != NULL) { continue; } active_sys_count += it.count; } /* Count total number of systems in pipeline */ it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { sys_count += it.count; } /* Also count synchronization points */ ecs_vec_t *ops = &pq->ops; ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); int32_t pip_count = active_sys_count + ecs_vec_count(ops); if (!sys_count) { return false; } if (op) { ecs_entity_t *systems = NULL; if (pip_count) { ecs_vec_init_if_t(&s->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); systems = ecs_vec_first_t(&s->systems, ecs_entity_t); /* Populate systems vector, keep track of sync points */ it = ecs_query_iter(stage, pq->query); int32_t i, i_system = 0, ran_since_merge = 0; while (ecs_query_next(&it)) { if (cr_empty && flecs_component_get_table(cr_empty, it.table) != NULL) { continue; } for (i = 0; i < it.count; i ++) { systems[i_system ++] = it.entities[i]; ran_since_merge ++; if (op != op_last && ran_since_merge == op->count) { ran_since_merge = 0; op++; systems[i_system ++] = 0; /* 0 indicates a merge point */ } } } systems[i_system ++] = 0; /* Last merge */ ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); } /* Get sync point statistics */ int32_t i, count = ecs_vec_count(ops); if (count) { ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); op = ecs_vec_first_t(ops, ecs_pipeline_op_t); for (i = 0; i < count; i ++) { ecs_pipeline_op_t *cur = &op[i]; ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, ecs_sync_stats_t, i); ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, cur->commands_enqueued); el->system_count = cur->count; el->multi_threaded = cur->multi_threaded; el->immediate = cur->immediate; } } } s->t = t_next(s->t); return true; error: return false; } void ecs_pipeline_stats_fini( ecs_pipeline_stats_t *stats) { ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); } void ecs_pipeline_stats_reduce( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t system_count = ecs_vec_count(&src->systems); ecs_vec_init_if_t(&dst->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, src->t); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->immediate = src_el->immediate; } dst->t = t_next(dst->t); } void ecs_pipeline_stats_reduce_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src, int32_t count) { int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, src->t, count); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->immediate = src_el->immediate; } dst->t = t_prev(dst->t); } void ecs_pipeline_stats_repeat_last( ecs_pipeline_stats_t *stats) { int32_t i, sync_count = ecs_vec_count(&stats->sync_points); ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *el = &syncs[i]; flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), (stats->t)); } stats->t = t_next(stats->t); } void ecs_pipeline_stats_copy_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->immediate = src_el->immediate; } } #endif void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { int32_t t = s->t; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_counter_print("Frame", t, &s->frame.frame_count); ecs_trace("-------------------------------------"); flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); flecs_counter_print("systems ran", t, &s->frame.systems_ran); ecs_trace(""); flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); ecs_trace(""); flecs_gauge_print("actual FPS", t, &s->performance.fps); flecs_counter_print("frame time", t, &s->performance.frame_time); flecs_counter_print("system time", t, &s->performance.system_time); flecs_counter_print("merge time", t, &s->performance.merge_time); flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); ecs_trace(""); flecs_gauge_print("tag id count", t, &s->components.tag_count); flecs_gauge_print("component id count", t, &s->components.component_count); flecs_gauge_print("pair id count", t, &s->components.pair_count); flecs_gauge_print("type count", t, &s->components.type_count); flecs_counter_print("id create count", t, &s->components.create_count); flecs_counter_print("id delete count", t, &s->components.delete_count); ecs_trace(""); flecs_gauge_print("alive entity count", t, &s->entities.count); flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); ecs_trace(""); flecs_gauge_print("query count", t, &s->queries.query_count); flecs_gauge_print("observer count", t, &s->queries.observer_count); flecs_gauge_print("system count", t, &s->queries.system_count); ecs_trace(""); flecs_gauge_print("table count", t, &s->tables.count); flecs_gauge_print("empty table count", t, &s->tables.empty_count); flecs_counter_print("table create count", t, &s->tables.create_count); flecs_counter_print("table delete count", t, &s->tables.delete_count); ecs_trace(""); flecs_counter_print("add commands", t, &s->commands.add_count); flecs_counter_print("remove commands", t, &s->commands.remove_count); flecs_counter_print("delete commands", t, &s->commands.delete_count); flecs_counter_print("clear commands", t, &s->commands.clear_count); flecs_counter_print("set commands", t, &s->commands.set_count); flecs_counter_print("ensure commands", t, &s->commands.ensure_count); flecs_counter_print("modified commands", t, &s->commands.modified_count); flecs_counter_print("other commands", t, &s->commands.other_count); flecs_counter_print("discarded commands", t, &s->commands.discard_count); flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); flecs_counter_print("batched commands", t, &s->commands.batched_count); ecs_trace(""); error: return; } #endif #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsSystemStats); static void flecs_system_monitor_dtor(EcsSystemStats *ptr) { ecs_map_iter_t it = ecs_map_iter(&ptr->stats); while (ecs_map_next(&it)) { ecs_system_stats_t *stats = ecs_map_ptr(&it); ecs_os_free(stats); } ecs_map_fini(&ptr->stats); } static ECS_CTOR(EcsSystemStats, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->stats, NULL); }) static ECS_COPY(EcsSystemStats, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); }) static ECS_MOVE(EcsSystemStats, dst, src, { flecs_system_monitor_dtor(dst); ecs_os_memcpy_t(dst, src, EcsSystemStats); ecs_os_zeromem(src); }) static ECS_DTOR(EcsSystemStats, ptr, { flecs_system_monitor_dtor(ptr); }) static void flecs_system_stats_set_t( void *stats, int32_t t) { ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); ((ecs_system_stats_t*)stats)->query.t = t; } static void flecs_system_stats_copy_last( void *stats, void *src) { ecs_system_stats_copy_last(stats, src); } static void flecs_system_stats_get( ecs_world_t *world, ecs_entity_t res, void *stats) { ecs_system_stats_get(world, res, stats); } static void flecs_system_stats_reduce( void *stats, void *src) { ecs_system_stats_reduce(stats, src); } static void flecs_system_stats_reduce_last( void *stats, void *last, int32_t reduce_count) { ecs_system_stats_reduce_last(stats, last, reduce_count); } static void flecs_system_stats_repeat_last( void* stats) { ecs_system_stats_repeat_last(stats); } void FlecsSystemMonitorImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsSystemStats); ecs_set_hooks(world, EcsSystemStats, { .ctor = ecs_ctor(EcsSystemStats), .copy = ecs_copy(EcsSystemStats), .move = ecs_move(EcsSystemStats), .dtor = ecs_dtor(EcsSystemStats) }); ecs_stats_api_t api = { .copy_last = flecs_system_stats_copy_last, .get = flecs_system_stats_get, .reduce = flecs_system_stats_reduce, .reduce_last = flecs_system_stats_reduce_last, .repeat_last = flecs_system_stats_repeat_last, .set_t = flecs_system_stats_set_t, .stats_size = ECS_SIZEOF(ecs_system_stats_t), .monitor_component_id = ecs_id(EcsSystemStats), .query_component_id = EcsSystem }; flecs_stats_api_import(world, &api); } #endif #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsWorldStats); static ECS_CTOR(EcsWorldStats, ptr, { ecs_os_zeromem(ptr); ptr->stats = ecs_os_calloc_t(ecs_world_stats_t); }) static ECS_COPY(EcsWorldStats, dst, src, { ecs_os_free(dst->stats); ecs_os_memcpy_t(dst, src, EcsWorldStats); dst->stats = ecs_os_calloc_t(ecs_world_stats_t); ecs_os_memcpy_t(dst->stats, src->stats, ecs_world_stats_t); }) static ECS_MOVE(EcsWorldStats, dst, src, { ecs_os_free(dst->stats); ecs_os_memcpy_t(dst, src, EcsWorldStats); src->stats = NULL; }) static ECS_DTOR(EcsWorldStats, ptr, { ecs_os_free(ptr->stats); ptr->stats = NULL; }) static void flecs_world_stats_get( ecs_world_t *world, ecs_entity_t res, void *stats) { (void)res; ecs_world_stats_get(world, stats); } static void flecs_world_stats_set_t( void *stats, int32_t t) { ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); ((ecs_world_stats_t*)stats)->t = t; } static void flecs_world_stats_copy_last( void *stats, void *src) { ecs_world_stats_copy_last(stats, src); } static void flecs_world_stats_reduce( void *stats, void *src) { ecs_world_stats_reduce(stats, src); } static void flecs_world_stats_reduce_last( void *stats, void *last, int32_t reduce_count) { ecs_world_stats_reduce_last(stats, last, reduce_count); } static void flecs_world_stats_repeat_last( void* stats) { ecs_world_stats_repeat_last(stats); } void FlecsWorldMonitorImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldStats); ecs_set_hooks(world, EcsWorldStats, { .ctor = ecs_ctor(EcsWorldStats), .copy = ecs_copy(EcsWorldStats), .move = ecs_move(EcsWorldStats), .dtor = ecs_dtor(EcsWorldStats) }); ecs_stats_api_t api = { .copy_last = flecs_world_stats_copy_last, .get = flecs_world_stats_get, .reduce = flecs_world_stats_reduce, .reduce_last = flecs_world_stats_reduce_last, .repeat_last = flecs_world_stats_repeat_last, .set_t = flecs_world_stats_set_t, .fini = NULL, .stats_size = ECS_SIZEOF(ecs_world_stats_t), .monitor_component_id = ecs_id(EcsWorldStats), .is_pointer = true }; flecs_stats_api_import(world, &api); } #endif #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsWorldSummary); static void flecs_copy_world_summary( ecs_world_t *world, EcsWorldSummary *dst) { flecs_poly_assert(world, ecs_world_t); const ecs_world_info_t *info = ecs_get_world_info(world); dst->target_fps = (double)info->target_fps; dst->time_scale = (double)info->time_scale; dst->fps = 1.0 / (double)info->delta_time_raw; dst->frame_time_frame = (double)info->frame_time_total - dst->frame_time_total; dst->system_time_frame = (double)info->system_time_total - dst->system_time_total; dst->merge_time_frame = (double)info->merge_time_total - dst->merge_time_total; dst->merge_count_frame = info->merge_count_total - dst->merge_count; dst->systems_ran_frame = info->systems_ran_total - dst->systems_ran_total; dst->observers_ran_frame = info->observers_ran_total - dst->observers_ran_total; dst->queries_ran_frame = info->queries_ran_total - dst->queries_ran_total; int64_t command_count = info->cmd.add_count + info->cmd.remove_count + info->cmd.delete_count + info->cmd.clear_count + info->cmd.set_count + info->cmd.ensure_count + info->cmd.modified_count + info->cmd.discard_count + info->cmd.event_count + info->cmd.other_count; dst->command_count_frame = command_count - dst->command_count; dst->frame_time_total = (double)info->frame_time_total; dst->system_time_total = (double)info->system_time_total; dst->merge_time_total = (double)info->merge_time_total; dst->entity_count = flecs_entities_count(world); dst->table_count = flecs_sparse_count(&world->store.tables); dst->frame_count = info->frame_count_total; dst->command_count = command_count; dst->merge_count = info->merge_count_total; dst->systems_ran_total = info->systems_ran_total; dst->observers_ran_total = info->observers_ran_total; dst->queries_ran_total = info->queries_ran_total; dst->tag_count = info->tag_id_count; dst->component_count = info->component_id_count; dst->pair_count = info->pair_id_count; dst->simulation_time += (double)info->delta_time; ecs_time_t now; ecs_os_get_time(&now); dst->uptime = now.sec - info->creation_time; dst->build_info = *ecs_get_build_info(); } static void UpdateWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { flecs_copy_world_summary(it->world, &summary[i]); } } static void OnSetWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); } } void FlecsWorldSummaryImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldSummary); #if defined(FLECS_META) && defined(FLECS_UNITS) ecs_entity_t build_info = ecs_lookup(world, "flecs.core.BuildInfo"); ecs_struct(world, { .entity = ecs_id(EcsWorldSummary), .members = { /* Time */ { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, { .name = "fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, /* Totals */ { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "entity_count", .type = ecs_id(ecs_i64_t) }, { .name = "table_count", .type = ecs_id(ecs_i64_t) }, { .name = "frame_count", .type = ecs_id(ecs_i64_t) }, { .name = "command_count", .type = ecs_id(ecs_i64_t) }, { .name = "merge_count", .type = ecs_id(ecs_i64_t) }, { .name = "systems_ran_total", .type = ecs_id(ecs_i64_t) }, { .name = "observers_ran_total", .type = ecs_id(ecs_i64_t) }, { .name = "queries_ran_total", .type = ecs_id(ecs_i64_t) }, { .name = "tag_count", .type = ecs_id(ecs_i32_t) }, { .name = "component_count", .type = ecs_id(ecs_i32_t) }, { .name = "pair_count", .type = ecs_id(ecs_i32_t) }, /* Per frame */ { .name = "frame_time_frame", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "system_time_frame", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_time_frame", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_count_frame", .type = ecs_id(ecs_i64_t) }, { .name = "systems_ran_frame", .type = ecs_id(ecs_i64_t) }, { .name = "observers_ran_frame", .type = ecs_id(ecs_i64_t) }, { .name = "queries_ran_frame", .type = ecs_id(ecs_i64_t) }, { .name = "command_count_frame", .type = ecs_id(ecs_i64_t) }, { .name = "simulation_time", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "uptime", .type = ecs_id(ecs_u32_t), .unit = EcsSeconds }, /* Build info */ { .name = "build_info", .type = build_info } } }); #endif const ecs_world_info_t *info = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .name = "UpdateWorldSummary" }), .phase = EcsPreFrame, .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, .callback = UpdateWorldSummary }); ecs_observer(world, { .events = { EcsOnSet }, .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, .callback = OnSetWorldSummary, .global_observer = true }); ecs_set(world, EcsWorld, EcsWorldSummary, { .target_fps = (double)info->target_fps, .time_scale = (double)info->time_scale }); EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); flecs_copy_world_summary(world, summary); ecs_modified(world, EcsWorld, EcsWorldSummary); } #endif #ifdef FLECS_SYSTEM ecs_mixins_t ecs_system_t_mixins = { .type_name = "ecs_system_t", .elems = { [EcsMixinDtor] = offsetof(ecs_system_t, dtor) } }; /* -- Public API -- */ ecs_entity_t flecs_run_system( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, void *param) { flecs_poly_assert(world, ecs_world_t); ecs_ftime_t time_elapsed = delta_time; ecs_entity_t tick_source = system_data->tick_source; /* Support legacy behavior */ if (!param) { param = system_data->ctx; } if (tick_source) { const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); if (tick) { time_elapsed = tick->time_elapsed; /* If timer hasn't fired we shouldn't run the system */ if (!tick->tick) { return 0; } } else { /* If a timer has been set but the timer entity does not have the * EcsTimer component, don't run the system. This can be the result * of a single-shot timer that has fired already. Not resetting the * timer field of the system will ensure that the system won't be * run after the timer has fired. */ return 0; } } ecs_os_perf_trace_push(system_data->name); if (ecs_should_log_3()) { char *path = ecs_get_path(world, system); ecs_dbg_3("worker %d: %s", stage_index, path); ecs_os_free(path); } ecs_time_t time_start; bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); if (measure_time) { ecs_os_get_time(&time_start); } if (!stage) { stage = world->stages[0]; } flecs_poly_assert(stage, ecs_stage_t); ecs_iter_t wit, qit = ecs_query_iter( (ecs_world_t*)stage, system_data->query); ecs_iter_t *it = &qit; qit.system = system; qit.delta_time = delta_time; qit.delta_system_time = time_elapsed; qit.param = param; qit.ctx = system_data->ctx; qit.callback_ctx = system_data->callback_ctx; qit.run_ctx = system_data->run_ctx; #ifdef FLECS_CACHED_QUERIES if (system_data->group_id_set) { ecs_iter_set_group(&qit, system_data->group_id); } #endif if (stage_count > 1 && system_data->multi_threaded) { wit = ecs_worker_iter(it, stage_index, stage_count); it = &wit; } ecs_entity_t old_system = flecs_stage_set_system(stage, system); ecs_iter_action_t action = system_data->action; it->callback = action; ecs_run_action_t run = system_data->run; if (run) { /* If system query matches nothing, the system run callback doesn't have * anything to iterate, so the iterator resources don't get cleaned up * automatically, so clean it up here. */ if (system_data->query->flags & EcsQueryMatchNothing) { it->next = flecs_default_next_callback; /* Return once */ run(it); ecs_iter_fini(&qit); } else { #ifdef FLECS_CACHED_QUERIES if (it == &qit && (qit.flags & EcsIterTrivialCached)) { it->next = flecs_query_trivial_cached_next; } #endif run(it); } } else { if (system_data->query->term_count) { if (it == &qit) { #ifdef FLECS_CACHED_QUERIES if (qit.flags & EcsIterTrivialCached) { while (flecs_query_trivial_cached_next(&qit)) { ECS_TABLE_LOCK(world, qit.table); action(&qit); ECS_TABLE_UNLOCK(world, qit.table); } } else #endif { while (ecs_query_next(&qit)) { ECS_TABLE_LOCK(world, qit.table); action(&qit); ECS_TABLE_UNLOCK(world, qit.table); } } } else { while (ecs_iter_next(it)) { ECS_TABLE_LOCK(world, qit.table); action(it); ECS_TABLE_UNLOCK(world, qit.table); } } } else { action(&qit); ecs_iter_fini(&qit); } } flecs_stage_set_system(stage, old_system); if (measure_time) { system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); } ecs_os_perf_trace_pop(system_data->name); return it->interrupted_by; } /* -- Public API -- */ ecs_entity_t ecs_run_worker( ecs_world_t *world, ecs_entity_t system, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, void *param) { bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); if (!deferred) { flecs_commands_begin(world, stage); } ecs_entity_t result = flecs_run_system( world, stage, system, system_data, stage_index, stage_count, delta_time, param); if (!deferred) { flecs_commands_end(world, stage); } return result; } ecs_entity_t ecs_run( ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, void *param) { bool deferred = ecs_is_stage(world); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); if (!deferred) { flecs_commands_begin(world, stage); } ecs_entity_t result = flecs_run_system( world, stage, system, system_data, 0, 0, delta_time, param); if (!deferred) { flecs_commands_end(world, stage); } return result; } /* System deinitialization */ static void flecs_system_fini(ecs_system_t *sys) { if (sys->ctx_free) { sys->ctx_free(sys->ctx); } if (sys->callback_ctx_free) { sys->callback_ctx_free(sys->callback_ctx); } if (sys->run_ctx_free) { sys->run_ctx_free(sys->run_ctx); } /* Safe cast, type owns name */ ecs_os_free(ECS_CONST_CAST(char*, sys->name)); flecs_poly_free(sys, ecs_system_t); } /* ecs_poly_dtor_t-compatible wrapper */ static void flecs_system_poly_fini(void *sys) { flecs_system_fini(sys); } static int flecs_system_init_timer( ecs_world_t *world, ecs_entity_t entity, const ecs_system_desc_t *desc) { if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { char *name = ecs_get_path(world, entity); ecs_err("system %s cannot have both interval and rate set", name); ecs_os_free(name); return -1; } if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || ECS_NEQZERO(desc->tick_source)) { #ifdef FLECS_TIMER if (ECS_NEQZERO(desc->interval)) { ecs_set_interval(world, entity, desc->interval); } if (desc->rate) { ecs_entity_t tick_source = desc->tick_source; ecs_set_rate(world, entity, desc->rate, tick_source); } else if (desc->tick_source) { ecs_set_tick_source(world, entity, desc->tick_source); } #else (void)world; (void)entity; ecs_abort(ECS_UNSUPPORTED, "timer module not available"); #endif } return 0; } ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_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_system_desc_t was not initialized to zero"); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t entity = desc->entity; if (!entity) { entity = ecs_entity(world, {0}); } EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); ecs_check(poly->poly == NULL, ECS_INVALID_OPERATION, "entity %s already is a system, use ecs_system_update() to modify", flecs_errstr(ecs_get_path(world, entity))); ecs_check(desc->callback != NULL || desc->run != NULL, ECS_INVALID_PARAMETER, "missing implementation for system %s (set .callback or .run)", desc->entity ? flecs_errstr(ecs_get_path(world, desc->entity)) : ""); ecs_system_t *system = flecs_poly_new(ecs_system_t); ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); poly->poly = system; system->dtor = flecs_system_poly_fini; ecs_query_desc_t query_desc = desc->query; query_desc.entity = entity; ecs_query_t *query = ecs_query_init(world, &query_desc); if (!query) { ecs_delete(world, entity); return 0; } /* Prevent the system from moving while we're initializing */ flecs_commands_begin(world, world->stages[0]); if (desc->phase) { ecs_add_id(world, entity, desc->phase); ecs_add_pair(world, entity, EcsDependsOn, desc->phase); } system->query = query; system->run = desc->run; system->action = desc->callback; system->ctx = desc->ctx; system->callback_ctx = desc->callback_ctx; system->run_ctx = desc->run_ctx; system->ctx_free = desc->ctx_free; system->callback_ctx_free = desc->callback_ctx_free; system->run_ctx_free = desc->run_ctx_free; system->tick_source = desc->tick_source; system->multi_threaded = desc->multi_threaded; system->immediate = desc->immediate; system->name = ecs_get_path(world, entity); if (flecs_system_init_timer(world, entity, desc)) { ecs_delete(world, entity); flecs_commands_end(world, flecs_stage_from_readonly_world(world)); goto error; } if (ecs_get_name(world, entity)) { ecs_trace("#[green]system#[reset] %s created", ecs_get_name(world, entity)); } flecs_commands_end(world, flecs_stage_from_readonly_world(world)); flecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } ecs_entity_t ecs_system_update( ecs_world_t *world, ecs_entity_t entity, const ecs_system_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_system_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_system_desc_t::entity does not match system entity"); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_system_t *system = flecs_poly_get(world, entity, ecs_system_t); ecs_check(system != NULL, ECS_INVALID_PARAMETER, "entity %s is not a system, use ecs_system_init() to create it", flecs_errstr(ecs_get_path(world, entity))); flecs_poly_update_ctx(&system->ctx, &system->ctx_free, desc->ctx, desc->ctx_free, true); flecs_poly_update_ctx(&system->callback_ctx, &system->callback_ctx_free, desc->callback_ctx, desc->callback_ctx_free, false); flecs_poly_update_ctx(&system->run_ctx, &system->run_ctx_free, desc->run_ctx, desc->run_ctx_free, false); if (desc->run) { system->run = desc->run; if (!desc->callback) { system->action = NULL; } } if (desc->callback) { system->action = desc->callback; if (!desc->run) { system->run = NULL; } } if (desc->multi_threaded) { system->multi_threaded = desc->multi_threaded; } if (desc->immediate) { system->immediate = desc->immediate; } if (flecs_system_init_timer(world, entity, desc)) { return 0; } flecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } const ecs_system_t* ecs_system_get( const ecs_world_t *world, ecs_entity_t entity) { return flecs_poly_get(world, entity, ecs_system_t); } void ecs_system_set_group( ecs_world_t *world, ecs_entity_t system, uint64_t group_id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); system_data->group_id = group_id; system_data->group_id_set = true; error: return; } void FlecsSystemImport( ecs_world_t *world) { ECS_MODULE(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_tag(world, EcsSystem); flecs_bootstrap_component(world, EcsTickSource); ecs_set_hooks(world, EcsTickSource, { .ctor = flecs_default_ctor }); /* Make sure to never inherit system component. This makes sure that any * term created for the System component will default to 'self' traversal, * which improves efficiency of the query. */ ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); } #endif #ifdef FLECS_CACHED_QUERIES const ecs_entity_t EcsOnQueryCacheRevalidate = FLECS_HI_COMPONENT_ID + 59; /* 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_table_range_t range = { .table = table }; ecs_iter_set_var_as_range(&it, 0, &range); 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; } static void flecs_query_cache_on_rematch_event( ecs_iter_t *it) { ecs_observer_t *o = it->ctx; ecs_query_impl_t *impl = o->ctx; flecs_poly_assert(impl, ecs_query_t); ecs_assert(impl->cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = it->world; if (it->table->flags & EcsTableHasParent) { const ecs_term_t *term = &o->query->terms[it->term_index]; if (term->trav == EcsChildOf) { return; } } ecs_enqueue(it->stage, &(ecs_event_desc_t){ .event = EcsOnQueryCacheRevalidate, .entity = impl->cache->entity, .ids = &(ecs_type_t){ .array = (ecs_id_t[]){ ecs_pair(ecs_id(EcsPoly), EcsQuery) }, .count = 1 }, .const_param = &(ecs_query_cache_revalidate_t){ .query = impl->cache->entity, .table_id = it->table->id }, .observable = world }); } static void flecs_query_cache_on_revalidate_event( ecs_iter_t *it) { const ecs_query_cache_revalidate_t *ev = it->param; ecs_assert(ev != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = it->world; ecs_query_t *q = flecs_poly_get(world, ev->query, ecs_query_t); if (!q) { return; } ecs_query_impl_t *impl = flecs_query_impl(q); if (!impl->cache) { return; } flecs_query_revalidate_table(world, impl, ev->table_id); } void flecs_query_cache_bootstrap( ecs_world_t *world) { ecs_component_init(world, &(ecs_component_desc_t){ .entity = ecs_entity(world, { .id = EcsOnQueryCacheRevalidate, .parent = EcsFlecsCore, .name = "OnQueryCacheRevalidate" }), .type.size = ECS_SIZEOF(ecs_query_cache_revalidate_t), .type.alignment = ECS_ALIGNOF(ecs_query_cache_revalidate_t) }); ecs_observer_init(world, &(ecs_observer_desc_t){ .global_observer = true, .events[0] = EcsOnQueryCacheRevalidate, .query.terms[0] = { .id = ecs_pair(ecs_id(EcsPoly), EcsQuery) }, .query.flags = EcsQueryNested|EcsQueryMatchPrefab| EcsQueryMatchDisabled, .flags_ = EcsObserverBypassQuery, .run = flecs_query_cache_on_revalidate_event }); } static int flecs_query_cache_rematch_observers_init( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache) { ecs_entity_t entity = cache->entity; ecs_term_t *terms = cache->query->terms; int32_t i, count = cache->query->term_count; ecs_observer_desc_t desc = {0}; int32_t term_count = 0; ecs_observer_desc_t ancestry_desc = {0}; int32_t ancestry_term_count = 0; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *src = &term->src; if (!(src->id & EcsUp)) { continue; } ecs_assert(term_count < FLECS_TERM_COUNT_MAX, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_term_match_this(term), ECS_INTERNAL_ERROR, NULL); ecs_term_t *ot = &desc.query.terms[term_count ++]; *ot = *term; ot->oper = EcsAnd; ot->inout = EcsInOutNone; ot->src.id = EcsThis|EcsIsVariable|EcsUp; ecs_id_t ancestry_ids[2] = { ecs_pair(term->trav, EcsWildcard), ecs_pair(EcsIsA, EcsWildcard) }; int32_t a, a_count = (term->trav != EcsIsA) ? 2 : 1; for (a = 0; a < a_count; a ++) { int32_t t; for (t = 0; t < ancestry_term_count; t ++) { ecs_term_t *at = &ancestry_desc.query.terms[t]; if (at->id == ancestry_ids[a] && at->trav == term->trav) { break; } } if (t != ancestry_term_count) { continue; } ecs_assert(ancestry_term_count < FLECS_TERM_COUNT_MAX, ECS_INTERNAL_ERROR, NULL); ecs_term_t *at = &ancestry_desc.query.terms[ ancestry_term_count ++]; at->id = ancestry_ids[a]; at->src.id = EcsThis|EcsIsVariable|EcsUp; at->trav = term->trav; at->inout = EcsInOutNone; } } if (!term_count) { return 0; } desc.run = flecs_query_cache_on_rematch_event; desc.ctx = impl; desc.events[0] = EcsOnAdd; desc.events[1] = EcsOnRemove; desc.flags_ = EcsObserverBypassQuery|EcsObserverIsUpNotify; desc.query.flags = EcsQueryNested|EcsQueryMatchPrefab| EcsQueryMatchDisabled; cache->rematch_observer = flecs_observer_init(world, entity, &desc); if (!cache->rematch_observer) { return -1; } ancestry_desc.run = flecs_query_cache_on_rematch_event; ancestry_desc.ctx = impl; ancestry_desc.events[0] = EcsOnAdd; ancestry_desc.events[1] = EcsOnRemove; ancestry_desc.flags_ = EcsObserverBypassQuery|EcsObserverIsUpNotify; ancestry_desc.query.flags = EcsQueryNested|EcsQueryMatchPrefab| EcsQueryMatchDisabled; cache->ancestry_observer = flecs_observer_init( world, entity, &ancestry_desc); if (!cache->ancestry_observer) { return -1; } return 0; } /* 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; } } return flecs_query_cache_rematch_observers_init(world, impl, cache); } /* -- 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.stage; 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); } } if (cache->rematch_observer) { flecs_observer_fini(cache->rematch_observer); } if (cache->ancestry_observer) { flecs_observer_fini(cache->ancestry_observer); } 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.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; 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 & EcsQueryHasComponentInheritance) && !(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; } } } /* 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; } #endif #ifdef FLECS_CACHED_QUERIES /* 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 ? cache->first_group : &cache->default_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->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; /* 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 ++; } #ifdef FLECS_QUERY_PLANS ctx->vars[0].range.table = table; #else it->table = table; #endif return 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) { #ifdef FLECS_QUERY_PLANS ecs_table_t *table = ctx->vars[0].range.table; #else ecs_table_t *table = it->table; #endif ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the iterator constraint 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; } if (redo) { ecs_query_iter_t *qit = &it->priv_.iter.query; if (qit->tables == qit->all_tables && !qit->elem->wildcard_matches) { return NULL; } } 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; } /* 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 ? cache->first_group : &cache->default_group; qit->tables = qit->all_tables = &qit->group->tables; qit->cur = 0; } } /* 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; } 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_QUERY_PLANS ctx->vars[0].range.count = node->_count; ctx->vars[0].range.offset = node->_offset; #endif 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) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_vec_t *tables = qit->tables; ecs_query_triv_cache_match_t *matches = tables->array; int32_t cur = qit->cur; int32_t table_count = tables->count; bool match_empty = (it->flags & EcsIterMatchEmptyTables) != 0; while (cur < table_count) { ecs_query_triv_cache_match_t *qm = &matches[cur ++]; ecs_table_t *table = qm->table; int32_t count = table->data.count; if (!count && !match_empty) { continue; } qit->cur = cur; qit->elem = (ecs_query_cache_match_t*)qm; it->table = table; it->count = count; it->entities = table->data.entities; it->columns = qm->columns; it->set_fields = qm->set_fields; return true; } qit->cur = cur; return false; } /* 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) { #ifdef FLECS_QUERY_PLANS ecs_table_t *table = ctx->vars[0].range.table; #else ecs_table_t *table = it->table; #endif ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the iterator constraint 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; } #ifdef FLECS_QUERY_PLANS 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); } } /* 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.table = node->base.table; ctx->vars[0].range.count = node->_count; ctx->vars[0].range.offset = node->_offset; return true; } /* 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; } bool flecs_query_tree_cache_search( ecs_query_run_ctx_t *ctx, bool redo) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = ctx->qit; const ecs_query_impl_t *impl = ctx->query; const ecs_query_op_t *ops = qit->ops; ecs_query_lbl_t cache_op = impl->tree_cache_op; int32_t last_op = impl->op_count - 1; bool test = (it->constrained_vars & 1) != 0; ecs_assert(cache_op >= 0, ECS_INTERNAL_ERROR, NULL); if (!redo) { if (!test) { flecs_query_cache_iter_restart(ctx); } qit->op = -1; } else if (qit->op != -1) { if (flecs_query_run_until(true, ctx, ops, cache_op, qit->op, last_op)) { qit->op = flecs_itolbl(ctx->op_index - 1); flecs_query_set_iter_this(it, ctx); return true; } qit->op = -1; } if (test && !redo) { ecs_table_t *table = ctx->vars[0].range.table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the iterator constraint is missing a table"); ecs_query_cache_table_t *qt = flecs_query_cache_get_table( impl->cache, table); if (!qt) { return false; } ecs_query_cache_group_t *group = qt->group; ecs_query_cache_match_t *node = ecs_vec_get_t( &group->tables, ecs_query_cache_match_t, qt->index); ecs_assert(node->base.table == table, ECS_INTERNAL_ERROR, NULL); if (!node->wildcard_matches && !(table->flags & EcsTableHasParent)) { qit->group = group; qit->tables = qit->all_tables = &group->tables; qit->cur = qt->index + 1; qit->elem = node; if (node->_trs) { 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 } else { flecs_query_cache_init_mapped_fields(ctx, node); } flecs_query_set_iter_this(it, ctx); return true; } } do { ecs_query_cache_match_t *node; if (test) { node = flecs_query_test(ctx, redo); redo = true; } else { node = flecs_query_cache_next(ctx, false); } if (!node) { return false; } ecs_table_t *table = node->base.table; flecs_query_cache_init_mapped_fields(ctx, node); if (!test) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = node->_count; ctx->vars[0].range.offset = node->_offset; } if (!(table->flags & EcsTableHasParent)) { flecs_query_set_iter_this(it, ctx); return true; } if (flecs_query_run_until(false, ctx, ops, cache_op, flecs_itolbl(cache_op + 1), last_op)) { qit->op = flecs_itolbl(ctx->op_index - 1); flecs_query_set_iter_this(it, ctx); return true; } } while (true); } #endif // FLECS_QUERY_PLANS #endif #ifdef FLECS_CACHED_QUERIES 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->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->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); ecs_assert(!flecs_query_cache_is_trivial(cache), 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 (!(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->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->stage, 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->stage, 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; for (; cur; cur = cur->next) { 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; } } } } } 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; for (; cur; cur = cur->next) { 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]); } } } } } } /* 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->stage, 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->stage; int32_t i, field_count = query->field_count; ecs_flags64_t set_fields = 0; if (it) { set_fields = it->set_fields; } else { set_fields = match->base.set_fields; } ecs_entity_t *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 */ } } /* 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; ecs_world_t *world = q->world; if (!world->has_dirty_state) { return; } /* Evaluate all writable non-fixed, set fields */ ecs_termset_t write_fields = (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields & ~impl->dont_fragment_fields); if (!write_fields || (it->flags & EcsIterNoData)) { return; } 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_world_t *world = q->world; if (!world->has_dirty_state) { return; } ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; if (!fixed_write_fields) { return; } 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->stage, 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->stage == q->world, ECS_INTERNAL_ERROR, NULL); monitor[field + 1] = flecs_table_get_dirty_state( q->stage, 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; } #endif #ifdef FLECS_CACHED_QUERIES /* 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->stage, 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) { ecs_query_cache_group_t **link = &cache->first_group; if (cache->query->flags & EcsQueryGroupByOrdered) { bool desc = (cache->query->flags & EcsQueryGroupByDesc) != 0; while (*link && (desc ? (*link)->info.id > group->info.id : (*link)->info.id < group->info.id)) { link = &(*link)->next; } } group->next = *link; *link = group; } static ecs_query_cache_group_t* flecs_query_cache_ensure_group( ecs_query_cache_t *cache, uint64_t group_id) { ecs_query_cache_group_t *group = flecs_query_cache_get_group(cache, group_id); if (group && group->info.table_count) { return group; } if (group_id) { group = ecs_map_insert_alloc_t(&cache->groups, ecs_query_cache_group_t, group_id); } else if (ecs_map_is_init(&cache->groups)) { ecs_map_insert_ptr(&cache->groups, 0, group); } ecs_allocator_t *a = &cache->query->world->allocator; ecs_vec_init(a, &group->tables, flecs_query_cache_elem_size(cache), 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->stage, 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) { if (cache->on_group_delete && group->info.table_count) { cache->on_group_delete(cache->query->stage, 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->world->allocator; int32_t count = ecs_vec_count(&group->tables); for (int32_t i = 0; i < count; i ++) { flecs_query_cache_match_fini(cache, ecs_vec_get(&group->tables, elem_size, i)); } 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 **link = &cache->first_group; while (*link != group) { ecs_assert(*link != NULL, ECS_INTERNAL_ERROR, NULL); link = &(*link)->next; } *link = group->next; group->next = NULL; 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->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 ++; cache->parent_table_count += (table->flags & EcsTableHasParent) != 0; 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_query_cache_match_t *removed = ecs_vec_get( &group->tables, elem_size, index); cache->parent_table_count -= (removed->base.table->flags & EcsTableHasParent) != 0; ecs_assert(cache->parent_table_count >= 0, ECS_INTERNAL_ERROR, NULL); 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); bool was_empty = !ecs_map_count(&cache->tables); 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->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); ecs_query_cache_match_t *result = flecs_query_cache_add_table_to_group( cache, group, qt, table); if (was_empty && cache->entity) { ecs_remove_id(ecs_get_stage(cache->query->world, 0), cache->entity, EcsEmpty); } return result; } /* 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->world->allocator; flecs_free_t(a, ecs_query_cache_table_t, qt); ecs_map_remove(&cache->tables, table->id); } void flecs_query_cache_remove_all_tables( ecs_query_cache_t *cache) { ecs_allocator_t *a = &cache->query->world->allocator; ecs_query_cache_group_t *group = cache->first_group; while (group) { ecs_query_cache_group_t *next = group->next; flecs_query_cache_group_fini(cache, group); group = next; } cache->first_group = NULL; cache->default_group.next = NULL; ecs_map_iter_t it = ecs_map_iter(&cache->tables); while (ecs_map_next(&it)) { flecs_free_t(a, ecs_query_cache_table_t, ecs_map_ptr(&it)); } ecs_map_clear(&cache->tables); cache->parent_table_count = 0; ecs_assert(ecs_map_count(&cache->groups) == 0, ECS_INTERNAL_ERROR, NULL); } #endif #ifdef FLECS_CACHED_QUERIES static bool flecs_query_cache_borrows_columns( const ecs_query_cache_t *cache) { const ecs_query_t *query = cache->query; return flecs_query_cache_is_trivial(cache) && query->field_count == 1 && query->terms[0].oper == EcsAnd; } /* 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_query_cache_borrows_columns(cache)) { 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->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 (flecs_query_cache_borrows_columns(cache)) { ecs_assert(it->trs[0] != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(it->trs[0]->column == it->columns[0], ECS_INTERNAL_ERROR, NULL); qm->base.columns = ECS_CONST_CAST(int16_t*, &it->trs[0]->column); return; } 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); if (trivial_cache) { return; } /* 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->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->world->allocator; ecs_vec_t *wildcard_matches = first->wildcard_matches; int32_t wildcard_elem = 0; bool result = true, has_more = true; 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); } static void flecs_query_rematch_table( ecs_world_t *world, ecs_query_impl_t *impl, ecs_table_t *table) { 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 (table->flags & EcsTableNotQueryable) { return; } ecs_iter_t it = flecs_query_iter(world, cache->query); ECS_BIT_SET(it.flags, EcsIterNoData); ecs_table_range_t range = { .table = table }; ecs_iter_set_var_as_range(&it, 0, &range); if (!ecs_query_next(&it)) { flecs_query_cache_remove_table(cache, table); return; } if (flecs_query_cache_rematch_next(cache, &it)) { ecs_abort(ECS_INTERNAL_ERROR, NULL); } } void flecs_query_revalidate_table( ecs_world_t *world, ecs_query_impl_t *impl, uint64_t table_id) { flecs_poly_assert(world, ecs_world_t); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, table_id); if (!table) { return; } if (table->flags & EcsTableMarkedForDelete) { return; } ecs_os_perf_trace_push("flecs.query.revalidate_table"); ecs_time_t t = {0}; if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_measure(&t); } world->info.eval_comp_monitors_total ++; cache->match_count ++; flecs_query_rematch_table(world, impl, table); if (world->flags & EcsWorldMeasureFrameTime) { world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); } ecs_os_perf_trace_pop("flecs.query.revalidate_table"); } #endif #ifdef FLECS_CACHED_QUERIES 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->stage; 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; for (; cur; cur = cur->next) { flecs_query_cache_build_sorted_table_range(cache, cur); } } 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; for (; cur; cur = cur->next) { 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; } } 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 */ } } #endif static bool flecs_query_needs_plan( const ecs_query_impl_t *query) { ecs_flags32_t flags = query->pub.flags; if (query->cache) { if (flags & EcsQueryIsCacheable) { if (!(flags & EcsQueryCacheWithFilter)) { return false; } } } else { ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; if ((flags & trivial_flags) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { return false; } } } return true; } #ifdef FLECS_QUERY_PLANS static void flecs_query_compile_trivial( ecs_query_impl_t *query) { /* 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; } 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 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 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); /* 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_entity_id = flecs_query_find_var_id( query, var_name, EcsVarEntity); if (base_entity_id == EcsVarNone) { if (ecs_os_strcmp(var_name, EcsThisName)) { ecs_err("unresolved lookup base '%s'", var_name); ecs_os_free(var_name); goto error; } base_entity_id = flecs_query_add_var( query, EcsThisName, 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); } 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) { var->table_id = ecs_vec_get_t( vars, ecs_query_var_t, var->base_id - 1)->table_id; } } /* 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; if (query->var_size > EcsQueryMaxVarCount) { ecs_err( "query has too many (%d) variables (maximum is %d)", query->var_size, EcsQueryMaxVarCount); goto error; } 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_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 ++; } bool first_uncompiled_dont_fragment = false; for (i = 0; i < term_count; i ++) { if (*compiled & (1ull << i)) { continue; } ecs_term_t *term = &terms[i]; first_uncompiled_dont_fragment = (term->flags_ & EcsTermDontFragment) && !(trivial_set & (1llu << i)) && (term->oper == EcsAnd) && !flecs_term_is_or(q, term) && ecs_term_match_this(term) && ((term->src.id & EcsTraverseFlags) == EcsSelf) && !(term->flags_ & EcsTermIsScope) && !ecs_id_is_wildcard(term->id); break; } if (trivial_terms >= 2 || (trivial_terms && first_uncompiled_dont_fragment)) { /* 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}; 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; #ifdef FLECS_CACHED_QUERIES if (q->cache_kind == EcsQueryCacheAll) { /* If all terms are cacheable, make sure no other terms are compiled */ *compiled = 0xFFFFFFFFFFFFFFFF; } else #endif 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->stage, 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; } bool is_or = term->flags_ & EcsTermIsOr; bool filter = term->trav == EcsChildOf && (is_or || term->oper == EcsAnd || term->oper == EcsOptional || term->oper == EcsNot); if (is_or && !filter) { for (int32_t j = i; j > 0 && terms[j - 1].oper == EcsOr; j --) { filter |= terms[j - 1].trav == EcsChildOf; } for (int32_t j = i; j < count - 1 && terms[j].oper == EcsOr; j ++) { filter |= terms[j + 1].trav == EcsChildOf; } } if (filter) { ecs_oper_kind_t oper = term->oper; if (!is_or) { term->oper = EcsAnd; } flecs_query_compile_term(q->stage, query, term, ctx); term->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; } static int16_t flecs_query_find_tree_cache_op( ecs_query_impl_t *query) { if (!query->cache || query->pub.cache_kind != EcsQueryCacheAuto) { return -1; } int32_t i, cache_op = -1, count = query->op_count; for (i = 0; i < count; i ++) { ecs_query_op_kind_t kind = query->ops[i].kind; if (kind == EcsQueryCache) { if (cache_op != -1) { return -1; } cache_op = i; } else if (kind == EcsQuerySetIds) { if (cache_op != -1) { return -1; } } else if (kind == EcsQueryTreeUpPost || kind == EcsQueryTreeSelfUpPost) { if (cache_op == -1) { return -1; } } else if (kind == EcsQueryYield) { if (i != (count - 1)) { return -1; } } else { return -1; } } if (cache_op == -1 || cache_op == (count - 2)) { return -1; } #if defined(FLECS_DEBUG) && defined(FLECS_CACHED_QUERIES) { ecs_query_cache_t *cache = query->cache; int32_t f, field_count = cache->query->field_count; ecs_assert(field_count == query->pub.field_count, ECS_INTERNAL_ERROR, NULL); for (f = 0; f < field_count; f ++) { ecs_assert(cache->field_map[f] == f, ECS_INTERNAL_ERROR, NULL); } } #endif return flecs_ito(int16_t, cache_op); } static bool flecs_query_op_has_ctx( ecs_query_op_kind_t kind) { switch(kind) { case EcsQueryCache: case EcsQueryIsCache: case EcsQueryIds: case EcsQueryReset: case EcsQueryLookup: case EcsQuerySetVars: case EcsQuerySetFixed: case EcsQuerySetIds: case EcsQuerySetId: case EcsQueryContain: case EcsQueryPairEq: case EcsQueryEnd: case EcsQueryPredEq: case EcsQueryPredEqName: case EcsQueryYield: case EcsQueryNothing: return false; default: return true; } } static bool flecs_query_op_has_ctx_fini( ecs_query_op_kind_t kind) { switch(kind) { case EcsQueryTrav: case EcsQueryUp: case EcsQuerySelfUp: case EcsQueryTreeUpPre: case EcsQueryTreeSelfUpPre: case EcsQueryTreeUpPost: case EcsQueryTreeSelfUpPost: case EcsQueryTreeUpNot: case EcsQueryTreeSelfUpNot: case EcsQuerySparseUp: case EcsQuerySparseSelfUp: return true; default: return false; } } static void flecs_query_compute_op_ctx_ranges( ecs_query_impl_t *query) { int32_t i, count = query->op_count; int32_t zero_first = -1, zero_last = -1; int32_t fini_first = -1, fini_last = -1; for (i = 0; i < count; i ++) { ecs_query_op_kind_t kind = query->ops[i].kind; if (flecs_query_op_has_ctx(kind)) { if (zero_first == -1) { zero_first = i; } zero_last = i; } if (flecs_query_op_has_ctx_fini(kind)) { if (fini_first == -1) { fini_first = i; } fini_last = i; } } if (zero_first == -1) { query->op_ctx_zero_first = 0; query->op_ctx_zero_count = 0; } else { query->op_ctx_zero_first = flecs_ito(int16_t, zero_first); query->op_ctx_zero_count = flecs_ito(int16_t, zero_last - zero_first + 1); } if (fini_first == -1) { query->op_ctx_fini_first = 0; query->op_ctx_fini_count = 0; } else { query->op_ctx_fini_first = flecs_ito(int16_t, fini_first); query->op_ctx_fini_count = flecs_ito(int16_t, fini_last - fini_first + 1); } } int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query) { query->tree_cache_op = -1; query->op_ctx_zero_first = 0; query->op_ctx_zero_count = 1; query->op_ctx_fini_first = 0; query->op_ctx_fini_count = 0; /* Compile query to operations. Only necessary for non-trivial queries, as * trivial queries use trivial iterators that don't use query ops. */ if (!flecs_query_needs_plan(query)) { flecs_query_compile_trivial(query); 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. */ #ifdef FLECS_CACHED_QUERIES if (q->cache_kind != EcsQueryCacheAll) #endif { 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->flags & (EcsQueryIsVar << EcsQuerySrc)) && (op->src.var == 0)) { 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); query->tree_cache_op = flecs_query_find_tree_cache_op(query); flecs_query_compute_op_ctx_ranges(query); } return 0; } #else int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query) { (void)world; (void)stage; (void)query; (void)flecs_query_needs_plan; ecs_check(!flecs_query_needs_plan(query), ECS_UNSUPPORTED, "query uses features that require the FLECS_QUERY_PLANS addon"); return 0; error: return -1; } #endif #ifdef FLECS_QUERY_PLANS 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); } #define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ 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); ecs_var_id_t first_src_var = first->src.var; 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); } 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; } #ifdef FLECS_META 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) { ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); first_id = ECS_TERM_REF_ID(&term->first); /* First compile as if it's a regular term, to match the component */ term->flags_ &= (uint16_t)~EcsTermIsMember; /* Replace term id with member parent (the component) */ ecs_entity_t component = ecs_get_parent(world, first_id); if (!component) { ecs_err("member without parent in query"); return -1; } if (!ecs_has(world, component, EcsComponent)) { ecs_err("parent of member is not a component"); return -1; } bool second_wildcard = (ECS_TERM_REF_ID(&term->second) == EcsWildcard || ECS_TERM_REF_ID(&term->second) == EcsAny) && (term->second.id & EcsIsVariable) && !term->second.name; term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); term->second.id = 0; term->id = component; ctx->oper = (ecs_oper_kind_t)term->oper; if (term->oper == EcsNot && !second_wildcard) { /* When matching a member term with not operator, we need to cover both * the case where an entity doesn't have the component, and where it * does have the component, but doesn't match the member. */ term->oper = EcsOptional; } 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) { ecs_entity_t component = ECS_TERM_REF_ID(&term->first); const EcsComponent *comp = ecs_get(world, component, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); /* Restore term values */ term->id = term_id; term->first.id = first_id; term->second.id = second_id; term->flags_ |= EcsTermIsMember; term->oper = flecs_ito(int16_t, ctx->oper); first_id = ECS_TERM_REF_ID(&term->first); const EcsMember *member = ecs_get(world, first_id, EcsMember); ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_var_t *var = &impl->vars[op->src.var]; const char *var_name = flecs_term_ref_var_name(&term->src); ecs_var_id_t evar = flecs_query_find_var_id( impl, var_name, EcsVarEntity); bool second_wildcard = (ECS_TERM_REF_ID(&term->second) == EcsWildcard || ECS_TERM_REF_ID(&term->second) == EcsAny) && (term->second.id & EcsIsVariable) && !term->second.name; if (term->oper == EcsOptional) { second_wildcard = true; } ecs_query_op_t mbr_op = *op; mbr_op.kind = EcsQueryMemberEq; mbr_op.first.entity = /* Encode type size and member offset */ flecs_ito(uint32_t, member->offset) | (flecs_ito(uint64_t, comp->size) << 32); /* If this is a term with a Not operator, conditionally evaluate member on * whether term was set by previous operation (see begin_member_term). */ if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { if (second_wildcard && ctx->oper == EcsNot) { /* A !(T.value, *) term doesn't need special operations */ return 0; } /* Resolve to entity variable before entering if block, so that we * don't have different branches of the query working with different * versions of the same variable. */ if (var->kind == EcsVarTable) { flecs_query_insert_each(op->src.var, evar, ctx, cond_write); var = &impl->vars[evar]; } ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); if_op->other = term->field_index; if (ctx->oper == EcsNot) { mbr_op.kind = EcsQueryMemberNeq; } } if (var->kind == EcsVarTable) { /* If MemberEq is called on table variable, store it on .other member. * This causes MemberEq to do double duty as 'each' instruction, * which is faster than having to go back & forth between instructions * while finding matching values. */ mbr_op.other = flecs_itolbl(op->src.var + 1); /* Mark entity variable as written */ flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &mbr_op.written); } flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); if (second_wildcard) { mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); mbr_op.second.entity = EcsWildcard; } else { flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); if (term->second.id & EcsIsVariable) { if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, EcsQuerySecond, ctx, cond_write, NULL)) { goto error; } flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); flecs_query_write(mbr_op.second.var, &mbr_op.written); } } flecs_query_op_insert(&mbr_op, ctx); if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { flecs_query_end_block(ctx, false); } return 0; error: return -1; } #else 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; } #endif 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->oper == EcsNot && !(term->flags_ & EcsTermIsToggle)) { op->kind = EcsQueryTreeUpNot; } } } 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->oper == EcsNot && !(term->flags_ & EcsTermIsToggle)) { op->kind = EcsQueryTreeSelfUpNot; } } } 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); if (ECS_IS_VALUE_PAIR(term->id)) { op.flags |= EcsQueryIsValuePair; } flecs_query_set_op_kind(query, &op, term, src_is_var); bool is_not = (term->oper == EcsNot) && !builtin_pred; if (op.kind == EcsQuerySparseNot || op.kind == EcsQueryTreeUpNot || op.kind == EcsQueryTreeSelfUpNot) { 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); } 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 && !(first_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 && second_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; } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS // #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); /* Test whether the type has to be searched to find the ids matched by a term * with component inheritance. A table record that was registered for a base id * describes the derived ids in the table exactly, so the record index/count can * be used as-is. A table that stores the base id itself can also store ids that * derive from it, which are not covered by the record. */ static bool flecs_query_must_search_derived( const ecs_query_op_t *op, const ecs_component_record_t *cr, ecs_table_t *table, const ecs_table_record_t *tr) { return (op->match_flags & EcsTermIdInherited) && (cr->flags & EcsIdHasDerived) && (table->flags & EcsTableHasDerived) && !flecs_table_record_is_inherited(table, tr); } /* Find the first id in the table type that matches or derives from id. */ static int16_t flecs_query_search_derived( const ecs_query_run_ctx_t *ctx, ecs_table_t *table, ecs_id_t id) { int32_t column = flecs_table_offset_search_w_inherited( ctx->world, table, 0, id, NULL); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_ito(int16_t, column); } 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; ecs_table_t *table = NULL; 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_iter(&cr->cache, &op_ctx->it, EcsTableEmpty|EcsTableNotEmpty)) { return false; } } else { if (!flecs_table_cache_iter(&cr->cache, &op_ctx->it, EcsTableNotEmpty)) { return false; } } } repeat: { bool next_table = !redo; if (redo) { ecs_assert(op_ctx->it.cur != NULL, ECS_INTERNAL_ERROR, NULL); table = op_ctx->it.cur->table; if (op_ctx->remaining <= 0) { next_table = true; } else if (op->match_flags & EcsTermIdInherited) { /* Derived ids are not stored consecutively in the table type */ int32_t column = flecs_table_offset_search_w_inherited( ctx->world, table, op_ctx->column + 1, cr->id, NULL); if (column == -1) { next_table = true; } else { op_ctx->column = flecs_ito(int16_t, column); op_ctx->remaining --; } } else { op_ctx->column = flecs_query_next_column( table, cr->id, op_ctx->column); op_ctx->remaining --; } } if (next_table) { const ecs_table_cache_elem_t *elem = flecs_table_cache_next( &op_ctx->it); if (!elem) { return false; } table = elem->table; if (flecs_query_must_search_derived(op, cr, table, elem->tr)) { op_ctx->column = flecs_query_search_derived(ctx, table, cr->id); op_ctx->remaining = flecs_ito(int16_t, table->type.count - 1); } else { op_ctx->column = elem->index; op_ctx->remaining = flecs_ito(int16_t, elem->count - 1); } } flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); } 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; ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); if (!table) { return false; } if (!redo) { const ecs_table_record_t *tr; if (cr && (op_ctx->prev_table == table) && !(op->flags & ((EcsQueryIsVar << EcsQueryFirst)|(EcsQueryIsVar << EcsQuerySecond)))) { tr = op_ctx->prev_tr; } else { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (!cr || cr->id != id) { int8_t field_index = op->field_index; if (field_index != -1) { ecs_component_record_t **cr_cache = ctx->query->cr_cache; cr = cr_cache[field_index]; if (!cr || cr->id != id) { cr = cr_cache[field_index] = flecs_components_get(ctx->world, id); } } else { cr = flecs_components_get(ctx->world, id); } op_ctx->cr = cr; if (!cr) { return false; } } if ((id < FLECS_HI_COMPONENT_ID) && !(op->match_flags & EcsTermIdInherited)) { int16_t res = table->component_map[id]; if (res) { int32_t type_index = res > 0 ? table->column_map[table->type.count + (res - 1)] : (-res - 1); tr = &table->_->records[type_index]; ecs_assert(tr->hdr.cr == cr, ECS_INTERNAL_ERROR, NULL); } else { tr = NULL; } op_ctx->it.cur = NULL; } else { const ecs_table_cache_elem_t *elem = flecs_table_cache_get_elem(&cr->cache, table); tr = elem ? elem->tr : NULL; op_ctx->it.cur = elem; } op_ctx->prev_table = table; op_ctx->prev_tr = tr; } if (!tr) { return false; } if (flecs_query_must_search_derived(op, cr, table, tr)) { op_ctx->column = flecs_query_search_derived(ctx, table, cr->id); op_ctx->remaining = flecs_ito(int16_t, table->type.count); } else { op_ctx->column = tr->index; op_ctx->remaining = flecs_ito(int16_t, tr->count); } } else { ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); if (--op_ctx->remaining <= 0) { return false; } if (op->match_flags & EcsTermIdInherited) { /* Derived ids are not stored consecutively in the table type */ int32_t column = flecs_table_offset_search_w_inherited( ctx->world, table, op_ctx->column + 1, cr->id, NULL); if (column == -1) { return false; } op_ctx->column = flecs_ito(int16_t, column); } else { ecs_assert((op_ctx->remaining + op_ctx->column) < table->type.count, ECS_INTERNAL_ERROR, NULL); 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; } } static ecs_table_t* flecs_query_select_dont_fragment_table( ecs_world_t *world, int32_t index) { if (!index) { return &world->store.root; } return flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, index); } static bool flecs_query_select_dont_fragment( 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_world_t *world = ctx->world; ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; ecs_sparse_t *tables = &world->store.tables; ecs_table_t *table; ecs_id_t id = 0; if (!redo) { op_ctx->cur = -1; goto next_table; } else { table = flecs_query_select_dont_fragment_table(world, op_ctx->cur); goto next_component; } next_table: op_ctx->cur ++; if (op_ctx->cur >= flecs_sparse_count(tables)) { return false; } table = flecs_query_select_dont_fragment_table(world, op_ctx->cur); if (!(table->flags & EcsTableHasDontFragment) || !ecs_table_count(table)) { goto next_table; } if (flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { goto next_table; } op_ctx->row = -1; op_ctx->count = ecs_table_count(table); next_entity: op_ctx->row ++; if (op_ctx->row >= op_ctx->count) { goto next_table; } op_ctx->column = -1; op_ctx->df_cr = NULL; goto this_component; next_component: if (op_ctx->df_cr) { op_ctx->df_cr = op_ctx->df_cr->non_fragmenting.next; goto next_dont_fragment; } this_component: op_ctx->column ++; if (op_ctx->column < table->type.count) { flecs_query_var_set_range(op, op->src.var, table, op_ctx->row, 1, ctx); flecs_query_set_match(op, table, op_ctx->column, ctx); return true; } op_ctx->df_cr = world->cr_non_fragmenting_head; next_dont_fragment: { ecs_entity_t e = ecs_table_entities(table)[op_ctx->row]; while (op_ctx->df_cr) { ecs_component_record_t *df_cr = op_ctx->df_cr; if (!ecs_id_is_wildcard(df_cr->id) && df_cr->sparse && flecs_sparse_has(df_cr->sparse, e)) { id = df_cr->id; break; } op_ctx->df_cr = df_cr->non_fragmenting.next; } } if (!op_ctx->df_cr) { goto next_entity; } flecs_query_var_set_range(op, op->src.var, table, op_ctx->row, 1, ctx); if (field_index != -1) { it->ids[field_index] = id; flecs_query_it_set_tr(it, field_index, NULL); } flecs_query_set_vars(op, id, ctx); return true; } 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 { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and_); if (!redo) { op_ctx->non_fragmenting = false; } if (!op_ctx->non_fragmenting) { if (flecs_query_select(op, redo, ctx)) { return true; } ecs_id_t id = flecs_query_op_get_id(op, ctx); if (ECS_IS_PAIR(id) || !ecs_id_is_wildcard(id) || !ctx->world->cr_non_fragmenting_head) { return false; } op_ctx->non_fragmenting = true; return flecs_query_select_dont_fragment(op, false, ctx); } return flecs_query_select_dont_fragment(op, true, ctx); } } 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, op_ctx->it.cur ? op_ctx->it.cur->tr : NULL); 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); } } #ifdef FLECS_CACHED_QUERIES 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); } } #endif 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 (!flecs_table_cache_count(&cur->cache)) { 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, EcsTableNotEmpty); if (flecs_table_cache_next(&it)) { 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_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); #ifdef FLECS_CACHED_QUERIES case EcsQueryCache: return flecs_query_cache(op, redo, ctx); case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); #endif 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 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 EcsQueryTreeUpNot: return flecs_query_tree_up_not(op, redo, ctx, false); case EcsQueryTreeSelfUpNot: return flecs_query_tree_up_not(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; } #endif // FLECS_QUERY_PLANS 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->world; ctx->query = impl; ctx->it = it; #ifdef FLECS_QUERY_PLANS ctx->vars = qit->vars; ctx->query_vars = qit->query_vars; ctx->written = qit->written; ctx->op_ctx = qit->op_ctx; #endif ctx->qit = qit; } #ifdef FLECS_QUERY_PLANS 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|EcsIterTrivialSparse|EcsIterTreeCached| EcsIterCached); /* 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; #ifdef FLECS_CACHED_QUERIES ecs_query_cache_t *cache = query->cache; #endif 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_trivial_set_iter_this(it, &ctx); if (!query->cache) { if ((flags & (trivial_flags)) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialTest; flecs_query_trivial_set_ids(&ctx); } } else if ((flags & (EcsQueryMatchOnlyThis|EcsQueryMatchOnlySelf)) == (EcsQueryMatchOnlyThis|EcsQueryMatchOnlySelf)) { it->flags |= EcsIterTrivialChangeDetection; } #ifdef FLECS_CACHED_QUERIES } 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 (query->tree_cache_op != -1) { int32_t i, op_count = query->op_count; for (i = query->tree_cache_op + 1; i < op_count; i ++) { ctx.written[i] |= 1ull; } it->flags |= EcsIterTreeCached; #endif } } else { if (!query->cache) { if ((flags & (trivial_flags)) == trivial_flags) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialSearch| EcsIterTrivialChangeDetection; flecs_query_trivial_set_ids(&ctx); } } else if (flags & EcsQueryTrivialSparse) { it->flags |= EcsIterTrivialSparse| EcsIterTrivialChangeDetection; flecs_query_trivial_set_ids(&ctx); } else if ((flags & (EcsQueryMatchOnlyThis|EcsQueryMatchOnlySelf)) == (EcsQueryMatchOnlyThis|EcsQueryMatchOnlySelf)) { it->flags |= EcsIterTrivialChangeDetection; } #ifdef FLECS_CACHED_QUERIES } 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; } } } else if (query->tree_cache_op != -1) { int32_t i, op_count = query->op_count; for (i = query->tree_cache_op + 1; i < op_count; i ++) { ctx.written[i] |= 1ull; } it->flags |= EcsIterTreeCached; #endif } } } #else void flecs_query_iter_constrain( ecs_iter_t *it) { ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); const ecs_query_impl_t *query = ctx.query; const ecs_query_t *q = &query->pub; ecs_flags32_t flags = q->flags; (void)flags; bool constrained = ctx.qit->constrained_this; it->flags &= ~(EcsIterTrivialTest|EcsIterTrivialCached| EcsIterTrivialSearch|EcsIterTrivialSparse); #ifdef FLECS_CACHED_QUERIES ecs_query_cache_t *cache = query->cache; if (cache) { ecs_assert(flags & EcsQueryIsCacheable, ECS_INTERNAL_ERROR, NULL); if (!cache->order_by_callback && (cache->query->flags & EcsQueryTrivialCache) && !(q->flags & EcsQueryHasChangeDetection)) { it->flags |= (constrained ? EcsIterTrivialTest : 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 |= constrained ? EcsIterTrivialTest : EcsIterTrivialSearch; it->flags |= EcsIterCached; } return; } #endif ecs_assert(flags & EcsQueryIsTrivial, ECS_INTERNAL_ERROR, NULL); ecs_assert(flags & EcsQueryMatchOnlySelf, ECS_INTERNAL_ERROR, NULL); ecs_assert(!(flags & EcsQueryMatchWildcards), ECS_INTERNAL_ERROR, NULL); it->flags |= constrained ? EcsIterTrivialTest : (EcsIterTrivialSearch|EcsIterTrivialChangeDetection); flecs_query_trivial_set_ids(&ctx); } #endif // FLECS_QUERY_PLANS #ifdef FLECS_CACHED_QUERIES 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 || !it->table->dirty_state) { return; } flecs_query_change_detection(it, qit, impl); } #endif #ifdef FLECS_QUERY_PLANS #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 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 EcsQueryTreeUpPre: case EcsQueryTreeSelfUpPre: case EcsQueryTreeUpPost: case EcsQueryTreeSelfUpPost: case EcsQueryTreeUpNot: case EcsQueryTreeSelfUpNot: 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, first = query->op_ctx_fini_first; int32_t last = first + query->op_ctx_fini_count; ecs_query_op_t *ops = query->ops; ecs_query_op_ctx_t *ctx = qit->op_ctx; if (!ctx) { return; } for (i = first; i < last; i ++) { ecs_query_op_t *op = &ops[i]; flecs_query_op_ctx_fini(it, op, &ctx[i]); } } #define FLECS_QUERY_ITER_CURSOR_SIZE \ ECS_ALIGN(ECS_SIZEOF(ecs_stack_cursor_t), ECS_ALIGNOF(ecs_query_op_ctx_t)) typedef struct ecs_query_iter_layout_t { ecs_size_t written_size; ecs_size_t vars_size; ecs_size_t fields_size; ecs_size_t op_ctx_offset; ecs_size_t op_ctx_size; ecs_size_t total; int32_t field_count; bool op_ctx; bool op_ctx_split; } ecs_query_iter_layout_t; static void flecs_query_iter_layout( const ecs_query_impl_t *impl, ecs_query_iter_layout_t *l) { const ecs_query_t *q = &impl->pub; int32_t field_count = q->field_count; bool op_ctx = true; #ifdef FLECS_CACHED_QUERIES if (impl->cache) { if ((q->flags & EcsQueryIsCacheable) && !(q->flags & EcsQueryCacheWithFilter)) { field_count = 0; } op_ctx = impl->ops != NULL; } #endif int32_t op_count = impl->op_count ? impl->op_count : 1; l->field_count = field_count; l->op_ctx = op_ctx; l->written_size = ECS_SIZEOF(ecs_write_flags_t) * op_count; l->vars_size = ECS_SIZEOF(ecs_var_t) * impl->var_count; l->fields_size = (ECS_SIZEOF(ecs_id_t) + ECS_SIZEOF(ecs_entity_t) + ECS_SIZEOF(ecs_table_record_t*)) * field_count; ecs_size_t cols_size = ECS_SIZEOF(int16_t) * field_count; l->op_ctx_offset = ECS_ALIGN(l->written_size + l->vars_size + l->fields_size + cols_size, ECS_ALIGNOF(ecs_query_op_ctx_t)); l->op_ctx_size = op_ctx ? ECS_SIZEOF(ecs_query_op_ctx_t) * op_count : 0; l->total = l->op_ctx_offset + l->op_ctx_size; l->op_ctx_split = false; if ((l->total + FLECS_QUERY_ITER_CURSOR_SIZE) > FLECS_STACK_PAGE_SIZE) { l->op_ctx_split = true; l->total = l->op_ctx_offset; } } static void flecs_query_iter_alloc( ecs_iter_t *it, const ecs_query_impl_t *impl) { ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_iter_layout_t l; flecs_query_iter_layout(impl, &l); ecs_world_t *world = it->stage; ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_stack_t *stack = &stage->allocators.iter_stack; char *buf; if (!l.op_ctx_split) { void *data; it->priv_.stack_cursor = flecs_stack_get_cursor_w_alloc(stack, l.total, ECS_ALIGNOF(ecs_query_op_ctx_t), &data); buf = data; } else { it->priv_.stack_cursor = flecs_stack_get_cursor(stack); buf = flecs_stack_alloc(stack, l.total, ECS_ALIGNOF(ecs_query_op_ctx_t)); it->flags |= EcsIterOpCtxSplit; } ecs_os_memset(buf, 0, l.written_size + l.vars_size + l.fields_size); qit->written = (ecs_write_flags_t*)(void*)buf; qit->vars = impl->var_count ? (ecs_var_t*)(void*)(buf + l.written_size) : NULL; int32_t i, field_count = l.field_count; if (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); char *fields = buf + l.written_size + l.vars_size; it->ids = (ecs_id_t*)(void*)fields; it->sources = (ecs_entity_t*)(void*)(fields + ECS_SIZEOF(ecs_id_t) * field_count); it->trs = (const ecs_table_record_t**)(void*)(fields + (ECS_SIZEOF(ecs_id_t) + ECS_SIZEOF(ecs_entity_t)) * field_count); int16_t *columns = (int16_t*)(void*)(fields + l.fields_size); for (i = 0; i < field_count; i ++) { columns[i] = -1; } it->columns = columns; } if (l.op_ctx) { ecs_query_op_ctx_t *op_ctx; if (l.op_ctx_split) { op_ctx = flecs_iter_alloc(it, l.op_ctx_size, ECS_ALIGNOF(ecs_query_op_ctx_t)); } else { op_ctx = (ecs_query_op_ctx_t*)(void*)(buf + l.op_ctx_offset); } int32_t zero_count = impl->op_ctx_zero_count; if (zero_count) { ecs_os_memset(&op_ctx[impl->op_ctx_zero_first], 0, ECS_SIZEOF(ecs_query_op_ctx_t) * zero_count); } qit->op_ctx = op_ctx; } } #endif // FLECS_QUERY_PLANS bool ecs_query_next( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_CACHED_QUERIES ecs_assert(it->next == ecs_query_next || it->next == flecs_query_trivial_cached_next || it->next == flecs_default_next_callback, ECS_INVALID_PARAMETER, NULL); #else ecs_assert(it->next == ecs_query_next || it->next == flecs_default_next_callback, ECS_INVALID_PARAMETER, NULL); #endif #ifdef FLECS_CACHED_QUERIES if ((it->flags & (EcsIterTrivialCached|EcsIterTrivialSearch)) == (EcsIterTrivialCached|EcsIterTrivialSearch)) { return flecs_query_trivial_cached_next(it); } #endif ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, it->query); (void)impl; ecs_assert(impl != NULL, ECS_INVALID_OPERATION, "cannot call ecs_query_next on invalid iterator"); if (!impl->pub.term_count) { it->flags |= EcsIterSkip; ecs_iter_fini(it); ecs_os_linc(&it->world->info.queries_ran_total); return false; } ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); bool redo = it->flags & EcsIterIsValid; #ifdef FLECS_CACHED_QUERIES if (redo) { if (it->flags & EcsIterTrivialChangeDetection) { flecs_query_self_change_detection(it, qit, impl); } else { flecs_query_change_detection(it, qit, impl); } } #endif 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. */ #ifdef FLECS_CACHED_QUERIES if (it->flags & EcsIterTrivialCached) { /* Trivial cache iterator. Only supported for search */ 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) { if (flecs_query_is_cache_search(&ctx, redo)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialTest) { if (flecs_query_is_cache_test(&ctx, redo)) { goto yield; } } #ifdef FLECS_QUERY_PLANS } else if (it->flags & EcsIterTreeCached) { if (flecs_query_tree_cache_search(&ctx, redo)) { goto yield; } #endif } else #endif { /* Uncached iterator modes */ if (it->flags & EcsIterTrivialSearch) { #ifdef FLECS_QUERY_PLANS ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; #else ecs_query_trivial_ctx_t *op_ctx = &qit->trivial; #endif if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { goto yield; } #ifdef FLECS_QUERY_PLANS } else if (it->flags & EcsIterTrivialSparse) { if (flecs_query_trivial_sparse_search(&ctx, redo)) { goto yield; } #endif } else if (it->flags & EcsIterTrivialTest) { 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 { #ifdef FLECS_QUERY_PLANS 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; } #else ecs_abort(ECS_INTERNAL_ERROR, "query without plan entered query plan evaluator"); #endif } } /* Done iterating */ #ifdef FLECS_CACHED_QUERIES if (impl->pub.write_fields & impl->pub.fixed_fields) { 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)); } #endif it->flags |= EcsIterSkip; /* Prevent change detection on fini */ ecs_iter_fini(it); ecs_os_linc(&it->world->info.queries_ran_total); return false; #ifdef FLECS_CACHED_QUERIES trivial_search_yield: #ifdef FLECS_QUERY_PLANS it->table = ctx.vars[0].range.table; #endif it->count = ecs_table_count(it->table); it->entities = ecs_table_entities(it->table); #endif yield: return true; } #ifdef FLECS_CACHED_QUERIES 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 is constrained, use ecs_query_next instead"); 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->world->info.queries_ran_total); return false; } #endif // FLECS_CACHED_QUERIES static void flecs_query_iter_fini( ecs_iter_t *it) { #if defined(FLECS_CACHED_QUERIES) || defined(FLECS_QUERY_PLANS) ecs_query_iter_t *qit = &it->priv_.iter.query; #endif const ecs_query_t *q = it->query; (void)q; ecs_assert(q != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(q, ecs_query_t); #ifdef FLECS_CACHED_QUERIES if ((it->flags & (EcsIterIsValid|EcsIterSkip)) == EcsIterIsValid) { flecs_query_change_detection(it, qit, flecs_query_impl(q)); } #endif #ifdef FLECS_QUERY_PLANS #ifdef FLECS_DEBUG if (it->flags & EcsIterProfile) { char *str = ecs_query_plan_w_profile(q, it); printf("%s\n", str); ecs_os_free(str); } int32_t op_count = flecs_query_impl(q)->op_count; flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); #endif if (!q->term_count) { it->query = NULL; return; } if ((it->flags & EcsIterTrivialSparse) && qit->op_ctx) { ecs_query_sparse_trivial_ctx_t *op_ctx = &qit->op_ctx[0].is.sparse_trivial; if (op_ctx->entities) { flecs_iter_free_n(op_ctx->entities, ecs_entity_t, FLECS_QUERY_SPARSE_BATCH_SIZE); } } if (it->flags & EcsIterOpCtxFini) { flecs_query_iter_fini_ctx(it, qit); } if (it->flags & EcsIterOpCtxSplit) { ecs_query_iter_layout_t l; flecs_query_iter_layout(flecs_query_impl(q), &l); ecs_assert(l.op_ctx_split, ECS_INTERNAL_ERROR, NULL); flecs_iter_free(qit->op_ctx, l.op_ctx_size); flecs_iter_free(qit->written, l.total); } qit->vars = NULL; qit->written = NULL; qit->op_ctx = NULL; #endif // FLECS_QUERY_PLANS it->query = NULL; } #ifdef FLECS_QUERY_PLANS 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->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 } #endif // FLECS_QUERY_PLANS 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; #ifndef FLECS_QUERY_PLANS qit->entity = 0; qit->constrained_this = false; #endif ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); #ifdef FLECS_DEBUG flecs_check_exclusive_world_access_write(q->world); #endif #ifdef FLECS_QUERY_PLANS flecs_query_validate_final_fields(q); #endif flecs_poly_assert(q, ecs_query_t); #if defined(FLECS_CACHED_QUERIES) || defined(FLECS_QUERY_PLANS) ecs_query_impl_t *impl = flecs_query_impl(q); #endif it.stage = 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.stage, ecs_world_t) && flecs_poly_is(q->stage, ecs_stage_t)) { it.stage = ECS_CONST_CAST(ecs_world_t*, q->stage); } it.world = q->world; ecs_assert(flecs_poly_is(it.world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_check(!(it.world->flags & EcsWorldMultiThreaded) || it.stage != it.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); #ifdef FLECS_QUERY_PLANS if (!q->term_count) { flecs_iter_init(it.stage, &it, false); } #else #ifdef FLECS_CACHED_QUERIES bool fully_cached = (q->flags & EcsQueryIsCacheable) && !(q->flags & EcsQueryCacheWithFilter); flecs_iter_init(it.stage, &it, !impl->cache || !fully_cached); #else flecs_iter_init(it.stage, &it, true); #endif #endif #ifdef FLECS_QUERY_PLANS qit->query_vars = impl->vars; qit->ops = impl->ops; #endif if (q->flags & EcsQueryMatchEmptyTables) { it.flags |= EcsIterMatchEmptyTables; } #ifdef FLECS_CACHED_QUERIES flecs_query_cache_iter_init(&it, qit, impl); #endif #ifdef FLECS_QUERY_PLANS if (!q->term_count) { #ifdef FLECS_DEBUG int32_t op_count = impl->op_count ? impl->op_count : 1; qit->profile = flecs_iter_calloc_n( &it, ecs_query_op_profile_t, op_count); #endif return it; } int32_t i, var_count = impl->var_count; flecs_query_iter_alloc(&it, impl); #ifdef FLECS_DEBUG int32_t op_count = impl->op_count ? impl->op_count : 1; 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; } #endif // FLECS_QUERY_PLANS /* Set flags for unconstrained query iteration. Can be reinitialized when * the iterator is constrained. */ 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_flags32_t flags = q->flags; ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; /* A query whose only non-trivial property is that one of its ids can be * inherited still resolves like a trivial query for tables that own all * of its ids. That is the common case for observers, which are handed a * single table to test: try the table first and only fall back to the * query engine when an id is missing and could still be found on a base * entity. */ if ( #ifdef FLECS_CACHED_QUERIES flecs_query_impl(q)->cache || #endif (((flags & trivial_flags) != trivial_flags) && !(flags & EcsQuerySelfTrivial)) || (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.stage = ECS_CONST_CAST(ecs_world_t*, world); lit.world = ECS_CONST_CAST(ecs_world_t*, q->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.stage, &lit, true); lit.flags |= EcsIterIsValid; ECS_BIT_COND(lit.flags, EcsIterComponentInheritance, ECS_BIT_IS_SET(flags, EcsQueryHasComponentInheritance)); 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]; /* An id that is not owned by the table can still be matched on a * base entity when the term traverses IsA, in which case the query * engine has to do the work. */ bool up = (term->src.id & EcsUp) != 0; bool is_not = term->oper == EcsNot; const ecs_table_record_t *tr = NULL; ecs_component_record_t *cr = flecs_components_get( lit.world, term->id); if (cr) { tr = flecs_component_get_table(cr, table); } if (!tr) { if (up) { goto not_trivial; } if (!is_not) { goto no_match; } continue; } if (is_not) { 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; not_trivial: lit.flags |= EcsIterSkip; ecs_iter_fini(&lit); ECS_CONST_CAST(ecs_query_t*, q)->eval_count --; return -1; } 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); return flecs_query_iter(world, q); } #ifdef FLECS_QUERY_PLANS 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); } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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); } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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; const ecs_entity_t *entities = ecs_table_entities(op_ctx->range.table); 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 = entities[op_ctx->cur]; bool result; if (ptr_out) { void *ptr = flecs_sparse_get( op_ctx->sparse, op_ctx->sparse->size, e); result = ptr != NULL; *ptr_out = ptr; } else { result = flecs_sparse_has(op_ctx->sparse, e); } if (not) { result = !result; } if (!result) { goto next; } if (!ptr_out && (op->flags & (EcsQueryIsVar << EcsQuerySrc)) && !op->src.var) { int32_t start = op_ctx->cur; while ((op_ctx->cur + 1) < end) { result = flecs_sparse_has(op_ctx->sparse, entities[op_ctx->cur + 1]); if (not) { result = !result; } if (!result) { break; } op_ctx->cur ++; } if (op_ctx->cur != start) { ecs_table_range_t range = { .table = op_ctx->range.table, .offset = start, .count = op_ctx->cur - start + 1 }; flecs_query_src_set_range(op, &range, ctx); return true; } } 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"); const uint64_t *sparse_ids = flecs_sparse_ids(op_ctx->sparse); ecs_entity_t e = sparse_ids[op_ctx->cur]; ecs_record_t *r = flecs_entities_get(ctx->world, e); ecs_table_t *table = r->table; int32_t row = ECS_RECORD_TO_ROW(r->row); int32_t count = 1; if (flecs_query_table_filter(table, op->other, table_mask)) { goto next; } while (!op->src.var && op_ctx->cur > 0) { ecs_record_t *r_next = flecs_entities_get( ctx->world, sparse_ids[op_ctx->cur - 1]); if (r_next->table != table) { break; } int32_t row_next = ECS_RECORD_TO_ROW(r_next->row); if (row_next == row - 1) { row --; } else if (row_next != row + count) { break; } count ++; op_ctx->cur --; } flecs_query_var_set_range(op, op->src.var, table, row, count, ctx); it->ids[field_index] = id; flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } 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_component_record_t *cr, void **ptr_out) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); if (!redo) { if (!flecs_query_sparse_init_sparse(op_ctx, cr)) { goto no_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, op_ctx->cr, &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 ecs_component_record_t* flecs_query_sparse_next_component( ecs_component_record_t *cr, ecs_id_t id) { bool all = id == ecs_pair(EcsWildcard, EcsWildcard); do { if (all) { cr = cr->non_fragmenting.next; } else if (ECS_PAIR_FIRST(id) == EcsWildcard) { cr = cr->pair->second.next; } else { cr = cr->pair->first.next; } } while (cr && (!(cr->flags & EcsIdDontFragment) || (all && (!ECS_IS_PAIR(cr->id) || ecs_id_is_wildcard(cr->id))))); return cr; } static FLECS_ALWAYS_INLINE bool flecs_query_sparse_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_id_t id, bool select, bool not, ecs_flags32_t table_mask) { ecs_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); bool all = id == ecs_pair(EcsWildcard, EcsWildcard); if (!redo) { ecs_component_record_t *cr = all ? ctx->world->cr_non_fragmenting_head : flecs_components_get(ctx->world, id); if (cr && all) { if (!ECS_IS_PAIR(cr->id) || ecs_id_is_wildcard(cr->id)) { cr = flecs_query_sparse_next_component(cr, id); } } else if (cr && (select || (!not && !(cr->flags & EcsIdExclusive)))) { cr = flecs_query_sparse_next_component(cr, id); } op_ctx->cr = cr; if (!cr) { return !all && not; } } if (!select && !all && op_ctx->cr && (op_ctx->cr->flags & EcsIdExclusive)) { return flecs_query_sparse_with_exclusive(op, redo, ctx, not, id); } while (op_ctx->cr) { ecs_id_t actual_id = op_ctx->cr->id; if (!redo && !flecs_query_sparse_init_sparse(op_ctx, op_ctx->cr)) { return !all && not; } bool result = select ? flecs_query_sparse_select_id(op, true, ctx, table_mask, actual_id) : flecs_query_sparse_with_id(op, redo, ctx, not, op_ctx->cr, NULL); if (result) { if (!select) { ctx->it->ids[op->field_index] = actual_id; flecs_query_set_vars(op, actual_id, ctx); } if (!all && (op->match_flags & EcsTermMatchAny)) { ctx->it->ids[op->field_index] = id; } return true; } op_ctx->cr = !all && not ? NULL : flecs_query_sparse_next_component(op_ctx->cr, id); redo = false; } return false; } 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_query_sparse_ctx_t *op_ctx = flecs_op_ctx(ctx, sparse); ecs_id_t id = redo ? op_ctx->id : (op_ctx->id = flecs_query_op_get_id(op, ctx)); if (ecs_id_is_wildcard(id)) { return flecs_query_sparse_wildcard( op, redo, ctx, id, true, false, table_mask); } return flecs_query_sparse_select_id(op, redo, ctx, table_mask, id); } bool flecs_query_sparse_with( 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); ecs_id_t id = redo ? op_ctx->id : (op_ctx->id = flecs_query_op_get_id(op, ctx)); if (ecs_id_is_wildcard(id)) { return flecs_query_sparse_wildcard(op, redo, ctx, id, false, not, 0); } return flecs_query_sparse_with_id(op, redo, ctx, not, redo ? NULL : flecs_components_get(ctx->world, 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)); } } bool flecs_query_trivial_sparse_search( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_iter_t *it = ctx->it; const ecs_query_t *q = &ctx->query->pub; ecs_query_sparse_trivial_ctx_t *op_ctx = &ctx->op_ctx[0].is.sparse_trivial; int8_t i, field_count = q->field_count; if (!redo) { op_ctx->entities = flecs_iter_calloc_n(it, ecs_entity_t, FLECS_QUERY_SPARSE_BATCH_SIZE); if (field_count == 1) { ecs_component_record_t *cr = flecs_components_get( ctx->world, q->ids[0]); if (!cr || !cr->sparse) { return false; } op_ctx->lead_sparse = cr->sparse; } else { op_ctx->sparse = flecs_iter_calloc_n( it, ecs_sparse_t*, field_count); int8_t lead = 0; for (i = 0; i < field_count; i ++) { ecs_component_record_t *cr = flecs_components_get( ctx->world, q->ids[i]); if (!cr || !cr->sparse) { return false; } op_ctx->sparse[i] = cr->sparse; if (op_ctx->sparse[i]->count < op_ctx->sparse[lead]->count) { lead = i; } } op_ctx->lead_sparse = op_ctx->sparse[lead]; op_ctx->sparse[lead] = op_ctx->sparse[0]; op_ctx->sparse[0] = op_ctx->lead_sparse; } op_ctx->cur = 0; } ecs_sparse_t *lead_sparse = op_ctx->lead_sparse; const uint64_t *ids = flecs_sparse_ids(lead_sparse); int32_t cur = op_ctx->cur, count = flecs_sparse_count(lead_sparse); ecs_entity_t *entities = op_ctx->entities; int32_t n = 0; if (field_count == 1) { ecs_table_t *prev_table = NULL; bool filtered = false; for (; cur < count && n < FLECS_QUERY_SPARSE_BATCH_SIZE; cur ++) { ecs_entity_t e = ids[cur]; ecs_record_t *r = flecs_entities_get(ctx->world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table != prev_table) { prev_table = table; filtered = table->flags & (EcsTableNotQueryable| EcsTableIsPrefab|EcsTableIsDisabled); } if (!filtered) { entities[n ++] = e; } } goto yield; } for (; cur < count && n < FLECS_QUERY_SPARSE_BATCH_SIZE; cur ++) { ecs_entity_t e = ids[cur]; for (i = 1; i < field_count; i ++) { if (!flecs_sparse_has(op_ctx->sparse[i], e)) { goto next; } } { ecs_record_t *r = flecs_entities_get(ctx->world, e); 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 & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { goto next; } } entities[n ++] = e; next: continue; } yield: op_ctx->cur = cur; if (!n) { return false; } it->table = NULL; it->offset = 0; it->count = n; it->entities = entities; return true; } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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->stage, 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; } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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]; ctx->it->flags |= EcsIterOpCtxFini; 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); } } } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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(op->src.var, 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 (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { 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 (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { 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 (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { 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 (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { 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(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), 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; } } static void flecs_query_tree_up_not_yield( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_query_op_get_id(op, ctx); if (id) { it->ids[op->field_index] = id; } it->sources[op->field_index] = 0; flecs_query_it_set_tr(it, op->field_index, NULL); flecs_reset_source_set_flag(it, op->field_index); } static bool flecs_query_tree_up_rows( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_table_range_t range, bool not_oper, bool yield_not) { ecs_query_tree_up_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_up); ecs_query_up_ctx_t *up_ctx = &op_ctx->is.up_; ecs_iter_t *it = ctx->it; uint64_t field_bit = 1llu << op->field_index; if (!redo) { if (!range.count) { range.count = ecs_table_count(range.table); } op_ctx->range = range; op_ctx->cur = flecs_query_up_init(op, ctx, up_ctx); if (!op_ctx->cur) { if (not_oper) { if (yield_not) { flecs_query_tree_up_not_yield(op, ctx); } } else { it->set_fields &= (ecs_termset_t)~field_bit; } return true; } flecs_query_up_parent_begin(op, ctx, up_ctx, op_ctx->range); } else { if (!op_ctx->cur) { return false; } } ecs_trav_up_t up; bool found; while (flecs_query_up_parent_next(op, ctx, up_ctx, &up, &found)) { if (not_oper) { if (found) { continue; } if (yield_not) { flecs_query_tree_up_not_yield(op, ctx); } return true; } if (found) { flecs_query_up_set_fields(op, ctx, up_ctx, &up); it->set_fields |= (ecs_termset_t)field_bit; } else { it->set_fields &= (ecs_termset_t)~field_bit; } return true; } flecs_query_src_set_range(op, &op_ctx->range, ctx); return false; } 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_up_ctx_t *op_ctx = flecs_op_ctx(ctx, tree_up); /* 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(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), 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]; } const ecs_term_t *term = &ctx->query->pub.terms[op->term_index]; /* Passthrough tables that own the component */ if (term->oper != EcsNot && 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); bool result; if (term->oper == EcsNot) { result = flecs_query_tree_up_rows(op, redo, ctx, range, true, false); } else if (term->oper == EcsOptional) { result = flecs_query_tree_up_rows(op, redo, ctx, range, false, false); } else if (self) { result = flecs_query_self_up_with(op, redo, ctx); } else { result = flecs_query_up_with(op, redo, ctx); } if (!result) { ctx->it->sources[op->field_index] = op_ctx->tgt; } return result; } bool flecs_query_tree_up_not( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool self) { ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!range.table) { return false; } if (!(range.table->flags & EcsTableHasParent)) { if (redo) { return false; } bool result; if (self) { result = flecs_query_self_up_with(op, false, ctx); } else { result = flecs_query_up_with(op, false, ctx); } if (result) { return false; } flecs_query_tree_up_not_yield(op, ctx); return true; } return flecs_query_tree_up_rows(op, redo, ctx, range, true, true); } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS /* 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(op_ctx->with) == EcsChildOf) { if (op_ctx->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 == FlecsQueryUpSelectDefault) { result = flecs_query_select_w_id(op, redo, ctx, op_ctx->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]; impl->start_down_walk = true; 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, op_ctx->trav, entity, op_ctx->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); it->flags |= EcsIterOpCtxFini; } ecs_iter_t *it = ctx->it; bool redo_select = redo; const ecs_query_t *q = &ctx->query->pub; bool self = trav_kind == FlecsQueryUpSelectSelfUp; op_ctx->trav = q->terms[op->term_index].trav; /* Reuse component record from previous iteration if possible */ if (!op_ctx->cr_trav) { op_ctx->cr_trav = flecs_components_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } /* If component record is not found, or if it doesn't have any tables, revert to * iterating owned components (no traversal) */ if (!op_ctx->cr_trav || !flecs_table_cache_count(&op_ctx->cr_trav->cache)) { if (!self) { /* If operation does not match owned components, return false */ return false; } 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 */ op_ctx->with = flecs_query_op_get_id(op, ctx); /* Get component record for component to match */ op_ctx->cr_with = flecs_components_get(ctx->world, op_ctx->with); if (!op_ctx->cr_with) { /* If component record does not exist, there can't be any results */ return false; } impl->down = NULL; impl->cache_elem = 0; impl->last_down_table = NULL; impl->start_down_walk = false; } /* 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 ++; } if (impl->start_down_walk) { impl->start_down_walk = false; if (table == impl->last_down_table) { impl->table = NULL; continue; } impl->last_down_table = table; } /* 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; } void flecs_query_up_set_fields( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, const ecs_query_up_ctx_t *op_ctx, const ecs_trav_up_t *up) { ecs_iter_t *it = ctx->it; it->sources[op->field_index] = 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(op_ctx->trav, EcsWildcard); } flecs_query_set_vars(op, up->id, ctx); flecs_set_source_set_flag(it, op->field_index); } static bool flecs_query_up_resolve( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx, ecs_table_t *table, int32_t row, ecs_trav_up_t *out) { (void)op; int result = flecs_query_up_reachable(ctx->world, table, row, op_ctx->with, op_ctx->trav, op_ctx->cr_with, op_ctx->match_inherited, out); if (result != -1) { return result == 1; } ecs_query_up_impl_t *impl = op_ctx->impl; if (!impl) { ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); impl = op_ctx->impl = flecs_calloc_t(a, ecs_query_up_impl_t); ctx->it->flags |= EcsIterOpCtxFini; } ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &impl->cache, table, row, op_ctx->with, op_ctx->trav, op_ctx->cr_with, op_ctx->cr_trav); if (!up) { return false; } out->src = flecs_entities_get_alive(ctx->world, up->src); out->tr = up->tr; out->id = up->id; return true; } bool flecs_query_up_init( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx) { const ecs_query_t *q = &ctx->query->pub; const ecs_term_t *term = &q->terms[op->term_index]; op_ctx->trav = term->trav; if (!op_ctx->cr_trav) { if (term->trav == EcsChildOf) { op_ctx->cr_trav = ctx->world->cr_childof_wildcard; } else { op_ctx->cr_trav = flecs_components_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } } if (!op_ctx->cr_trav || !flecs_table_cache_count(&op_ctx->cr_trav->cache)) { return false; } ecs_id_t with = op_ctx->with = flecs_query_op_get_id(op, ctx); ecs_component_record_t *cr_with = NULL; int8_t field_index = op->field_index; if (field_index != -1) { ecs_component_record_t **cr_cache = ctx->query->cr_cache; cr_with = cr_cache[field_index]; if (!cr_with || cr_with->id != with) { cr_with = cr_cache[field_index] = flecs_components_get(ctx->world, with); } } else { cr_with = flecs_components_get(ctx->world, with); } op_ctx->cr_with = cr_with; if (!cr_with) { return false; } op_ctx->match_inherited = (term->flags_ & EcsTermIdInherited) != 0 || ecs_id_is_wildcard(op_ctx->with); return true; } void flecs_query_up_parent_begin( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx, ecs_table_range_t range) { (void)op; (void)ctx; if (!range.count) { range.count = ecs_table_count(range.table); } op_ctx->range = range; op_ctx->parents = flecs_query_tree_get_parents(range); op_ctx->cur = 0; op_ctx->cur_count = 0; } bool flecs_query_up_parent_next( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_ctx_t *op_ctx, ecs_trav_up_t *up, bool *found) { int32_t cur = op_ctx->cur + op_ctx->cur_count; int32_t count = op_ctx->range.count; if (cur >= count) { op_ctx->cur = cur; op_ctx->cur_count = 0; return false; } const EcsParent *parents = op_ctx->parents; ecs_entity_t parent = parents[cur].value; int32_t run = 1; if (op->kind != EcsQuerySparseUp && op->kind != EcsQuerySparseSelfUp) { while ((cur + run) < count && parents[cur + run].value == parent) { run ++; } } op_ctx->cur = cur; op_ctx->cur_count = run; ecs_table_range_t range = { .table = op_ctx->range.table, .offset = op_ctx->range.offset + cur, .count = run }; flecs_query_src_set_range(op, &range, ctx); *found = flecs_query_up_resolve(op, ctx, op_ctx, range.table, range.offset, up); 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_iter_t *it = ctx->it; ecs_trav_up_t up; bool found; if (!redo) { /* 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; } const ecs_term_t *term = &q->terms[op->term_index]; bool parent_table = term->trav == EcsChildOf && (range.table->flags & EcsTableHasParent) != 0; if (parent_table && (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. */ if (term->oper == EcsNot) { return false; } /* 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. */ op_ctx->trav = term->trav; op_ctx->cur = -1; it->sources[op->field_index] = EcsWildcard; ECS_CONST_CAST(int16_t*, it->columns)[op->field_index] = -1; return true; } if (!flecs_query_up_init(op, ctx, op_ctx)) { 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 (parent_table) { if (!op_ctx->range.count) { return false; } flecs_query_up_parent_begin(op, ctx, op_ctx, op_ctx->range); goto next_row; } if (!flecs_query_up_resolve(op, ctx, op_ctx, op_ctx->range.table, -1, &up)) { return false; } flecs_query_up_set_fields(op, ctx, op_ctx, &up); return true; } 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; } next_row: while (flecs_query_up_parent_next(op, ctx, op_ctx, &up, &found)) { if (found) { flecs_query_up_set_fields(op, ctx, op_ctx, &up); return true; } } return false; } /* 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_iter_t *it = ctx->it; if (!redo) { flecs_reset_source_set_flag(ctx->it, op->field_index); op_ctx->cur = -1; op_ctx->trav = 0; op_ctx->with = flecs_query_op_get_id(op, ctx); } /* First check if table has the component */ if (op_ctx->trav == 0) { bool result; ecs_id_t with = op_ctx->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); } } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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_w_var_refs( 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 (op->flags & EcsQueryIsValuePair) { return ecs_value_pair(first, second); } else 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 { /* Only called for terms without component inheritance, so derived ids * don't have to be matched. */ ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); column = flecs_table_offset_search(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; } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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, EcsTableNotEmpty)) { const ecs_table_cache_elem_t *celem; while ((celem = flecs_table_cache_next(&it))) { const ecs_table_record_t *tr = celem->tr; ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); (void)tr; ecs_table_t *table = celem->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->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->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; } } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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, EcsTableNotEmpty)) { const ecs_table_cache_elem_t *elem; while ((elem = flecs_table_cache_next(&it))) { ecs_table_t *table = elem->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); ecs_trav_down_elem_t *prev = NULL; 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; } } int32_t row = ECS_RECORD_TO_ROW(r->row); if (prev && prev->range.table == r->table && prev->leaf == leaf && (prev->range.offset + prev->range.count) == row) { prev->range.count ++; continue; } /* 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 = row; elem->range.count = 1; elem->leaf = leaf; prev = elem; } } 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_iter(&cr_trav->cache, &it, EcsTableEmpty|EcsTableNotEmpty); } else { result = flecs_table_cache_iter(&cr_trav->cache, &it, EcsTableNotEmpty); } if (result) { const ecs_table_cache_elem_t *celem; while ((celem = flecs_table_cache_next(&it))) { ecs_table_record_t *tr = celem->tr; ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = celem->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 = celem->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->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); } #endif // FLECS_QUERY_PLANS #ifdef FLECS_QUERY_PLANS 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; } int flecs_query_up_reachable( ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t with, ecs_entity_t trav, ecs_component_record_t *cr_with, bool match_inherited, ecs_trav_up_t *out) { if (trav != EcsChildOf) { return -1; } if (cr_with->flags & EcsIdDontFragment) { return -1; } if (ECS_IS_PAIR(with) && ECS_PAIR_FIRST(with) == EcsChildOf) { return -1; } if (match_inherited) { return -1; } ecs_entity_t parent; ecs_component_record_t *cr_rel = NULL; ecs_record_t *r; if (table->flags & EcsTableHasParent) { ecs_assert(row >= 0, ECS_INTERNAL_ERROR, NULL); const EcsParent *p = flecs_query_tree_get_parents((ecs_table_range_t){ .table = table, .offset = row, .count = 1 }); parent = p->value; r = flecs_entities_get_any(world, parent); } else { int16_t index = table->childof_index; if (index == -1) { goto not_found; } cr_rel = table->_->records[index].hdr.cr; ecs_entity_index_t *entity_index = ecs_eis(world); r = flecs_entity_index_get_any(entity_index, ECS_PAIR_SECOND(cr_rel->id)); ecs_assert(r->dense < entity_index->alive_count, ECS_INTERNAL_ERROR, NULL); parent = ecs_vec_get_t(&entity_index->dense, uint64_t, r->dense)[0]; } ecs_table_t *parent_table = r ? r->table : NULL; if (parent_table) { const ecs_table_record_t *tr = NULL; if (with < FLECS_HI_COMPONENT_ID) { int16_t res = parent_table->component_map[with]; if (res) { int32_t type_index = res > 0 ? parent_table->column_map[parent_table->type.count + (res - 1)] : (-res - 1); tr = &parent_table->_->records[type_index]; } } else { tr = flecs_component_get_table(cr_with, parent_table); } if (tr) { out->src = parent; out->tr = ECS_CONST_CAST(ecs_table_record_t*, tr); out->id = with; return 1; } } if (!cr_rel) { cr_rel = flecs_components_get(world, ecs_childof(parent)); if (!cr_rel) { goto not_found; } } const ecs_vec_t *ids = flecs_reachable_cache_get(world, cr_rel); if (!ids) { return -1; } const ecs_reachable_elem_t *elems = ecs_vec_first(ids); int32_t i, count = ecs_vec_count(ids); for (i = 0; i < count; i ++) { if (elems[i].id == with) { goto found; } } not_found: out->src = 0; out->tr = NULL; out->id = 0; return 0; found: out->src = elems[i].src; out->tr = ECS_CONST_CAST(ecs_table_record_t*, elems[i].tr); out->id = elems[i].id; return 1; } 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->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, remaining = tr->count; for (; remaining; i ++) { i = flecs_table_offset_search_w_inherited( world, table, i, cr_trav->id, NULL); if (i == -1) { break; } remaining --; 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); } #endif // FLECS_QUERY_PLANS void flecs_query_trivial_set_ids( ecs_query_run_ctx_t *ctx) { const ecs_query_t *q = &ctx->query->pub; ecs_iter_t *it = ctx->it; 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; } } 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->terms[t].id); if (!cr) { return false; } if (query->flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_queryable_iter(&cr->cache, &op_ctx->it, EcsTableEmpty|EcsTableNotEmpty)) { return false; } } else { if (!flecs_table_cache_queryable_iter(&cr->cache, &op_ctx->it, EcsTableNotEmpty)) { 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; for (t = 0; t < query->term_count; t++) { if (term_set && !(term_set & (1llu << t))) { continue; } cr = flecs_query_impl(query)->cr_cache[t] = flecs_components_get( ctx->world, query->terms[t].id); if (!cr) { return false; } } } 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; 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; ecs_component_record_t **cr_cache = query->cr_cache; next: { const ecs_table_cache_elem_t *elem = flecs_table_cache_next( &op_ctx->it); if (!elem) { return false; } ecs_table_t *table = elem->table; 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 = cr_cache[t]; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_cache_elem_t *elem_with = flecs_table_cache_get_elem( &cr->cache, table); if (!elem_with) { goto next; } it->trs[t] = elem_with->tr; columns[t] = elem_with->column; } it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); it->trs[0] = elem->tr; columns[0] = elem->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 iterator constraint 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; } } #ifdef FLECS_QUERY_PLANS void flecs_query_trivial_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; } } 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; 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; const ecs_term_t *terms = q->terms; do { const ecs_table_cache_elem_t *elem = flecs_table_cache_next( &op_ctx->it); if (!elem) { return false; } ecs_table_t *table = elem->table; 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; } ecs_component_record_t *cr = query->cr_cache[t]; ecs_assert(cr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_cache_elem_t *elem_with = flecs_table_cache_get_elem( &cr->cache, table); if (!elem_with) { break; } const ecs_term_t *term = &terms[t]; it->trs[term->field_index] = elem_with->tr; columns[term->field_index] = elem_with->column; } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; const ecs_term_t *term = &terms[op_ctx->start_from]; it->trs[term->field_index] = elem->tr; columns[term->field_index] = elem->column; break; } } while (true); return true; } #endif // FLECS_QUERY_PLANS #ifdef FLECS_META static void flecs_set_array(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsArray *array = ecs_field(it, EcsArray, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; int32_t elem_count = array[i].count; if (!elem_type) { ecs_err("array '%s' has no element type", ecs_get_name(world, e)); continue; } if (!elem_count) { ecs_err("array '%s' has size 0", ecs_get_name(world, e)); continue; } if (!ecs_is_alive(world, elem_type)) { ecs_err("array '%s' has invalid element type", ecs_get_name(world, e)); continue; } flecs_meta_detect_cycles(world, elem_type, e); const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); if (!elem_ptr) { ecs_err("array '%s' has invalid element type", ecs_get_name(world, e)); continue; } if (!ecs_get_type_info(world, elem_type)) { ecs_err("array '%s' element type has no type info", ecs_get_name(world, e)); continue; } if (flecs_init_type(world, e, EcsArrayType, elem_ptr->size * elem_count, elem_ptr->alignment)) { continue; } } } static void flecs_set_vector(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsVector *array = ecs_field(it, EcsVector, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; if (!elem_type) { ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); continue; } if (!ecs_is_alive(world, elem_type)) { ecs_err("vector '%s' has invalid element type", ecs_get_name(world, e)); continue; } if (!ecs_get(world, elem_type, EcsComponent)) { ecs_err("vector '%s' has invalid element type", ecs_get_name(world, e)); continue; } if (!ecs_get_type_info(world, elem_type)) { ecs_err("vector '%s' element type has no type info", ecs_get_name(world, e)); continue; } if (flecs_init_type_t(world, e, EcsVectorType, ecs_vec_t)) { continue; } } } ecs_entity_t ecs_array_init( ecs_world_t *world, const ecs_array_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsArray, { .type = desc->type, .count = desc->count }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_vector_init( ecs_world_t *world, const ecs_vector_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsVector, { .type = desc->type }); flecs_resume_readonly(world, &rs); return t; } void flecs_meta_array_init( ecs_world_t *world) { ecs_entity_t array = ecs_entity(world, { .id = ecs_id(EcsArray), .name = "array", .symbol = "EcsArray" }); ecs_add_pair(world, array, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = array, .type.size = sizeof(EcsArray), .type.alignment = ECS_ALIGNOF(EcsArray) }); ecs_entity_t vector = ecs_entity(world, { .id = ecs_id(EcsVector), .name = "vector", .symbol = "EcsVector" }); ecs_add_pair(world, vector, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = vector, .type.size = sizeof(EcsVector), .type.alignment = ECS_ALIGNOF(EcsVector) }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsArray) }, .events = {EcsOnSet}, .callback = flecs_set_array, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsVector) }, .events = {EcsOnSet}, .callback = flecs_set_vector, .global_observer = true }); } #endif #ifdef FLECS_META /* EcsConstants lifecycle */ static void flecs_constants_dtor( ecs_map_t *constants) { ecs_map_iter_t it = ecs_map_iter(constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_os_free(c); } ecs_map_fini(constants); } static void flecs_ordered_constants_dtor( ecs_vec_t *ordered_constants) { /* Shallow fini is ok since map deallocs name c-string member */ ecs_vec_fini_t(NULL, ordered_constants, ecs_enum_constant_t); } static ECS_CTOR(EcsConstants, ptr, { ptr->constants = ecs_os_malloc_t(ecs_map_t); ecs_map_init(ptr->constants, NULL); ecs_vec_init_t(NULL, &ptr->ordered_constants, ecs_enum_constant_t, 0); }) static ECS_MOVE(EcsConstants, dst, src, { if (dst->constants) { flecs_constants_dtor(dst->constants); ecs_os_free(dst->constants); } dst->constants = src->constants; src->constants = NULL; flecs_ordered_constants_dtor(&dst->ordered_constants); dst->ordered_constants = src->ordered_constants; ecs_os_zeromem(&src->ordered_constants); }) static ECS_DTOR(EcsConstants, ptr, { if (ptr->constants) { flecs_constants_dtor(ptr->constants); ecs_os_free(ptr->constants); } flecs_ordered_constants_dtor(&ptr->ordered_constants); }) static void flecs_constants_remove( EcsConstants *ptr, ecs_entity_t entity) { ecs_enum_constant_t *constants = ecs_vec_first(&ptr->ordered_constants); int32_t i, count = ecs_vec_count(&ptr->ordered_constants); for (i = 0; i < count; i ++) { ecs_enum_constant_t *c = &constants[i]; if (c->constant == entity) { ecs_map_key_t key = (uint64_t)c->value | c->value_unsigned; ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_map_remove_free(ptr->constants, key); ecs_vec_remove_ordered_t( &ptr->ordered_constants, ecs_enum_constant_t, i); break; } } } static int flecs_constants_insert( ecs_world_t *world, EcsConstants *ptr, ecs_entity_t entity, int64_t value, uint64_t value_unsigned, bool abort_on_conflict) { ecs_map_key_t key = (uint64_t)value | value_unsigned; ecs_enum_constant_t *c = ecs_map_get_ptr(ptr->constants, key); if (c) { char *path = ecs_get_path(world, entity); if (abort_on_conflict) { ecs_abort(ECS_INTERNAL_ERROR, "conflicting constant value %u for '%s' (other is '%s')", value_unsigned, path, c->name); } else { ecs_err("conflicting constant value for '%s' (other is '%s')", path, c->name); } ecs_os_free(path); return -1; } if (!ptr->constants) { ptr->constants = ecs_os_malloc_t(ecs_map_t); ecs_map_init(ptr->constants, NULL); } ecs_map_init_if(ptr->constants, &world->allocator); c = ecs_map_insert_alloc_t(ptr->constants, ecs_enum_constant_t, key); *c = (ecs_enum_constant_t){ .name = ecs_os_strdup(ecs_get_name(world, entity)), .value = value, .value_unsigned = value_unsigned, .constant = entity }; ecs_vec_init_if_t(&ptr->ordered_constants, ecs_enum_constant_t); *ecs_vec_append_t(NULL, &ptr->ordered_constants, ecs_enum_constant_t) = *c; return 0; } static int flecs_add_constant_to_enum( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsEnum *eptr = ecs_ensure(world, type, EcsEnum); EcsConstants *ptr = ecs_ensure(world, type, EcsConstants); ecs_entity_t ut = eptr->underlying_type; /* It's possible that a constant is added to an entity that didn't have an * Enum component yet. In that case derive the underlying type from the * first constant. */ if (!ut) { if (ecs_id_is_pair(constant_id)) { ut = eptr->underlying_type = ecs_pair_second(world, constant_id); } else { /* Default to i32 */ ut = ecs_id(ecs_i32_t); } } ecs_assert(ut != 0, ECS_INVALID_OPERATION, "missing underlying type for enum"); const EcsPrimitive *p = ecs_get(world, ut, EcsPrimitive); if (!p) { char *path = ecs_get_path(world, ut); ecs_err("underlying type '%s' must be a primitive type", path); ecs_os_free(path); return -1; } bool ut_is_unsigned = false; ecs_primitive_kind_t kind = p->kind; if (kind == EcsU8 || kind == EcsU16 || kind == EcsU32 || kind == EcsU64) { ut_is_unsigned = true; } flecs_constants_remove(ptr, e); /* Check if constant sets explicit value */ int64_t value = 0; uint64_t value_unsigned = 0; bool value_set = false; if (ecs_id_is_pair(constant_id)) { ecs_value_t v = { .type = ut }; v.ptr = ecs_get_mut_id(world, e, ecs_pair(EcsConstant, ut)); if (!v.ptr) { char *has_pair = ecs_id_str(world, constant_id); char *expect_pair = ecs_id_str(world, ecs_pair(EcsConstant, ut)); char *path = ecs_get_path(world, e); ecs_err( "enum constant '%s' has incorrect value pair (expected %s, got %s)", path, expect_pair, has_pair); ecs_os_free(path); ecs_os_free(has_pair); ecs_os_free(expect_pair); return -1; } ecs_meta_cursor_t c; if (ut_is_unsigned) { c = ecs_meta_cursor(world, ecs_id(ecs_u64_t), &value_unsigned); } else { c = ecs_meta_cursor(world, ecs_id(ecs_i64_t), &value); } if (ecs_meta_set_value(&c, &v)) { char *path = ecs_get_path(world, e); ecs_err("failed to get constant value for '%s'", path); ecs_os_free(path); return -1; } value_set = true; } if (!value_set) { ecs_map_iter_t it = ecs_map_iter(ptr->constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); if (ut_is_unsigned) { if (c->value_unsigned >= value_unsigned) { value_unsigned = c->value_unsigned + 1; } } else if (c->value >= value) { value = c->value + 1; } } } if (flecs_constants_insert(world, ptr, e, value, value_unsigned, ut_is_unsigned)) { return -1; } if (!value_set) { const ecs_type_info_t *ti = ecs_get_type_info(world, ut); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, "underlying type is not a type"); void *cptr = ecs_ensure_id( ecs_get_stage(world, 0), e, ecs_pair(EcsConstant, ut), flecs_ito(size_t, ti->size)); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_cursor_t cur = ecs_meta_cursor(world, ut, cptr); int ret = ut_is_unsigned ? ecs_meta_set_uint(&cur, value_unsigned) : ecs_meta_set_int(&cur, value); if (ret) { char *path = ecs_get_path(world, e); ecs_err("failed to assign value to constant '%s'", path); ecs_os_free(path); return -1; } } ecs_modified(world, type, EcsConstants); ecs_modified(world, type, EcsEnum); return 0; } static int flecs_add_constant_to_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { ecs_add(world, type, EcsBitmask); EcsConstants *ptr = ecs_ensure(world, type, EcsConstants); flecs_constants_remove(ptr, e); /* Check if constant sets explicit value */ uint32_t value = 1; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { char *path = ecs_get_path(world, e); ecs_err("expected u32 type for bitmask constant '%s'", path); ecs_os_free(path); return -1; } const uint32_t *value_ptr = ecs_get_pair_second( ecs_get_stage(world, 0), e, EcsConstant, ecs_u32_t); ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); value = *value_ptr; } else { value = 1u << (ecs_u32_t)ecs_map_count(ptr->constants); } if (flecs_constants_insert(world, ptr, e, value, 0, false)) { return -1; } ecs_u32_t *cptr = ecs_ensure_pair_second( ecs_get_stage(world, 0), e, EcsConstant, ecs_u32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; cptr = ecs_ensure_id(ecs_get_stage(world, 0), e, type, sizeof(uint32_t)); cptr[0] = value; return 0; } static void flecs_add_enum(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsEnum *data = ecs_field(it, EcsEnum, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t underlying_type = data[i].underlying_type; if (!underlying_type) { underlying_type = data[i].underlying_type = ecs_id(ecs_i32_t); } const EcsComponent *uc = ecs_get(world, underlying_type, EcsComponent); if (!uc) { char *str = ecs_get_path(world, underlying_type); ecs_err("underlying_type entity for enum '%s' is not a type", str); ecs_os_free(str); continue; } if (flecs_init_type(world, e, EcsEnumType, uc->size, uc->alignment)) { continue; } ecs_add_id(world, e, EcsExclusive); #ifdef FLECS_CONSTRAINT_TRAITS ecs_add_id(world, e, EcsOneOf); #endif ecs_add_id(world, e, EcsPairIsTag); } } static void flecs_add_bitmask(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (flecs_init_type_t(it->stage, e, EcsBitmaskType, ecs_u32_t)) { continue; } } } static void flecs_add_constant(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); continue; } if (ecs_has(world, parent, EcsEnum)) { flecs_add_constant_to_enum(world, parent, e, it->event_id); } else if (ecs_has(world, parent, EcsBitmask)) { flecs_add_constant_to_bitmask(world, parent, e, it->event_id); } } } ecs_entity_t ecs_enum_init( ecs_world_t *world, const ecs_enum_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t underlying = desc->underlying_type; if (!underlying) { underlying = ecs_id(ecs_i32_t); } ecs_assert(ecs_is_valid(world, underlying), ECS_INVALID_PARAMETER, "invalid underlying type for enum"); const EcsPrimitive *p = ecs_get(world, underlying, EcsPrimitive); if (!p) { char *path = ecs_get_path(world, underlying); ecs_err("underlying type '%s' must be a primitive type", path); ecs_os_free(path); return 0; } bool ut_is_unsigned = false; ecs_primitive_kind_t kind = p->kind; if (kind == EcsU8 || kind == EcsU16 || kind == EcsU32 || kind == EcsU64) { ut_is_unsigned = true; } EcsEnum *enum_data = ecs_ensure(world, t, EcsEnum); enum_data->underlying_type = underlying; ecs_modified(world, t, EcsEnum); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_enum_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value && !m_desc->value_unsigned) { ecs_add_id(world, c, EcsConstant); } else { const ecs_type_info_t *ti = ecs_get_type_info(world, underlying); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "underlying type is not a type"); void *ptr = ecs_ensure_id(world, c, ecs_pair(EcsConstant, underlying), flecs_ito(size_t, ti->size)); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_cursor_t cur = ecs_meta_cursor(world, underlying, ptr); int ret; if (m_desc->value) { if (ut_is_unsigned) { char *path = ecs_get_path(world, c); ecs_err("use desc::value_unsigned for constant '%s' which " "has an unsigned underlying type", path); ecs_os_free(path); return 0; } ret = ecs_meta_set_int(&cur, m_desc->value); } else { if (!ut_is_unsigned) { char *path = ecs_get_path(world, c); ecs_err("use desc::value for constant '%s' which " "has a signed underlying type", path); ecs_os_free(path); return 0; } ret = ecs_meta_set_uint(&cur, m_desc->value_unsigned); } if (ret) { char *type_str = ecs_get_path(world, t); char *utype_str = ecs_get_path(world, underlying); ecs_err("value for constant '%s' for enum '%s' is not valid " "for underlying type '%s'", type_str, utype_str); ecs_os_free(utype_str); ecs_os_free(type_str); continue; } ecs_modified_id(world, c, ecs_pair(EcsConstant, underlying)); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_bitmask_init( ecs_world_t *world, const ecs_bitmask_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsBitmask); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_second(world, c, EcsConstant, ecs_u32_t, { flecs_uto(uint32_t, m_desc->value) }); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_constant_to_entity_id( const ecs_world_t *world, ecs_entity_t type, int64_t value) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); const EcsConstants *ptr = ecs_get(world, type, EcsConstants); if (!ptr || !ptr->constants) { return 0; } const ecs_enum_constant_t *c = ecs_map_get_deref( ptr->constants, ecs_enum_constant_t, (ecs_map_key_t)value); if (!c) { return 0; } return c->constant; error: return 0; } void flecs_meta_enum_init( ecs_world_t *world) { ecs_entity_t enum_component = ecs_entity(world, { .id = ecs_id(EcsEnum), .name = "enum", .symbol = "EcsEnum" }); ecs_add_pair(world, enum_component, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = enum_component, .type.size = sizeof(EcsEnum), .type.alignment = ECS_ALIGNOF(EcsEnum) }); ecs_entity_t bitmask = ecs_entity(world, { .id = ecs_id(EcsBitmask), .name = "bitmask", .symbol = "EcsBitmask" }); ecs_add_pair(world, bitmask, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = bitmask, .type.size = sizeof(EcsBitmask), .type.alignment = ECS_ALIGNOF(EcsBitmask) }); ecs_entity_t constants = ecs_entity(world, { .id = ecs_id(EcsConstants), .name = "constants", .symbol = "EcsConstants" }); ecs_add_pair(world, constants, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = constants, .type.size = sizeof(EcsConstants), .type.alignment = ECS_ALIGNOF(EcsConstants) }); ecs_set_hooks(world, EcsEnum, { .ctor = flecs_default_ctor, }); ecs_set_hooks(world, EcsBitmask, { .ctor = flecs_default_ctor, }); ecs_set_hooks(world, EcsConstants, { .ctor = ecs_ctor(EcsConstants), .move = ecs_move(EcsConstants), .dtor = ecs_dtor(EcsConstants), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_add_pair(world, ecs_id(EcsEnum), EcsWith, ecs_id(EcsConstants)); ecs_add_pair(world, ecs_id(EcsBitmask), EcsWith, ecs_id(EcsConstants)); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsEnum) }, .events = {EcsOnSet}, .callback = flecs_add_enum, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsBitmask) }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = EcsConstant }, .events = {EcsOnAdd}, .callback = flecs_add_constant, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard) }, .events = {EcsOnSet}, .callback = flecs_add_constant, .global_observer = true }); } #endif #ifdef FLECS_META static bool flecs_map_valid_key_type( ecs_world_t *world, ecs_entity_t key_type) { const EcsType *kt = ecs_get(world, key_type, EcsType); if (kt && (kt->kind == EcsEnumType || kt->kind == EcsBitmaskType)) { return true; } const EcsPrimitive *p = ecs_get(world, key_type, EcsPrimitive); if (!p) { return false; } switch(p->kind) { case EcsBool: case EcsChar: case EcsByte: case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsUPtr: case EcsIPtr: case EcsEntity: case EcsId: return true; case EcsF32: case EcsF64: case EcsString: return false; default: return false; } } static void flecs_set_map(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMap *map = ecs_field(it, EcsMap, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t key_type = map[i].key_type; ecs_entity_t value_type = map[i].type; if (!key_type) { ecs_err("map '%s' has no key type", ecs_get_name(world, e)); continue; } if (!value_type) { ecs_err("map '%s' has no value type", ecs_get_name(world, e)); continue; } if (!ecs_is_alive(world, key_type)) { ecs_err("map '%s' has invalid key type", ecs_get_name(world, e)); continue; } if (!flecs_map_valid_key_type(world, key_type)) { ecs_err("map '%s' key type must be a primitive (other than " "string, f32 or f64), enum or bitmask type", ecs_get_name(world, e)); continue; } if (!ecs_is_alive(world, value_type)) { ecs_err("map '%s' has invalid value type", ecs_get_name(world, e)); continue; } if (!ecs_get_type_info(world, value_type)) { ecs_err("map '%s' value type has no type info", ecs_get_name(world, e)); continue; } if (flecs_init_type_t(world, e, EcsMapType, ecs_map_t)) { continue; } } } ecs_entity_t ecs_map_type_init( ecs_world_t *world, const ecs_map_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsMap, { .key_type = desc->key_type, .type = desc->type }); flecs_resume_readonly(world, &rs); return t; } void flecs_meta_map_init( ecs_world_t *world) { ecs_entity_t map_component = ecs_entity(world, { .id = ecs_id(EcsMap), .name = "map", .symbol = "EcsMap" }); ecs_add_pair(world, map_component, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = map_component, .type.size = sizeof(EcsMap), .type.alignment = ECS_ALIGNOF(EcsMap) }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMap) }, .events = {EcsOnSet}, .callback = flecs_set_map, .global_observer = true }); } #endif #ifdef FLECS_META static void flecs_set_opaque_type(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsOpaque *serialize = ecs_field(it, EcsOpaque, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t as_type = serialize[i].as_type; if (!as_type) { ecs_err( "opaque type '%s' has no mapping type", ecs_get_name(world, e)); continue; } /* If the as_type is anonymous and has no parent, parent it under the * opaque type. That way we don't end up with a bunch of anonymous entities * in the root scope. */ if (!ecs_get_parent(world, as_type) && !ecs_get_name(world, as_type)) { ecs_add_pair(world, as_type, EcsChildOf, e); } const EcsComponent *comp = ecs_get(world, e, EcsComponent); if (!comp || !comp->size || !comp->alignment) { ecs_err( "opaque type '%s' has no size/alignment, register as component first", ecs_get_name(world, e)); continue; } if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { continue; } } } ecs_entity_t ecs_opaque_init( ecs_world_t *world, const ecs_opaque_desc_t *desc) { ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set_ptr(world, t, EcsOpaque, &desc->type); flecs_resume_readonly(world, &rs); return t; } void flecs_meta_opaque_init( ecs_world_t *world) { ecs_entity_t opaque = ecs_entity(world, { .id = ecs_id(EcsOpaque), .name = "opaque", .symbol = "EcsOpaque" }); ecs_add_pair(world, opaque, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = opaque, .type.size = sizeof(EcsOpaque), .type.alignment = ECS_ALIGNOF(EcsOpaque) }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsOpaque) }, .events = {EcsOnSet}, .callback = flecs_set_opaque_type, .global_observer = true }); } #endif #ifdef FLECS_META /* ecs_string_t lifecycle */ static ECS_COPY(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = ecs_os_strdup(*(const ecs_string_t*)src); }) static ECS_MOVE(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = *(ecs_string_t*)src; *(ecs_string_t*)src = NULL; }) static ECS_DTOR(ecs_string_t, ptr, { ecs_os_free(*(ecs_string_t*)ptr); *(ecs_string_t*)ptr = NULL; }) /* Primitive comparers */ #define FLECS_PRIMITIVE_COMPARE(T, NAME, EQUALS) \ static int flecs_compare_##NAME( \ const void *a_ptr, \ const void *b_ptr, \ const ecs_type_info_t *ti) \ { \ (void)ti; \ T a = *(const T*)a_ptr; \ T b = *(const T*)b_ptr; \ return (a > b) - (a < b); \ } \ static bool flecs_equals_##NAME( \ const void *a_ptr, \ const void *b_ptr, \ const ecs_type_info_t *ti) \ { \ return EQUALS(a_ptr, b_ptr, ti) == 0; \ } FLECS_PRIMITIVE_COMPARE(ecs_bool_t, bool, flecs_compare_bool) FLECS_PRIMITIVE_COMPARE(ecs_char_t, char, flecs_compare_char) FLECS_PRIMITIVE_COMPARE(ecs_byte_t, byte, flecs_compare_byte) FLECS_PRIMITIVE_COMPARE(ecs_u8_t, u8, flecs_compare_u8) FLECS_PRIMITIVE_COMPARE(ecs_u16_t, u16, flecs_compare_u16) FLECS_PRIMITIVE_COMPARE(ecs_u32_t, u32, flecs_compare_u32) FLECS_PRIMITIVE_COMPARE(ecs_u64_t, u64, flecs_compare_u64) FLECS_PRIMITIVE_COMPARE(ecs_uptr_t, uptr, flecs_compare_uptr) FLECS_PRIMITIVE_COMPARE(ecs_i8_t, i8, flecs_compare_i8) FLECS_PRIMITIVE_COMPARE(ecs_i16_t, i16, flecs_compare_i16) FLECS_PRIMITIVE_COMPARE(ecs_i32_t, i32, flecs_compare_i32) FLECS_PRIMITIVE_COMPARE(ecs_i64_t, i64, flecs_compare_i64) FLECS_PRIMITIVE_COMPARE(ecs_iptr_t, iptr, flecs_compare_iptr) FLECS_PRIMITIVE_COMPARE(ecs_f32_t, f32, flecs_compare_u32) FLECS_PRIMITIVE_COMPARE(ecs_f64_t, f64, flecs_compare_u64) FLECS_PRIMITIVE_COMPARE(ecs_entity_t, entity, flecs_compare_entity) FLECS_PRIMITIVE_COMPARE(ecs_id_t, id, flecs_compare_id) #undef FLECS_PRIMITIVE_COMPARE int flecs_compare_string( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { (void)ti; const ecs_string_t str_a = *((const ecs_string_t *) a_ptr); const ecs_string_t str_b = *((const ecs_string_t *) b_ptr); if(str_a == str_b) { return 0; } if(str_a == NULL) { return -1; } if(str_b == NULL) { return 1; } return ecs_os_strcmp(str_a, str_b); } bool flecs_equals_string( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { return flecs_compare_string(a_ptr, b_ptr, ti) == 0; } static void flecs_set_primitive(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsPrimitive *type = ecs_field(it, EcsPrimitive, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t builtin = flecs_meta_primitive_type(type->kind); if (builtin) { const ecs_type_info_t *ti = ecs_get_type_info(world, builtin); flecs_init_type(world, e, EcsPrimitiveType, ti->size, ti->alignment); } } } ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsPrimitive, { desc->kind }); flecs_resume_readonly(world, &rs); return t; } void flecs_meta_primitives_init( ecs_world_t *world) { ecs_entity_t primitive = ecs_entity(world, { .id = ecs_id(EcsPrimitive), .name = "primitive", .symbol = "EcsPrimitive" }); ecs_add_pair(world, primitive, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = primitive, .type.size = sizeof(EcsPrimitive), .type.alignment = ECS_ALIGNOF(EcsPrimitive) }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsPrimitive) }, .events = {EcsOnSet}, .callback = flecs_set_primitive, .global_observer = true }); /* Initialize primitive types */ #define ECS_PRIMITIVE(world, T, primitive_kind)\ ecs_component_init(world, &(ecs_component_desc_t){\ .entity = ecs_entity(world, {\ .id = ecs_id(ecs_##T##_t),\ .name = #T,\ .symbol = #T \ }),\ .type.size = ECS_SIZEOF(ecs_##T##_t),\ .type.alignment = ECS_ALIGNOF(ecs_##T##_t),\ });\ ecs_set(world, ecs_id(ecs_##T##_t), EcsPrimitive, {\ .kind = primitive_kind\ });\ ecs_set_hooks(world, ecs_##T##_t, { \ .cmp = flecs_compare_##T, \ .equals = flecs_equals_##T \ }) ECS_PRIMITIVE(world, bool, EcsBool); ECS_PRIMITIVE(world, char, EcsChar); ECS_PRIMITIVE(world, byte, EcsByte); ECS_PRIMITIVE(world, u8, EcsU8); ECS_PRIMITIVE(world, u16, EcsU16); ECS_PRIMITIVE(world, u32, EcsU32); ECS_PRIMITIVE(world, u64, EcsU64); ECS_PRIMITIVE(world, uptr, EcsUPtr); ECS_PRIMITIVE(world, i8, EcsI8); ECS_PRIMITIVE(world, i16, EcsI16); ECS_PRIMITIVE(world, i32, EcsI32); ECS_PRIMITIVE(world, i64, EcsI64); ECS_PRIMITIVE(world, iptr, EcsIPtr); ECS_PRIMITIVE(world, f32, EcsF32); ECS_PRIMITIVE(world, f64, EcsF64); ECS_PRIMITIVE(world, string, EcsString); ECS_PRIMITIVE(world, entity, EcsEntity); ECS_PRIMITIVE(world, id, EcsId); #undef ECS_PRIMITIVE ecs_type_hooks_t string_hooks = *ecs_get_hooks(world, ecs_string_t); string_hooks.ctor = flecs_default_ctor; string_hooks.copy = ecs_copy(ecs_string_t); string_hooks.move = ecs_move(ecs_string_t); string_hooks.dtor = ecs_dtor(ecs_string_t); string_hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, ecs_id(ecs_string_t), &string_hooks); } #endif #ifdef FLECS_META static void flecs_struct_dtor( EcsStruct *ptr) { ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); int32_t i, count = ecs_vec_count(&ptr->members); for (i = 0; i < count; i ++) { ecs_os_free(ECS_CONST_CAST(char*, members[i].name)); } ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); } static ECS_COPY(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); int32_t m, count = ecs_vec_count(&dst->members); for (m = 0; m < count; m ++) { members[m].name = ecs_os_strdup(members[m].name); } }) static ECS_MOVE(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = src->members; src->members = (ecs_vec_t){0}; }) static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) static void flecs_set_struct_member( ecs_member_t *member, ecs_entity_t entity, ecs_member_t *m, ecs_entity_t unit) { member->member = entity; member->type = m->type; member->count = m->count; member->unit = unit; member->offset = m->offset; member->use_offset = m->use_offset; ecs_os_strset(ECS_CONST_CAST(char**, &member->name), m->name); member->range = m->range; member->error_range = m->error_range; member->warning_range = m->warning_range; } static bool flecs_struct_range_equals( const ecs_member_value_range_t *a, const ecs_member_value_range_t *b) { return ECS_EQ(a->min, b->min) && ECS_EQ(a->max, b->max); } static bool flecs_struct_member_unchanged( const ecs_member_t *existing, ecs_entity_t member_entity, const ecs_member_t *m, ecs_entity_t unit) { if (existing->member != member_entity) { return false; } if (ecs_os_strcmp(existing->name, m->name)) { return false; } if (existing->type != m->type || existing->count != m->count) { return false; } if (existing->unit != unit) { return false; } if (existing->use_offset != m->use_offset) { return false; } if ((m->offset || m->use_offset) && existing->offset != m->offset) { return false; } if (!flecs_struct_range_equals(&existing->range, &m->range) || !flecs_struct_range_equals(&existing->error_range, &m->error_range) || !flecs_struct_range_equals(&existing->warning_range, &m->warning_range)) { return false; } return true; } static ecs_entity_t flecs_struct_base( ecs_world_t *world, ecs_entity_t struct_type, const EcsStruct **base_struct) { int32_t i = 0; ecs_entity_t base; while ((base = ecs_get_target(world, struct_type, EcsIsA, i ++))) { const EcsStruct *bs = ecs_get(world, base, EcsStruct); if (bs) { if (base_struct) { *base_struct = bs; } return base; } } return 0; } bool flecs_struct_is_derived_from( const ecs_world_t *world, ecs_entity_t derived, ecs_entity_t base) { if (!ecs_has(world, base, EcsStruct)) { return false; } int32_t depth = 0; ecs_entity_t cur = derived; while (cur && cur != base && depth < 64) { if (!ecs_has(world, cur, EcsStruct)) { return false; } cur = flecs_struct_base(ECS_CONST_CAST(ecs_world_t*, world), cur, NULL); depth ++; } return cur == base && derived != base; } int32_t flecs_struct_inherited_count( ecs_world_t *world, ecs_entity_t struct_type, const EcsStruct *s) { const EcsStruct *base_st = NULL; if (!flecs_struct_base(world, struct_type, &base_st)) { return 0; } int32_t count = ecs_vec_count(&base_st->members); int32_t own_count = ecs_vec_count(&s->members); return count < own_count ? count : own_count; } static ecs_size_t flecs_struct_base_size( ecs_world_t *world, ecs_entity_t base, ecs_size_t size) { const EcsComponent *base_comp = ecs_get(world, base, EcsComponent); if (base_comp && base_comp->size > size) { return base_comp->size; } return size; } #if !defined(FLECS_NDEBUG) || defined(FLECS_KEEP_ASSERT) static bool flecs_struct_has_derived( ecs_world_t *world, ecs_entity_t struct_type) { ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsIsA, struct_type)); while (ecs_each_next(&it)) { int32_t i; for (i = 0; i < it.count; i ++) { if (ecs_has(world, it.entities[i], EcsStruct)) { ecs_iter_fini(&it); return true; } } } return false; } #endif static int32_t flecs_struct_inherit( ecs_world_t *world, ecs_entity_t struct_type, EcsStruct *s) { if (ecs_vec_count(&s->members)) { return 0; } const EcsStruct *base_st = NULL; if (!flecs_struct_base(world, struct_type, &base_st)) { return 0; } const ecs_member_t *base_members = ecs_vec_first_t( &base_st->members, ecs_member_t); int32_t i, count = ecs_vec_count(&base_st->members); if (!count) { return 0; } ecs_vec_init_if_t(&s->members, ecs_member_t); for (i = 0; i < count; i ++) { ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); *elem = base_members[i]; elem->name = ecs_os_strdup(base_members[i].name); } return count; } static int flecs_struct_layout( ecs_world_t *world, ecs_entity_t struct_type, EcsStruct *s, int32_t explicit_member, bool member_unchanged, bool deferred); static int flecs_add_member_to_struct( ecs_world_t *world, ecs_entity_t struct_type, ecs_entity_t member_entity, const ecs_member_t *m_ptr) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(struct_type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(m_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_member_t m = *m_ptr; const char *name = m.name; if (!name) { char *path = ecs_get_path(world, struct_type); ecs_err("member for struct '%s' does not have a name", path); ecs_os_free(path); return -1; } if (!m.type) { char *path = ecs_get_path(world, struct_type); ecs_err("member '%s.%s' does not have a type", path, name); ecs_os_free(path); return -1; } flecs_meta_detect_cycles(world, m.type, struct_type); if (ecs_get_typeid(world, m.type) == 0) { char *path = ecs_get_path(world, struct_type); char *ent_path = ecs_get_path(world, m.type); ecs_err("member '%s.%s.type' is '%s' which is not a type", path, name, ent_path); ecs_os_free(path); ecs_os_free(ent_path); return -1; } ecs_entity_t unit = m.unit; if (unit) { if (!ecs_has(world, unit, EcsUnit)) { ecs_err("entity '%s' for member '%s' is not a unit", ecs_get_name(world, unit), name); return -1; } if (ecs_has(world, m.type, EcsUnit) && m.type != unit) { ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", ecs_get_name(world, m.type), ecs_get_name(world, unit), name); return -1; } } else { if (ecs_has(world, m.type, EcsUnit)) { ecs_entity_t unit_base = ecs_get_target_for( world, m.type, EcsIsA, EcsUnit); if (unit_base) { unit = m.unit = unit_base; } else { unit = m.unit = m.type; } if (member_entity) { ecs_assert(ecs_owns(world, member_entity, EcsMember), ECS_INTERNAL_ERROR, NULL); ecs_get_mut(world, member_entity, EcsMember)->unit = m.type; } } } EcsStruct *s = ecs_ensure(world, struct_type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); flecs_struct_inherit(world, struct_type, s); /* First check if member is already added to struct */ ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t i, count = ecs_vec_count(&s->members); int32_t inherited = flecs_struct_inherited_count(world, struct_type, s); bool has_member = false; bool unchanged = false; for (i = 0; i < count; i ++) { if (i < inherited) { if (!ecs_os_strcmp(name, members[i].name)) { char *path = ecs_get_path(world, struct_type); ecs_err("member '%s.%s' is already defined by base type", path, name); ecs_os_free(path); return -1; } continue; } if (member_entity && members[i].member) { if (members[i].member == member_entity) { unchanged = flecs_struct_member_unchanged( &members[i], member_entity, &m, unit); if (!unchanged) { flecs_set_struct_member( &members[i], member_entity, &m, unit); } break; } } else { if (!ecs_os_strcmp(name, members[i].name)) { unchanged = flecs_struct_member_unchanged( &members[i], member_entity, &m, unit); if (!unchanged) { flecs_set_struct_member( &members[i], member_entity, &m, unit); } break; } } } has_member = i != count; /* If member wasn't added yet, add a new element to vector */ if (!has_member) { ecs_assert(!flecs_struct_has_derived(world, struct_type), ECS_ALREADY_IN_USE, "cannot add member '%s' to struct that is " "the base of other structs", name); ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member_entity, &m, unit); count ++; } bool explicit_offset = m.offset || m.use_offset; return flecs_struct_layout( world, struct_type, s, explicit_offset ? i : -1, unchanged, false); } static int flecs_struct_layout( ecs_world_t *world, ecs_entity_t struct_type, EcsStruct *s, int32_t explicit_member, bool member_unchanged, bool deferred) { ecs_world_t *stage = deferred ? ecs_get_stage(world, 0) : world; ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t count = ecs_vec_count(&s->members); ecs_size_t size = 0, alignment = 0; bool layout_changed = false; bool explicit_offset = explicit_member != -1; const EcsStruct *base_st = NULL; ecs_entity_t base = explicit_offset ? 0 : flecs_struct_base(world, struct_type, &base_st); int32_t inherited = base_st ? ecs_vec_count(&base_st->members) : 0; if (inherited > count) { inherited = count; } int32_t first = explicit_offset ? explicit_member : 0; int32_t end = explicit_offset ? first + 1 : count; for (int32_t i = first; i < end; i ++) { ecs_member_t *elem = &members[i]; const EcsComponent *comp = ecs_get(world, elem->type, EcsComponent); if (!comp) { ecs_err("member '%s' is not a type", flecs_errstr(ecs_get_path(world, elem->type))); return -1; } int64_t member_size = (int64_t)comp->size * (elem->count ? elem->count : 1); if (!member_size || member_size > INT32_MAX || !comp->alignment) { ecs_err("member '%s' has 0 size/alignment", flecs_errstr(ecs_get_path(world, elem->type))); return -1; } ecs_size_t member_alignment = comp->alignment; ecs_size_t offset = elem->offset; if (!explicit_offset) { if (i == inherited && inherited) { size = flecs_struct_base_size(world, base, size); } offset = i < inherited ? ecs_vec_get_t(&base_st->members, ecs_member_t, i)->offset : ECS_ALIGN(size, member_alignment); if (elem->member && i >= inherited) { ecs_ensure(world, elem->member, EcsMember)->offset = offset; } } layout_changed |= elem->size != member_size || elem->offset != offset; elem->size = (ecs_size_t)member_size; elem->offset = offset; size = offset + elem->size; if (member_alignment > alignment) { alignment = member_alignment; } } if (explicit_offset) { const EcsComponent *comp = ecs_get(world, struct_type, EcsComponent); if (comp) { alignment = comp->alignment; } } else if (inherited && inherited == count) { size = flecs_struct_base_size(world, base, size); } if (!count) { size = 1; alignment = 1; } if (size == 0) { ecs_err("struct '%s' has 0 size", ecs_get_name(world, struct_type)); return -1; } if (alignment == 0) { ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, struct_type)); return -1; } /* Align struct size to struct alignment */ size = ECS_ALIGN(size, alignment); if (member_unchanged && !layout_changed) { const EcsComponent *comp = ecs_get(world, struct_type, EcsComponent); const EcsType *type = ecs_get(world, struct_type, EcsType); if (comp && type && type->kind == EcsStructType && comp->size == size && comp->alignment == alignment) { return 0; } } ecs_modified(stage, struct_type, EcsStruct); /* Do this last as it triggers the update of EcsTypeSerializer */ if (flecs_init_type(stage, struct_type, EcsStructType, size, alignment)) { return -1; } /* If current struct is also a member, assign to itself */ if (ecs_has(world, struct_type, EcsMember)) { EcsMember *type_mbr = ecs_ensure(stage, struct_type, EcsMember); ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); type_mbr->type = struct_type; type_mbr->count = 0; ecs_modified(stage, struct_type, EcsMember); } return 0; } static void flecs_set_member_from_component( ecs_world_t *world, ecs_entity_t e, EcsMember *member, EcsMemberRanges *ranges) { ecs_entity_t struct_type = ecs_get_parent(world, e); if (!struct_type) { ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); return; } ecs_member_t m = { .name = ecs_get_name(world, e), .type = member->type, .count = member->count, .offset = member->offset, .use_offset = member->use_offset, .unit = member->unit }; if (ranges) { m.range = ranges->value; m.error_range = ranges->error; m.warning_range = ranges->warning; } flecs_add_member_to_struct(world, struct_type, e, &m); } static void flecs_set_member(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMember *member = ecs_field(it, EcsMember, 0); EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, ecs_id(EcsMemberRanges), it->offset); int i, count = it->count; for (i = 0; i < count; i ++) { flecs_set_member_from_component(world, it->entities[i], &member[i], ranges ? &ranges[i] : NULL); } } static void flecs_set_member_ranges(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 0); EcsMember *member = ecs_table_get_id(world, it->table, ecs_id(EcsMember), it->offset); if (!member) { return; } int i, count = it->count; for (i = 0; i < count; i ++) { flecs_set_member_from_component(world, it->entities[i], &member[i], &ranges[i]); } } static bool flecs_member_range_overlaps( const ecs_member_value_range_t *range, const ecs_member_value_range_t *with) { if (ECS_EQ(with->min, with->max)) { return false; } if (ECS_EQ(range->min, range->max)) { return false; } if (range->min < with->min || range->max > with->max) { return true; } return false; } static bool flecs_type_is_number( ecs_world_t *world, ecs_entity_t type) { const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); if (!p) { return false; } switch(p->kind) { case EcsChar: case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsF32: case EcsF64: return true; case EcsBool: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: return false; default: ecs_abort(ECS_INVALID_PARAMETER, NULL); } } static int flecs_member_validate_ranges( ecs_world_t *world, ecs_entity_t type, const ecs_member_t *member, bool *ranges_set_out) { const ecs_member_value_range_t *range = &member->range; const ecs_member_value_range_t *error = &member->error_range; const ecs_member_value_range_t *warning = &member->warning_range; bool ranges_set = false; if (ECS_NEQ(range->min, range->max)) { ranges_set = true; if (range->min > range->max) { ecs_err("member '%s.%s' has an invalid value range [%f..%f]", flecs_errstr(ecs_get_path(world, type)), member->name, range->min, range->max); goto error; } } if (ECS_NEQ(error->min, error->max)) { ranges_set = true; if (error->min > error->max) { ecs_err("member '%s.%s' has an invalid error range [%f..%f]", flecs_errstr(ecs_get_path(world, type)), member->name, error->min, error->max); goto error; } if (flecs_member_range_overlaps(error, range)) { ecs_err("error range of member '%s.%s' overlaps with value range", flecs_errstr(ecs_get_path(world, type)), member->name); goto error; } } if (ECS_NEQ(warning->min, warning->max)) { ranges_set = true; if (warning->min > warning->max) { ecs_err("member '%s.%s' has an invalid warning range [%f..%f]", flecs_errstr(ecs_get_path(world, type)), member->name, warning->min, warning->max); goto error; } if (flecs_member_range_overlaps(warning, range)) { ecs_err("warning range of member '%s.%s' overlaps with value range", flecs_errstr(ecs_get_path(world, type)), member->name); goto error; } if (flecs_member_range_overlaps(warning, error)) { ecs_err("warning range of member '%s.%s' overlaps with error range", flecs_errstr(ecs_get_path(world, type)), member->name); goto error; } } if (ranges_set && !flecs_type_is_number(world, member->type)) { ecs_err("member '%s.%s' has a value/error/warning range, but is " "not a number", flecs_errstr(ecs_get_path(world, type)), member->name); goto error; } *ranges_set_out = ranges_set; return 0; error: return -1; } static void flecs_struct_create_member_entity( ecs_world_t *world, ecs_entity_t type, const ecs_member_t *member, bool set_ranges) { ecs_entity_t member_entity = ecs_new_from_path(world, type, member->name); ecs_set(world, member_entity, EcsMember, { .type = member->type, .count = member->count, .offset = member->offset, .use_offset = member->use_offset, .unit = member->unit }); if (set_ranges) { ecs_set(world, member_entity, EcsMemberRanges, { .value = member->range, .error = member->error_range, .warning = member->warning_range }); } } ecs_entity_t ecs_struct_init( ecs_world_t *world, const ecs_struct_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t type = desc->entity; if (!type) { type = ecs_new_low_id(world); } ecs_entity_t old_scope = ecs_set_scope(world, type); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_member_t *m_desc = &desc->members[i]; if (!m_desc->name) { break; } if (!m_desc->type) { ecs_err("member '%s' of struct '%s' does not have a type", m_desc->name, ecs_get_name(world, type)); goto error; } ecs_member_t member = *m_desc; bool ranges_set = false; bool create_member_entity = desc->create_member_entities; #ifdef FLECS_CREATE_MEMBER_ENTITIES create_member_entity = true; #endif if (flecs_member_validate_ranges(world, type, &member, &ranges_set)) { goto error; } if (!create_member_entity) { if (flecs_add_member_to_struct(world, type, 0, &member)) { goto error; } } else { flecs_struct_create_member_entity(world, type, &member, ranges_set); } } ecs_set_scope(world, old_scope); if (i == 0) { EcsStruct *s = ecs_ensure(world, type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_init_if_t(&s->members, ecs_member_t); flecs_struct_inherit(world, type, s); if (flecs_struct_layout(world, type, s, -1, false, false)) { goto error; } ecs_modified(world, type, EcsStruct); } else if (!ecs_has(world, type, EcsStruct)) { goto error; } flecs_resume_readonly(world, &rs); return type; error: flecs_resume_readonly(world, &rs); if (type) { ecs_delete(world, type); } return 0; } static void flecs_struct_on_add_base(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_entity_t base = ecs_pair_second(world, ecs_field_id(it, 0)); if (!ecs_has(world, base, EcsStruct)) { return; } int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (!ecs_owns(world, e, EcsStruct)) { continue; } EcsStruct *s = ecs_ensure(world, e, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_vec_count(&s->members)) { char *path = ecs_get_path(world, e); char *base_path = ecs_get_path(world, base); ecs_err("cannot add base type '%s' to struct '%s' that already " "has members", base_path, path); ecs_os_free(path); ecs_os_free(base_path); continue; } if (flecs_struct_inherit(world, e, s)) { flecs_struct_layout(world, e, s, -1, false, true); } ecs_modified(it->stage, e, EcsStruct); } } int ecs_struct_add_member( ecs_world_t *world, ecs_entity_t type, const ecs_member_t *member) { flecs_stage_from_world(&world); bool ranges_set; if (flecs_member_validate_ranges(world, type, member, &ranges_set)) { goto error; } #ifdef FLECS_CREATE_MEMBER_ENTITIES flecs_struct_create_member_entity(world, type, member, ranges_set); #else if (flecs_add_member_to_struct(world, type, 0, member)) { goto error; } #endif return 0; error: return -1; } ecs_member_t* ecs_struct_get_member( ecs_world_t *world, ecs_entity_t type, const char *name) { const EcsStruct *s = ecs_get(world, type, EcsStruct); if (!s) { return NULL; } ecs_member_t *members = ecs_vec_first(&s->members); int32_t i, count = ecs_vec_count(&s->members); for (i = 0; i < count; i ++) { if (!ecs_os_strcmp(members[i].name, name)) { return &members[i]; } } return NULL; } ecs_member_t* ecs_struct_get_nth_member( ecs_world_t *world, ecs_entity_t type, int32_t i) { const EcsStruct *s = ecs_get(world, type, EcsStruct); if (!s) { return NULL; } ecs_member_t *members = ecs_vec_first(&s->members); int32_t count = ecs_vec_count(&s->members); if (i >= count) { return NULL; } return &members[i]; } void flecs_meta_struct_init( ecs_world_t *world) { ecs_entity_t member = ecs_entity(world, { .id = ecs_id(EcsMember), .name = "member", .symbol = "EcsMember" }); ecs_add_pair(world, member, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = member, .type.size = sizeof(EcsMember), .type.alignment = ECS_ALIGNOF(EcsMember) }); ecs_entity_t member_ranges = ecs_entity(world, { .id = ecs_id(EcsMemberRanges), .name = "member_ranges", .symbol = "EcsMemberRanges" }); ecs_add_pair(world, member_ranges, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = member_ranges, .type.size = sizeof(EcsMemberRanges), .type.alignment = ECS_ALIGNOF(EcsMemberRanges) }); ecs_entity_t struct_component = ecs_entity(world, { .id = ecs_id(EcsStruct), .name = "struct", .symbol = "EcsStruct" }); ecs_add_pair(world, struct_component, EcsOnInstantiate, EcsDontInherit); ecs_component(world, { .entity = struct_component, .type.size = sizeof(EcsStruct), .type.alignment = ECS_ALIGNOF(EcsStruct) }); ecs_set_hooks(world, EcsStruct, { .ctor = flecs_default_ctor, .move = ecs_move(EcsStruct), .copy = ecs_copy(EcsStruct), .dtor = ecs_dtor(EcsStruct) }); ecs_set_hooks(world, EcsMember, { .ctor = flecs_default_ctor, }); ecs_set_hooks(world, EcsMemberRanges, { .ctor = flecs_default_ctor }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMember) }, .events = {EcsOnSet}, .callback = flecs_set_member, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMemberRanges) }, .events = {EcsOnSet}, .callback = flecs_set_member_ranges, .global_observer = true }); ecs_observer(world, { .query.terms[0] = { .id = ecs_pair(EcsIsA, EcsWildcard) }, .events = {EcsOnAdd}, .callback = flecs_struct_on_add_base, .global_observer = true }); ecs_add_pair(world, ecs_id(EcsStruct), EcsWith, ecs_id(EcsComponent)); } #endif #ifdef FLECS_META /* EcsUnit lifecycle */ static void flecs_unit_dtor( EcsUnit *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnit, dst, src, { flecs_unit_dtor(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; }) static ECS_MOVE(EcsUnit, dst, src, { flecs_unit_dtor(dst); dst->symbol = src->symbol; dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; src->symbol = NULL; src->base = 0; src->over = 0; src->prefix = 0; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnit, ptr, { flecs_unit_dtor(ptr); }) /* EcsUnitPrefix lifecycle */ static void flecs_unit_prefix_dtor( EcsUnitPrefix *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnitPrefix, dst, src, { flecs_unit_prefix_dtor(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->translation = src->translation; }) static ECS_MOVE(EcsUnitPrefix, dst, src, { flecs_unit_prefix_dtor(dst); dst->symbol = src->symbol; dst->translation = src->translation; src->symbol = NULL; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnitPrefix, ptr, { flecs_unit_prefix_dtor(ptr); }) static bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data) { char *derived_symbol = NULL; const char *symbol = data->symbol; ecs_entity_t base = data->base; ecs_entity_t over = data->over; ecs_entity_t prefix = data->prefix; ecs_unit_translation_t translation = data->translation; if (base) { if (!ecs_has(world, base, EcsUnit)) { ecs_err("entity '%s' for unit '%s' used as base is not a unit", ecs_get_name(world, base), ecs_get_name(world, t)); goto error; } } if (over) { if (!base) { ecs_err("invalid unit '%s': cannot specify over without base", ecs_get_name(world, t)); goto error; } if (!ecs_has(world, over, EcsUnit)) { ecs_err("entity '%s' for unit '%s' used as over is not a unit", ecs_get_name(world, over), ecs_get_name(world, t)); goto error; } } if (prefix) { if (!base) { ecs_err("invalid unit '%s': cannot specify prefix without base", ecs_get_name(world, t)); goto error; } const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); if (!prefix_ptr) { ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", ecs_get_name(world, over), ecs_get_name(world, t)); goto error; } if (translation.factor || translation.power) { if (prefix_ptr->translation.factor != translation.factor || prefix_ptr->translation.power != translation.power) { ecs_err( "factor for unit '%s' is inconsistent with prefix '%s'", ecs_get_name(world, t), ecs_get_name(world, prefix)); goto error; } } else { translation = prefix_ptr->translation; } } if (base) { bool must_match = false; /* Must base symbol match symbol? */ ecs_strbuf_t sbuf = ECS_STRBUF_INIT; if (prefix) { const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (ptr->symbol) { ecs_strbuf_appendstr(&sbuf, ptr->symbol); must_match = true; } } const EcsUnit *uptr = ecs_get(world, base, EcsUnit); ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); if (uptr->symbol) { ecs_strbuf_appendstr(&sbuf, uptr->symbol); } if (over) { uptr = ecs_get(world, over, EcsUnit); ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); if (uptr->symbol) { ecs_strbuf_appendch(&sbuf, '/'); ecs_strbuf_appendstr(&sbuf, uptr->symbol); must_match = true; } } derived_symbol = ecs_strbuf_get(&sbuf); if (derived_symbol && !ecs_os_strlen(derived_symbol)) { ecs_os_free(derived_symbol); derived_symbol = NULL; } if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { if (must_match) { ecs_err("symbol '%s' for unit '%s' does not match base" " symbol '%s'", symbol, ecs_get_name(world, t), derived_symbol); goto error; } } if (!symbol && derived_symbol && (prefix || over)) { ecs_os_free(data->symbol); data->symbol = derived_symbol; } else { ecs_os_free(derived_symbol); } } data->base = base; data->over = over; data->prefix = prefix; data->translation = translation; return true; error: ecs_os_free(derived_symbol); return false; } static void flecs_set_unit(ecs_iter_t *it) { EcsUnit *u = ecs_field(it, EcsUnit, 0); ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; flecs_unit_validate(world, e, &u[i]); } } static void flecs_unit_quantity_monitor(ecs_iter_t *it) { int i, count = it->count; if (it->event == EcsOnAdd) { for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_add_pair(it->stage, e, EcsQuantity, e); } } else { for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_remove_pair(it->stage, e, EcsQuantity, e); } } } ecs_entity_t ecs_unit_init( ecs_world_t *world, const ecs_unit_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t quantity = desc->quantity; if (quantity) { if (!ecs_has_id(world, quantity, EcsQuantity)) { ecs_err("entity '%s' for unit '%s' is not a quantity", ecs_get_name(world, quantity), ecs_get_name(world, t)); goto error; } ecs_add_pair(world, t, EcsQuantity, desc->quantity); } else { ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); } EcsUnit *value = ecs_ensure(world, t, EcsUnit); value->base = desc->base; value->over = desc->over; value->translation = desc->translation; value->prefix = desc->prefix; ecs_os_strset(&value->symbol, desc->symbol); if (!flecs_unit_validate(world, t, value)) { goto error; } ecs_modified(world, t, EcsUnit); flecs_resume_readonly(world, &rs); return t; error: if (t) { ecs_delete(world, t); } flecs_resume_readonly(world, &rs); return 0; } ecs_entity_t ecs_unit_prefix_init( ecs_world_t *world, const ecs_unit_prefix_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsUnitPrefix, { .symbol = ECS_CONST_CAST(char*, desc->symbol), .translation = desc->translation }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_quantity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = ecs_entity_init(world, desc); if (!t) { return 0; } ecs_add_id(world, t, EcsQuantity); flecs_resume_readonly(world, &rs); return t; } void flecs_meta_units_init( ecs_world_t *world) { ecs_entity_t unit = ecs_entity(world, { .id = ecs_id(EcsUnit), .name = "unit", .symbol = "EcsUnit" }); #ifdef FLECS_PREFAB ecs_add_pair(world, unit, EcsOnInstantiate, EcsInherit); #endif ecs_component(world, { .entity = unit, .type.size = sizeof(EcsUnit), .type.alignment = ECS_ALIGNOF(EcsUnit) }); ecs_entity_t unit_prefix = ecs_entity(world, { .id = ecs_id(EcsUnitPrefix), .name = "unit_prefix", .symbol = "EcsUnitPrefix" }); #ifdef FLECS_PREFAB ecs_add_pair(world, unit_prefix, EcsOnInstantiate, EcsInherit); #endif ecs_component(world, { .entity = unit_prefix, .type.size = sizeof(EcsUnitPrefix), .type.alignment = ECS_ALIGNOF(EcsUnitPrefix) }); ecs_entity_t quantity = ecs_entity(world, { .id = EcsQuantity, .name = "quantity", .symbol = "EcsQuantity" }); #ifdef FLECS_PREFAB ecs_add_pair(world, quantity, EcsOnInstantiate, EcsInherit); #endif ecs_component(world, { .entity = quantity }); ecs_set_hooks(world, EcsUnit, { .ctor = flecs_default_ctor, .move = ecs_move(EcsUnit), .copy = ecs_copy(EcsUnit), .dtor = ecs_dtor(EcsUnit) }); ecs_set_hooks(world, EcsUnitPrefix, { .ctor = flecs_default_ctor, .move = ecs_move(EcsUnitPrefix), .copy = ecs_copy(EcsUnitPrefix), .dtor = ecs_dtor(EcsUnitPrefix) }); ecs_add_id(world, EcsQuantity, EcsExclusive); ecs_add_id(world, EcsQuantity, EcsPairIsTag); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsUnit) }, .events = {EcsOnSet}, .callback = flecs_set_unit, .global_observer = true }); ecs_observer(world, { .query.terms = { { .id = ecs_id(EcsUnit) }, { .id = EcsQuantity } }, .events = { EcsMonitor }, .callback = flecs_unit_quantity_monitor, .global_observer = true }); } #endif #ifdef FLECS_SCRIPT_ASYNC ECS_COMPONENT_DECLARE(EcsScriptTask); typedef EcsScriptTask EcsScriptAsyncTasks; static ECS_COMPONENT_DECLARE(EcsScriptAsyncTasks); struct ecs_script_task_t { ecs_script_t *script; ecs_script_runner_t *runner; ecs_script_ir_vm_t *vm; ecs_script_eval_visitor_t *v; flecs_script_async_state_t *async; ecs_script_eval_desc_t eval_desc; ecs_script_vars_t *initial_vars; ecs_entity_t entity; void *ctx; ecs_ctx_free_t ctx_free; int32_t iterations; int32_t completed_iterations; bool running; ecs_script_task_loop_t loop; ecs_script_task_status_t status; /* Scope (runner) or entry (IR) that the task runs. Defaults to the script * root, or the body of an async block. */ ecs_script_scope_t *scope; const ecs_script_ir_entry_t *entry; /* Async block tasks */ ecs_script_state_t state; ecs_entity_t owner_instance; ecs_entity_t owner_template; int32_t sched_index; bool bound_registered; bool is_async_block; bool retains_script; bool free_pending; }; static void flecs_script_future_lock( ecs_script_future_t *future) { if (future->mutex) { ecs_os_mutex_lock(future->mutex); } } static void flecs_script_future_unlock( ecs_script_future_t *future) { if (future->mutex) { ecs_os_mutex_unlock(future->mutex); } } ecs_expr_function_t* flecs_script_await_call( ecs_script_eval_visitor_t *v, const ecs_script_node_t *stmt, ecs_expr_node_t *expr) { ecs_expr_function_t *call = (ecs_expr_function_t*)expr; if (!expr || (expr->kind != EcsExprFunction && expr->kind != EcsExprMethod) || !call->calldata.async_callback) { flecs_script_eval_error(v, stmt, "await expression must be an async function call"); return NULL; } return call; } void flecs_script_await_launch( ecs_world_t *world, flecs_script_async_state_t *async, const ecs_expr_function_t *call, ecs_value_t *argv) { ecs_script_future_t *future = ecs_os_calloc_t(ecs_script_future_t); future->type = call->node.type; future->cancel = call->calldata.async_cancel; future->function_ctx = (ecs_function_ctx_t){ .world = world, .function = call->calldata.function, .entity = async->entity, .ctx = call->calldata.ctx }; future->refs = 2; if (ecs_os_has_threading()) { future->mutex = ecs_os_mutex_new(); } call->calldata.async_callback(&future->function_ctx, ecs_vec_count(&call->args->elements), argv, future); async->future = future; } flecs_script_future_state_t flecs_script_future_poll( const ecs_script_future_t *future) { ecs_script_future_t *ptr = ECS_CONST_CAST(ecs_script_future_t*, future); flecs_script_future_lock(ptr); flecs_script_future_state_t state = ptr->state; flecs_script_future_unlock(ptr); return state; } static void flecs_script_future_cancel( ecs_script_future_t *future) { flecs_script_future_lock(future); bool cancel = future->state == FlecsScriptFuturePending; if (cancel) { future->state = FlecsScriptFutureCancelled; } flecs_script_future_unlock(future); if (cancel && future->cancel) { future->cancel(&future->function_ctx, future); } } ecs_script_future_t* ecs_script_future_retain( ecs_script_future_t *future) { ecs_check(future != NULL, ECS_INVALID_PARAMETER, NULL); flecs_script_future_lock(future); future->refs ++; flecs_script_future_unlock(future); return future; error: return NULL; } void ecs_script_future_release( ecs_script_future_t *future) { if (!future) { return; } flecs_script_future_lock(future); int32_t refs = -- future->refs; flecs_script_future_unlock(future); if (refs) { return; } if (future->value.ptr) { ecs_value_fini(future->function_ctx.world, &future->value); } ecs_os_free(future->error); if (future->ctx_free) { future->ctx_free(future->ctx); } if (future->mutex) { ecs_os_mutex_free(future->mutex); } ecs_os_free(future); } int ecs_script_future_resolve( ecs_script_future_t *future, const ecs_value_t *value) { ecs_check(future != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(value->type == future->type, ECS_INVALID_PARAMETER, NULL); ecs_check(value->ptr != NULL, ECS_INVALID_PARAMETER, NULL); flecs_script_future_lock(future); if (future->state != FlecsScriptFuturePending) { flecs_script_future_unlock(future); goto error; } future->value = ecs_value_new(future->function_ctx.world, future->type); ecs_ptr_copy(future->function_ctx.world, future->type, future->value.ptr, value->ptr); future->state = FlecsScriptFutureResolved; flecs_script_future_unlock(future); return 0; error: return -1; } int ecs_script_future_reject( ecs_script_future_t *future, const char *error) { ecs_check(error != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_script_future_reject_id(future, 0, error); error: return -1; } int ecs_script_future_reject_id( ecs_script_future_t *future, ecs_entity_t error, const char *msg) { ecs_check(future != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(error || msg, ECS_INVALID_PARAMETER, NULL); flecs_script_future_lock(future); if (future->state != FlecsScriptFuturePending) { flecs_script_future_unlock(future); goto error; } future->error = ecs_os_strdup(msg ? msg : "async function failed"); future->error_id = error; future->state = FlecsScriptFutureRejected; flecs_script_future_unlock(future); return 0; error: return -1; } bool ecs_script_future_is_cancelled( const ecs_script_future_t *future) { ecs_check(future != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_future_poll(future) == FlecsScriptFutureCancelled; error: return false; } void ecs_script_future_set_ctx( ecs_script_future_t *future, void *ctx, ecs_ctx_free_t ctx_free) { ecs_check(future != NULL, ECS_INVALID_PARAMETER, NULL); flecs_script_future_lock(future); ecs_assert(future->ctx == NULL, ECS_INVALID_OPERATION, NULL); future->ctx = ctx; future->ctx_free = ctx_free; flecs_script_future_unlock(future); error: return; } void* ecs_script_future_get_ctx( const ecs_script_future_t *future) { ecs_check(future != NULL, ECS_INVALID_PARAMETER, NULL); return future->ctx; error: return NULL; } /* Async statement evaluation, invoked by the script runner */ void flecs_script_throw_clear( flecs_script_async_state_t *state) { if (state->thrown) { ecs_script_future_release(state->thrown); state->thrown = NULL; } state->throw_node = NULL; } void flecs_script_async_fini( flecs_script_async_state_t *state) { ecs_script_future_release(state->future); state->future = NULL; flecs_script_throw_clear(state); } void flecs_script_report_throw( ecs_script_eval_visitor_t *v, flecs_script_async_state_t *state) { ecs_entity_t error_id = state->thrown->error_id; const char *msg = state->thrown->error; if (error_id) { char *path = ecs_get_path(v->world, error_id); flecs_script_eval_error(v, state->throw_node, "%s: %s", path, msg); ecs_os_free(path); } else { flecs_script_eval_error(v, state->throw_node, "%s", msg); } flecs_script_throw_clear(state); } int flecs_script_await_poll( ecs_script_eval_visitor_t *v, flecs_script_async_state_t *async, const ecs_script_node_t *stmt, ecs_script_future_t **ready) { ecs_assert(async->future != NULL, ECS_INTERNAL_ERROR, NULL); flecs_script_future_state_t state = flecs_script_future_poll(async->future); if (state == FlecsScriptFuturePending) { return 1; } ecs_script_future_t *future = async->future; async->future = NULL; if (state == FlecsScriptFutureRejected) { flecs_script_throw_clear(async); async->thrown = future; async->throw_node = stmt; return -1; } if (state == FlecsScriptFutureCancelled) { flecs_script_eval_error(v, stmt, "awaited operation was cancelled"); ecs_script_future_release(future); return -1; } *ready = future; return 0; } static int flecs_script_await_start( ecs_script_runner_t *r, ecs_script_node_t *stmt, ecs_expr_node_t **expr_ptr) { ecs_script_eval_visitor_t *v = &r->v; ecs_expr_function_t *call = flecs_script_await_call(v, stmt, *expr_ptr); if (!call) { return -1; } int32_t arg_offset = call->node.kind == EcsExprMethod; int32_t i, count = ecs_vec_count(&call->args->elements) + arg_offset; ecs_value_t *argv = count ? ecs_os_alloca_n(ecs_value_t, count) : NULL; ecs_expr_initializer_element_t *elems = ecs_vec_first( &call->args->elements); int result = 0; for (i = 0; !result && i < count; i ++) { ecs_expr_node_t **arg = arg_offset && !i ? &call->left : &elems[i - arg_offset].value; argv[i] = ecs_value_new(v->world, (*arg)->type); result = flecs_script_eval_expr(v, arg, &argv[i]); } if (!result) { flecs_script_await_launch(v->world, &r->async, call, argv); } while (i --) { ecs_value_fini(v->world, &argv[i]); } return result; } static int flecs_script_await_assign_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node, const ecs_value_t *value) { ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); return -1; } const ecs_type_info_t *ti = ecs_get_type_info(v->world, value->type); var->value.type = value->type; var->value.ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); var->type_info = ti; var->owned = true; flecs_type_info_ctor(var->value.ptr, 1, ti); ecs_ptr_copy(v->world, value->type, var->value.ptr, value->ptr); return 0; } int32_t flecs_script_catch( ecs_script_eval_visitor_t *v, flecs_script_async_state_t *state, const ecs_script_try_t *node) { if (!state->thrown) { return -1; } ecs_entity_t throw_id = state->thrown->error_id; int32_t i, count = ecs_vec_count(&node->catches); ecs_script_catch_t *catches = ecs_vec_first(&node->catches); for (i = 0; i < count; i ++) { if (!catches[i].error) { break; } if (!throw_id) { continue; } ecs_entity_t e = catches[i].eval_error; if (catches[i].error_symbol != -1) { e = flecs_script_symbol_entity(v, catches[i].error_symbol); } if (e == throw_id) { break; } } if (i == count) { return -1; } flecs_script_throw_clear(state); return i; } /* Returns 0 to continue, -1 on error, 1 when execution suspends */ int flecs_script_step_await( ecs_script_runner_t *r, ecs_script_node_t *stmt) { ecs_script_eval_visitor_t *v = &r->v; ecs_expr_node_t **expr; ecs_script_var_node_t *var = NULL; if (stmt->kind == EcsAstAwait) { expr = &((ecs_script_await_t*)stmt)->expr; } else { var = (ecs_script_var_node_t*)stmt; expr = &var->expr; } if (!r->can_suspend) { flecs_script_eval_error(v, stmt, "await requires resumable script execution"); return -1; } if (!r->async.future && flecs_script_await_start(r, stmt, expr)) { return -1; } ecs_script_future_t *future = NULL; int result = flecs_script_await_poll(v, &r->async, stmt, &future); if (result) { return result; } if (var) { result = flecs_script_await_assign_const(v, var, &future->value); } ecs_script_future_release(future); return result; } bool flecs_script_try_catch( ecs_script_runner_t *r, flecs_script_frame_t *frame) { /* Runtime errors are not catchable, and an error thrown from a catch * handler can only be caught by an enclosing try block. */ if (frame->node->kind != EcsAstTry || frame->pc != 1) { return false; } ecs_script_try_t *node = (ecs_script_try_t*)frame->node; int32_t i = flecs_script_catch(&r->v, &r->async, node); if (i == -1) { return false; } frame->pc = 2; flecs_script_scope_push(r, ecs_vec_get_t(&node->catches, ecs_script_catch_t, i)->scope); return true; } static void flecs_script_task_push_this( ecs_script_eval_visitor_t *v, ecs_script_runtime_t *runtime, ecs_entity_t entity) { v->vars = flecs_script_vars_push( v->vars, &runtime->stack, runtime->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); var->value.type = ecs_id(ecs_entity_t); var->value.ptr = flecs_stack_alloc(&runtime->stack, ECS_SIZEOF(ecs_entity_t), ECS_ALIGNOF(ecs_entity_t)); *(ecs_entity_t*)var->value.ptr = entity; var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_entity_t)); var->owned = true; } static void flecs_script_task_register( ecs_script_task_t *task) { ecs_world_t *world = task->script->world; if (!ecs_id(EcsScriptTask)) { /* Script module wasn't imported for world */ return; } EcsScriptTask *t = ecs_ensure(world, task->entity, EcsScriptTask); ecs_vec_init_if_t(&t->tasks, ecs_script_task_t*); ecs_script_task_t **elem = ecs_vec_append_t( NULL, &t->tasks, ecs_script_task_t*); *elem = task; ecs_modified(world, task->entity, EcsScriptTask); } static void flecs_script_task_vec_remove( ecs_vec_t *tasks, const ecs_script_task_t *task) { int32_t i, count = ecs_vec_count(tasks); ecs_script_task_t **array = ecs_vec_first(tasks); for (i = 0; i < count; i ++) { if (array[i] == task) { ecs_vec_remove_t(tasks, ecs_script_task_t*, i); return; } } } static void flecs_script_task_unbind( ecs_script_task_t *task) { if (!task->bound_registered) { return; } task->bound_registered = false; EcsScriptAsyncTasks *bound = ecs_get_mut( task->script->world, task->entity, EcsScriptAsyncTasks); if (bound) { flecs_script_task_vec_remove(&bound->tasks, task); } } static void flecs_script_task_unregister( ecs_script_task_t *task) { ecs_entity_t entity = task->entity; if (!entity || task->is_async_block) { return; } task->entity = 0; ecs_world_t *world = task->script->world; if (!ecs_id(EcsScriptTask) || ecs_is_fini(world) || !ecs_is_alive(world, entity)) { return; } EcsScriptTask *t = ecs_get_mut(world, entity, EcsScriptTask); if (!t) { if (!ecs_is_stage(world)) { return; } t = ecs_ensure(world, entity, EcsScriptTask); if (!t) { return; } } flecs_script_task_vec_remove(&t->tasks, task); if (!ecs_vec_count(&t->tasks)) { ecs_remove(world, entity, EcsScriptTask); } } static ecs_script_task_t* flecs_script_task_alloc( ecs_script_impl_t *impl) { ecs_size_t engine_size = impl->ir_enabled ? ECS_SIZEOF(ecs_script_ir_vm_t) : ECS_SIZEOF(ecs_script_runner_t); ecs_size_t engine_align = impl->ir_enabled ? ECS_ALIGNOF(ecs_script_ir_vm_t) : ECS_ALIGNOF(ecs_script_runner_t); ecs_size_t offset = ECS_ALIGN(ECS_SIZEOF(ecs_script_task_t), engine_align); ecs_script_task_t *result = ecs_os_malloc(offset + engine_size); ecs_os_zeromem(result); if (impl->ir_enabled) { result->vm = ECS_OFFSET(result, offset); result->v = &result->vm->v; result->async = &result->vm->async; } else { result->runner = ECS_OFFSET(result, offset); result->v = &result->runner->v; result->async = &result->runner->async; } result->script = &impl->pub; result->sched_index = -1; impl->task_refcount ++; result->eval_desc.runtime = flecs_script_runtime_new_shared( flecs_script_runtime_get(impl->pub.world)); flecs_script_eval_visit_init(impl, result->v, &result->eval_desc); return result; } static void flecs_script_task_dealloc( ecs_script_task_t *task) { ecs_script_eval_visitor_t *v = task->v; while (v->vars) { v->vars = ecs_script_vars_pop(v->vars); } if (task->is_async_block) { flecs_script_state_fini(&task->state); } ecs_script_runtime_free(task->eval_desc.runtime); if (task->ctx_free) { task->ctx_free(task->ctx); } ecs_script_impl_t *impl = flecs_script_impl(task->script); ecs_assert(impl->task_refcount > 0, ECS_INTERNAL_ERROR, NULL); impl->task_refcount --; bool release = task->retains_script; ecs_os_free(task); if (release) { ecs_script_free(&impl->pub); } } static int flecs_script_task_engine_init( ecs_script_task_t *task, const void *node) { if (task->vm) { flecs_script_ir_vm_init(task->vm); if (task->vm->ir) { task->entry = flecs_script_ir_entry(task->vm->ir, node); } if (!task->entry) { flecs_script_ir_vm_fini(task->vm, &task->eval_desc); return -1; } task->vm->can_suspend = true; } else { flecs_script_runner_init(task->runner, task->v); task->runner->can_suspend = true; } task->async->entity = task->entity; return 0; } ecs_script_task_t* ecs_script_task_new( const ecs_script_t *script, const ecs_script_task_desc_t *desc) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!desc || (desc->loop >= EcsScriptTaskLoopOnce && desc->loop <= EcsScriptTaskLoopForever), ECS_INVALID_PARAMETER, NULL); ecs_check(!desc || desc->loop != EcsScriptTaskLoopCount || desc->iterations > 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!desc || !desc->entity || ecs_is_alive(script->world, desc->entity), ECS_INVALID_PARAMETER, "task entity is not alive"); ecs_script_impl_t *impl = ECS_CONST_CAST(ecs_script_impl_t*, script); ecs_script_task_t *result = flecs_script_task_alloc(impl); if (desc) { result->entity = desc->entity; result->ctx = desc->ctx; result->ctx_free = desc->ctx_free; result->loop = desc->loop; result->iterations = desc->iterations; } if (result->entity) { flecs_script_task_push_this( result->v, result->eval_desc.runtime, result->entity); } result->scope = impl->root; if (flecs_script_compile(result->v) || flecs_script_task_engine_init(result, impl->root)) { flecs_script_task_dealloc(result); return NULL; } if (result->entity) { flecs_script_task_register(result); } result->initial_vars = result->v->vars; return result; error: return NULL; } static void flecs_script_task_cancel_future( ecs_script_task_t *task) { ecs_script_future_t *future = task->async->future; task->async->future = NULL; if (future) { flecs_script_future_cancel(future); ecs_script_future_release(future); } } ecs_script_task_status_t ecs_script_task_resume( ecs_script_task_t *task, ecs_script_eval_result_t *result) { ecs_check(task != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!task->running, ECS_INVALID_OPERATION, "cannot resume a task that is already running"); if (task->status != EcsScriptTaskPending) { return task->status; } task->running = true; if (result) { flecs_log_capture_push(true); } ecs_script_runtime_t *world_rt = flecs_script_runtime_get(task->v->world); bool prev_error = world_rt->error; world_rt->error = false; flecs_script_run_status_t status; if (task->vm) { status = flecs_script_ir_vm_run(task->vm, task->entry); } else { status = flecs_script_runner_run_scope(task->runner, task->scope); } bool nested_error = world_rt->error; world_rt->error = prev_error; if (task->status == EcsScriptTaskCancelled) { flecs_script_task_cancel_future(task); } else if (status == FlecsScriptRunError || nested_error) { task->status = EcsScriptTaskError; } else if (status == FlecsScriptRunDone) { task->status = EcsScriptTaskDone; } else { task->status = EcsScriptTaskPending; } if (result) { result->error = flecs_log_capture_pop(); flecs_log_get_captured_error_pos(&result->line, &result->column); } if (task->status == EcsScriptTaskDone) { bool repeat = task->loop == EcsScriptTaskLoopForever; if (task->loop != EcsScriptTaskLoopOnce) { task->completed_iterations ++; } if (task->loop == EcsScriptTaskLoopCount) { repeat = task->completed_iterations < task->iterations; } if (repeat) { ecs_assert(task->vm ? task->vm->frame_count == 0 : task->runner->frame_count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(task->v->vars == task->initial_vars, ECS_INTERNAL_ERROR, NULL); task->status = EcsScriptTaskPending; } } task->running = false; return task->status; error: return EcsScriptTaskError; } bool ecs_script_task_is_ready( const ecs_script_task_t *task) { ecs_check(task != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_future_t *future = task->async->future; if (!future) { return true; } return flecs_script_future_poll(future) != FlecsScriptFuturePending; error: return false; } void ecs_script_task_cancel( ecs_script_task_t *task) { ecs_check(task != NULL, ECS_INVALID_PARAMETER, NULL); if (task->status != EcsScriptTaskPending) { return; } task->status = EcsScriptTaskCancelled; flecs_script_task_cancel_future(task); error: return; } static void flecs_script_task_sched_remove( ecs_script_task_t *task); void ecs_script_task_free( ecs_script_task_t *task) { if (!task) { return; } flecs_script_task_unbind(task); if (task->running) { /* Async block task freed by its owner while it is being resumed. The * scheduler frees the task after it yields. */ ecs_check(task->is_async_block, ECS_INVALID_OPERATION, "cannot free a task that is already running"); ecs_script_task_cancel(task); task->owner_instance = 0; task->owner_template = 0; task->free_pending = true; return; } ecs_script_task_cancel(task); flecs_script_task_unregister(task); flecs_script_task_sched_remove(task); if (task->vm) { flecs_script_ir_vm_fini(task->vm, &task->eval_desc); } else { flecs_script_runner_fini(task->runner, &task->eval_desc); } ecs_assert(task->v->vars == task->initial_vars, ECS_INTERNAL_ERROR, NULL); flecs_script_task_dealloc(task); error: return; } static ECS_MOVE(EcsScriptTask, dst, src, { ecs_vec_fini_t(NULL, &dst->tasks, ecs_script_task_t*); *dst = *src; ecs_os_zeromem(src); }) static ECS_DTOR(EcsScriptTask, ptr, { int32_t t, task_count = ecs_vec_count(&ptr->tasks); ecs_script_task_t **tasks = ecs_vec_first(&ptr->tasks); if (task_count) { ecs_assert(!(ecs_get_world(tasks[0]->script->world)->flags & (EcsWorldFini|EcsWorldQuit)), ECS_INVALID_OPERATION, "script still has alive tasks, free tasks before ecs_fini"); } for (t = 0; t < task_count; t ++) { /* Component doesn't own tasks; cancel so the application observes a * cancelled status on the next resume and frees them. */ tasks[t]->entity = 0; ecs_script_task_cancel(tasks[t]); } ecs_vec_fini_t(NULL, &ptr->tasks, ecs_script_task_t*); }) /* Must match the task_state_t struct registered in the import function */ typedef struct flecs_script_task_state_t { ecs_entity_t script; int32_t status; int32_t line; int32_t column; ecs_entity_t awaiting; int32_t iteration; } flecs_script_task_state_t; static int flecs_script_task_component_serialize( const ecs_serializer_t *ser, const void *ptr) { const EcsScriptTask *data = ptr; ecs_entity_t state_type = ecs_lookup( ser->world, "flecs.script.task_state_t"); ecs_assert(state_type != 0, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&data->tasks); ecs_script_task_t **tasks = ecs_vec_first(&data->tasks); for (i = 0; i < count; i ++) { const ecs_script_task_t *task = tasks[i]; flecs_script_task_state_t state = {0}; state.script = flecs_script_impl(task->script)->entity; state.status = task->status; state.iteration = task->completed_iterations; ecs_script_future_t *future = task->async->future; if (future) { state.awaiting = future->function_ctx.function; } const char *pos = task->vm ? flecs_script_ir_vm_stmt_pos(task->vm) : flecs_script_runner_stmt_pos(task->runner); flecs_script_pos_to_line_col(task->script->code, pos, &state.line, &state.column); ser->value(ser, state_type, &state); } return 0; } static size_t flecs_script_task_component_count( const void *ptr) { const EcsScriptTask *data = ptr; return flecs_ito(size_t, ecs_vec_count(&data->tasks)); } /* Async blocks */ static void flecs_script_task_sched_add( ecs_script_task_t *task) { ecs_script_runtime_t *rt = flecs_script_runtime_get(task->script->world); task->sched_index = ecs_vec_count(&rt->async_tasks); ecs_vec_append_t(NULL, &rt->async_tasks, ecs_script_task_t*)[0] = task; } static void flecs_script_task_sched_remove( ecs_script_task_t *task) { int32_t index = task->sched_index; if (index < 0) { return; } task->sched_index = -1; ecs_script_runtime_t *rt = flecs_script_runtime_get(task->script->world); ecs_script_task_t **tasks = ecs_vec_first(&rt->async_tasks); int32_t count = ecs_vec_count(&rt->async_tasks); ecs_assert(index < count && tasks[index] == task, ECS_INTERNAL_ERROR, NULL); if (rt->async_progressing) { tasks[index] = NULL; return; } ecs_script_task_t *last = tasks[count - 1]; tasks[index] = last; if (last) { last->sched_index = index; } ecs_vec_remove_last(&rt->async_tasks); } static ecs_script_vars_t* flecs_script_vars_snapshot( ecs_world_t *world, const ecs_script_vars_t *src, const ecs_script_template_t *template, ecs_stack_t *stack, ecs_allocator_t *allocator) { ecs_script_vars_t *parent = NULL; if (src->parent) { parent = flecs_script_vars_snapshot( world, src->parent, template, stack, allocator); } ecs_script_vars_t *dst = flecs_script_vars_push(parent, stack, allocator); dst->world = world; dst->sp = src->sp; int32_t i, count = ecs_vec_count(&src->vars); ecs_script_var_t *vars = ecs_vec_first(&src->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *src_var = &vars[i]; ecs_script_var_t *var = ecs_script_vars_declare(dst, src_var->name); if (!var) { var = ecs_script_vars_declare(dst, NULL); } ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->sp == src_var->sp, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = src_var->type_info; if (!ti && src_var->value.type) { ti = ecs_get_type_info(world, src_var->value.type); } var->value.type = src_var->value.type; var->type_info = ti; var->is_const = src_var->is_const; if (template) { const ecs_script_template_member_t *members = ecs_vec_first( &template->members); int32_t member_count = ecs_vec_count(&template->members); for (int32_t m = 0; m < member_count; m ++) { if (members[m].sp == src_var->sp && members[m].is_mut) { var->value.ptr = src_var->value.ptr; break; } } if (var->value.ptr) { continue; } } if (src_var->value.ptr && ti) { var->value.ptr = flecs_stack_alloc(stack, ti->size, ti->alignment); flecs_type_info_ctor(var->value.ptr, 1, ti); ecs_ptr_copy_w_type_info(world, ti, var->value.ptr, src_var->value.ptr); var->owned = true; } } return dst; } static void flecs_script_task_state_init( ecs_script_task_t *task, const ecs_script_state_t *src) { ecs_script_state_t *state = &task->state; flecs_script_state_init(state); flecs_script_state_resize(state, ecs_vec_count(&src->scope_slots), ecs_vec_count(&src->component_slots), ecs_vec_count(&src->for_slots)); flecs_script_state_resize_computed(state, ecs_vec_count(&src->computed)); ecs_vec_fini_t(NULL, &state->symbol_slots, ecs_script_symbol_slot_t); state->symbol_slots = ecs_vec_copy_t( NULL, &src->symbol_slots, ecs_script_symbol_slot_t); } int flecs_script_async_spawn( ecs_script_eval_visitor_t *v, ecs_script_async_t *node) { ecs_script_impl_t *impl = v->base.script; ecs_world_t *world = v->world; ecs_entity_t entity = 0; ecs_entity_t owner_instance = 0, owner_template = 0; if (v->instance_template && v->body_template) { entity = v->entity ? v->entity->eval : 0; if (!entity || !ecs_is_alive(world, entity)) { flecs_script_eval_error(v, node, "async block has no template instance"); return -1; } owner_instance = v->template_instance; owner_template = v->body_template; EcsScriptTemplateRoot *root = ecs_get_mut_pair( world, owner_instance, EcsScriptTemplateRoot, owner_template); ecs_assert(root != NULL, ECS_INTERNAL_ERROR, NULL); for (int32_t i = 0; i < ecs_vec_count(&root->async_blocks); ) { ecs_script_async_block_t *block = ecs_vec_get_t( &root->async_blocks, ecs_script_async_block_t, i); if (!ecs_is_alive(world, block->entity)) { ecs_vec_remove_t(&root->async_blocks, ecs_script_async_block_t, i); continue; } if (block->entity == entity && block->scope == node->scope) { return 0; } i ++; } } else { entity = v->script_entity; } ecs_script_task_t *task = flecs_script_task_alloc(impl); task->entity = entity; task->loop = EcsScriptTaskLoopOnce; task->is_async_block = true; task->owner_instance = owner_instance; task->owner_template = owner_template; ecs_script_runtime_t *rt = task->eval_desc.runtime; ecs_script_eval_visitor_t *tv = task->v; tv->script_entity = v->script_entity; tv->script_tag = v->script_tag; tv->module = v->module; tv->symbol_offset = v->symbol_offset; tv->template_instance = owner_instance; int32_t i, using_count = ecs_vec_count(&v->r->using); ecs_entity_t *using = ecs_vec_first(&v->r->using); for (i = 0; i < using_count; i ++) { ecs_vec_append_t(rt->allocator, &rt->using, ecs_entity_t)[0] = using[i]; } if (v->vars) { tv->vars = flecs_script_vars_snapshot( world, v->vars, v->instance_template, &rt->stack, rt->allocator); } if (owner_instance) { flecs_script_task_push_this(tv, rt, entity); } flecs_script_task_state_init(task, v->state); tv->state = &task->state; task->scope = node->scope; if (flecs_script_task_engine_init(task, node)) { flecs_script_eval_error(v, node, "failed to create task for async block"); flecs_script_task_dealloc(task); return -1; } task->initial_vars = tv->vars; if (owner_template) { EcsScriptTemplateRoot *root = ecs_ensure_pair( world, owner_instance, EcsScriptTemplateRoot, owner_template); ecs_vec_append_t(NULL, &root->tasks, ecs_script_task_t*)[0] = task; impl->refcount ++; task->retains_script = true; ecs_script_async_block_t *block = ecs_vec_append_t( NULL, &root->async_blocks, ecs_script_async_block_t); block->entity = entity; block->scope = node->scope; block->scope_slot = v->scope_slot; if (entity != owner_instance) { EcsScriptAsyncTasks *bound = ecs_ensure( world, entity, EcsScriptAsyncTasks); ecs_vec_init_if_t(&bound->tasks, ecs_script_task_t*); ecs_vec_append_t(NULL, &bound->tasks, ecs_script_task_t*)[0] = task; task->bound_registered = true; } } else { ecs_vec_append_t(NULL, &impl->async_tasks, ecs_script_task_t*)[0] = task; } flecs_script_task_sched_add(task); return 0; } void flecs_script_async_tasks_free( ecs_vec_t *tasks) { int32_t i, count = ecs_vec_count(tasks); ecs_script_task_t **array = ecs_vec_first(tasks); for (i = 0; i < count; i ++) { ecs_script_task_t *task = array[i]; task->owner_instance = 0; task->owner_template = 0; ecs_script_task_free(task); } ecs_vec_clear(tasks); } static void flecs_script_task_detach( ecs_script_task_t *task) { ecs_vec_t *owner = NULL; ecs_world_t *world = task->script->world; if (task->owner_template) { if (ecs_is_alive(world, task->owner_instance) && ecs_is_alive(world, task->owner_template)) { EcsScriptTemplateRoot *root = ecs_get_mut_pair(world, task->owner_instance, EcsScriptTemplateRoot, task->owner_template); if (root) { owner = &root->tasks; } } } else { owner = &flecs_script_impl(task->script)->async_tasks; } task->owner_instance = 0; task->owner_template = 0; if (owner) { flecs_script_task_vec_remove(owner, task); } } void flecs_script_async_cleanup( ecs_script_eval_visitor_t *v) { EcsScriptTemplateRoot *root = ecs_get_mut_pair(v->world, v->template_instance, EcsScriptTemplateRoot, v->body_template); ecs_assert(root != NULL, ECS_INTERNAL_ERROR, NULL); for (int32_t i = 0; i < ecs_vec_count(&root->async_blocks); ) { ecs_script_async_block_t block = ecs_vec_get_t( &root->async_blocks, ecs_script_async_block_t, i)[0]; if (ecs_is_alive(v->world, block.entity) && flecs_script_scope_visited(v, block.scope_slot)) { i ++; continue; } ecs_vec_remove_t(&root->async_blocks, ecs_script_async_block_t, i); for (int32_t t = 0; t < ecs_vec_count(&root->tasks); t ++) { ecs_script_task_t *task = ecs_vec_get_t( &root->tasks, ecs_script_task_t*, t)[0]; if (task->entity == block.entity && task->scope == block.scope) { flecs_script_task_detach(task); ecs_script_task_free(task); break; } } } } static ECS_DTOR(EcsScriptAsyncTasks, ptr, { ecs_vec_fini_t(NULL, &ptr->tasks, ecs_script_task_t*); }) static void flecs_script_async_tasks_on_remove( ecs_iter_t *it) { EcsScriptAsyncTasks *bound = ecs_field(it, EcsScriptAsyncTasks, 0); for (int32_t i = 0; i < it->count; i ++) { ecs_vec_t tasks = bound[i].tasks; ecs_os_zeromem(&bound[i].tasks); int32_t count = ecs_vec_count(&tasks); ecs_script_task_t **array = ecs_vec_first(&tasks); for (int32_t t = 0; t < count; t ++) { array[t]->bound_registered = false; flecs_script_task_detach(array[t]); } for (int32_t t = 0; t < count; t ++) { ecs_script_task_free(array[t]); } ecs_vec_fini_t(NULL, &tasks, ecs_script_task_t*); } } int32_t ecs_script_tasks_progress( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); ecs_script_runtime_t *rt = flecs_script_runtime_get(world); if (rt->async_progressing) { return 0; } bool is_readonly = ecs_stage_is_readonly(world); ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_readonly) { real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } rt->async_progressing = true; int32_t i, resumed = 0; for (i = 0; i < ecs_vec_count(&rt->async_tasks); i ++) { ecs_script_task_t *task = ecs_vec_get_t( &rt->async_tasks, ecs_script_task_t*, i)[0]; if (!task) { continue; } if (task->status != EcsScriptTaskPending || !ecs_script_task_is_ready(task)) { continue; } /* Keep the script alive while the task runs, in case the task frees * its own script (e.g. by deleting or updating the script entity). */ ecs_script_impl_t *impl = flecs_script_impl(task->script); impl->refcount ++; ecs_script_task_status_t status = ecs_script_task_resume(task, NULL); resumed ++; if (task->free_pending) { ecs_script_task_free(task); } else if (status != EcsScriptTaskPending) { flecs_script_task_detach(task); ecs_script_task_free(task); } ecs_script_free(&impl->pub); } ecs_script_task_t **tasks = ecs_vec_first(&rt->async_tasks); int32_t count = ecs_vec_count(&rt->async_tasks), dst = 0; for (i = 0; i < count; i ++) { if (tasks[i]) { tasks[i]->sched_index = dst; tasks[dst ++] = tasks[i]; } } ecs_vec_set_count_t(NULL, &rt->async_tasks, ecs_script_task_t*, dst); rt->async_progressing = false; if (is_readonly) { flecs_resume_readonly(real_world, &srs); } return resumed; error: return 0; } #ifdef FLECS_PIPELINE static void flecs_script_progress_tasks_system( ecs_iter_t *it) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(it->world)); ecs_script_runtime_t *rt = world->stages[0]->runtime; if (!rt || !ecs_vec_count(&rt->async_tasks)) { return; } ecs_script_tasks_progress(world); } #endif void flecs_script_async_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsScriptTask); ECS_COMPONENT_DEFINE(world, EcsScriptAsyncTasks); ecs_set_hooks(world, EcsScriptAsyncTasks, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScriptTask), .dtor = ecs_dtor(EcsScriptAsyncTasks), .on_remove = flecs_script_async_tasks_on_remove, .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_add_pair(world, ecs_id(EcsScriptAsyncTasks), EcsOnInstantiate, EcsDontInherit); #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); ecs_system(world, { .entity = ecs_entity(world, { .name = "ProgressTasks" }), .phase = EcsPreUpdate, .callback = flecs_script_progress_tasks_system, .immediate = true }); #endif ecs_set_hooks(world, EcsScriptTask, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScriptTask), .dtor = ecs_dtor(EcsScriptTask), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_add_pair(world, ecs_id(EcsScriptTask), EcsOnInstantiate, EcsDontInherit); ecs_entity_t status_type = ecs_enum(world, { .entity = ecs_entity(world, { .name = "task_status_t" }), .constants = { { .name = "Pending" }, { .name = "Done" }, { .name = "Error" }, { .name = "Cancelled" } } }); ecs_entity_t state_type = ecs_struct(world, { .entity = ecs_entity(world, { .name = "task_state_t" }), .members = { { .name = "script", .type = ecs_id(ecs_entity_t) }, { .name = "status", .type = status_type }, { .name = "line", .type = ecs_id(ecs_i32_t) }, { .name = "column", .type = ecs_id(ecs_i32_t) }, { .name = "awaiting", .type = ecs_id(ecs_entity_t) }, { .name = "iteration", .type = ecs_id(ecs_i32_t) } } }); ecs_entity_t state_vec = ecs_vector(world, { .entity = ecs_entity(world, { .name = "task_state_vec_t" }), .type = state_type }); ecs_opaque(world, { .entity = ecs_id(EcsScriptTask), .type = { .as_type = state_vec, .serialize = flecs_script_task_component_serialize, .count = flecs_script_task_component_count } }); } #endif #ifdef FLECS_SCRIPT ecs_script_runtime_t* flecs_script_runtime_new_shared( ecs_script_runtime_t *parent) { ecs_script_runtime_t *r = ecs_os_calloc_t(ecs_script_runtime_t); if (parent) { while (parent->parent) { parent = parent->parent; } r->parent = parent; r->allocator = parent->allocator; } else { r->allocator = ecs_os_calloc_t(ecs_allocator_t); flecs_allocator_init(r->allocator); } flecs_stack_init(&r->stack); ecs_vec_init_t(r->allocator, &r->using, ecs_entity_t, 0); ecs_vec_init_t(r->allocator, &r->with, ecs_script_with_value_t, 0); ecs_vec_init_t(r->allocator, &r->annot, ecs_script_annot_t*, 0); ecs_vec_init_t(r->allocator, &r->pending_resolves, ecs_entity_t, 0); ecs_vec_init_t(NULL, &r->ir_vms, ecs_script_ir_vm_t*, 0); ecs_vec_init_t(NULL, &r->call_runtimes, ecs_script_runtime_t*, 0); ecs_vec_init_t(NULL, &r->template_pending, ecs_script_template_pending_t, 0); ecs_vec_init_t(NULL, &r->async_tasks, void*, 0); return r; } ecs_script_runtime_t* ecs_script_runtime_new(void) { return flecs_script_runtime_new_shared(NULL); } ecs_expr_stack_t* flecs_script_runtime_expr_stack( ecs_script_runtime_t *r) { if (r->parent) { r = r->parent; } if (!r->expr_stack) { r->expr_stack = ecs_os_calloc_t(ecs_expr_stack_t); flecs_expr_stack_init(r->expr_stack); } return r->expr_stack; } void ecs_script_runtime_free( ecs_script_runtime_t *r) { int32_t i, count = ecs_vec_count(&r->call_runtimes); ecs_script_runtime_t **calls = ecs_vec_first(&r->call_runtimes); for (i = 0; i < count; i ++) { ecs_script_runtime_free(calls[i]); } ecs_vec_fini_t(NULL, &r->call_runtimes, ecs_script_runtime_t*); if (r->expr_stack) { flecs_expr_stack_fini(r->expr_stack); ecs_os_free(r->expr_stack); } flecs_script_ir_vm_pool_fini(r); flecs_script_template_pending_fini(&r->template_pending); ecs_vec_fini_t(NULL, &r->async_tasks, void*); ecs_vec_fini_t(r->allocator, &r->pending_resolves, ecs_entity_t); ecs_vec_fini_t(r->allocator, &r->annot, ecs_script_annot_t*); ecs_vec_fini_t(r->allocator, &r->with, ecs_script_with_value_t); ecs_vec_fini_t(r->allocator, &r->using, ecs_entity_t); if (!r->parent) { flecs_allocator_fini(r->allocator); ecs_os_free(r->allocator); } flecs_stack_fini(&r->stack); ecs_os_free(r->error_name); ecs_os_free(r); } ecs_script_runtime_t* flecs_script_runtime_acquire_call( ecs_script_runtime_t *r) { if (r->call_runtimes.count) { return ((ecs_script_runtime_t**)r->call_runtimes.array) [-- r->call_runtimes.count]; } return ecs_script_runtime_new(); } void flecs_script_runtime_release_call( ecs_script_runtime_t *r, ecs_script_runtime_t *call) { ecs_script_runtime_clear(call); flecs_script_runtime_error_reset(call); ecs_vec_append_t(NULL, &r->call_runtimes, ecs_script_runtime_t*)[0] = call; } void flecs_script_runtime_error_reset( ecs_script_runtime_t *r) { ecs_os_free(r->error_name); r->error_name = NULL; r->error = false; } void ecs_script_runtime_clear( ecs_script_runtime_t *r) { ecs_vec_clear(&r->annot); ecs_vec_clear(&r->with); ecs_vec_clear(&r->using); r->error = false; } ecs_script_runtime_t* flecs_script_runtime_get( ecs_world_t *world) { ecs_stage_t *stage; if (flecs_poly_is(world, ecs_stage_t)) { stage = (ecs_stage_t*)world; } else { stage = world->stages[0]; } ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); if (!stage->runtime) { stage->runtime = ecs_script_runtime_new(); } return stage->runtime; } #endif #ifdef FLECS_SCRIPT static bool flecs_script_constant_registered( const ecs_world_t *world, ecs_entity_t type, ecs_entity_t constant) { const EcsConstants *ptr = ecs_get(world, type, EcsConstants); if (!ptr) { return false; } ecs_vec_t *ordered = ECS_CONST_CAST(ecs_vec_t*, &ptr->ordered_constants); const ecs_enum_constant_t *constants = ecs_vec_first_t( ordered, ecs_enum_constant_t); int32_t i, count = ecs_vec_count(ordered); for (i = 0; i < count; i ++) { if (constants[i].constant == constant) { return true; } } return false; } static int flecs_script_constants_visit( const ecs_script_visitor_ctx_t *ctx, bool is_bitmask) { ecs_world_t *world = ctx->world; ecs_script_eval_visitor_t *v = ctx->eval; ecs_script_t *script = &v->base.script->pub; ecs_expr_initializer_t *node = ctx->initializer; const char *kind_str = is_bitmask ? "bitmask" : "enum"; if (!node || node->node.kind == EcsExprEmptyInitializer || !ecs_vec_count(&node->elements)) { flecs_script_eval_error(v, NULL, "%s '%s' must have at least one constant", kind_str, ecs_get_name(world, ctx->entity)); return -1; } ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); ecs_entity_t underlying = is_bitmask ? ecs_id(ecs_u32_t) : 0; for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member || !elem->value) { continue; } if (elem->value->kind != EcsExprInitializer && elem->value->kind != EcsExprEmptyInitializer) { continue; } ecs_expr_initializer_t *cfg = (ecs_expr_initializer_t*)elem->value; ecs_expr_initializer_element_t *cfg_elems = ecs_vec_first( &cfg->elements); int32_t c, cfg_count = ecs_vec_count(&cfg->elements); for (c = 0; c < cfg_count; c ++) { ecs_expr_initializer_element_t *cfg_elem = &cfg_elems[c]; if (!cfg_elem->member || ecs_os_strcmp( cfg_elem->member, "underlying_type")) { flecs_expr_visit_error(script, elem->value, "invalid element in %s configuration (expected " "'{underlying_type: type}')", kind_str); return -1; } if (is_bitmask) { flecs_expr_visit_error(script, elem->value, "bitmask does not support underlying_type"); return -1; } if (underlying) { flecs_expr_visit_error(script, elem->value, "underlying_type specified more than once"); return -1; } ecs_value_t value = { .ptr = &underlying, .type = ecs_id(ecs_entity_t) }; if (flecs_script_eval_expr(v, &cfg_elem->value, &value)) { return -1; } if (!underlying) { flecs_expr_visit_error(script, elem->value, "invalid underlying_type for enum '%s'", ecs_get_name(world, ctx->entity)); return -1; } if (!flecs_meta_type_is_integer(world, underlying)) { char *type_str = ecs_get_path(world, underlying); flecs_expr_visit_error(script, elem->value, "underlying_type '%s' for enum '%s' is not an integer type", type_str, ecs_get_name(world, ctx->entity)); ecs_os_free(type_str); return -1; } const EcsEnum *existing = ecs_get(world, ctx->entity, EcsEnum); if (!existing || existing->underlying_type != underlying) { EcsEnum *ptr = ecs_ensure(world, ctx->entity, EcsEnum); ptr->underlying_type = underlying; ecs_modified(world, ctx->entity, EcsEnum); } } } if (!underlying) { underlying = ecs_id(ecs_i32_t); } for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (!elem->member && elem->value && (elem->value->kind == EcsExprInitializer || elem->value->kind == EcsExprEmptyInitializer)) { continue; } const char *name = elem->member; if (!name) { if (!elem->value || elem->value->kind != EcsExprIdentifier) { flecs_expr_visit_error(script, elem->value, "invalid constant for %s '%s' (expected identifier or " "'name: value')", kind_str, ecs_get_name(world, ctx->entity)); return -1; } name = ((ecs_expr_identifier_t*)elem->value)->value; } else if (elem->operator) { flecs_expr_visit_error(script, elem->value, "invalid operator for constant '%s'", elem->member); return -1; } ecs_entity_t c = flecs_script_create_entity(v, name); if (!c) { return -1; } if (elem->member) { const ecs_type_info_t *ti = ecs_get_type_info(world, underlying); if (!ti) { flecs_expr_visit_error(script, elem->value, "underlying type is not a type"); return -1; } /* Evaluate into temporary storage. Evaluating the expression can * move the constant entity, which would invalidate a pointer into * the component storage. */ ecs_value_t value = { .ptr = ecs_os_alloca(ti->size), .type = underlying }; ecs_os_memset(value.ptr, 0, ti->size); if (flecs_script_eval_expr(v, &elem->value, &value)) { return -1; } ecs_set_id(world, c, ecs_pair(EcsConstant, underlying), flecs_ito(size_t, ti->size), value.ptr); } else { ecs_add_id(world, c, EcsConstant); } if (!ecs_is_stage(world) && !flecs_script_constant_registered( world, ctx->entity, c)) { flecs_expr_visit_error(script, elem->value, "failed to add constant '%s' to %s '%s'", name, kind_str, ecs_get_name(world, ctx->entity)); return -1; } } return 0; } int flecs_script_enum_visit( const ecs_script_visitor_ctx_t *ctx) { return flecs_script_constants_visit(ctx, false); } int flecs_script_bitmask_visit( const ecs_script_visitor_ctx_t *ctx) { return flecs_script_constants_visit(ctx, true); } #endif #ifdef FLECS_SCRIPT static ecs_script_symbol_slot_t* flecs_script_runtime_symbol_slot( ecs_script_eval_visitor_t *v, int32_t slot); static ecs_entity_t flecs_script_component_slot_src( ecs_script_eval_visitor_t *v, const ecs_script_component_slot_t *slot); static bool flecs_script_valid_lookup_path( const char *path) { int32_t template_nesting = 0; char ch; for (; (ch = path[0]); path ++) { if (ch == '<') { template_nesting ++; } else if (ch == '>') { template_nesting --; if (template_nesting < 0) { return false; } } } return template_nesting == 0; } void flecs_script_eval_error_( ecs_script_eval_visitor_t *v, const ecs_script_node_t *node, const char *fmt, ...) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); if (!node && v->base.depth > 0) { node = v->base.nodes[v->base.depth - 1]; } const char *name = v->base.script->pub.name; const char *code = v->base.script->pub.code; const char *pos = node ? node->pos : NULL; if (code && pos && (pos >= code) && (pos <= &code[ecs_os_strlen(code)])) { ecs_parser_error(name, code, pos - code, "%s", msg); } else { ecs_parser_error(name, NULL, 0, "%s", msg); } ecs_os_free(msg); } ecs_value_t* flecs_script_with_append( ecs_script_eval_visitor_t *v, const ecs_type_info_t *ti) { ecs_script_with_value_t *elem = ecs_vec_append_t( v->r->allocator, &v->r->with, ecs_script_with_value_t); elem->ti = ti; return &elem->value; } void flecs_script_with_set_count( ecs_script_eval_visitor_t *v, int32_t count) { ecs_script_with_value_t *with = ecs_vec_first(&v->r->with); int32_t i = count, until = ecs_vec_count(&v->r->with); for (; i < until; i ++) { ecs_value_t *val = &with[i].value; const ecs_type_info_t *ti = with[i].ti; if (ti) { if (ti->hooks.dtor) { flecs_type_info_dtor(val->ptr, 1, ti); } flecs_stack_free(val->ptr, ti->size); } } ecs_vec_set_count_t(v->r->allocator, &v->r->with, ecs_script_with_value_t, count); } const ecs_type_info_t* flecs_script_get_type_info( ecs_script_eval_visitor_t *v, const void *node, ecs_id_t id) { ecs_component_record_t *cr = flecs_components_ensure(v->world, id); if (!cr) { goto error; } if (!cr->type_info) { goto error; } return cr->type_info; error: { char *idstr = ecs_id_str(v->world, id); flecs_script_eval_error(v, node, "cannot set value of '%s': not a component", idstr); ecs_os_free(idstr); } return NULL; } static int32_t flecs_script_symbol_runtime_slot( const ecs_script_eval_visitor_t *v, int32_t slot) { if (slot >= v->symbol_offset) { return slot - v->symbol_offset; } return slot; } static ecs_script_symbol_slot_t* flecs_script_symbol_slot( const ecs_script_eval_visitor_t *v, int32_t slot) { ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(slot >= 0, ECS_INTERNAL_ERROR, NULL); if (slot >= v->symbol_offset) { int32_t index = slot - v->symbol_offset; ecs_assert(index < ecs_vec_count(&v->state->symbol_slots), ECS_INTERNAL_ERROR, NULL); return ecs_vec_get_t( &v->state->symbol_slots, ecs_script_symbol_slot_t, index); } ecs_vec_t *slots = &v->base.script->state.symbol_slots; ecs_assert(slot < ecs_vec_count(slots), ECS_INTERNAL_ERROR, NULL); return ecs_vec_get_t(slots, ecs_script_symbol_slot_t, slot); } ecs_entity_t flecs_script_symbol_entity( const ecs_script_eval_visitor_t *v, int32_t slot) { return flecs_script_symbol_slot(v, slot)->entity; } void flecs_script_symbol_set( ecs_script_eval_visitor_t *v, int32_t slot, ecs_entity_t entity) { ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(slot >= 0, ECS_INTERNAL_ERROR, NULL); ecs_script_symbol_slot_t *symbol = flecs_script_symbol_slot(v, slot); symbol->entity = entity; if (entity && v->scope_slot >= 0) { symbol->scope_slot = v->scope_slot; } } int flecs_script_symbol_lookup( const ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_entity_t from, const char *name, ecs_flags32_t lookup_kind, flecs_script_symbol_t *symbol) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(symbol != NULL, ECS_INTERNAL_ERROR, NULL); *symbol = (flecs_script_symbol_t){ .slot = -1, .sp = -1 }; ecs_script_eval_visitor_t *v = desc ? desc->script_visitor : NULL; const ecs_script_vars_t *vars = desc ? desc->vars : NULL; bool explicit_var = name[0] == '$'; const char *var_name = explicit_var ? &name[1] : name; if ((lookup_kind & FlecsScriptLookupVariable) && vars) { ecs_script_var_t *var = ecs_script_vars_lookup(vars, var_name); if (var) { symbol->kind = FlecsScriptSymbolVariable; symbol->sp = var->sp; return 0; } } if (explicit_var || !(lookup_kind & FlecsScriptLookupEntity)) { return -1; } if (v && v->type_visitor && !from && !(lookup_kind & FlecsScriptLookupDynamic)) { int result = flecs_script_type_symbol_lookup( v->type_visitor, name, symbol); if (!result) { return 0; } } ecs_world_t *world = script->world; ecs_entity_t result = 0; bool valid_path = flecs_script_valid_lookup_path(name); if (from && valid_path) { result = ecs_lookup_path_w_sep( world, from, name, NULL, NULL, false); } else if (valid_path) { result = ecs_lookup_path_w_sep( world, FlecsMeta, name, NULL, NULL, false); if (!result && v) { result = ecs_lookup_path_w_sep( world, v->parent, name, NULL, NULL, true); } if (!result && v) { int32_t i, using_count = ecs_vec_count(&v->r->using); ecs_entity_t *using = ecs_vec_first(&v->r->using); for (i = using_count - 1; i >= 0; i --) { result = ecs_lookup_path_w_sep( world, using[i], name, NULL, NULL, false); if (result) { break; } } } if (!result && desc && desc->lookup_action) { result = desc->lookup_action(world, name, desc->lookup_ctx); } } if (!result) { return -1; } symbol->entity = result; if (flecs_script_global_var_get(world, result, NULL).ptr) { symbol->kind = FlecsScriptSymbolGlobalVariable; } else { symbol->kind = FlecsScriptSymbolEntity; } return 0; } int flecs_script_id_elem_lookup( const ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_entity_t first, const char *name, ecs_flags32_t lookup_kind, ecs_entity_t *from_out, flecs_script_symbol_t *symbol) { ecs_entity_t from = 0; if (first) { from = flecs_get_oneof(script->world, first); } if (from_out) { from_out[0] = from; } return flecs_script_symbol_lookup( script, desc, from, name, lookup_kind, symbol); } int flecs_script_id_lookup( const ecs_script_t *script, const ecs_expr_eval_desc_t *desc, const char *first_name, const char *second_name, ecs_flags32_t lookup_kind, ecs_entity_t *first_out, ecs_id_t *id_out, const char **unresolved) { flecs_script_symbol_t symbol; if (flecs_script_id_elem_lookup(script, desc, 0, first_name, lookup_kind, NULL, &symbol) || !symbol.entity) { if (unresolved) { unresolved[0] = first_name; } return -1; } ecs_entity_t first = symbol.entity; if (first_out) { first_out[0] = first; } if (!second_name) { id_out[0] = first; return 0; } if (flecs_script_id_elem_lookup(script, desc, first, second_name, lookup_kind, NULL, &symbol) || !symbol.entity) { if (unresolved) { unresolved[0] = second_name; } return -1; } id_out[0] = ecs_pair(first, symbol.entity); return 0; } ecs_entity_t flecs_script_lookup_name( ecs_script_eval_visitor_t *v, const char *name) { flecs_script_symbol_t symbol; ecs_expr_eval_desc_t desc = flecs_script_expr_desc(v, 0); if (flecs_script_symbol_lookup(&v->base.script->pub, &desc, 0, name, FlecsScriptLookupEntity | FlecsScriptLookupDynamic, &symbol)) { return 0; } return symbol.entity; } static int flecs_script_eval_name( ecs_script_eval_visitor_t *v, ecs_expr_node_t **expr, char **out) { ecs_value_t value = { .type = ecs_id(ecs_string_t), .ptr = out }; return flecs_script_eval_expr(v, expr, &value); } bool flecs_script_is_builtin( const ecs_world_t *world, ecs_entity_t e) { return ecs_has_pair(world, e, EcsChildOf, EcsFlecsCore); } static bool flecs_script_is_script_scope( const ecs_world_t *world, ecs_entity_t script, ecs_entity_t e) { while (script) { if (script == e) { return true; } script = ecs_get_target(world, script, EcsChildOf, 0); } return false; } bool flecs_script_can_own_entity( const ecs_world_t *world, ecs_entity_t script, ecs_entity_t e) { if (flecs_script_is_builtin(world, e)) { return false; } if (flecs_script_is_script_scope(world, script, e)) { return false; } if (!script) { return true; } if (ecs_has_id(world, e, EcsModule)) { return false; } ecs_entity_t owner = ecs_get_target(world, e, ecs_id(EcsScript), 0); if (owner && (owner != script)) { return false; } return true; } static void flecs_script_apply_with( ecs_script_eval_visitor_t *v, ecs_entity_t entity) { ecs_script_with_value_t *with = ecs_vec_first(&v->r->with); int32_t i, count = ecs_vec_count(&v->r->with); for (i = 0; i < count; i ++) { ecs_value_t *value = &with[i].value; if (value->ptr) { const ecs_type_info_t *ti = with[i].ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_set_id(v->world, entity, value->type, flecs_ito(size_t, ti->size), value->ptr); } else { ecs_add_id(v->world, entity, value->type); } } } ecs_entity_t flecs_script_new_entity( ecs_script_eval_visitor_t *v, ecs_entity_t parent, const char *name, bool non_fragmenting_parent) { ecs_entity_desc_t desc = {0}; if (non_fragmenting_parent) { desc.id = ecs_new_w_parent(v->world, parent, name); } else { desc.parent = parent; desc.name = name; } ecs_entity_t result = ecs_entity_init(v->world, &desc); if (result && v->script_tag && flecs_script_can_own_entity( v->world, v->script_entity, result)) { ecs_add_id(v->world, result, v->script_tag); } return result; } ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, const char *name) { ecs_entity_t result = flecs_script_new_entity(v, v->parent, name, v->entity && v->entity->node && v->entity->node->non_fragmenting_parent); if (result) { flecs_script_apply_with(v, result); } return result; } int flecs_script_eval_id_elem( ecs_script_eval_visitor_t *v, const void *node, const char *const *name, ecs_entity_t eval, int32_t slot, int32_t sp, ecs_entity_t *elem) { ecs_entity_t result = eval; v->id_ref = NULL; if (name) { if (!name[0]) { flecs_script_eval_error(v, node, "failed to evaluate entity name"); return -1; } result = flecs_script_lookup_name(v, name[0]); } else if (slot != -1) { result = flecs_script_symbol_entity(v, slot); } else if (sp != -1) { ecs_script_var_t *var = ecs_script_vars_from_sp(v->vars, sp); result = 0; if (var && var->value.ptr) { if (var->value.type == ecs_id(ecs_entity_t)) { result = *(ecs_entity_t*)var->value.ptr; } else if (var->value.type == ecs_id(ecs_script_template_ref_t)) { v->id_ref = var->value.ptr; result = v->id_ref->type; } } } if (result == EcsWildcard || result == EcsAny) { flecs_script_eval_error(v, node, "cannot use wildcard entity as id element"); return -1; } elem[0] = result; return 0; } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_index_elem( ecs_script_eval_visitor_t *v, const void *node, const ecs_script_id_t *id, int32_t index, ecs_entity_t *out) { ecs_script_var_t *var = ecs_script_vars_from_sp(v->vars, id->index_sp); const ecs_vec_t *vec = var ? var->value.ptr : NULL; int32_t count = vec ? ecs_vec_count(vec) : 0; v->id_ref = NULL; if (index < 0 || index >= count) { flecs_script_eval_error(v, node, "index %d out of range for vector prop '%s' with %d element(s)", index, id->first, count); return -1; } const ecs_script_template_ref_t *ref = ecs_vec_get_t( vec, ecs_script_template_ref_t, index); ecs_entity_t elem = ref->type; if (!elem || !ecs_is_alive(v->world, elem) || (id->interface && !flecs_script_template_interface_accepts(v->world, elem, id->interface))) { flecs_script_eval_error(v, node, "'%s' at index %d of vector prop '%s' is not %s", elem ? flecs_errstr(ecs_get_path(v->world, elem)) : "0", index, id->first, flecs_errstr_1( flecs_script_template_expected_str(v->world, id->interface))); return -1; } v->id_ref = ref; *out = elem; return 0; } int flecs_script_eval_id_resolve( ecs_script_eval_visitor_t *v, const void *node, const ecs_script_id_t *id, const char *first_name, const char *second_name, int32_t index, ecs_id_t *out) { ecs_entity_t first, second; if (id->index_expr) { if (flecs_script_eval_index_elem(v, node, id, index, &first)) { return -1; } } else if (flecs_script_eval_id_elem(v, node, id->first_expr ? &first_name : NULL, id->first_eval, id->first_symbol, id->first_sp, &first)) { return -1; } if (!first) { flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->first); return -1; } if (!id->second) { *out = id->flag | first; return 0; } if (flecs_script_eval_id_elem(v, node, id->second_expr ? &second_name : NULL, id->second_eval, id->second_symbol, id->second_sp, &second)) { return -1; } if (!second) { flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->second); return -1; } *out = id->flag | ecs_pair(first, second); return 0; } typedef struct flecs_script_id_args_t { char *first; char *second; int32_t index; } flecs_script_id_args_t; static int flecs_script_eval_id_args( ecs_script_eval_visitor_t *v, ecs_script_id_t *id, flecs_script_id_args_t *args) { ecs_os_zeromem(args); if (id->first_expr && flecs_script_eval_name(v, &id->first_expr, &args->first)) { return -1; } if (id->second_expr && flecs_script_eval_name(v, &id->second_expr, &args->second)) { return -1; } if (id->index_expr) { ecs_value_t value = { .type = ecs_id(ecs_i32_t), .ptr = &args->index }; return flecs_script_eval_expr(v, &id->index_expr, &value); } return 0; } static int flecs_script_eval_id( ecs_script_eval_visitor_t *v, void *node, ecs_script_id_t *id) { if (!id->first_expr && !id->second_expr && !id->index_expr) { return flecs_script_eval_id_resolve( v, node, id, NULL, NULL, 0, &id->eval); } flecs_script_id_args_t args; int result = flecs_script_eval_id_args(v, id, &args); if (!result) { result = flecs_script_eval_id_resolve(v, node, id, args.first, args.second, args.index, &id->eval); } ecs_os_free(args.first); ecs_os_free(args.second); return result; } int flecs_script_eval_interface_id( ecs_script_eval_visitor_t *v, const void *node, const ecs_script_id_t *id, ecs_id_t *out) { ecs_entity_t tmpl; if (flecs_script_eval_id_elem(v, node, NULL, id->first_eval, id->first_symbol, id->first_sp, &tmpl)) { return -1; } const char *expected = flecs_script_template_interface_error( v->world, tmpl, id->interface); if (!expected) { *out = id->flag | tmpl; return 0; } if (!tmpl) { flecs_script_eval_error(v, node, "template prop '%s' has no value: expected %s", id->first, expected); } else { flecs_script_eval_error(v, node, "'%s' passed to template prop '%s' is not %s", flecs_errstr(ecs_get_path(v->world, tmpl)), id->first, expected); } return -1; } ecs_expr_eval_desc_t flecs_script_expr_desc( ecs_script_eval_visitor_t *v, ecs_entity_t type) { return (ecs_expr_eval_desc_t){ .name = v->base.script->pub.name, .vars = v->vars, .type = type, .runtime = v->r, .disable_dynamic_variable_binding = true, .script_visitor = v }; } int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, ecs_expr_node_t **expr_ptr, ecs_value_t *value) { ecs_assert(*expr_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((*expr_ptr)->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_expr_eval_desc_t desc = flecs_script_expr_desc(v, value->type); return flecs_expr_visit_eval( &v->base.script->pub, *expr_ptr, &desc, value); } static void flecs_script_eval_scope_enter( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node, flecs_script_scope_state_t *state) { state->parent = v->parent; state->scope_slot = v->scope_slot; v->scope_slot = node->scope_slot; if (node->scope_slot >= 0 && node->scope_slot < ecs_vec_count(&v->state->scope_slots)) { ecs_vec_get_t(&v->state->scope_slots, int32_t, node->scope_slot)[0] = v->visit; } for (int32_t i = v->base.depth - 2; i >= 0; i --) { if (v->base.nodes[i]->kind == EcsAstScope) { node->parent = (ecs_script_scope_t*)v->base.nodes[i]; break; } } v->vars = flecs_script_vars_push(v->vars, &v->r->stack, v->r->allocator); } static void flecs_script_eval_scope_leave( ecs_script_eval_visitor_t *v, const flecs_script_scope_state_t *state) { v->vars = ecs_script_vars_pop(v->vars); v->parent = state->parent; v->scope_slot = state->scope_slot; } static int flecs_script_apply_tree_scope( ecs_script_scope_t *scope, void *ctx) { ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); for (int32_t i = 0; i < ecs_vec_count(&scope->stmts); i ++) { ecs_script_node_t *stmt = stmts[i]; if (stmt->kind == EcsAstTemplate || stmt->kind == EcsAstFunction) { continue; } if (stmt->kind == EcsAstEntity) { ((ecs_script_entity_t*)stmt)->non_fragmenting_parent = *(bool*)ctx; } if (stmt->kind == EcsAstScope) { flecs_script_apply_tree_scope((ecs_script_scope_t*)stmt, ctx); } else { flecs_script_visit_scopes(stmt, flecs_script_apply_tree_scope, ctx); } } return 0; } static void flecs_script_apply_non_fragmenting_childof( ecs_script_entity_t *node, bool enabled) { node->non_fragmenting_parent = enabled; if (node->scope) { flecs_script_apply_tree_scope(node->scope, &enabled); } } void flecs_script_apply_tree_annot( ecs_script_annot_t *annot, ecs_script_entity_t *node) { if (ecs_os_strcmp(annot->name, "tree")) { return; } if (!ecs_os_strcmp(annot->expr, "Parent")) { flecs_script_apply_non_fragmenting_childof(node, true); } else if (!ecs_os_strcmp(annot->expr, "ChildOf")) { flecs_script_apply_non_fragmenting_childof(node, false); } } int flecs_script_apply_annot( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node, ecs_entity_t entity, ecs_script_annot_t *annot) { ecs_entity_t e = entity; if (!ecs_os_strcmp(annot->name, "name")) { ecs_doc_set_name(v->world, e, annot->expr); } else if (!ecs_os_strcmp(annot->name, "brief")) { ecs_doc_set_brief(v->world, e, annot->expr); } else if (!ecs_os_strcmp(annot->name, "detail")) { ecs_doc_set_detail(v->world, e, annot->expr); } else if (!ecs_os_strcmp(annot->name, "link")) { ecs_doc_set_link(v->world, e, annot->expr); } else if (!ecs_os_strcmp(annot->name, "color")) { ecs_doc_set_color(v->world, e, annot->expr); } else if (!ecs_os_strcmp(annot->name, "uuid")) { ecs_doc_set_uuid(v->world, e, annot->expr); } else if (!ecs_os_strcmp(annot->name, "tree")) { if (ecs_os_strcmp(annot->expr, "Parent") && ecs_os_strcmp(annot->expr, "ChildOf")) { flecs_script_eval_error(v, annot, "invalid value for tree annotation: '%s' (expected 'Parent' or 'ChildOf')", annot->expr); return -1; } flecs_script_apply_tree_annot(annot, node); } else { flecs_script_eval_error(v, annot, "unknown annotation '%s'", annot->name); return -1; } return 0; } static void flecs_script_add_entity_kind( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_entity_t kind, bool w_expr) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(v->world)); ecs_stage_t *stage = world->stages[0]; const ecs_stage_ensure_t *ensure_add = stage->ensure_add; ecs_stage_ensure_t ensure_add_type = { entity, { &kind, 1 } }; if (w_expr) { stage->ensure_add = &ensure_add_type; } ecs_add_id(v->world, entity, kind); stage->ensure_add = ensure_add; } void flecs_script_scope_add_ids( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_script_scope_t *scope, const ecs_id_t *ids, int32_t count) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(v->world)); ecs_stage_t *stage = world->stages[0]; const ecs_stage_ensure_t *ensure_add = stage->ensure_add; ecs_stage_ensure_t ensure_add_type = { entity, { ecs_vec_first_t(&scope->set_components, ecs_id_t), ecs_vec_count(&scope->set_components) }}; if (ensure_add_type.ids.count) { stage->ensure_add = &ensure_add_type; } flecs_add_ids(v->world, entity, ids, count); stage->ensure_add = ensure_add; } static int flecs_script_self_template_check( ecs_script_eval_visitor_t *v, const void *node, ecs_entity_t entity, const char *name) { const EcsScript *tmpl = ecs_get(v->world, entity, EcsScript); if (tmpl && tmpl->template_) { flecs_script_eval_error(v, node, "cannot instantiate template '%s' on itself", name); return -1; } return 0; } int flecs_script_eval_entity_enter_w_name( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node, const ecs_value_t *name, flecs_script_entity_state_t *state) { state->node = node; state->eval = 0; state->eval_kind = 0; state->prev_entity = v->entity; state->prev_is_with_scope = v->is_with_scope; state->prev_template_entity = v->template_entity; state->prev_force = v->force; state->symbol = -1; state->for_slot = -1; state->created = false; if (v->entity && v->entity->eval_kind && !node->kind && !ecs_vec_count(&node->scope->stmts) && ecs_has( v->world, v->entity->eval_kind, EcsScriptVisitor)) { const char *parent_kind = v->entity->node->kind; flecs_script_eval_error(v, node, "invalid declaration for '%s', did you mean '%s %s(%s ...)'?", node->name ? node->name : "", parent_kind ? parent_kind : "struct", v->entity->node->name, node->name ? node->name : ""); return -1; } if (node->kind) { state->eval_kind = node->eval_kind; if (node->kind_symbol != -1) { state->eval_kind = flecs_script_symbol_entity( v, node->kind_symbol); } else if (node->kind_sp != -1) { ecs_script_var_t *var = ecs_script_vars_from_sp( v->vars, node->kind_sp); if (var && var->value.ptr && var->value.type == ecs_id(ecs_entity_t)) { state->eval_kind = *(ecs_entity_t*)var->value.ptr; } } if (!state->eval_kind) { flecs_script_eval_error(v, node, "unresolved identifier '%s'", node->kind); return -1; } } if (node->symbol != -1) { state->symbol = flecs_script_symbol_runtime_slot(v, node->symbol); state->eval = flecs_script_symbol_entity(v, node->symbol); if (state->eval && (!ecs_is_alive(v->world, state->eval) || ecs_get_target(v->world, state->eval, EcsChildOf, 0) != v->parent)) { state->eval = 0; } } else if (node->eval && ecs_is_alive(v->world, node->eval)) { state->eval = node->eval; } bool apply_with = state->eval != 0; if (node->name_expr) { bool is_ref = name && name->type == ecs_id(ecs_entity_t); if (!name || (!is_ref && !*(char**)name->ptr)) { flecs_script_eval_error(v, node, "failed to evaluate entity name"); return -1; } ecs_entity_t eval = is_ref ? *(ecs_entity_t*)name->ptr : flecs_script_create_entity(v, *(char**)name->ptr); if (!eval) { return -1; } if (is_ref) { flecs_script_apply_with(v, eval); } else if (state->eval && state->eval != eval && ecs_is_alive(v->world, state->eval)) { ecs_delete(v->world, state->eval); } state->eval = eval; state->created = true; } else if (!state->eval) { state->eval = flecs_script_create_entity(v, node->name); state->created = true; } if (apply_with && !node->name_expr && v->force) { flecs_script_apply_with(v, state->eval); } if (node->symbol != -1) { flecs_script_symbol_set(v, node->symbol, state->eval); } if (v->for_slot >= 0 && v->for_slot < ecs_vec_count(&v->state->for_slots) && !flecs_script_is_builtin(v->world, state->eval)) { bool named = false; flecs_script_for_slot_track(v->world, ecs_vec_get_t( &v->state->for_slots, ecs_script_for_slot_t, v->for_slot), state->eval, v->visit, &named); if (named) { state->for_slot = v->for_slot; } } if (v->body_template && (state->created || v->force)) { ecs_add_pair( v->world, state->eval, EcsScriptTemplate, v->body_template); } v->entity = state; if (state->eval_kind && flecs_script_eval_force( v, &node->node, state->created || v->force)) { if (state->eval_kind == state->eval && flecs_script_self_template_check( v, node, state->eval_kind, node->kind)) { goto error; } flecs_script_add_entity_kind( v, state->eval, state->eval_kind, node->kind_w_expr); } int32_t i, count = ecs_vec_count(&v->r->annot); if (count) { ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); for (i = 0; i < count ; i ++) { if (flecs_script_apply_annot(v, node, state->eval, annots[i])) { goto error; } } ecs_vec_clear(&v->r->annot); } v->is_with_scope = false; v->template_entity = 0; v->force = flecs_script_eval_force(v, &node->node, state->prev_force); return 0; error: v->entity = state->prev_entity; v->force = state->prev_force; return -1; } int flecs_script_eval_entity_enter( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node, flecs_script_entity_state_t *state) { ecs_value_t name = {0}; if (node->name_expr) { name.type = node->name_expr->type; if (flecs_script_eval_expr(v, &node->name_expr, &name)) { return -1; } } int result = flecs_script_eval_entity_enter_w_name( v, node, name.ptr ? &name : NULL, state); if (name.ptr) { ecs_ptr_free(v->world, name.type, name.ptr); } return result; } void flecs_script_eval_entity_leave( ecs_script_eval_visitor_t *v, flecs_script_entity_state_t *state) { ecs_script_entity_t *node = state->node; v->template_entity = state->prev_template_entity; v->is_with_scope = state->prev_is_with_scope; v->force = state->prev_force; if (state->eval_kind) { if (!node->kind_w_expr) { if (ecs_get_type_info(v->world, state->eval_kind) != NULL) { ecs_modified_id(v->world, state->eval, state->eval_kind); } } } v->entity = state->prev_entity; } ecs_entity_t flecs_script_get_src( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_id_t id) { if (entity == EcsVariable) { if (ECS_IS_PAIR(id)) { return ecs_pair_first(v->world, id); } else { return id & ECS_COMPONENT_MASK; } } return entity; } bool flecs_script_can_default_ctor( ecs_world_t *world, ecs_id_t component) { /* Check if tag is a component, and if so, if it can be default * constructed. */ ecs_entity_t type = ecs_get_typeid(world, component); if (type) { const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (ti->hooks.flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) { return false; } } return true; } void flecs_script_track_component( ecs_script_eval_visitor_t *v, int32_t component_slot, ecs_id_t component) { if (v->entity && v->entity->for_slot >= 0 && v->entity->for_slot < ecs_vec_count(&v->state->for_slots)) { flecs_script_for_slot_track_component(ecs_vec_get_t( &v->state->for_slots, ecs_script_for_slot_t, v->entity->for_slot), v->entity->eval, component, v->visit); return; } if (!v->entity || v->entity->symbol < 0 || component_slot < 0 || component_slot >= ecs_vec_count(&v->state->component_slots)) { return; } int32_t entity_slot = v->entity->symbol; ecs_script_component_slot_t *slot = ecs_vec_get_t( &v->state->component_slots, ecs_script_component_slot_t, component_slot); if (slot->component && (slot->component != component || slot->entity_slot != entity_slot)) { ecs_entity_t src = flecs_script_component_slot_src(v, slot); if (src && ecs_is_alive(v->world, src)) { ecs_remove_id(v->world, src, slot->component); } } slot->entity_slot = entity_slot; slot->component = component; slot->scope_slot = v->scope_slot; } static int flecs_script_default_ctor_check( ecs_script_eval_visitor_t *v, const void *node, const ecs_script_id_t *id, ecs_id_t eval) { if (flecs_script_can_default_ctor(v->world, eval)) { return 0; } if (id->second) { flecs_script_eval_error(v, node, "cannot add (%s, %s), type is not default constructible", id->first, id->second); } else { flecs_script_eval_error(v, node, "cannot add %s, type is not default constructible", id->first); } return -1; } static int flecs_script_set_template_ref( ecs_script_eval_visitor_t *v, const void *node, ecs_entity_t src, ecs_id_t id) { const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, id); if (!ti) { return -1; } void *value = ecs_os_alloca(ti->size); if (!flecs_type_info_ctor(value, 1, ti)) { ecs_os_memset(value, 0, ti->size); } int result = flecs_script_template_ref_apply( v->world, v->id_ref, ti, value); if (!result) { ecs_set_id(v->world, src, id, flecs_itosize(ti->size), value); } flecs_type_info_dtor(value, 1, ti); return result; } static int flecs_script_eval_struct_base( ecs_script_eval_visitor_t *v, const ecs_script_tag_t *node, ecs_id_t id) { const char *name = v->entity->node->name; ecs_entity_t base = ecs_pair_second(v->world, id); if (!ecs_has(v->world, base, EcsStruct)) { flecs_script_eval_error(v, node, "base '%s' of struct '%s' is not a struct type", node->id.second, name); return -1; } if (base == v->entity->eval) { flecs_script_eval_error(v, node, "struct '%s' cannot inherit from itself", name); return -1; } if (flecs_struct_is_derived_from(v->world, base, v->entity->eval)) { flecs_script_eval_error(v, node, "inheritance cycle between struct '%s' and base '%s'", name, node->id.second); return -1; } int32_t i = 0; ecs_entity_t existing; while ((existing = ecs_get_target( v->world, v->entity->eval, EcsIsA, i ++))) { if (existing != base && ecs_has(v->world, existing, EcsStruct)) { flecs_script_eval_error(v, node, "struct '%s' cannot have multiple base types", name); return -1; } } return 0; } int flecs_script_eval_tag_id( ecs_script_eval_visitor_t *v, const ecs_script_tag_t *node, ecs_id_t id, bool resolved) { if (id == ecs_id(EcsParent)) { flecs_script_eval_error(v, node, "Parent component cannot be added as tag (set to valid parent)"); return -1; } if (node->id.interface && id && !ECS_IS_PAIR(id) && !ecs_is_alive(v->world, id)) { flecs_script_eval_interface_id(v, node, &node->id, &id); return -1; } if (!resolved && flecs_script_default_ctor_check(v, node, &node->id, id)) { return -1; } ecs_assert(v->entity != NULL, ECS_INTERNAL_ERROR, NULL); if (v->entity->eval_kind == ecs_id(EcsStruct) && ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsIsA && flecs_script_eval_struct_base(v, node, id)) { return -1; } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, id); if (node->id.interface && v->id_ref && v->id_ref->value) { if (flecs_script_set_template_ref(v, node, src, id)) { return -1; } } else { ecs_add_id(v->world, src, id); } flecs_script_track_component(v, node->component_slot, id); return 0; } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_tag( ecs_script_eval_visitor_t *v, ecs_script_tag_t *node) { bool resolved = node->id.eval != 0; if (flecs_script_eval_id(v, node, &node->id)) { return -1; } return flecs_script_eval_tag_id(v, node, node->id.eval, resolved); } int flecs_script_eval_component_check( ecs_script_eval_visitor_t *v, const ecs_script_component_t *node, ecs_id_t id, bool resolved) { if (!resolved && flecs_script_default_ctor_check(v, node, &node->id, id)) { return -1; } ecs_assert(v->entity != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_script_get_src(v, v->entity->eval, id) == id) { return flecs_script_self_template_check(v, node, id, node->id.first); } return 0; } int flecs_script_eval_component_visit( ecs_script_eval_visitor_t *v, const ecs_script_component_t *node, ecs_id_t id, const EcsScriptVisitor *visitor) { ecs_assert(v->entity != NULL, ECS_INTERNAL_ERROR, NULL); if (v->entity->eval == EcsVariable) { flecs_script_eval_error(v, node, "cannot use '%s' in singleton scope", node->id.first); return -1; } if (node->expr && node->expr->kind != EcsExprInitializer && node->expr->kind != EcsExprEmptyInitializer) { flecs_script_eval_error(v, node, "expected initializer for '%s'", node->id.first); return -1; } ecs_script_visitor_ctx_t ctx = { .world = v->world, .entity = v->entity->eval, .kind = id, .initializer = (ecs_expr_initializer_t*)node->expr, .eval = v, .ctx = visitor->ctx }; ecs_assert(visitor->visit != NULL, ECS_INVALID_OPERATION, "EcsScriptVisitor component without visit callback"); return visitor->visit(&ctx); } int flecs_script_eval_component_add( ecs_script_eval_visitor_t *v, const ecs_script_component_t *node, ecs_id_t id) { ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, id); if (node->id.interface) { if (flecs_script_set_template_ref(v, node, src, id)) { return -1; } } else { ecs_add_id(v->world, src, id); } flecs_script_track_component(v, node->component_slot, id); return 0; } static int flecs_script_eval_component( ecs_script_eval_visitor_t *v, ecs_script_component_t *node) { bool resolved = node->id.eval != 0; if (node->id.interface && !node->id.index_expr) { if (flecs_script_eval_interface_id( v, node, &node->id, &node->id.eval)) { return -1; } } else if (flecs_script_eval_id(v, node, &node->id)) { return -1; } if (!node->id.second) { const EcsScriptVisitor *visitor = ecs_get( v->world, node->id.eval, EcsScriptVisitor); if (visitor) { return flecs_script_eval_component_visit( v, node, node->id.eval, visitor); } } if (flecs_script_eval_component_check(v, node, node->id.eval, resolved)) { return -1; } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); if (node->expr) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { return -1; } bool needs_set = ti->hooks.on_replace != NULL; bool partial = false; if (node->expr->kind == EcsExprInitializer) { partial = ((ecs_expr_initializer_t*)node->expr)->is_partial; } const void *existing = NULL; if (partial) { existing = ecs_get_id(v->world, src, node->id.eval); } /* Evaluate into temporary storage. Evaluating the expression can move * the entity, which would invalidate a pointer into the component * storage while members are still being assigned. */ ecs_value_t value = { .ptr = ecs_os_alloca(ti->size), .type = ti->component }; /* For full assignments, assign entire value including members not set * by expression. This prevents uninitialized or unexpected values. For * partial assignments, members not set by the expression keep the * existing component value. */ if (!ti->hooks.ctor) { ecs_os_memset(value.ptr, 0, ti->size); } else { flecs_type_info_ctor(value.ptr, 1, ti); } if (node->id.interface && v->id_ref && v->id_ref->value) { if (flecs_script_template_ref_apply( v->world, v->id_ref, ti, value.ptr)) { flecs_type_info_dtor(value.ptr, 1, ti); return -1; } } else if (existing) { ecs_ptr_copy_w_type_info(v->world, ti, value.ptr, existing); } if (flecs_script_eval_expr(v, &node->expr, &value)) { flecs_type_info_dtor(value.ptr, 1, ti); return -1; } if (needs_set) { ecs_set_id(v->world, src, node->id.eval, flecs_itosize(ti->size), value.ptr); flecs_type_info_dtor(value.ptr, 1, ti); } else { void *dst = ecs_ensure_id(v->world, src, node->id.eval, flecs_ito(size_t, ti->size)); flecs_type_info_move_dtor(dst, value.ptr, 1, ti); ecs_modified_id(v->world, src, node->id.eval); } } else { return flecs_script_eval_component_add(v, node, node->id.eval); } flecs_script_track_component( v, node->component_slot, node->id.eval); return 0; } int flecs_script_eval_with_tag_id( ecs_script_eval_visitor_t *v, const ecs_script_tag_t *node, ecs_id_t id) { if (node->id.interface && v->id_ref && v->id_ref->value) { const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, id); if (!ti) { return -1; } ecs_value_t *value = flecs_script_with_append(v, ti); value->type = id; value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); flecs_type_info_ctor(value->ptr, 1, ti); return flecs_script_template_ref_apply( v->world, v->id_ref, ti, value->ptr); } ecs_value_t *value = flecs_script_with_append(v, NULL); value->type = id; value->ptr = NULL; return 0; } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_with_tag( ecs_script_eval_visitor_t *v, ecs_script_tag_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } return flecs_script_eval_with_tag_id(v, node, node->id.eval); } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_with_component( ecs_script_eval_visitor_t *v, ecs_script_component_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { return -1; } ecs_value_t *value = flecs_script_with_append(v, ti); value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); value->type = ti->component; // Expression parser needs actual type flecs_type_info_ctor(value->ptr, 1, ti); if (node->id.interface && flecs_script_template_ref_apply( v->world, v->id_ref, ti, value->ptr)) { return -1; } if (flecs_script_eval_expr(v, &node->expr, value)) { return -1; } value->type = node->id.eval; // Restore so we're adding actual id return 0; } int flecs_script_eval_with_enter( ecs_script_eval_visitor_t *v, ecs_script_with_t *node, flecs_script_with_state_t *state) { state->with_count = ecs_vec_count(&v->r->with); state->cursor = flecs_stack_get_cursor(&v->r->stack); state->is_with_scope = v->is_with_scope; state->force = v->force; int32_t depth = v->base.depth; ecs_script_node_t **nodes = ecs_vec_first(&node->expressions->stmts); int32_t i, count = ecs_vec_count(&node->expressions->stmts); ecs_assert(depth + 1 < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); v->base.nodes[v->base.depth ++] = (ecs_script_node_t*)node->expressions; for (i = 0; i < count; i ++) { v->base.prev = i ? nodes[i - 1] : NULL; v->base.next = i + 1 < count ? nodes[i + 1] : NULL; ecs_assert(v->base.depth < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); v->base.nodes[v->base.depth ++] = nodes[i]; if (flecs_script_eval_node(&v->base, nodes[i])) { v->base.depth = depth; flecs_script_eval_with_leave(v, state); return -1; } v->base.depth --; } v->base.depth = depth; v->is_with_scope = true; v->force = flecs_script_eval_force(v, &node->node, state->force); return 0; } void flecs_script_eval_with_leave( ecs_script_eval_visitor_t *v, const flecs_script_with_state_t *state) { v->is_with_scope = state->is_with_scope; v->force = state->force; flecs_script_with_set_count(v, state->with_count); flecs_stack_restore_cursor(&v->r->stack, state->cursor); } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) { ecs_allocator_t *a = v->r->allocator; int32_t len = ecs_os_strlen(node->name); bool wildcard = len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*"); ecs_entity_t from = node->eval; ecs_assert(from != 0, ECS_INTERNAL_ERROR, NULL); if (wildcard) { ecs_iter_t it = ecs_children(v->world, from); while (ecs_children_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_vec_append_t( a, &v->r->using, ecs_entity_t)[0] = it.entities[i]; } } } else { ecs_vec_append_t(a, &v->r->using, ecs_entity_t)[0] = from; } return 0; } static int flecs_script_eval_module( ecs_script_eval_visitor_t *v, ecs_script_module_t *node) { ecs_assert(node->eval != 0, ECS_INTERNAL_ERROR, NULL); v->module = node->eval; v->parent = node->eval; return 0; } ecs_script_computed_t* flecs_script_computed_get( ecs_script_eval_visitor_t *v, const ecs_script_var_node_t *node) { if (!node->computed || node->computed > ecs_vec_count(&v->state->computed)) { return NULL; } return ecs_vec_get_t(&v->state->computed, ecs_script_computed_t, node->computed - 1); } static bool flecs_script_computed_equals( const ecs_type_info_t *ti, const void *a, const void *b) { if (ti->hooks.equals && !(ti->hooks.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL)) { return flecs_type_info_equals(a, b, ti); } if (!ti->hooks.ctor && !ti->hooks.copy && !ti->hooks.move && !ti->hooks.dtor) { return !ecs_os_memcmp(a, b, ti->size); } return false; } bool flecs_script_computed_store( ecs_script_eval_visitor_t *v, ecs_script_computed_t *slot, int32_t index, const ecs_type_info_t *ti, const void *value) { bool changed = true; if (!slot->ptr) { slot->ptr = ecs_os_malloc(ti->size); flecs_type_info_ctor(slot->ptr, 1, ti); slot->ti = ti; } else if (slot->ti != ti) { if (slot->ti && slot->ti->hooks.dtor) { flecs_type_info_dtor(slot->ptr, 1, slot->ti); } ecs_os_free(slot->ptr); slot->ptr = ecs_os_malloc(ti->size); flecs_type_info_ctor(slot->ptr, 1, ti); slot->ti = ti; slot->valid = false; } else if (slot->valid) { changed = !flecs_script_computed_equals(ti, slot->ptr, value); } if (changed) { if (ti->hooks.copy) { ti->hooks.copy(slot->ptr, value, 1, ti); } else { ecs_os_memcpy(slot->ptr, value, ti->size); } v->internal |= (uint64_t)1 << index; } slot->valid = true; return changed; } static ecs_script_var_t* flecs_script_const_declare( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_var_t *var = ecs_script_vars_declare(v->vars, v->template_entity ? NULL : node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); } return var; } int flecs_script_eval_const_cached( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_computed_t *slot = flecs_script_computed_get(v, node); ecs_assert(slot != NULL && slot->valid, ECS_INTERNAL_ERROR, NULL); ecs_script_var_t *var = flecs_script_const_declare(v, node); if (!var) { return -1; } flecs_script_const_var_init( var, slot->ti, node->eval_type, slot->ptr, false); return 0; } int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node, bool export) { const char *kind_str = node->node.kind == EcsAstExportMut ? "mut" : "const"; /* Declare variable. If this variable is declared while instantiating a * template, the variable sp has already been resolved in all expressions * that used it, so we don't need to create the variable with a name. */ ecs_script_var_t *var = NULL; if (!export) { var = flecs_script_const_declare(v, node); if (!var) { return -1; } } ecs_entity_t type = node->eval_type; ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = ecs_get_type_info(v->world, type); if (!ti) { flecs_script_eval_error(v, node, "invalid type %s for variable '%s'", flecs_errstr(ecs_get_path(v->world, type)), node->name); return -1; } ecs_value_t result = { .type = type, .ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment) }; flecs_type_info_ctor(result.ptr, 1, ti); int ret = -1; if (flecs_script_eval_expr(v, &node->expr, &result)) { flecs_script_eval_error(v, node, "failed to evaluate expression for %s variable '%s'", kind_str, node->name); goto done; } if (!export) { ecs_script_computed_t *slot = flecs_script_computed_get(v, node); if (!slot) { flecs_script_const_var_init(var, ti, type, result.ptr, true); return 0; } flecs_script_computed_store(v, slot, node->computed - 1, ti, result.ptr); flecs_script_const_var_init(var, ti, type, slot->ptr, false); ret = 0; goto done; } ecs_entity_t const_var = node->node.kind == EcsAstExportMut ? ecs_mut_var(v->world, { .parent = v->parent, .name = node->name, .type = type, .value = result.ptr }) : ecs_const_var(v->world, { .parent = v->parent, .name = node->name, .type = type, .value = result.ptr }); if (!const_var) { flecs_script_eval_error(v, node, "failed to create exported %s variable '%s'", kind_str, node->name); goto done; } if (v->script_tag) { ecs_add_id(v->world, const_var, v->script_tag); } if (node->symbol != -1) { flecs_script_symbol_set(v, node->symbol, const_var); } ret = 0; done: if (ti->hooks.dtor) { flecs_type_info_dtor(result.ptr, 1, ti); } flecs_stack_free(result.ptr, ti->size); return ret; } int flecs_script_eval_pair_elem( ecs_script_eval_visitor_t *v, const void *node, const char *const *name, const char *str, ecs_entity_t eval, int32_t symbol, int32_t sp, ecs_entity_t *out) { if (flecs_script_eval_id_elem(v, node, name, eval, symbol, sp, out)) { return -1; } if (*out) { return 0; } if (symbol == -1) { flecs_script_eval_error(v, node, "unresolved identifier '%s'", str); return -1; } *out = flecs_script_create_entity(v, str); if (!*out) { return -1; } flecs_script_symbol_set(v, symbol, *out); return 0; } void flecs_script_eval_pair_scope_leave( ecs_script_eval_visitor_t *v, const flecs_script_pair_scope_state_t *state) { if (state->second) { ecs_value_t *value = &ecs_vec_get_t( &v->r->with, ecs_script_with_value_t, v->with_relationship_sp)->value; value->type = ecs_pair(v->with_relationship, state->second); } else { flecs_script_with_set_count(v, v->with_relationship_sp); } v->with_relationship = state->with_relationship; v->with_relationship_sp = state->with_relationship_sp; v->force = state->force; } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, ecs_script_annot_t *node) { ecs_vec_append_t(v->r->allocator, &v->r->annot, ecs_script_annot_t*)[0] = node; return 0; } void flecs_script_user_function_bind( ecs_script_eval_visitor_t *v, const ecs_script_user_function_t *uf, int32_t argc, const ecs_value_t *argv, bool named) { ecs_allocator_t *a = v->r->allocator; int32_t i, count = ecs_vec_count(&uf->using); ecs_entity_t *using = ecs_vec_first(&uf->using); for (i = 0; i < count; i ++) { ecs_vec_append_t(a, &v->r->using, ecs_entity_t)[0] = using[i]; } v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); ecs_script_fn_param_t *params = ecs_vec_first(&uf->node->params); count = ecs_vec_count(&uf->node->params); for (i = 0; i < argc && i < count; i ++) { const ecs_type_info_t *ti = ecs_get_type_info(v->world, argv[i].type); ecs_script_var_t *var = ecs_script_vars_declare( v->vars, named ? params[i].name : NULL); var->value.type = argv[i].type; var->type_info = ti; if (ti) { var->value.ptr = flecs_stack_calloc( &v->r->stack, ti->size, ti->alignment); flecs_type_info_ctor(var->value.ptr, 1, ti); ecs_ptr_copy_w_type_info(v->world, ti, var->value.ptr, argv[i].ptr); var->owned = true; } else { var->value.ptr = argv[i].ptr; var->owned = false; } } } void flecs_script_user_function_callback( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { ecs_world_t *world = ctx->world; ecs_world_t *real_world = world; flecs_stage_from_world(&real_world); bool failed = false; const EcsScriptFunction *fcomp = ecs_get( real_world, ctx->function, EcsScriptFunction); if (!fcomp || !fcomp->binding_ctx) { ecs_err("script function entity is missing binding context"); flecs_script_runtime_get(world)->error = true; return; } ecs_script_user_function_t *uf = fcomp->binding_ctx; ecs_script_function_node_t *node = uf->node; ecs_script_impl_t *impl = flecs_script_impl(uf->script); if (impl->ir_enabled) { flecs_script_ir_call_function(ctx, uf, argc, argv, result); return; } ecs_script_eval_visitor_t v; ecs_script_runtime_t *runtime = flecs_script_runtime_get(world); ecs_script_eval_desc_t desc = { .runtime = flecs_script_runtime_acquire_call(runtime) }; flecs_script_eval_visit_init(impl, &v, &desc); flecs_script_user_function_bind(&v, uf, argc, argv, true); int32_t i, stmt_count = ecs_vec_count(&node->body->stmts); ecs_script_node_t **stmts = ecs_vec_first(&node->body->stmts); for (i = 0; i < stmt_count; i ++) { if (flecs_script_eval_node((ecs_script_visit_t*)&v, stmts[i])) { failed = true; goto done; } } if (node->return_expr) { if (flecs_script_eval_expr(&v, &node->return_expr, result)) { failed = true; } } done: v.vars = ecs_script_vars_pop(v.vars); flecs_script_eval_visit_fini(&v, &desc); flecs_script_runtime_release_call(runtime, desc.runtime); if (failed) { flecs_script_runtime_get(world)->error = true; } } void flecs_script_user_function_ctx_free( void *ctx) { ecs_script_user_function_t *uf = ctx; if (uf->script) { ecs_script_free(uf->script); } ecs_vec_fini_t(NULL, &uf->refs, ecs_script_ref_t); ecs_vec_fini_t(NULL, &uf->using, ecs_entity_t); ecs_os_free(uf); } static int flecs_script_function_ref( const ecs_script_ref_t *ref, ecs_expr_node_t *dynamic, void *ctx) { (void)dynamic; if (ref->entity) { flecs_script_ref_ensure(ctx, ref); } return 0; } int flecs_script_eval_function( ecs_script_eval_visitor_t *v, ecs_script_function_node_t *node, ecs_entity_t fn_entity) { ecs_world_t *world = v->world; ecs_assert(node->eval_return_type != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_t fn_refs; ecs_vec_init_t(NULL, &fn_refs, ecs_script_ref_t, 0); int32_t stmt_count = ecs_vec_count(&node->body->stmts); ecs_script_node_t **stmts = ecs_vec_first(&node->body->stmts); for (int32_t i = 0; i < stmt_count; i ++) { ecs_script_var_node_t *var = (ecs_script_var_node_t*)stmts[i]; ecs_assert(var->expr != NULL && var->expr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_expr_visit_refs(&v->base.script->pub, var->expr, flecs_script_function_ref, &fn_refs)) { ecs_vec_fini_t(NULL, &fn_refs, ecs_script_ref_t); return -1; } } if (node->return_expr && flecs_expr_visit_refs( &v->base.script->pub, node->return_expr, flecs_script_function_ref, &fn_refs)) { ecs_vec_fini_t(NULL, &fn_refs, ecs_script_ref_t); return -1; } ecs_script_user_function_t *uf = ecs_os_calloc_t( ecs_script_user_function_t); uf->script = &v->base.script->pub; v->base.script->refcount ++; uf->node = node; uf->refs = fn_refs; uf->using = ecs_vec_copy_t(NULL, &v->r->using, ecs_entity_t); EcsScriptFunction *fcomp = ecs_ensure(world, fn_entity, EcsScriptFunction); if (fcomp->binding_ctx && fcomp->binding_ctx_free) { fcomp->binding_ctx_free(fcomp->binding_ctx); } fcomp->binding_ctx = uf; fcomp->binding_ctx_free = flecs_script_user_function_ctx_free; ecs_modified(world, fn_entity, EcsScriptFunction); return 0; } int flecs_script_assign_value( ecs_script_eval_visitor_t *v, ecs_script_assign_t *node, const void *src) { ecs_world_t *world = v->world; ecs_entity_t instance = v->template_instance; if (node->parent && instance && ecs_is_alive(world, instance)) { instance = ecs_get_parent(world, instance); } if (!instance || !ecs_is_alive(world, instance) || !ecs_has_id(world, instance, node->component)) { flecs_script_eval_error(v, node, "cannot assign to '%s': template instance is no longer alive", node->name); return -1; } ecs_id_t component = node->component; ecs_search(world, ecs_get_table(world, instance), component, &component); const ecs_type_info_t *ti = ecs_get_type_info(world, node->eval_type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *comp_ti = ecs_get_type_info(world, component); ecs_assert(comp_ti != NULL, ECS_INTERNAL_ERROR, NULL); const void *cur = ecs_get_id(world, instance, component); ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); void *copy = ecs_ptr_new_w_type_info(world, comp_ti); ecs_ptr_copy_w_type_info(world, comp_ti, copy, cur); ecs_ptr_copy_w_type_info(world, ti, ECS_OFFSET(copy, node->offset), src); ecs_set_id(world, instance, component, flecs_ito(size_t, comp_ti->size), copy); ecs_ptr_free_w_type_info(world, comp_ti, copy); return 0; } static FLECS_SCRIPT_NOINLINE int flecs_script_eval_assign( ecs_script_eval_visitor_t *v, ecs_script_assign_t *node) { ecs_entity_t type = node->eval_type; ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = ecs_get_type_info(v->world, type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_value_t value = { .type = type }; value.ptr = flecs_stack_calloc(&v->r->stack, ti->size, ti->alignment); flecs_type_info_ctor(value.ptr, 1, ti); int result = 0; if (flecs_script_eval_expr(v, &node->expr, &value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for assignment to '%s'", node->name); result = -1; } else { result = flecs_script_assign_value(v, node, value.ptr); } if (ti->hooks.dtor) { flecs_type_info_dtor(value.ptr, 1, ti); } flecs_stack_free(value.ptr, ti->size); return result; } int flecs_script_value_to_bool( ecs_script_eval_visitor_t *v, const void *node, const ecs_value_t *value, bool *out) { if (value->type == ecs_id(ecs_bool_t)) { *out = *(bool*)value->ptr; return 0; } const EcsType *type = ecs_get(v->world, value->type, EcsType); if (!type || (type->kind != EcsPrimitiveType && type->kind != EcsEnumType && type->kind != EcsBitmaskType)) { flecs_script_eval_error(v, node, "value of type '%s' cannot be used as condition", flecs_errstr(ecs_get_path(v->world, value->type))); return -1; } ecs_meta_cursor_t cur = ecs_meta_cursor(v->world, value->type, value->ptr); *out = ecs_meta_get_bool(&cur); return 0; } static int flecs_script_eval_cond( ecs_script_eval_visitor_t *v, ecs_script_node_t *node, ecs_expr_node_t **expr, bool *cond_out) { ecs_value_t condval = { .type = 0, .ptr = NULL }; if (flecs_script_eval_expr(v, expr, &condval)) { return -1; } int result = 0; if (condval.type == ecs_id(ecs_bool_t)) { *cond_out = *(bool*)condval.ptr; } else { result = flecs_script_value_to_bool(v, node, &condval, cond_out); } ecs_ptr_free(v->world, condval.type, condval.ptr); return result; } int flecs_script_eval_node( ecs_script_visit_t *_v, ecs_script_node_t *node) { ecs_script_eval_visitor_t *v = (ecs_script_eval_visitor_t*)_v; ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(v->template == NULL, ECS_INTERNAL_ERROR, NULL); if (node->skip) { return 0; } switch(node->kind) { case EcsAstTag: return flecs_script_eval_tag( v, (ecs_script_tag_t*)node); case EcsAstComponent: return flecs_script_eval_component( v, (ecs_script_component_t*)node); case EcsAstWithTag: return flecs_script_eval_with_tag( v, (ecs_script_tag_t*)node); case EcsAstWithComponent: return flecs_script_eval_with_component( v, (ecs_script_component_t*)node); case EcsAstUsing: return flecs_script_eval_using( v, (ecs_script_using_t*)node); case EcsAstModule: return flecs_script_eval_module( v, (ecs_script_module_t*)node); case EcsAstAnnotation: return flecs_script_eval_annot( v, (ecs_script_annot_t*)node); case EcsAstTemplate: ecs_vec_clear(&v->r->annot); return flecs_script_template_update_vars( v, (ecs_script_template_node_t*)node); case EcsAstProp: return 0; case EcsAstMut: if (!v->template_entity) { flecs_script_eval_error(v, node, "mut variables are only allowed in templates"); return -1; } return 0; case EcsAstConst: return flecs_script_eval_const( v, (ecs_script_var_node_t*)node, false); case EcsAstExportConst: case EcsAstExportMut: return 0; case EcsAstInclude: return flecs_script_eval_include( v, (ecs_script_include_t*)node); case EcsAstFunction: return 0; case EcsAstAsync: #ifdef FLECS_SCRIPT_ASYNC return flecs_script_async_spawn(v, (ecs_script_async_t*)node); #else flecs_script_eval_error(v, node, "async blocks require FLECS_SCRIPT_ASYNC"); return -1; #endif case EcsAstAssign: return flecs_script_eval_assign(v, (ecs_script_assign_t*)node); default: break; } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); } /* Script runner. Evaluates statements iteratively with an explicit frame * stack, so execution can suspend at await statements and resume without * having to rebuild a C call stack. Each frame owns the evaluation state of * the statement it executes; the AST is never written to during evaluation. */ static flecs_script_frame_t* flecs_script_frame_at( const ecs_script_runner_t *r, int32_t frame_index) { uint32_t index = flecs_ito(uint32_t, frame_index); return &r->frames[index / ECS_SCRIPT_FRAME_CHUNK_SIZE] [index % ECS_SCRIPT_FRAME_CHUNK_SIZE]; } static void flecs_script_runner_frames_fini( ecs_script_runner_t *r) { int32_t i; for (i = 1; i < (int32_t)(sizeof(r->frames) / sizeof(r->frames[0])); i ++) { ecs_os_free(r->frames[i]); } } static flecs_script_frame_t* flecs_script_frame_push( ecs_script_runner_t *r, ecs_script_node_t *node) { ecs_script_visit_t *bv = &r->v.base; /* The parser enforces ECS_PARSER_MAX_RECURSION_DEPTH, which bounds the * frame and node stacks of any parsed script. */ ecs_assert(bv->depth < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->frame_count < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); bv->nodes[bv->depth ++] = node; uint32_t chunk = (uint32_t)r->frame_count / ECS_SCRIPT_FRAME_CHUNK_SIZE; if (!r->frames[chunk]) { r->frames[chunk] = ecs_os_malloc_n( flecs_script_frame_t, ECS_SCRIPT_FRAME_CHUNK_SIZE); } flecs_script_frame_t *frame = flecs_script_frame_at(r, r->frame_count ++); frame->node = node; frame->pc = 0; return frame; } static void flecs_script_frame_leave( ecs_script_runner_t *r, flecs_script_frame_t *frame) { ecs_script_eval_visitor_t *v = &r->v; switch(frame->node->kind) { case EcsAstScope: flecs_script_eval_scope_leave(v, &frame->state.scope); break; case EcsAstEntity: if (frame->pc >= 1) { flecs_script_eval_entity_leave(v, &frame->state.entity); } break; case EcsAstWith: if (frame->pc >= 1) { flecs_script_eval_with_leave(v, &frame->state.with); } break; case EcsAstPairScope: if (frame->pc >= 1) { flecs_script_eval_pair_scope_leave(v, &frame->state.pair_scope); } break; case EcsAstFor: if (frame->pc >= 1) { flecs_script_eval_for_leave(v, &frame->state.for_); } break; case EcsAstIf: if (frame->pc >= 1) { v->force = frame->state.if_.force; } break; case EcsAstTry: case EcsAstWhile: break; default: ecs_abort(ECS_INTERNAL_ERROR, "corrupt script frame node"); } flecs_script_frame_pop(r); } void flecs_script_frame_pop( ecs_script_runner_t *r) { ecs_script_visit_t *bv = &r->v.base; bv->depth --; ecs_assert(bv->nodes[bv->depth] == flecs_script_frame_at(r, r->frame_count - 1)->node, ECS_INTERNAL_ERROR, NULL); r->frame_count --; } void flecs_script_scope_push( ecs_script_runner_t *r, ecs_script_scope_t *scope) { flecs_script_frame_t *frame = flecs_script_frame_push( r, (ecs_script_node_t*)scope); ecs_script_eval_visitor_t *v = &r->v; flecs_script_eval_scope_enter(v, scope, &frame->state.scope); if (v->entity) { ecs_entity_t src = v->entity->eval; int32_t count = ecs_vec_count(&scope->components); if (src != EcsVariable && count) { flecs_script_scope_add_ids(v, src, scope, ecs_vec_first(&scope->components), count); } } } static void flecs_script_mark_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { if (!node->region) { return; } const ecs_script_region_t *region = ecs_vec_get_t( &v->base.script->regions, ecs_script_region_t, node->region - 1); flecs_script_state_mark(v->state, region, v->visit); } bool flecs_script_stmt_support( const ecs_script_node_t *node) { return (node->kind == EcsAstConst && !((const ecs_script_var_node_t*)node)->computed) || node->kind == EcsAstUsing || node->kind == EcsAstModule; } static bool flecs_script_stmt_run( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { if (node->skip) { return false; } return v->force || flecs_script_stmt_support(node) || ((node->input & v->input) != 0) || ((node->internal & v->internal) != 0); } static int flecs_script_step_scope( ecs_script_runner_t *r, flecs_script_frame_t *frame) { ecs_script_eval_visitor_t *v = &r->v; ecs_script_scope_t *scope = (ecs_script_scope_t*)frame->node; ecs_script_node_t **nodes = ecs_vec_first(&scope->stmts); int32_t count = ecs_vec_count(&scope->stmts); while (frame->pc < count) { ecs_script_node_t *stmt = nodes[frame->pc]; v->base.prev = frame->pc ? nodes[frame->pc - 1] : NULL; v->base.next = (frame->pc + 1) < count ? nodes[frame->pc + 1] : NULL; if (!flecs_script_stmt_run(v, stmt)) { ecs_script_computed_t *slot = stmt->kind == EcsAstConst ? flecs_script_computed_get(v, (ecs_script_var_node_t*)stmt) : NULL; if (!slot || slot->valid) { if (slot) { if (flecs_script_eval_const_cached( v, (ecs_script_var_node_t*)stmt)) { return -1; } } flecs_script_mark_node(v, stmt); frame->pc ++; continue; } } if (stmt->kind == EcsAstContinue) { v->base.nodes[v->base.depth] = stmt; return 2; } if (stmt->kind == EcsAstScope) { frame->pc ++; flecs_script_scope_push(r, (ecs_script_scope_t*)stmt); return 0; } else if (stmt->kind == EcsAstEntity && ((ecs_script_entity_t*)stmt)->is_type) { ecs_vec_clear(&v->r->annot); flecs_script_mark_node(v, stmt); frame->pc ++; } else if (stmt->kind == EcsAstEntity || stmt->kind == EcsAstIf || stmt->kind == EcsAstFor || stmt->kind == EcsAstWith || stmt->kind == EcsAstPairScope || stmt->kind == EcsAstWhile) { frame->pc ++; flecs_script_frame_push(r, stmt); return 0; } else if (stmt->kind == EcsAstTry) { #ifdef FLECS_SCRIPT_ASYNC if (r->can_suspend) { frame->pc ++; flecs_script_frame_push(r, stmt); return 0; } #endif flecs_script_eval_error(v, stmt, "try requires resumable script execution"); return -1; } else { ecs_assert(v->base.depth < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); v->base.nodes[v->base.depth ++] = stmt; int res; if (stmt->kind == EcsAstAwait || (stmt->kind == EcsAstConst && ((ecs_script_var_node_t*)stmt)->is_await)) { #ifdef FLECS_SCRIPT_ASYNC res = flecs_script_step_await(r, stmt); #else flecs_script_eval_error(v, stmt, "await requires resumable script execution"); res = -1; #endif } else { res = flecs_script_eval_node(&v->base, stmt); } v->base.depth --; if (res) { return res; } frame->pc ++; } } flecs_script_eval_scope_leave(v, &frame->state.scope); flecs_script_frame_pop(r); return 0; } static int flecs_script_step_entity( ecs_script_runner_t *r, flecs_script_frame_t *frame) { ecs_script_entity_t *node = (ecs_script_entity_t*)frame->node; if (frame->pc == 0) { if (flecs_script_eval_entity_enter( &r->v, node, &frame->state.entity)) { return -1; } frame->pc = 1; flecs_script_scope_push(r, node->scope); if (!r->v.template) { r->v.parent = frame->state.entity.eval; } return 0; } r->last_entity = frame->state.entity.eval; flecs_script_eval_entity_leave(&r->v, &frame->state.entity); flecs_script_frame_pop(r); return 0; } static int flecs_script_control_enter( ecs_script_runner_t *r, flecs_script_frame_t *frame) { ecs_script_eval_visitor_t *v = &r->v; ecs_script_scope_t *scope; switch(frame->node->kind) { case EcsAstIf: { ecs_script_if_t *node = (ecs_script_if_t*)frame->node; frame->state.if_.force = v->force; bool cond; if (flecs_script_eval_cond(v, &node->node, &node->expr, &cond)) { return -1; } v->force = flecs_script_eval_force( v, &node->node, frame->state.if_.force); scope = cond ? node->if_true : node->if_false; break; } case EcsAstWith: { ecs_script_with_t *node = (ecs_script_with_t*)frame->node; if (flecs_script_eval_with_enter(v, node, &frame->state.with)) { return -1; } scope = node->scope; break; } case EcsAstPairScope: { ecs_script_pair_scope_t *node = (ecs_script_pair_scope_t*)frame->node; flecs_script_id_args_t args; int res = flecs_script_eval_id_args(v, &node->id, &args); if (!res) { res = flecs_script_eval_pair_scope_enter(v, node, args.first, args.second, &frame->state.pair_scope); } ecs_os_free(args.first); ecs_os_free(args.second); if (res) { return -1; } scope = node->scope; break; } #ifdef FLECS_SCRIPT_ASYNC case EcsAstTry: scope = ((ecs_script_try_t*)frame->node)->try_scope; break; #endif default: ecs_abort(ECS_INTERNAL_ERROR, "corrupt script frame node"); } frame->pc = 1; flecs_script_scope_push(r, scope); return 0; } void flecs_script_runner_abandon( ecs_script_runner_t *r) { while (r->frame_count) { flecs_script_frame_leave(r, flecs_script_frame_at(r, r->frame_count - 1)); } } /* Pop frames until the for loop that contains the continue statement is * reached. Returns false if the statement isn't inside a for loop. */ static bool flecs_script_runner_continue( ecs_script_runner_t *r) { int32_t frame = r->frame_count; while (frame > 0) { ecs_script_node_kind_t kind = flecs_script_frame_at(r, frame - 1)->node->kind; if (kind == EcsAstFor || kind == EcsAstWhile) { break; } frame --; } if (!frame) { return false; } while (r->frame_count > frame) { flecs_script_frame_leave(r, flecs_script_frame_at(r, r->frame_count - 1)); } return true; } /* Pop frames after an error until a try frame catches it. Returns true if a * catch clause was entered, false when the error propagates out of the * script. */ static bool flecs_script_runner_unwind( ecs_script_runner_t *r) { while (r->frame_count) { flecs_script_frame_t *frame = flecs_script_frame_at(r, r->frame_count - 1); #ifdef FLECS_SCRIPT_ASYNC if (flecs_script_try_catch(r, frame)) { return true; } #endif flecs_script_frame_leave(r, frame); } #ifdef FLECS_SCRIPT_ASYNC if (r->async.thrown) { /* Thrown error wasn't caught by a try block, report it */ flecs_script_report_throw(&r->v, &r->async); } #endif return false; } static int flecs_script_step_while( ecs_script_runner_t *r, flecs_script_frame_t *frame) { ecs_script_eval_visitor_t *v = &r->v; ecs_script_while_t *node = (ecs_script_while_t*)frame->node; bool cond; if (flecs_script_eval_cond(v, &node->node, &node->expr, &cond)) { return -1; } if (cond) { frame->pc = 1; flecs_script_scope_push(r, node->scope); return 0; } flecs_script_frame_leave(r, frame); return 0; } static flecs_script_run_status_t flecs_script_runner_exec( ecs_script_runner_t *r) { while (r->frame_count) { /* Code that runs while the script is evaluated (like a component hook) * can delete the script entity. Stop evaluating when that happens, as * the remaining statements would create entities for a script that no * longer exists. */ if (r->v.script_entity && !ecs_is_alive(r->v.world, r->v.script_entity)) { return FlecsScriptRunError; } flecs_script_frame_t *frame = flecs_script_frame_at(r, r->frame_count - 1); int res; switch(frame->node->kind) { case EcsAstScope: res = flecs_script_step_scope(r, frame); break; case EcsAstEntity: res = flecs_script_step_entity(r, frame); break; case EcsAstFor: res = flecs_script_step_for(r, frame); break; case EcsAstWhile: res = flecs_script_step_while(r, frame); break; default: if (!frame->pc) { res = flecs_script_control_enter(r, frame); } else { flecs_script_frame_leave(r, frame); res = 0; } break; } if (res == -1) { if (!flecs_script_runner_unwind(r)) { return FlecsScriptRunError; } } else if (res == 1) { return FlecsScriptRunSuspended; } else if (res == 2) { if (!flecs_script_runner_continue(r)) { flecs_script_eval_error(&r->v, frame->node, "continue is only allowed inside a loop"); if (!flecs_script_runner_unwind(r)) { return FlecsScriptRunError; } } } } return FlecsScriptRunDone; } void flecs_script_runner_init( ecs_script_runner_t *r, const ecs_script_eval_visitor_t *v) { r->v = *v; ecs_os_memset(r->frames, 0, sizeof(r->frames)); r->frames[0] = r->frame_storage; r->frame_count = 0; r->last_entity = 0; r->can_suspend = false; #ifdef FLECS_SCRIPT_ASYNC ecs_os_zeromem(&r->async); #endif } void flecs_script_runner_fini( ecs_script_runner_t *r, const ecs_script_eval_desc_t *desc) { flecs_script_runner_abandon(r); #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_fini(&r->async); #endif flecs_script_runner_frames_fini(r); flecs_script_eval_visit_fini(&r->v, desc); } flecs_script_run_status_t flecs_script_runner_run_scope( ecs_script_runner_t *r, ecs_script_scope_t *scope) { if (!r->frame_count) { if (!scope) { return FlecsScriptRunError; } flecs_script_scope_push(r, scope); } return flecs_script_runner_exec(r); } int flecs_script_eval_entity( ecs_script_eval_visitor_t *v, const ecs_script_t *script, const ecs_script_vars_t *vars, ecs_script_entity_t *node, ecs_entity_t *out) { ecs_script_runner_t runner; ecs_script_eval_desc_t desc = {0}; if (v) { flecs_script_runner_init(&runner, v); } else { flecs_script_eval_visit_init( (const ecs_script_impl_t*)script, &runner.v, &desc); flecs_script_runner_init(&runner, &runner.v); if (vars) { flecs_script_eval_push_vars(&runner.v, vars); } } flecs_script_frame_push(&runner, (ecs_script_node_t*)node); flecs_script_run_status_t status = flecs_script_runner_exec(&runner); if (status == FlecsScriptRunDone && out) { *out = runner.last_entity; } ecs_assert(runner.frame_count == 0, ECS_INTERNAL_ERROR, NULL); if (v) { flecs_script_runner_frames_fini(&runner); } else { if (vars) { flecs_script_eval_pop_vars(&runner.v); } flecs_script_runner_fini(&runner, &desc); } return status == FlecsScriptRunDone ? 0 : -1; } /* Source position of the statement the runner is suspended on */ const char* flecs_script_runner_stmt_pos( const ecs_script_runner_t *r) { int32_t i; for (i = r->frame_count - 1; i >= 0; i --) { const flecs_script_frame_t *frame = flecs_script_frame_at(r, i); if (frame->node->kind != EcsAstScope) { continue; } ecs_script_scope_t *scope = (ecs_script_scope_t*)frame->node; if (frame->pc < ecs_vec_count(&scope->stmts)) { ecs_script_node_t **nodes = ecs_vec_first(&scope->stmts); return nodes[frame->pc]->pos; } } return NULL; } void flecs_script_eval_visit_init( const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc) { /* The node stack is not zeroed: it is large, and nodes are always written * before they are read. */ v->base.script = ECS_CONST_CAST(ecs_script_impl_t*, script); v->base.prev = NULL; v->base.next = NULL; v->base.depth = 0; ecs_os_memset(&v->world, 0, ECS_SIZEOF(ecs_script_eval_visitor_t) - (ecs_size_t)offsetof(ecs_script_eval_visitor_t, world)); v->world = script->pub.world; v->r = desc ? desc->runtime : NULL; v->state = ECS_CONST_CAST(ecs_script_state_t*, &script->state); v->input = UINT64_MAX; v->scope_slot = -1; v->for_slot = -1; v->force = true; if (!v->r) { v->r = ecs_script_runtime_new(); } ecs_id_t tag = flecs_script_runtime_get(v->world)->current_tag; if (tag) { v->script_tag = tag; if (ECS_HAS_ID_FLAG(tag, PAIR)) { if (ECS_PAIR_FIRST(tag) == ecs_id(EcsScript)) { v->script_entity = ecs_pair_second(v->world, tag); } } } } void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc) { if (!desc || (v->r != desc->runtime)) { ecs_script_runtime_free(v->r); } } void flecs_script_eval_begin( ecs_script_eval_visitor_t *v, uint64_t input, int32_t visit) { v->input = input; v->internal = 0; v->visit = visit; v->scope_slot = -1; v->for_slot = -1; v->force = input == UINT64_MAX; } static ecs_script_symbol_slot_t* flecs_script_runtime_symbol_slot( ecs_script_eval_visitor_t *v, int32_t slot) { ecs_vec_t *slots = &v->state->symbol_slots; if (slot < 0 || slot >= ecs_vec_count(slots)) { return NULL; } return ecs_vec_get_t(slots, ecs_script_symbol_slot_t, slot); } static bool flecs_script_slot_visited( const ecs_vec_t *scope_slots, int32_t visit, int32_t slot) { if (slot < 0 || slot >= ecs_vec_count(scope_slots)) { return true; } return ((const int32_t*)scope_slots->array)[slot] == visit; } bool flecs_script_scope_visited( ecs_script_eval_visitor_t *v, int32_t slot) { return flecs_script_slot_visited(&v->state->scope_slots, v->visit, slot); } static ecs_entity_t flecs_script_component_slot_src( ecs_script_eval_visitor_t *v, const ecs_script_component_slot_t *slot) { ecs_script_symbol_slot_t *symbol = flecs_script_runtime_symbol_slot( v, slot->entity_slot); if (!symbol || !symbol->entity) { return 0; } return flecs_script_get_src(v, symbol->entity, slot->component); } static void flecs_script_cleanup_slots( ecs_script_eval_visitor_t *v) { ecs_vec_t scope_slots = v->state->scope_slots; int32_t visit = v->visit; { ecs_script_component_slot_t *slots = ecs_vec_first( &v->state->component_slots); int32_t i, j, count = ecs_vec_count(&v->state->component_slots); for (i = 0; i < count; i ++) { ecs_script_component_slot_t *slot = &slots[i]; if (!slot->component || flecs_script_slot_visited( &scope_slots, visit, slot->scope_slot)) { continue; } ecs_entity_t src = flecs_script_component_slot_src(v, slot); bool active = false; for (j = 0; j < count; j ++) { if (i == j || slots[j].component != slot->component) { continue; } if (!flecs_script_slot_visited( &scope_slots, visit, slots[j].scope_slot)) { continue; } if (slots[j].entity_slot == slot->entity_slot) { active = true; break; } if (src && flecs_script_component_slot_src( v, &slots[j]) == src) { active = true; break; } } if (src && ecs_is_alive(v->world, src) && !active) { ecs_remove_id(v->world, src, slot->component); } ecs_os_zeromem(slot); } } ecs_vec_t *symbol_slots = &v->state->symbol_slots; ecs_script_symbol_slot_t *symbols = ecs_vec_first(symbol_slots); int32_t i, j, count = ecs_vec_count(symbol_slots); for (i = 0; i < count; i ++) { ecs_script_symbol_slot_t *slot = &symbols[i]; if (!slot->entity || flecs_script_slot_visited(&scope_slots, visit, slot->scope_slot)) { continue; } if (ecs_is_alive(v->world, slot->entity) && flecs_script_is_builtin(v->world, slot->entity)) { continue; } bool active = false; for (j = 0; j < count; j ++) { if (i == j || symbols[j].entity != slot->entity) { continue; } if (flecs_script_slot_visited( &scope_slots, visit, symbols[j].scope_slot)) { active = true; break; } } if (!active && ecs_is_alive(v->world, slot->entity)) { ecs_delete(v->world, slot->entity); } ecs_os_zeromem(slot); } } void flecs_script_eval_cleanup( ecs_script_eval_visitor_t *v, bool dirty) { if (dirty || v->input == UINT64_MAX) { flecs_script_cleanup_slots(v); } { ecs_script_for_slot_t *slots = ecs_vec_first(&v->state->for_slots); int32_t i, count = ecs_vec_count(&v->state->for_slots); for (i = 0; i < count; i ++) { ecs_script_for_slot_t *slot = &slots[i]; if (!flecs_script_scope_visited(v, slot->scope_slot)) { flecs_script_for_slot_clear(v->world, slot, true); } else { flecs_script_for_slot_purge(v->world, slot, v->visit); } } } } void flecs_script_eval_push_vars( ecs_script_eval_visitor_t *v, const ecs_script_vars_t *vars) { /* Safe const cast, evaluated code only contains an entity statement and * won't modify the variables. */ v->vars = flecs_script_vars_push( v->vars, &v->r->stack, v->r->allocator); v->vars->parent = ECS_CONST_CAST(ecs_script_vars_t*, vars); v->vars->sp = ecs_vec_count(&vars->vars); } void flecs_script_eval_pop_vars( ecs_script_eval_visitor_t *v) { v->vars = ecs_script_vars_pop(v->vars); } int flecs_script_compile( ecs_script_eval_visitor_t *v) { ecs_script_impl_t *impl = v->base.script; if (impl->compiled) { return 0; } if (flecs_script_visit_include(v, impl->root) || flecs_script_visit_type(v, impl->root) || flecs_script_analyze_dependencies(v, impl->root)) { return -1; } impl->compiled = true; return 0; } int flecs_script_eval( const ecs_script_t *script, const ecs_script_eval_desc_t *desc, ecs_id_t tag, uint64_t input, ecs_script_eval_result_t *result) { ecs_script_runner_t runner; ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); ecs_script_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.runtime) { priv_desc.runtime = flecs_script_runtime_get(script->world); } ecs_script_runtime_t *runtime = flecs_script_runtime_get(script->world); flecs_script_runtime_error_reset(runtime); if (result) { flecs_log_capture_push(true); } ecs_id_t prev_tag = runtime->current_tag; runtime->current_tag = tag; bool use_ir = priv_desc.ir || impl->ir_enabled; if (use_ir) { impl->ir_enabled = true; } flecs_script_eval_visit_init(impl, &runner.v, &priv_desc); flecs_script_runner_init(&runner, &runner.v); int r = flecs_script_compile(&runner.v); if (!r && input) { flecs_script_eval_begin(&runner.v, input, flecs_script_state_next(&impl->state)); if (use_ir) { if (flecs_script_ir_eval_root(&runner.v, impl)) { r = -1; } } else if (flecs_script_runner_run_scope(&runner, impl->root) != FlecsScriptRunDone) { r = -1; } else { flecs_script_eval_cleanup(&runner.v, true); } } flecs_script_runner_fini(&runner, &priv_desc); runtime->current_tag = prev_tag; if (runtime->error) { runtime->error = false; r = -1; } if (result) { result->error = flecs_log_capture_pop(); flecs_log_get_captured_error_pos(&result->line, &result->column); if (!r && result->error) { ecs_err("%s", result->error); ecs_os_free(result->error); result->error = NULL; } } ecs_script_runtime_clear(priv_desc.runtime); return r; } int ecs_script_eval( const ecs_script_t *script, const ecs_script_eval_desc_t *desc, ecs_script_eval_result_t *result) { ecs_entity_t prev_scope = ecs_set_scope(script->world, 0); int r = flecs_script_eval(script, desc, 0, UINT64_MAX, result); ecs_set_scope(script->world, prev_scope); return r; } #endif #ifdef FLECS_SCRIPT void flecs_script_for_prepare( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state) { ecs_os_zeromem(state); state->for_slot = v->for_slot; state->force = v->force; bool visited = node->scope->scope_slot >= 0 && node->scope->scope_slot < ecs_vec_count(&v->state->scope_slots) && ecs_vec_get_t(&v->state->scope_slots, int32_t, node->scope->scope_slot)[0] == v->visit; if (node->for_slot >= 0 && node->for_slot < ecs_vec_count(&v->state->for_slots)) { ecs_script_for_slot_t *slot = ecs_vec_get_t( &v->state->for_slots, ecs_script_for_slot_t, node->for_slot); slot->scope_slot = node->scope->scope_slot; if (!visited) { flecs_script_for_slot_clear(v->world, slot, false); } } } int flecs_script_for_init( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state, const ecs_value_t *collection) { ecs_entity_t key_type = 0, elem_type = ecs_id(ecs_i32_t); if (collection) { ecs_entity_t type = collection->type; const EcsType *type_ptr = ecs_get(v->world, type, EcsType); switch(type_ptr ? type_ptr->kind : EcsPrimitiveType) { case EcsArrayType: { const EcsArray *array = ecs_get(v->world, type, EcsArray); ecs_assert(array != NULL, ECS_INTERNAL_ERROR, NULL); state->kind = FlecsScriptForArray; state->elems = collection->ptr; state->count = array->count; elem_type = array->type; break; } case EcsVectorType: { const EcsVector *vector = ecs_get(v->world, type, EcsVector); ecs_assert(vector != NULL, ECS_INTERNAL_ERROR, NULL); state->kind = FlecsScriptForVector; if (collection->ptr) { state->elems = ecs_vec_first(collection->ptr); state->count = ecs_vec_count(collection->ptr); } elem_type = vector->type; break; } case EcsMapType: { const EcsMap *map = ecs_get(v->world, type, EcsMap); ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); state->kind = FlecsScriptForMap; if (collection->ptr) { state->map_it = ecs_map_iter(collection->ptr); } key_type = map->key_type; elem_type = map->type; break; } default: flecs_script_eval_error(v, node, "for loop cannot iterate value of type '%s': " "not an array, vector or map", flecs_errstr(ecs_get_path(v->world, type))); return -1; } } bool map = state->kind == FlecsScriptForMap; int32_t var_count = node->loop_var_count; if (var_count > (map ? 3 : 2)) { if (state->kind == FlecsScriptForRange) { flecs_script_eval_error(v, node, "too many loop variables for range " "(expected 'value' or '(index, value)')"); } else if (map) { flecs_script_eval_error(v, node, "too many loop variables for map (expected 'elem', " "'(key, elem)' or '(key, index, elem)')"); } else { flecs_script_eval_error(v, node, "too many loop variables for %s " "(expected 'elem' or '(index, elem)')", state->kind == FlecsScriptForArray ? "array" : "vector"); } return -1; } const ecs_type_info_t *elem_ti = ecs_get_type_info(v->world, elem_type); if (!elem_ti) { flecs_script_eval_error(v, node, "for loop element type is not a valid type"); return -1; } state->elem_size = elem_ti->size; int32_t i = 0; state->key_var = state->index_var = -1; if (map && var_count >= 2) { const ecs_type_info_t *key_ti = ecs_get_type_info(v->world, key_type); ecs_assert(key_ti != NULL, ECS_INTERNAL_ERROR, NULL); state->key_size = key_ti->size; state->var_types[i] = key_type; state->key_var = i ++; } if (var_count == (map ? 3 : 2)) { state->var_types[i] = ecs_id(ecs_i32_t); state->index_var = i ++; } state->var_types[i] = elem_type; state->elem_var = i; return 0; } int flecs_script_for_declare_vars( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state, bool alloc_elem) { v->vars = flecs_script_vars_push(v->vars, &v->r->stack, v->r->allocator); ecs_script_vars_set_size(v->vars, node->loop_var_count); state->vars = v->vars; state->var_base = 0; int32_t i; for (i = 0; i < node->loop_var_count; i ++) { const char *name = node->loop_vars[i]; ecs_script_var_t *var = ecs_script_vars_declare(v->vars, name); if (!var) { flecs_script_eval_error(v, node, "duplicate loop variable '%s'", name); goto error; } var->value.type = state->var_types[i]; if (!alloc_elem && i == state->elem_var) { continue; } const ecs_type_info_t *ti = ecs_get_type_info( v->world, var->value.type); if (!ti) { flecs_script_eval_error(v, node, "invalid type for loop variable '%s'", name); goto error; } var->value.ptr = flecs_stack_calloc( &v->r->stack, ti->size, ti->alignment); var->type_info = ti; var->owned = true; } return 0; error: v->vars = ecs_script_vars_pop(v->vars); return -1; } static int flecs_script_for_enter( ecs_script_eval_visitor_t *v, ecs_script_for_t *node, flecs_script_for_state_t *state) { flecs_script_for_prepare(v, node, state); if (!node->expr) { ecs_value_t from = { .type = ecs_id(ecs_i32_t), .ptr = &state->from }; ecs_value_t to = { .type = ecs_id(ecs_i32_t), .ptr = &state->count }; if (flecs_script_eval_expr(v, &node->from, &from) || flecs_script_eval_expr(v, &node->to, &to)) { return -1; } } else if (flecs_script_eval_expr(v, &node->expr, &state->collection)) { return -1; } if (flecs_script_for_init(v, node, state, node->expr ? &state->collection : NULL) || flecs_script_for_declare_vars(v, node, state, state->kind == FlecsScriptForRange)) { if (state->collection.type) { ecs_ptr_free(v->world, state->collection.type, state->collection.ptr); } return -1; } v->for_slot = node->for_slot; v->force = true; return 0; } bool flecs_script_for_next( flecs_script_for_state_t *state) { ecs_script_var_t *vars = &((ecs_script_var_t*) state->vars->vars.array)[state->var_base]; ecs_script_var_t *elem = &vars[state->elem_var]; int32_t i = state->index; switch(state->kind) { case FlecsScriptForRange: { int32_t value = state->from + i; if (value >= state->count) { return false; } *(int32_t*)elem->value.ptr = value; break; } case FlecsScriptForArray: case FlecsScriptForVector: if (i >= state->count) { return false; } elem->value.ptr = ECS_OFFSET(state->elems, state->elem_size * i); elem->owned = false; break; case FlecsScriptForMap: if (!ecs_map_next(&state->map_it)) { return false; } if (state->key_var != -1) { ecs_map_key_t key = ecs_map_key(&state->map_it); ecs_os_memcpy(vars[state->key_var].value.ptr, &key, state->key_size); } if (state->elem_size > ECS_SIZEOF(ecs_map_val_t)) { elem->value.ptr = ecs_map_ptr(&state->map_it); } else { elem->value.ptr = &state->map_it.res[1]; } elem->owned = false; break; } if (state->index_var != -1) { *(int32_t*)vars[state->index_var].value.ptr = i; } state->index = i + 1; return true; } void flecs_script_eval_for_leave( ecs_script_eval_visitor_t *v, flecs_script_for_state_t *state) { v->vars = ecs_script_vars_pop(v->vars); v->for_slot = state->for_slot; v->force = state->force; if (state->collection.type) { ecs_ptr_free(v->world, state->collection.type, state->collection.ptr); } } int flecs_script_step_for( ecs_script_runner_t *r, flecs_script_frame_t *frame) { ecs_script_eval_visitor_t *v = &r->v; ecs_script_for_t *node = (ecs_script_for_t*)frame->node; flecs_script_for_state_t *state = &frame->state.for_; if (frame->pc == 0) { if (flecs_script_for_enter(v, node, state)) { return -1; } frame->pc = 1; } if (flecs_script_for_next(state)) { flecs_script_scope_push(r, node->scope); return 0; } flecs_script_eval_for_leave(v, state); flecs_script_frame_pop(r); return 0; } #endif #ifdef FLECS_SCRIPT /* Maximum number of includes that can be nested. Guards against scripts that * (indirectly) include themselves. */ #ifndef FLECS_SCRIPT_INCLUDE_DEPTH_MAX #define FLECS_SCRIPT_INCLUDE_DEPTH_MAX (32) #endif static bool flecs_script_include_has_parent_dir( const char *path) { const char *p = path; while (p[0]) { if (p[0] == '.' && p[1] == '.') { char before = (p == path) ? '/' : p[-1]; char after = p[2]; if ((before == '/' || before == '\\') && (after == '/' || after == '\\' || after == '\0')) { return true; } } p ++; } return false; } static char* flecs_script_include_resolve( const char *script_name, const char *include_path) { int32_t i, dir_len = 0; for (i = 0; script_name && script_name[i]; i ++) { if (script_name[i] == '/' || script_name[i] == '\\') { dir_len = i + 1; } } static const char ext[] = ".flecs"; ecs_size_t len = ecs_os_strlen(include_path); ecs_size_t ext_len = ECS_SIZEOF(ext) - 1; bool has_ext = len >= ext_len && !ecs_os_strcmp(include_path + len - ext_len, ext); return flecs_asprintf("%.*s%s%s", dir_len, script_name ? script_name : "", include_path, has_ext ? "" : ext); } static bool flecs_script_include_is_absolute( const char *path) { if (path[0] == '/' || path[0] == '\\') { return true; } if (path[0] && path[1] == ':') { return true; } return false; } static int flecs_script_include_node( ecs_script_eval_visitor_t *v, ecs_script_include_t *node) { if (flecs_script_include_is_absolute(node->filename)) { flecs_script_eval_error(v, node, "include path '%s' must be relative", node->filename); return -1; } if (flecs_script_include_has_parent_dir(node->filename)) { flecs_script_eval_error(v, node, "include path '%s' must not contain '..'", node->filename); return -1; } ecs_script_runtime_t *rt = flecs_script_runtime_get(v->world); if (rt->include_nesting >= FLECS_SCRIPT_INCLUDE_DEPTH_MAX) { flecs_script_eval_error(v, node, "too many nested includes for '%s', is there an include cycle?", node->filename); return -1; } const char *script_name = NULL; if (v->script_entity) { const EcsScript *sc = ecs_get(v->world, v->script_entity, EcsScript); if (sc && sc->filename) { script_name = sc->filename; } } if (!script_name) { script_name = v->base.script->pub.name; } char *resolved = flecs_script_include_resolve( script_name, node->filename); int result = 0; rt->include_nesting ++; if (v->script_entity) { ecs_entity_t existing = ecs_lookup_path_w_sep( v->world, 0, resolved, "/", "/", false); if (existing && ecs_has(v->world, existing, EcsScript)) { const EcsScript *existing_script = ecs_get( v->world, existing, EcsScript); if (!existing_script->error) { goto done; } } ecs_entity_t prev_scope = ecs_set_scope(v->world, 0); rt->include_depth ++; ecs_entity_t e = ecs_script_init(v->world, &(ecs_script_desc_t){ .filename = resolved, .ir = v->base.script->ir_enabled }); rt->include_depth --; ecs_set_scope(v->world, prev_scope); if (!rt->include_depth) { flecs_script_run_pending_resolves(v->world); } if (!e) { flecs_script_eval_error(v, node, "failed to include managed script '%s'", resolved); result = -1; goto done; } const EcsScript *sc = ecs_get(v->world, e, EcsScript); if (sc && sc->error) { flecs_script_eval_error(v, node, "failed to include managed script '%s': %s", resolved, sc->error); result = -1; } } else { char *code = flecs_load_from_file(resolved); if (!code) { flecs_script_eval_error(v, node, "failed to load include '%s'", resolved); result = -1; goto done; } ecs_script_eval_desc_t parse_desc = { .ir = v->base.script->ir_enabled }; ecs_script_t *included = ecs_script_parse( v->world, resolved, code, &parse_desc, NULL); ecs_os_free(code); if (!included) { result = -1; goto done; } ecs_script_eval_desc_t desc = { .runtime = ecs_script_runtime_new(), .ir = v->base.script->ir_enabled }; if (ecs_script_eval(included, &desc, NULL)) { result = -1; } ecs_script_runtime_free(desc.runtime); ecs_script_free(included); } done: rt->include_nesting --; ecs_os_free(resolved); return result; } int flecs_script_eval_include( ecs_script_eval_visitor_t *v, ecs_script_include_t *node) { int32_t i = v->base.depth - 1; for (; i >= 0; i --) { if (v->base.nodes[i]->kind == EcsAstScope) { break; } } if (i < 0 || (ecs_script_scope_t*)v->base.nodes[i] != v->base.script->root) { flecs_script_eval_error(v, node, "include is only allowed at the root scope"); return -1; } return 0; } int flecs_script_visit_include( ecs_script_eval_visitor_t *v, ecs_script_scope_t *scope) { ecs_script_node_t **nodes = ecs_vec_first(&scope->stmts); int32_t i, count = ecs_vec_count(&scope->stmts); ecs_vec_t pending; ecs_vec_init_t(NULL, &pending, ecs_script_include_t*, 0); for (i = 0; i < count; i ++) { if (nodes[i]->kind != EcsAstInclude) { continue; } ecs_vec_append_t(NULL, &pending, ecs_script_include_t*)[0] = (ecs_script_include_t*)nodes[i]; } count = ecs_vec_count(&pending); if (!count) { ecs_vec_fini_t(NULL, &pending, ecs_script_include_t*); return 0; } if (count == 1) { int result = flecs_script_include_node( v, ecs_vec_first_t(&pending, ecs_script_include_t*)[0]); ecs_vec_fini_t(NULL, &pending, ecs_script_include_t*); return result; } char *error = NULL; bool progress = true; while (progress && ecs_vec_count(&pending)) { ecs_os_free(error); error = NULL; progress = false; flecs_log_capture_push(true); ecs_script_include_t **elems = ecs_vec_first(&pending); int32_t remaining = 0; count = ecs_vec_count(&pending); for (i = 0; i < count; i ++) { if (flecs_script_include_node(v, elems[i])) { elems[remaining ++] = elems[i]; } else { progress = true; } } ecs_vec_set_count_t(NULL, &pending, ecs_script_include_t*, remaining); error = flecs_log_capture_pop(); } int result = 0; if (ecs_vec_count(&pending)) { if (error) { ecs_err("%s", error); } result = -1; } ecs_os_free(error); ecs_vec_fini_t(NULL, &pending, ecs_script_include_t*); return result; } #endif #ifdef FLECS_SCRIPT bool flecs_script_struct_member_is_inherited( ecs_world_t *world, ecs_entity_t struct_type, const char *name) { const EcsStruct *st = ecs_get(world, struct_type, EcsStruct); if (!st) { return false; } int32_t i, inherited = flecs_struct_inherited_count(world, struct_type, st); const ecs_member_t *members = ecs_vec_first_t(&st->members, ecs_member_t); for (i = 0; i < inherited; i ++) { if (!ecs_os_strcmp(members[i].name, name)) { return true; } } return false; } int flecs_script_struct_visit( const ecs_script_visitor_ctx_t *ctx) { ecs_world_t *world = ctx->world; ecs_script_eval_visitor_t *v = ctx->eval; ecs_script_t *script = &v->base.script->pub; ecs_expr_initializer_t *node = ctx->initializer; if (!node || node->node.kind == EcsExprEmptyInitializer || !ecs_vec_count(&node->elements)) { const EcsStruct *st = ecs_get(world, ctx->entity, EcsStruct); if (st && flecs_struct_inherited_count(world, ctx->entity, st) > 0) { return 0; } if (!ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ctx->entity })) { flecs_script_eval_error(v, NULL, "invalid layout for struct '%s'", ecs_get_name(world, ctx->entity)); return -1; } return 0; } ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (!elem->member) { flecs_expr_visit_error(script, elem->value, "missing name for struct member (expected 'name: type')"); return -1; } if (elem->operator) { flecs_expr_visit_error(script, elem->value, "invalid operator for struct member '%s'", elem->member); return -1; } if (!elem->value) { flecs_expr_visit_error(script, NULL, "missing type for struct member '%s'", elem->member); return -1; } if (flecs_script_struct_member_is_inherited( world, ctx->entity, elem->member)) { flecs_expr_visit_error(script, elem->value, "member '%s' of struct '%s' is already defined by base type", elem->member, ecs_get_name(world, ctx->entity)); return -1; } ecs_entity_t m = flecs_script_create_entity(v, elem->member); if (!m) { return -1; } /* Evaluate into temporary storage. Evaluating the expression can * move the member entity, which would invalidate a pointer into * the component storage. */ EcsMember member = {0}; bool is_init = elem->value->kind == EcsExprInitializer || elem->value->kind == EcsExprEmptyInitializer; ecs_value_t value = is_init ? (ecs_value_t){ .ptr = &member, .type = ecs_id(EcsMember) } : (ecs_value_t){ .ptr = &member.type, .type = ecs_id(ecs_entity_t) }; if (flecs_script_eval_expr(v, &elem->value, &value)) { return -1; } if (!is_init && !member.type) { flecs_expr_visit_error(script, elem->value, "invalid type for struct member '%s'", elem->member); return -1; } if (member.type == ctx->entity) { flecs_expr_visit_error(script, elem->value, "member '%s' of struct '%s' cannot be of its own type", elem->member, ecs_get_name(world, ctx->entity)); return -1; } if (member.count < 0) { flecs_expr_visit_error(script, elem->value, "invalid count %d for member '%s' of struct '%s'", member.count, elem->member, ecs_get_name(world, ctx->entity)); return -1; } ecs_set_ptr(world, m, EcsMember, &member); } const EcsComponent *comp = ecs_get(world, ctx->entity, EcsComponent); if (!comp || comp->size <= 0) { flecs_script_eval_error(v, NULL, "invalid layout for struct '%s'", ecs_get_name(world, ctx->entity)); return -1; } return 0; } #endif #ifdef FLECS_SCRIPT typedef struct flecs_script_type_name_t { const char *name; uint64_t scope; ecs_size_t length; bool table; } flecs_script_type_name_t; static uint64_t flecs_script_type_name_hash( const void *ptr) { const flecs_script_type_name_t *key = ptr; uint64_t hash[] = { flecs_hash(key->name, key->length), key->scope, key->table }; return flecs_hash(hash, ECS_SIZEOF(hash)); } static int flecs_script_type_name_compare( const void *first, const void *second) { const flecs_script_type_name_t *a = first, *b = second; if (a->scope != b->scope || a->table != b->table || a->length != b->length) { return 1; } return ecs_os_memcmp(a->name, b->name, a->length); } static ecs_entity_t flecs_script_type_interface_var( const ecs_script_type_visitor_t *t, int32_t sp); static ecs_entity_t flecs_script_type_var_interface( const ecs_script_type_visitor_t *t, int32_t sp) { ecs_entity_t interface = t->v->template ? flecs_script_template_member_interface(t->v->template, sp) : 0; return interface ? interface : flecs_script_type_interface_var(t, sp); } static void flecs_script_type_init( ecs_script_type_visitor_t *t) { ecs_vec_init_t(NULL, &t->tables, ecs_script_type_table_t, 0); ecs_vec_init_t(NULL, &t->entities, ecs_script_type_entity_t, 0); ecs_vec_init_t(NULL, &t->skipped_vars, const char*, 0); ecs_vec_init_t(NULL, &t->interface_vars, ecs_script_type_interface_var_t, 0); flecs_hashmap_init(&t->names, flecs_script_type_name_t, int32_t, flecs_script_type_name_hash, flecs_script_type_name_compare, NULL); flecs_script_type_table_new(t, -1, NULL); } static void flecs_script_type_fini( ecs_script_type_visitor_t *t) { flecs_hashmap_fini(&t->names); ecs_vec_fini_t(NULL, &t->interface_vars, ecs_script_type_interface_var_t); ecs_vec_fini_t(NULL, &t->skipped_vars, const char*); ecs_vec_fini_t(NULL, &t->entities, ecs_script_type_entity_t); ecs_vec_fini_t(NULL, &t->tables, ecs_script_type_table_t); } int32_t flecs_script_type_table_new( ecs_script_type_visitor_t *t, int32_t parent, ecs_script_entity_t *owner) { ecs_script_type_table_t *table = ecs_vec_append_t( NULL, &t->tables, ecs_script_type_table_t); table->parent = parent; table->owner = owner; return ecs_vec_count(&t->tables) - 1; } static ecs_script_entity_t* flecs_script_type_owner( ecs_script_type_visitor_t *t) { return ecs_vec_get_t(&t->tables, ecs_script_type_table_t, t->table)->owner; } static ecs_size_t flecs_script_type_path_elem( const char *name, const char **sep) { const char *ptr = name; while ((ptr = strchr(ptr, '.'))) { if (ptr == name || ptr[-1] != '\\') { *sep = ptr; return flecs_ito(ecs_size_t, ptr - name); } ptr ++; } *sep = NULL; return ecs_os_strlen(name); } static void flecs_script_type_unresolved_ref( ecs_script_type_visitor_t *t, void *node, const char *name, flecs_script_unresolved_kind_t kind) { flecs_script_unresolved_ref_add(t->v->base.script, node ? ((ecs_script_node_t*)node)->pos : NULL, name, kind); } static int flecs_script_type_report_unresolved( ecs_script_eval_visitor_t *v) { ecs_script_impl_t *impl = v->base.script; int32_t i, count = ecs_vec_count(&impl->unresolved_refs); if (!count) { return 0; } const char *code = impl->pub.code; ecs_script_unresolved_ref_t *refs = ecs_vec_first( &impl->unresolved_refs); bool prev_append = flecs_log_capture_set_append(true); for (i = 0; i < count; i ++) { ecs_script_unresolved_ref_t *ref = &refs[i]; if (ref->offset != -1) { ecs_parser_error(impl->pub.name, code, ref->offset, "unresolved reference '%s'", ref->name); } else { ecs_parser_error(impl->pub.name, NULL, 0, "unresolved reference '%s' (line %d, column %d)", ref->name, ref->line, ref->column); } } flecs_log_capture_set_append(prev_append); return -1; } static ecs_script_type_entity_t* flecs_script_type_find_in( ecs_script_type_visitor_t *t, uint64_t scope, bool table, const char *name, ecs_size_t length) { flecs_script_type_name_t key = {name, scope, length, table}; const int32_t *index = flecs_hashmap_get(&t->names, &key, int32_t); return index ? ecs_vec_get_t( &t->entities, ecs_script_type_entity_t, *index) : NULL; } static ecs_script_type_entity_t* flecs_script_type_find( ecs_script_type_visitor_t *t, const char *name) { const char *sep; ecs_size_t length = flecs_script_type_path_elem(name, &sep); int32_t table = t->table; ecs_script_type_entity_t *entity = NULL; while (!entity && table != -1) { entity = flecs_script_type_find_in( t, (uint64_t)table, true, name, length); table = ecs_vec_get_t( &t->tables, ecs_script_type_table_t, table)->parent; } while (entity && sep) { name = sep + 1; length = flecs_script_type_path_elem(name, &sep); ecs_script_type_entity_t *next = NULL; if (entity->child_table != -1) { next = flecs_script_type_find_in( t, (uint64_t)entity->child_table, true, name, length); } if (!next) { ecs_entity_t parent = flecs_script_symbol_entity( t->v, entity->slot); if (parent) { next = flecs_script_type_find_in( t, parent, false, name, length); } } entity = next; } return entity; } int32_t flecs_script_type_slot_new( ecs_script_type_visitor_t *t, int32_t expected) { ecs_script_impl_t *impl = t->v->base.script; if (expected != -1) { ecs_assert(expected < ecs_vec_count(&impl->state.symbol_slots), ECS_INTERNAL_ERROR, NULL); return expected; } int32_t result = ecs_vec_count(&impl->state.symbol_slots); ecs_script_symbol_slot_t *slot = ecs_vec_append_t( NULL, &impl->state.symbol_slots, ecs_script_symbol_slot_t); slot->entity = 0; slot->scope_slot = -1; return result; } static ecs_script_type_entity_t* flecs_script_type_declare( ecs_script_type_visitor_t *t, const char *name, ecs_script_entity_t *node, int32_t *slot, bool has_scope) { ecs_size_t length = ecs_os_strlen(name); ecs_script_type_entity_t *entity = flecs_script_type_find_in( t, (uint64_t)t->table, true, name, length); if (entity) { *slot = entity->slot; if (node && !entity->node) { entity->node = node; } if (node && entity->node == node) { node->type_index = (int32_t)(entity - (ecs_script_type_entity_t*)ecs_vec_first(&t->entities)) + 1; } if (has_scope && entity->child_table == -1) { entity->child_table = flecs_script_type_table_new( t, t->table, node); } else if (node && entity->child_table != -1) { ecs_script_type_table_t *child_table = ecs_vec_get_t( &t->tables, ecs_script_type_table_t, entity->child_table); if (!child_table->owner) { child_table->owner = node; } } return entity; } entity = ecs_vec_append_t( NULL, &t->entities, ecs_script_type_entity_t); entity->node = node; entity->parent_node = flecs_script_type_owner(t); entity->parent = t->v->parent; entity->child_table = -1; entity->slot = flecs_script_type_slot_new(t, *slot); entity->in_template = t->template_scope; entity->in_for = t->for_depth != 0; int32_t index = ecs_vec_count(&t->entities) - 1; flecs_script_type_name_t key = {name, (uint64_t)t->table, length, true}; flecs_hashmap_result_t result = flecs_hashmap_ensure( &t->names, &key, int32_t); *(int32_t*)result.value = index; if (!entity->parent_node) { key.scope = entity->parent; key.table = false; result = flecs_hashmap_ensure(&t->names, &key, int32_t); *(int32_t*)result.value = index; } if (node) { node->type_index = index + 1; } *slot = entity->slot; if (has_scope) { entity->child_table = flecs_script_type_table_new( t, t->table, node); } return entity; } int flecs_script_type_symbol_lookup( void *ctx, const char *name, flecs_script_symbol_t *symbol) { ecs_script_type_visitor_t *t = ctx; ecs_script_type_entity_t *entity = flecs_script_type_find(t, name); if (!entity) { return -1; } symbol->kind = FlecsScriptSymbolEntitySlot; symbol->slot = entity->slot; symbol->sp = -1; symbol->entity = flecs_script_symbol_entity(t->v, entity->slot); if (symbol->entity && flecs_script_global_var_get( t->v->world, symbol->entity, NULL).ptr) { symbol->kind = FlecsScriptSymbolGlobalVariable; } return 0; } static ecs_script_type_entity_t* flecs_script_type_entity_from_node( ecs_script_type_visitor_t *t, ecs_script_entity_t *node) { return node->type_index ? ecs_vec_get_t( &t->entities, ecs_script_type_entity_t, node->type_index - 1) : NULL; } static int flecs_script_type_lookup( ecs_script_type_visitor_t *t, ecs_entity_t first, const char *name, ecs_flags32_t lookup_kind, ecs_entity_t *from_out, flecs_script_symbol_t *symbol) { ecs_expr_eval_desc_t desc = flecs_script_expr_desc(t->v, 0); return flecs_script_id_elem_lookup(&t->v->base.script->pub, &desc, first, name, lookup_kind, from_out, symbol); } static int flecs_script_type_resolve_type( ecs_script_type_visitor_t *t, const char *name, ecs_entity_t *type) { flecs_script_symbol_t symbol; if (flecs_script_type_lookup( t, 0, name, FlecsScriptLookupEntity, NULL, &symbol) || !symbol.entity) { return -1; } type[0] = symbol.entity; return 0; } static void flecs_script_skip_unknown_skip_refs( ecs_script_type_visitor_t *t, int32_t prev_unresolved) { ecs_script_impl_t *impl = t->v->base.script; int32_t i, count = ecs_vec_count(&impl->unresolved_refs); ecs_script_unresolved_ref_t *refs = ecs_vec_first(&impl->unresolved_refs); for (i = prev_unresolved; i < count; i ++) { flecs_script_skip_unknown_warn(&impl->pub, refs[i].name, "skipped expression with unresolved reference"); } ecs_vec_set_count_t(NULL, &impl->unresolved_refs, ecs_script_unresolved_ref_t, prev_unresolved); t->skip_count ++; ecs_script_visit_t *base = &t->v->base; if (base->depth) { base->nodes[base->depth - 1]->skip = true; } } static void flecs_script_type_skip_var( ecs_script_type_visitor_t *t, const char *name) { t->skip_count ++; if (!name) { return; } ecs_vec_append_t(NULL, &t->skipped_vars, const char*)[0] = name; } bool flecs_script_type_is_skipped_var( ecs_script_type_visitor_t *t, const char *name) { int32_t i, count = ecs_vec_count(&t->skipped_vars); const char **vars = ecs_vec_first(&t->skipped_vars); for (i = 0; i < count; i ++) { ecs_size_t len = ecs_os_strlen(vars[i]); if (ecs_os_strncmp(name, vars[i], len)) { continue; } if (!name[len] || name[len] == '.') { return true; } } return false; } static void flecs_script_type_prune_skipped_refs( ecs_script_type_visitor_t *t, int32_t prev_unresolved) { if (!ecs_vec_count(&t->skipped_vars)) { return; } ecs_script_impl_t *impl = t->v->base.script; int32_t i, count = ecs_vec_count(&impl->unresolved_refs); ecs_script_unresolved_ref_t *refs = ecs_vec_first(&impl->unresolved_refs); int32_t dst = prev_unresolved; for (i = prev_unresolved; i < count; i ++) { if (refs[i].kind != FlecsScriptUnresolvedComponent && flecs_script_type_is_skipped_var(t, refs[i].name)) { continue; } refs[dst ++] = refs[i]; } ecs_vec_set_count_t(NULL, &impl->unresolved_refs, ecs_script_unresolved_ref_t, dst); } static int flecs_script_type_check_expr( ecs_script_type_visitor_t *t, ecs_expr_node_t **expr_ptr, ecs_entity_t *type) { ecs_script_eval_visitor_t *v = t->v; ecs_script_t *script = &v->base.script->pub; ecs_entity_t expected_type = type ? type[0] : 0; ecs_expr_eval_desc_t desc = flecs_script_expr_desc(v, expected_type); bool was_typed = expr_ptr[0]->type_info != NULL; if (!was_typed) { int32_t prev_unresolved = ecs_vec_count( &v->base.script->unresolved_refs); if (flecs_expr_visit_type(script, expr_ptr, &desc)) { if (ecs_vec_count(&v->base.script->unresolved_refs) > prev_unresolved) { flecs_script_type_prune_skipped_refs(t, prev_unresolved); if (v->base.script->skip_unknown) { flecs_script_skip_unknown_skip_refs(t, prev_unresolved); } return 1; } return -1; } } if (was_typed) { if (expected_type && expr_ptr[0]->type != expected_type && (!ecs_get(v->world, expected_type, EcsPrimitive) || !ecs_get(v->world, expr_ptr[0]->type, EcsPrimitive))) { flecs_expr_visit_error(script, expr_ptr[0], "expression of type %s is incompatible with expected type %s", flecs_errstr(ecs_get_path(v->world, expr_ptr[0]->type)), flecs_errstr_1(ecs_get_path(v->world, expected_type))); return -1; } } else { if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { return -1; } } if (type) { type[0] = expr_ptr[0]->type; } return 0; } static void flecs_script_skip_unknown_drop_expr( ecs_script_type_visitor_t *t, ecs_expr_node_t **expr) { flecs_expr_visit_free(&t->v->base.script->pub, *expr); *expr = NULL; } static bool flecs_script_skip_unknown_drop_var( ecs_script_type_visitor_t *t, ecs_script_var_node_t *node) { if (!flecs_script_is_skip_unknown(&t->v->base.script->pub)) { return false; } flecs_script_skip_unknown_warn(&t->v->base.script->pub, node->type, "skipped variable with unresolved type"); flecs_script_type_skip_var(t, node->name); if (node->expr) { flecs_script_skip_unknown_drop_expr(t, &node->expr); } node->eval_type = 0; node->node.skip = true; return true; } static bool flecs_script_skip_unknown_drop_fn( ecs_script_type_visitor_t *t, ecs_script_function_node_t *node, const char *type) { if (!flecs_script_is_skip_unknown(&t->v->base.script->pub)) { return false; } flecs_script_skip_unknown_warn(&t->v->base.script->pub, type, "skipped function with unresolved type"); node->node.skip = true; return true; } static bool flecs_script_is_var_name( const char *name) { if (!name || name[0] != '$' || !name[1]) { return false; } const char *ptr; for (ptr = &name[1]; ptr[0]; ptr ++) { if (!isalnum((unsigned char)ptr[0]) && ptr[0] != '_') { return false; } } return true; } static int flecs_script_type_template_prop_id( ecs_script_type_visitor_t *t, void *node, ecs_script_id_t *id, const char *name, int32_t var_sp, ecs_entity_t interface, int32_t *sp) { if (t->v->template && flecs_script_template_member_is_vector( t->v->template, var_sp)) { flecs_script_eval_error(t->v, node, "prop '%s' is a vector, use '%s[index]' to select an " "element", name, name); return -1; } if (id->second) { flecs_script_eval_error(t->v, node, "template prop '%s' cannot be used as pair element", name); return -1; } id->interface = interface; *sp = var_sp; id->dynamic = true; return 0; } static int flecs_script_type_id_elem( ecs_script_type_visitor_t *t, ecs_script_id_t *id, void *node, const char *name, ecs_expr_node_t **name_expr, ecs_entity_t first, ecs_entity_t *eval, int32_t *slot, int32_t *sp) { if (name_expr && *name_expr) { if (t->v->template && (*name_expr)->kind == EcsExprInterpolatedString) { const char *value = ((ecs_expr_interpolated_string_t*) *name_expr)->value; if (flecs_script_is_var_name(value)) { ecs_script_var_t *var = ecs_script_vars_lookup( t->v->vars, &value[1]); ecs_entity_t interface = var ? flecs_script_type_var_interface(t, var->sp) : 0; if (interface) { if (flecs_script_type_template_prop_id( t, node, id, &value[1], var->sp, interface, sp)) { return -1; } flecs_expr_visit_free(&t->v->base.script->pub, *name_expr); *name_expr = NULL; return 0; } } } ecs_entity_t type = ecs_id(ecs_string_t); id->dynamic = true; return flecs_script_type_check_expr(t, name_expr, &type); } ecs_entity_t from; flecs_script_symbol_t symbol; if (flecs_script_type_lookup( t, first, name, FlecsScriptLookupAll, &from, &symbol)) { if (from) { char *parent_str = ecs_id_str(t->v->world, from); flecs_script_eval_error(t->v, node, "target '%s' not found in parent '%s'", name, parent_str); ecs_os_free(parent_str); return -1; } ecs_script_node_kind_t node_kind = ((ecs_script_node_t*)node)->kind; if (flecs_script_is_skip_unknown(&t->v->base.script->pub) && (node_kind == EcsAstTag || node_kind == EcsAstComponent || node_kind == EcsAstWithTag || node_kind == EcsAstWithComponent) && first != EcsIsA && id->first_eval != EcsIsA) { flecs_script_skip_unknown_warn(&t->v->base.script->pub, name, "skipped statement with unresolved component"); ((ecs_script_node_t*)node)->skip = true; return 1; } flecs_script_unresolved_kind_t ref_kind = FlecsScriptUnresolvedEntity; if (!id->second) { if (node_kind == EcsAstComponent || node_kind == EcsAstWithComponent) { if (((ecs_script_component_t*)node)->expr) { ref_kind = FlecsScriptUnresolvedComponent; } } } flecs_script_type_unresolved_ref(t, node, name, ref_kind); return 1; } if (symbol.kind == FlecsScriptSymbolVariable) { ecs_script_var_t *var = ecs_script_vars_from_sp( t->v->vars, symbol.sp); ecs_entity_t interface = var ? flecs_script_type_var_interface(t, symbol.sp) : 0; if (interface) { return flecs_script_type_template_prop_id( t, node, id, name, symbol.sp, interface, sp); } if (!var || var->value.type != ecs_id(ecs_entity_t)) { flecs_script_eval_error(t->v, node, "variable '%s' must be of type entity", name); return -1; } *sp = symbol.sp; id->dynamic = true; } else if (symbol.kind == FlecsScriptSymbolEntitySlot) { *slot = symbol.slot; id->dynamic = true; } else { *eval = symbol.entity; } return 0; } static int flecs_script_type_index_id( ecs_script_type_visitor_t *t, void *node, ecs_script_id_t *id) { if (id->second) { flecs_script_eval_error(t->v, node, "vector prop '%s' cannot be used as pair element", id->first); return -1; } ecs_script_var_t *var = ecs_script_vars_lookup(t->v->vars, id->first); if (!var || !t->v->template || !flecs_script_template_member_is_vector( t->v->template, var->sp)) { flecs_script_eval_error(t->v, node, "'%s' is not a vector prop, cannot use '[]'", id->first); return -1; } ecs_entity_t index_type = ecs_id(ecs_i32_t); int result = flecs_script_type_check_expr(t, &id->index_expr, &index_type); if (result) { return result; } id->interface = flecs_script_template_member_interface( t->v->template, var->sp); id->index_sp = var->sp; id->dynamic = true; return 0; } static int flecs_script_type_id( ecs_script_type_visitor_t *t, void *node, ecs_script_id_t *id) { if (!id->first || (!id->first[0] && !id->first_expr) || (id->second && !id->second[0] && !id->second_expr)) { flecs_script_eval_error(t->v, node, "invalid component identifier"); return -1; } id->eval = 0; id->first_eval = 0; id->second_eval = 0; id->first_symbol = -1; id->second_symbol = -1; id->first_sp = -1; id->second_sp = -1; id->index_sp = -1; id->interface = 0; id->dynamic = id->first_expr || id->second_expr; if (id->index_expr) { return flecs_script_type_index_id(t, node, id); } int result = flecs_script_type_id_elem(t, id, node, id->first, &id->first_expr, 0, &id->first_eval, &id->first_symbol, &id->first_sp); if (result) { return result; } ecs_entity_t first; if (flecs_script_eval_id_elem(t->v, node, NULL, id->first_eval, id->first_symbol, id->first_sp, &first)) { return -1; } if (id->second) { result = flecs_script_type_id_elem(t, id, node, id->second, &id->second_expr, first, &id->second_eval, &id->second_symbol, &id->second_sp); if (result) { return result; } ecs_entity_t second; if (flecs_script_eval_id_elem(t->v, node, NULL, id->second_eval, id->second_symbol, id->second_sp, &second)) { return -1; } if (first && second) { id->eval = id->flag | ecs_pair(first, second); } } else if (first) { id->eval = id->flag | first; } return 0; } static int flecs_script_type_ensure_node( ecs_script_type_visitor_t *t, ecs_script_entity_t *node); static ecs_entity_t flecs_script_type_node_entity( ecs_script_type_visitor_t *t, ecs_script_entity_t *node) { return node->symbol != -1 ? flecs_script_symbol_entity(t->v, node->symbol) : node->eval; } static ecs_entity_t flecs_script_type_parent( ecs_script_type_visitor_t *t, ecs_script_entity_t *owner, ecs_entity_t parent) { if (!owner) { return parent; } if (flecs_script_type_ensure_node(t, owner)) { return 0; } return flecs_script_type_node_entity(t, owner); } static int flecs_script_type_ensure_owner( ecs_script_type_visitor_t *t, ecs_entity_t *parent) { ecs_script_entity_t *owner = flecs_script_type_owner(t); *parent = flecs_script_type_parent(t, owner, t->v->parent); return owner && !*parent ? -1 : 0; } static int flecs_script_type_ensure_node( ecs_script_type_visitor_t *t, ecs_script_entity_t *node) { ecs_entity_t current = flecs_script_type_node_entity(t, node); if (current && ecs_is_alive(t->v->world, current)) { return 0; } ecs_script_type_entity_t *entry = flecs_script_type_entity_from_node( t, node); ecs_script_entity_t *parent_node = entry ? entry->parent_node : NULL; ecs_entity_t parent = flecs_script_type_parent( t, parent_node, entry ? entry->parent : 0); if (parent_node && !parent) { return -1; } ecs_value_t value = {0}; if (node->name_expr) { value.type = node->name_expr->type; if (flecs_script_eval_expr(t->v, &node->name_expr, &value)) { return -1; } } current = value.ptr && value.type == ecs_id(ecs_entity_t) ? *(ecs_entity_t*)value.ptr : flecs_script_new_entity(t->v, parent, value.ptr ? *(char**)value.ptr : node->name, parent_node && parent_node->non_fragmenting_parent); if (value.ptr) { ecs_ptr_free(t->v->world, value.type, value.ptr); } if (!current) { return -1; } node->eval = current; if (node->symbol != -1) { flecs_script_symbol_set(t->v, node->symbol, current); } return 0; } static int flecs_script_type_ensure_entities( ecs_script_type_visitor_t *t) { int32_t i, count = ecs_vec_count(&t->entities); for (i = 0; i < count; i ++) { ecs_script_type_entity_t *entries = ecs_vec_first(&t->entities); ecs_script_entity_t *node = entries[i].node; if (entries[i].in_template && !t->template_scope) { /* Entities declared in a template body are created when the * template is instantiated, not in the scope of the script. */ continue; } if (entries[i].in_for && !t->for_depth) { continue; } if (node && flecs_script_type_ensure_node(t, node)) { return -1; } } return 0; } static int flecs_script_type_tag( ecs_script_type_visitor_t *t, ecs_script_tag_t *node) { int result = flecs_script_type_id(t, node, &node->id); if (result) { return result; } if (t->v->is_with_scope) { flecs_script_eval_error(t->v, node, "invalid tag in with scope"); return -1; } if (!t->v->entity) { flecs_script_eval_error(t->v, node, "missing entity for tag %s", node->id.first); return -1; } if (node->id.eval && !flecs_script_can_default_ctor(t->v->world, node->id.eval)) { flecs_script_eval_error(t->v, node, "cannot add %s, type is not default constructible", node->id.first); return -1; } return 0; } static ecs_entity_t flecs_script_type_component_type( ecs_script_type_visitor_t *t, ecs_script_component_t *node) { if (node->id.interface) { return node->id.interface; } if (node->id.eval) { return ecs_get_typeid(t->v->world, node->id.eval); } ecs_entity_t candidates[2] = {0, 0}; if (flecs_script_eval_id_elem(t->v, node, NULL, node->id.first_eval, node->id.first_symbol, node->id.first_sp, &candidates[0]) || flecs_script_eval_id_elem(t->v, node, NULL, node->id.second_eval, node->id.second_symbol, node->id.second_sp, &candidates[1])) { return 0; } if (candidates[0] && node->id.second && ecs_has_id(t->v->world, candidates[0], EcsPairIsTag)) { return 0; } for (int32_t i = 0; i < 2; i ++) { if (!candidates[i]) { continue; } const ecs_type_info_t *ti = ecs_get_type_info( t->v->world, candidates[i]); if (ti) { return ti->component; } } return 0; } static int flecs_script_type_struct_expr( ecs_script_type_visitor_t *t, ecs_expr_node_t *expr) { if (!expr || (expr->kind != EcsExprInitializer && expr->kind != EcsExprEmptyInitializer)) { return 0; } ecs_expr_initializer_t *initializer = (ecs_expr_initializer_t*)expr; int32_t i, count = ecs_vec_count(&initializer->elements); ecs_expr_initializer_element_t *elems = ecs_vec_first( &initializer->elements); for (i = 0; i < count; i ++) { ecs_entity_t type = elems[i].value && (elems[i].value->kind == EcsExprInitializer || elems[i].value->kind == EcsExprEmptyInitializer) ? ecs_id(EcsMember) : ecs_id(ecs_entity_t); if (elems[i].value) { int result = flecs_script_type_check_expr( t, &elems[i].value, &type); if (result) { return result; } } } return 0; } static int flecs_script_type_constants_expr( ecs_script_type_visitor_t *t, ecs_expr_node_t *expr, bool is_bitmask) { if (!expr || (expr->kind != EcsExprInitializer && expr->kind != EcsExprEmptyInitializer)) { return 0; } ecs_expr_initializer_t *initializer = (ecs_expr_initializer_t*)expr; int32_t i, count = ecs_vec_count(&initializer->elements); ecs_expr_initializer_element_t *elems = ecs_vec_first( &initializer->elements); ecs_entity_t underlying = is_bitmask ? ecs_id(ecs_u32_t) : 0; for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member || !elem->value || (elem->value->kind != EcsExprInitializer && elem->value->kind != EcsExprEmptyInitializer)) { continue; } ecs_expr_initializer_t *cfg = (ecs_expr_initializer_t*)elem->value; int32_t c, cfg_count = ecs_vec_count(&cfg->elements); ecs_expr_initializer_element_t *cfg_elems = ecs_vec_first( &cfg->elements); for (c = 0; c < cfg_count; c ++) { ecs_entity_t type = ecs_id(ecs_entity_t); if (cfg_elems[c].value) { int result = flecs_script_type_check_expr( t, &cfg_elems[c].value, &type); if (result) { return result; } } if (!is_bitmask && cfg_elems[c].member && !ecs_os_strcmp(cfg_elems[c].member, "underlying_type")) { ecs_value_t value = { .type = ecs_id(ecs_entity_t) }; if (flecs_script_eval_expr( t->v, &cfg_elems[c].value, &value)) { return -1; } underlying = *(ecs_entity_t*)value.ptr; ecs_ptr_free(t->v->world, value.type, value.ptr); } } } if (!underlying) { underlying = ecs_id(ecs_i32_t); } for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member && elem->value) { int result = flecs_script_type_check_expr( t, &elem->value, &underlying); if (result) { return result; } } } return 0; } static int flecs_script_type_visitor_expr( ecs_script_type_visitor_t *t, ecs_script_component_t *node) { if (node->id.eval == ecs_id(EcsStruct)) { return flecs_script_type_struct_expr(t, node->expr); } if (node->id.eval == ecs_id(EcsEnum)) { return flecs_script_type_constants_expr(t, node->expr, false); } if (node->id.eval == ecs_id(EcsBitmask)) { return flecs_script_type_constants_expr(t, node->expr, true); } return 0; } static int flecs_script_type_check_component_expr( ecs_script_type_visitor_t *t, ecs_script_component_t *node, ecs_entity_t *type) { ecs_expr_initializer_t *init = NULL; if (node->id.interface && node->expr && (node->expr->kind == EcsExprInitializer || node->expr->kind == EcsExprEmptyInitializer)) { init = (ecs_expr_initializer_t*)node->expr; if (init->is_partial) { init = NULL; } else { init->is_partial = true; } } int result = flecs_script_type_check_expr(t, &node->expr, type); if (init && node->expr == (ecs_expr_node_t*)init) { init->is_partial = false; } return result; } static int flecs_script_type_untyped_template_check( ecs_script_type_visitor_t *t, ecs_script_component_t *node) { if (node->id.interface != ecs_id(EcsStruct)) { return 0; } flecs_script_eval_error(t->v, node, "cannot assign value to untyped template prop '%s'", node->id.first); return -1; } static int flecs_script_type_with_component( ecs_script_type_visitor_t *t, ecs_script_component_t *node) { int result = flecs_script_type_id(t, node, &node->id); if (result || !node->expr) { return result; } if (flecs_script_type_untyped_template_check(t, node)) { return -1; } ecs_entity_t type = flecs_script_type_component_type(t, node); if (!type) { flecs_script_eval_error(t->v, node, "unresolved component type '%s'", node->id.first); return -1; } return flecs_script_type_check_component_expr(t, node, &type); } static int flecs_script_type_component( ecs_script_type_visitor_t *t, ecs_script_component_t *node) { int result = flecs_script_type_id(t, node, &node->id); if (result) { return result; } if (!t->v->entity) { flecs_script_eval_error(t->v, node, "missing entity for component %s", node->id.first); return -1; } if (t->v->is_with_scope) { flecs_script_eval_error(t->v, node, "invalid component in with scope"); return -1; } if (!node->expr) { return 0; } if (flecs_script_type_untyped_template_check(t, node)) { return -1; } if (!node->id.second && node->id.eval && ecs_has( t->v->world, node->id.eval, EcsScriptVisitor)) { return flecs_script_type_visitor_expr(t, node); } ecs_entity_t component_type = flecs_script_type_component_type(t, node); if (!component_type || !ecs_get_type_info(t->v->world, component_type)) { if (t->v->base.script->skip_unknown) { flecs_script_skip_unknown_warn(&t->v->base.script->pub, node->id.first, "skipped value for component without reflection data"); flecs_script_skip_unknown_drop_expr(t, &node->expr); return 0; } flecs_script_eval_error(t->v, node, component_type ? "cannot set value of '%s': not a component" : "unresolved component type '%s'", node->id.first); return -1; } return flecs_script_type_check_component_expr(t, node, &component_type); } static int flecs_script_type_control_scope( ecs_script_type_visitor_t *t, ecs_script_scope_t *scope) { int32_t table = flecs_script_type_table_new( t, t->table, flecs_script_type_owner(t)); t->control_depth ++; int result = flecs_script_type_scope(t, scope, table, true, false); t->control_depth --; return result; } static int flecs_script_type_with( ecs_script_type_visitor_t *t, ecs_script_with_t *node) { if (flecs_script_type_scope( t, node->expressions, t->table, false, false)) { return -1; } bool old_with = t->v->is_with_scope; t->v->is_with_scope = true; t->control_depth ++; int result = flecs_script_type_scope( t, node->scope, t->table, true, false); t->control_depth --; t->v->is_with_scope = old_with; return result; } static int flecs_script_type_annot( ecs_script_type_visitor_t *t, ecs_script_annot_t *node) { ecs_script_eval_visitor_t *v = t->v; if (!v->base.next) { flecs_script_eval_error(v, node, "annotation '%s' is not applied to anything", node->name); return -1; } ecs_script_node_kind_t kind = v->base.next->kind; if (kind != EcsAstEntity && kind != EcsAstTemplate && kind != EcsAstAnnotation) { flecs_script_eval_error(v, node, "annotation must be applied to an entity or template"); return -1; } if (!v->base.prev || v->base.prev->kind != EcsAstAnnotation) { t->annot_start = ecs_vec_count(&v->r->annot); } /* Recording an annotation is safe while a template is being preprocessed, * as it does not evaluate anything in the world. Clear the template so the * evaluation entrypoint doesn't reject the node. */ ecs_script_template_t *template = v->template; v->template = NULL; int result = flecs_script_eval_node(&v->base, (ecs_script_node_t*)node); v->template = template; /* Apply the tree annotations to the ast while typing. Entities can be * created before the annotations are evaluated (see * flecs_script_type_ensure_node) which means the hierarchy storage has to * be known before evaluation starts. */ if (!result && kind == EcsAstEntity) { int32_t i, count = ecs_vec_count(&v->r->annot); ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); for (i = t->annot_start; i < count; i ++) { flecs_script_apply_tree_annot( annots[i], (ecs_script_entity_t*)v->base.next); } } return result; } static int flecs_script_type_const( ecs_script_type_visitor_t *t, ecs_script_var_node_t *node) { bool export = node->node.kind == EcsAstExportConst || node->node.kind == EcsAstExportMut; if (!node->expr) { flecs_script_eval_error(t->v, node, "const variable '%s' is missing initializer", node->name); return -1; } ecs_entity_t expected_type = 0; if (node->type && flecs_script_type_resolve_type( t, node->type, &expected_type)) { if (flecs_script_skip_unknown_drop_var(t, node)) { return 0; } flecs_script_type_unresolved_ref(t, node, node->type, FlecsScriptUnresolvedComponent); if (node->expr->kind == EcsExprInitializer || node->expr->kind == EcsExprEmptyInitializer) { return -1; } } ecs_entity_t type = expected_type; int expr_result = flecs_script_type_check_expr(t, &node->expr, &type); if (expr_result) { if (expr_result == 1) { flecs_script_type_skip_var(t, node->name); return 0; } return -1; } node->eval_type = type; if (node->is_await && export) { flecs_script_eval_error(t->v, node, "await is not allowed for exported variables"); return -1; } if (export) { if (ecs_script_vars_lookup(t->v->vars, node->name)) { flecs_script_eval_error(t->v, node, "exported variable '%s' shadows a local variable", node->name); return -1; } flecs_script_type_declare( t, node->name, NULL, &node->symbol, false); if (flecs_script_type_ensure_entities(t)) { return -1; } ecs_entity_t old_parent = t->v->parent, parent; if (flecs_script_type_ensure_owner(t, &parent)) { return -1; } t->v->parent = parent; int result = flecs_script_eval_const(t->v, node, true); t->v->parent = old_parent; if (result || !flecs_script_symbol_entity(t->v, node->symbol)) { return -1; } return 0; } ecs_script_entity_t *owner = flecs_script_type_owner(t); ecs_entity_t parent = owner ? flecs_script_type_node_entity(t, owner) : t->v->parent; if (parent || !owner) { ecs_entity_t existing = ecs_lookup_child( t->v->world, parent, node->name); if (existing && flecs_script_global_var_get( t->v->world, existing, NULL).ptr) { flecs_script_eval_error(t->v, node, "local variable '%s' shadows an exported variable", node->name); return -1; } } ecs_script_var_t *var = ecs_script_vars_declare( t->v->vars, node->name); if (!var) { flecs_script_eval_error(t->v, node, "variable '%s' redeclared", node->name); return -1; } var->value.type = type; var->type_info = ecs_get_type_info(t->v->world, type); var->is_const = true; node->sp = var->sp; if (node->is_await) { return 0; } ecs_expr_eval_desc_t desc = flecs_script_expr_desc(t->v, type); if (flecs_expr_visit_fold_const( &t->v->base.script->pub, &node->expr, &desc)) { return -1; } if (node->expr->kind == EcsExprValue) { ecs_expr_value_node_t *value = (ecs_expr_value_node_t*)node->expr; const ecs_type_info_t *ti = var->type_info; var->value.ptr = flecs_stack_calloc( &t->v->r->stack, ti->size, ti->alignment); flecs_type_info_ctor(var->value.ptr, 1, ti); ecs_ptr_copy_w_type_info( t->v->world, ti, var->value.ptr, value->ptr); var->owned = true; } return 0; } static int flecs_script_type_template_var( ecs_script_type_visitor_t *t, ecs_script_var_node_t *node, bool mut) { if (!node->expr && (mut || (!node->type && !node->type_is_template))) { flecs_script_eval_error(t->v, node, "%s variable '%s' is missing initializer", mut ? "mut" : "prop", node->name); return -1; } ecs_entity_t type = 0; if (node->type && flecs_script_type_resolve_type( t, node->type, &type)) { if (flecs_script_skip_unknown_drop_var(t, node)) { return 0; } flecs_script_type_unresolved_ref(t, node, node->type, FlecsScriptUnresolvedComponent); if (!node->expr || node->expr->kind == EcsExprInitializer || node->expr->kind == EcsExprEmptyInitializer) { return -1; } } if (type && t->v->template && type == t->v->template->props.type) { flecs_script_eval_error(t->v, node, "%s '%s' cannot have the type of the template that declares it", mut ? "mut" : "prop", node->name); return -1; } if (node->type_is_template && !node->type) { node->eval_interface = ecs_id(EcsStruct); type = ecs_id(ecs_script_template_ref_t); } else if (node->type_is_template) { if (type && ecs_has(t->v->world, type, EcsStruct)) { node->eval_interface = type; type = ecs_id(ecs_script_template_ref_t); } else { flecs_script_eval_error(t->v, node, "type '%s' of prop '%s' is not a template or struct", node->type, node->name); return -1; } } if (node->type_is_vector) { type = flecs_script_vector_type( t->v->world, ecs_id(ecs_script_template_ref_t)); } if (node->expr) { int result = flecs_script_type_check_expr(t, &node->expr, &type); if (result) { if (result == 1) { flecs_script_type_skip_var(t, node->name); return 0; } return -1; } } node->eval_type = type; return flecs_script_template_eval_var(t->v, node, mut); } static ecs_entity_t flecs_script_type_interface_var( const ecs_script_type_visitor_t *t, int32_t sp) { const ecs_script_type_interface_var_t *vars = ecs_vec_first( &t->interface_vars); int32_t i, count = ecs_vec_count(&t->interface_vars); for (i = count - 1; i >= 0; i --) { if (vars[i].sp == sp) { return vars[i].interface; } } return 0; } static ecs_entity_t flecs_script_type_for_interface( ecs_script_type_visitor_t *t, ecs_script_for_t *node, ecs_entity_t elem_type) { if (!node->expr || elem_type != ecs_id(ecs_script_template_ref_t) || !t->v->template) { return 0; } if (node->expr->kind != EcsExprVariable) { return 0; } int32_t sp = ((ecs_expr_variable_t*)node->expr)->sp; if (sp < 0) { return 0; } if (!flecs_script_template_member_is_vector(t->v->template, sp)) { return 0; } return flecs_script_template_member_interface(t->v->template, sp); } static int flecs_script_type_for( ecs_script_type_visitor_t *t, ecs_script_for_t *node) { flecs_script_for_state_t state = {0}; ecs_value_t collection = {0}; int expr_result; if (!node->expr) { ecs_entity_t type = ecs_id(ecs_i32_t); expr_result = flecs_script_type_check_expr(t, &node->from, &type); if (expr_result) { return expr_result == 1 ? 0 : -1; } type = ecs_id(ecs_i32_t); expr_result = flecs_script_type_check_expr(t, &node->to, &type); if (expr_result) { return expr_result == 1 ? 0 : -1; } } else { expr_result = flecs_script_type_check_expr( t, &node->expr, &collection.type); if (expr_result) { return expr_result == 1 ? 0 : -1; } } if (flecs_script_for_init(t->v, node, &state, node->expr ? &collection : NULL) || flecs_script_for_declare_vars(t->v, node, &state, true)) { return -1; } int32_t i; for (i = 0; i < node->loop_var_count; i ++) { node->loop_var_sp[i] = t->v->vars->sp + i; } int32_t interface_var_count = ecs_vec_count(&t->interface_vars); ecs_entity_t elem_interface = flecs_script_type_for_interface( t, node, state.var_types[state.elem_var]); if (elem_interface) { ecs_script_type_interface_var_t *iv = ecs_vec_append_t(NULL, &t->interface_vars, ecs_script_type_interface_var_t); iv->sp = node->loop_var_sp[state.elem_var]; iv->interface = elem_interface; } t->for_depth ++; int result = flecs_script_type_control_scope(t, node->scope); t->for_depth --; ecs_vec_set_count_t(NULL, &t->interface_vars, ecs_script_type_interface_var_t, interface_var_count); t->v->vars = ecs_script_vars_pop(t->v->vars); return result; } static int flecs_script_type_pair_scope( ecs_script_type_visitor_t *t, ecs_script_pair_scope_t *node) { const char *names[2] = { node->id.first, node->id.second }; ecs_expr_node_t *exprs[2] = { node->id.first_expr, node->id.second_expr }; int32_t *slots[2] = { &node->id.first_symbol, &node->id.second_symbol }; for (int32_t i = 0; i < 2; i ++) { const char *name = names[i]; if (!name || exprs[i] || name[0] == '$' || !ecs_os_strcmp(name, "this")) { continue; } if (i == 1) { flecs_script_type_declare( t, name, NULL, slots[i], true); continue; } flecs_script_symbol_t symbol; if (flecs_script_type_lookup( t, 0, name, FlecsScriptLookupEntity, NULL, &symbol)) { flecs_script_type_declare( t, name, NULL, slots[i], true); } } if (flecs_script_type_id(t, node, &node->id) == -1) { return -1; } int result = flecs_script_type_control_scope(t, node->scope); return result; } static int flecs_script_type_if( ecs_script_type_visitor_t *t, ecs_script_if_t *node) { if (flecs_script_type_check_expr(t, &node->expr, NULL) == -1) { return -1; } if (flecs_script_type_control_scope(t, node->if_true)) { return -1; } return flecs_script_type_control_scope(t, node->if_false); } static int flecs_script_type_try( ecs_script_type_visitor_t *t, ecs_script_try_t *node) { if (flecs_script_type_control_scope(t, node->try_scope)) { return -1; } int32_t i, count = ecs_vec_count(&node->catches); ecs_script_catch_t *catches = ecs_vec_first(&node->catches); for (i = 0; i < count; i ++) { ecs_script_catch_t *catch_ = &catches[i]; catch_->eval_error = 0; catch_->error_symbol = -1; if (catch_->error) { flecs_script_symbol_t symbol; if (flecs_script_type_lookup( t, 0, catch_->error, FlecsScriptLookupEntity, NULL, &symbol)) { flecs_script_type_unresolved_ref(t, node, catch_->error, FlecsScriptUnresolvedEntity); } else if (symbol.kind == FlecsScriptSymbolEntitySlot) { catch_->error_symbol = symbol.slot; } else { catch_->eval_error = symbol.entity; } } if (flecs_script_type_control_scope(t, catch_->scope)) { return -1; } } return 0; } static int flecs_script_type_function( ecs_script_type_visitor_t *t, ecs_script_function_node_t *node) { if (t->template_scope) { flecs_script_eval_error(t->v, node, "fn is not allowed in template"); return -1; } if (ecs_vec_count(&node->params) > FLECS_SCRIPT_FUNCTION_ARGS_MAX) { flecs_script_eval_error(t->v, node, "too many parameters for function '%s' (max %d)", node->name, FLECS_SCRIPT_FUNCTION_ARGS_MAX); return -1; } flecs_script_type_declare( t, node->name, NULL, &node->symbol, false); if (flecs_script_type_resolve_type( t, node->return_type, &node->eval_return_type)) { if (!flecs_script_skip_unknown_drop_fn(t, node, node->return_type)) { flecs_script_type_unresolved_ref( t, &node->return_type_node, node->return_type, FlecsScriptUnresolvedComponent); } return 0; } ecs_entity_t parent; if (flecs_script_type_ensure_owner(t, &parent)) { return -1; } ecs_entity_t fn_entity = 0; bool keep = false; int result = -1; ecs_script_vars_t *outer_vars = t->v->vars; t->v->vars = flecs_script_vars_push( NULL, &t->v->r->stack, t->v->r->allocator); int32_t i, param_count = ecs_vec_count(&node->params); ecs_script_fn_param_t *params = ecs_vec_first(&node->params); for (i = 0; i < param_count; i ++) { if (flecs_script_type_resolve_type( t, params[i].type, ¶ms[i].eval_type)) { if (!flecs_script_skip_unknown_drop_fn(t, node, params[i].type)) { flecs_script_type_unresolved_ref( t, ¶ms[i].node, params[i].type, FlecsScriptUnresolvedComponent); } result = 0; goto done; } ecs_script_var_t *var = ecs_script_vars_declare( t->v->vars, params[i].name); if (!var) { flecs_script_eval_error(t->v, ¶ms[i].node, "duplicate parameter '%s' in function '%s'", params[i].name, node->name); goto done; } params[i].sp = var->sp; var->value.type = params[i].eval_type; var->type_info = ecs_get_type_info( t->v->world, params[i].eval_type); } ecs_function_desc_t fn_desc = { .name = node->name, .parent = parent, .return_type = node->eval_return_type, .callback = flecs_script_user_function_callback }; for (i = 0; i < param_count; i ++) { fn_desc.params[i].name = params[i].name; fn_desc.params[i].type = params[i].eval_type; } fn_entity = ecs_function_init(t->v->world, &fn_desc); if (!fn_entity) { flecs_script_eval_error(t->v, node, "failed to register function '%s'", node->name); goto done; } flecs_script_symbol_set(t->v, node->symbol, fn_entity); if (flecs_script_type_ensure_entities(t)) { goto done; } int32_t table = flecs_script_type_table_new( t, t->table, flecs_script_type_owner(t)); int32_t prev_unresolved = ecs_vec_count( &t->v->base.script->unresolved_refs); int32_t prev_skip_count = t->skip_count; bool old_function_scope = t->function_scope; t->function_scope = true; result = flecs_script_type_scope(t, node->body, table, false, false); if (!result && node->return_expr) { ecs_entity_t return_type = node->eval_return_type; result = flecs_script_type_check_expr( t, &node->return_expr, &return_type); } t->function_scope = old_function_scope; if (!result && t->skip_count == prev_skip_count && ecs_vec_count(&t->v->base.script->unresolved_refs) == prev_unresolved) { result = flecs_script_eval_function(t->v, node, fn_entity); keep = !result; } else if (result != -1) { if (flecs_script_is_skip_unknown(&t->v->base.script->pub)) { flecs_script_skip_unknown_warn(&t->v->base.script->pub, node->name, "skipped function with unresolved reference"); node->node.skip = true; } result = 0; } done: ecs_script_vars_pop(t->v->vars); t->v->vars = outer_vars; if (fn_entity && !keep) { ecs_delete(t->v->world, fn_entity); flecs_script_symbol_set(t->v, node->symbol, 0); } return result; } static bool flecs_script_type_entity_name_ref( ecs_script_type_visitor_t *t, ecs_script_entity_t *node) { if (!flecs_script_is_var_name(node->name)) { return false; } ecs_script_var_t *var = ecs_script_vars_lookup(t->v->vars, &node->name[1]); if (!var || var->value.type != ecs_id(ecs_entity_t) || flecs_script_type_var_interface(t, var->sp)) { return false; } ecs_expr_node_t *expr = node->name_expr; if (expr->kind == EcsExprInterpolatedString) { ecs_script_t *script = &t->v->base.script->pub; node->name_expr = (ecs_expr_node_t*)flecs_expr_variable_from( script, expr, &node->name[1]); flecs_expr_visit_free(script, expr); } return true; } static int flecs_script_type_entity( ecs_script_type_visitor_t *t, ecs_script_entity_t *node, bool allow_type) { node->eval = 0; node->eval_kind = 0; node->kind_symbol = -1; node->kind_sp = -1; node->type_index = 0; ecs_script_type_entity_t *entry = NULL; int32_t child_table; if (node->name && !node->name_expr) { entry = flecs_script_type_declare( t, node->name, node, &node->symbol, true); child_table = entry->child_table; } else { if (node->symbol == -1 && !t->for_depth && !t->function_scope) { node->symbol = flecs_script_type_slot_new(t, -1); } child_table = flecs_script_type_table_new(t, t->table, node); } if (node->name_expr) { ecs_entity_t type = flecs_script_type_entity_name_ref(t, node) ? ecs_id(ecs_entity_t) : ecs_id(ecs_string_t); int result = flecs_script_type_check_expr(t, &node->name_expr, &type); if (result) { return result == 1 ? 0 : -1; } } if (node->kind) { if (!node->kind[0]) { flecs_script_eval_error(t->v, node, "invalid empty entity kind"); return -1; } if (!ecs_os_strcmp(node->kind, "prefab")) { #ifdef FLECS_PREFAB node->eval_kind = EcsPrefab; #else flecs_script_eval_error(t->v, node, "prefabs are unsupported in this flecs build, enable FLECS_PREFAB addon"); return -1; #endif } else { flecs_script_symbol_t symbol = {0}; if (flecs_script_type_lookup( t, 0, node->kind, FlecsScriptLookupAll, NULL, &symbol)) { flecs_script_type_unresolved_ref(t, node, node->kind, FlecsScriptUnresolvedEntity); } else if (symbol.kind == FlecsScriptSymbolVariable) { ecs_script_var_t *var = ecs_script_vars_from_sp( t->v->vars, symbol.sp); if (!var || var->value.type != ecs_id(ecs_entity_t)) { flecs_script_eval_error(t->v, node, "variable '%s' must be of type entity", node->kind); return -1; } node->kind_sp = symbol.sp; } else if (symbol.kind == FlecsScriptSymbolEntitySlot) { node->kind_symbol = symbol.slot; node->eval_kind = symbol.entity; } else { node->eval_kind = symbol.entity; } } } node->is_type = node->eval_kind && ecs_has( t->v->world, node->eval_kind, EcsScriptVisitor); if (node->is_type) { const EcsScript *script = ecs_get( t->v->world, node->eval_kind, EcsScript); if (script && script->template_) { node->is_type = false; } } if (node->is_type && (!allow_type || t->control_depth || t->template_scope || t->function_scope)) { flecs_script_eval_error(t->v, node, "type definitions are only allowed in entity scopes"); return -1; } flecs_script_entity_state_t state = { .node = node, .eval_kind = node->eval_kind }; flecs_script_entity_state_t *old_entity = t->v->entity; bool old_with = t->v->is_with_scope; t->v->entity = &state; t->v->is_with_scope = false; if (!node->is_type) { ecs_vec_clear(&t->v->r->annot); } int32_t prev_unresolved = ecs_vec_count( &t->v->base.script->unresolved_refs); int result = flecs_script_type_scope( t, node->scope, child_table, true, true); t->v->entity = old_entity; t->v->is_with_scope = old_with; if (result) { return -1; } if (node->is_type) { if (ecs_vec_count(&t->v->base.script->unresolved_refs) > prev_unresolved) { /* The definition references a symbol that isn't resolved yet. Skip * evaluating it, so the definition is either retried once a later * include resolves the symbol, or reported as an unresolved * reference when the type visitor is done. */ return 0; } if (flecs_script_type_ensure_node(t, node)) { return -1; } if (entry) { entry = flecs_script_type_entity_from_node(t, node); } ecs_entity_t old_parent = t->v->parent; ecs_entity_t parent = entry ? flecs_script_type_parent(t, entry->parent_node, entry->parent) : old_parent; t->v->parent = parent; ecs_entity_t out = 0; result = flecs_script_eval_entity(t->v, &t->v->base.script->pub, t->v->vars, node, &out); t->v->parent = old_parent; if (result) { return -1; } if (node->symbol != -1) { flecs_script_symbol_set(t->v, node->symbol, out); } } return 0; } static int flecs_script_type_template( ecs_script_type_visitor_t *t, ecs_script_template_node_t *node) { if (t->template_scope) { flecs_script_eval_error(t->v, node, "nested templates are not allowed"); return -1; } flecs_script_type_declare( t, node->name, NULL, &node->symbol, true); if (flecs_script_type_ensure_entities(t)) { return -1; } ecs_entity_t old_parent = t->v->parent, parent; if (flecs_script_type_ensure_owner(t, &parent)) { return -1; } t->v->parent = parent; node->eval_base = 0; if (node->base) { if (flecs_script_type_resolve_type(t, node->base, &node->eval_base)) { flecs_script_eval_error(t->v, node, "unresolved base '%s' for template '%s'", node->base, node->name); t->v->parent = old_parent; return -1; } } node->eval_parent = 0; if (node->parent && flecs_script_type_resolve_type( t, node->parent, &node->eval_parent)) { flecs_script_eval_error(t->v, node, "unresolved parent template '%s'", node->parent); t->v->parent = old_parent; return -1; } int result = flecs_script_eval_template(t->v, node); t->v->parent = old_parent; return result; } static int flecs_script_type_module( ecs_script_type_visitor_t *t, ecs_script_module_t *node) { flecs_script_type_declare( t, node->name, NULL, &node->symbol, false); ecs_entity_t old_parent = t->v->parent; ecs_entity_t old_scope = ecs_set_scope(t->v->world, 0); ecs_id_t old_script_tag = t->v->script_tag; t->v->parent = 0; t->v->script_tag = 0; node->eval = flecs_script_create_entity(t->v, node->name); t->v->script_tag = old_script_tag; ecs_set_scope(t->v->world, old_scope); if (!node->eval) { t->v->parent = old_parent; return -1; } ecs_add_id(t->v->world, node->eval, EcsModule); t->v->module = node->eval; t->v->parent = node->eval; flecs_script_symbol_set(t->v, node->symbol, node->eval); return 0; } static int flecs_script_type_using( ecs_script_type_visitor_t *t, ecs_script_using_t *node) { ecs_allocator_t *a = t->v->r->allocator; const char *name = node->name; int32_t len = ecs_os_strlen(name); char *path = NULL; if (len > 2 && !ecs_os_strcmp(&name[len - 2], ".*")) { path = flecs_strdup(a, name); path[len - 2] = '\0'; name = path; } flecs_script_symbol_t symbol; int result = flecs_script_type_lookup( t, 0, name, FlecsScriptLookupEntity, NULL, &symbol); if (path) { flecs_free_n(a, char, len + 1, path); } if (result || !symbol.entity) { if (flecs_script_is_skip_unknown(&t->v->base.script->pub)) { flecs_script_skip_unknown_warn(&t->v->base.script->pub, node->name, "skipped using with unresolved identifier"); node->node.skip = true; return 0; } flecs_script_type_unresolved_ref(t, node, node->name, FlecsScriptUnresolvedEntity); return -1; } node->eval = symbol.entity; return flecs_script_eval_node(&t->v->base, (ecs_script_node_t*)node); } static int flecs_script_type_async( ecs_script_type_visitor_t *t, ecs_script_async_t *node) { #ifndef FLECS_SCRIPT_ASYNC flecs_script_eval_error(t->v, node, "async blocks require FLECS_SCRIPT_ASYNC"); return -1; #else ecs_script_eval_visitor_t *v = t->v; if (t->async_scope) { flecs_script_eval_error(v, node, "async blocks cannot be nested"); return -1; } ecs_script_scope_t *parent = v->base.depth >= 2 ? (ecs_script_scope_t*)v->base.nodes[v->base.depth - 2] : NULL; bool in_root = parent == v->base.script->root; bool in_template = v->template && !t->function_scope && (parent == v->template->node->scope || (v->entity && parent == v->entity->node->scope)); if (!in_root && !in_template) { flecs_script_eval_error(v, node, "async blocks are only allowed in the root scope of a script or " "in template entity scopes"); return -1; } if (in_template) { v->vars = flecs_script_vars_push( v->vars, &v->r->stack, v->r->allocator); ecs_script_var_t *this_var = ecs_script_vars_declare(v->vars, "this"); this_var->value.type = ecs_id(ecs_entity_t); } int32_t old_for_depth = t->for_depth; t->for_depth = 0; t->async_scope = true; int result = flecs_script_type_control_scope(t, node->scope); t->async_scope = false; t->for_depth = old_for_depth; if (in_template) { v->vars = ecs_script_vars_pop(v->vars); } return result; #endif } static int flecs_script_type_while( ecs_script_type_visitor_t *t, ecs_script_while_t *node) { if (!t->async_scope) { flecs_script_eval_error(t->v, node, "while is only allowed in async blocks"); return -1; } ecs_entity_t type = 0; int expr_result = flecs_script_type_check_expr(t, &node->expr, &type); if (expr_result == -1) { return -1; } if (!expr_result && type) { const EcsType *type_ptr = ecs_get(t->v->world, type, EcsType); if (!type_ptr || (type_ptr->kind != EcsPrimitiveType && type_ptr->kind != EcsEnumType && type_ptr->kind != EcsBitmaskType)) { flecs_script_eval_error(t->v, node, "value of type %s cannot be used as while condition", flecs_errstr(ecs_get_path(t->v->world, type))); return -1; } } t->for_depth ++; int result = flecs_script_type_control_scope(t, node->scope); t->for_depth --; return result; } static bool flecs_script_type_is_number( const ecs_world_t *world, ecs_entity_t type) { const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); return p && p->kind != EcsString && p->kind != EcsEntity && p->kind != EcsId; } static int flecs_script_type_assign( ecs_script_type_visitor_t *t, ecs_script_assign_t *node) { ecs_script_eval_visitor_t *v = t->v; if (!t->async_scope) { flecs_script_eval_error(v, node, "assignment is only allowed in async blocks"); return -1; } ecs_script_template_t *template = v->template; ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); bool qualified = !ecs_os_strncmp(node->name, "parent.", 7); ecs_entity_t component = 0; const ecs_member_t *m = NULL; node->parent = false; if ((!var || qualified) && template && template->parent_type) { const char *name = qualified ? node->name + 7 : node->name; if (strchr(name, '.')) { flecs_script_eval_error(v, node, "assignment requires a mut variable name"); return -1; } m = flecs_script_template_parent_member( v->world, template, name, &component); if (m && component != template->parent_type) { node->parent = true; } else { m = NULL; } } if (!node->parent && var && template) { ecs_script_template_member_t *members = ecs_vec_first(&template->members); int32_t count = ecs_vec_count(&template->members); for (int32_t i = 0; i < count; i ++) { if (members[i].sp == var->sp && members[i].is_mut) { component = template->muts.type; const EcsStruct *st = ecs_get(v->world, component, EcsStruct); m = ecs_vec_get_t(&st->members, ecs_member_t, members[i].index); break; } } } if (!m) { flecs_script_eval_error(v, node, "cannot assign to '%s': only mut variables of a template can be " "assigned", node->name); return -1; } ecs_entity_t var_type = node->parent ? m->type : var->value.type; int32_t sp = node->parent ? template->parent_sp : var->sp; ecs_entity_t type = var_type; int result = flecs_script_type_check_expr(t, &node->expr, &type); if (result) { if (result == 1) { node->node.skip = true; return 0; } return -1; } if (type != var_type && (!flecs_script_type_is_number(v->world, var_type) || !flecs_script_type_is_number(v->world, type))) { flecs_script_eval_error(v, node, "expression of type %s is incompatible with type %s of " "variable '%s'", flecs_errstr(ecs_get_path(v->world, type)), flecs_errstr_1(ecs_get_path(v->world, var_type)), node->name); return -1; } type = var_type; node->eval_type = type; node->sp = sp; node->component = component; node->offset = m->offset; return 0; } static int flecs_script_type_node( ecs_script_type_visitor_t *t, ecs_script_node_t *node, bool allow_type) { if (t->function_scope && node->kind != EcsAstConst) { flecs_script_eval_error(t->v, node, "only const declarations are allowed in fn body"); return -1; } if (t->async_scope) { switch(node->kind) { case EcsAstScope: case EcsAstConst: case EcsAstAwait: case EcsAstTry: case EcsAstIf: case EcsAstFor: case EcsAstWhile: case EcsAstContinue: case EcsAstAssign: break; default: flecs_script_eval_error(t->v, node, "%s statement is not allowed in async block", flecs_script_node_kind_str(node)); return -1; } } switch (node->kind) { case EcsAstScope: return flecs_script_type_control_scope( t, (ecs_script_scope_t*)node); case EcsAstTag: return flecs_script_type_tag(t, (ecs_script_tag_t*)node); case EcsAstWithTag: return flecs_script_type_id(t, node, &((ecs_script_tag_t*)node)->id); case EcsAstComponent: return flecs_script_type_component( t, (ecs_script_component_t*)node); case EcsAstWithComponent: return flecs_script_type_with_component( t, (ecs_script_component_t*)node); case EcsAstWith: return flecs_script_type_with(t, (ecs_script_with_t*)node); case EcsAstUsing: if (t->template_scope) { flecs_script_eval_error(t->v, node, "using in template is not allowed"); return -1; } return flecs_script_type_using( t, (ecs_script_using_t*)node); case EcsAstModule: return flecs_script_type_module( t, (ecs_script_module_t*)node); case EcsAstAnnotation: return flecs_script_type_annot(t, (ecs_script_annot_t*)node); case EcsAstTemplate: return flecs_script_type_template( t, (ecs_script_template_node_t*)node); case EcsAstProp: if (!t->template_scope) { flecs_script_eval_error(t->v, node, "prop variables are only allowed in templates"); return -1; } return flecs_script_type_template_var( t, (ecs_script_var_node_t*)node, false); case EcsAstMut: if (!t->template_scope) { flecs_script_eval_error(t->v, node, "mut variables are only allowed in templates"); return -1; } return flecs_script_type_template_var( t, (ecs_script_var_node_t*)node, true); case EcsAstConst: case EcsAstExportConst: case EcsAstExportMut: return flecs_script_type_const( t, (ecs_script_var_node_t*)node); case EcsAstEntity: return flecs_script_type_entity( t, (ecs_script_entity_t*)node, allow_type); case EcsAstPairScope: return flecs_script_type_pair_scope( t, (ecs_script_pair_scope_t*)node); case EcsAstIf: return flecs_script_type_if(t, (ecs_script_if_t*)node); case EcsAstFor: return flecs_script_type_for(t, (ecs_script_for_t*)node); case EcsAstInclude: if (t->template_scope) { flecs_script_eval_error(t->v, node, "include is not allowed in template"); return -1; } return flecs_script_eval_include( t->v, (ecs_script_include_t*)node); case EcsAstFunction: return flecs_script_type_function( t, (ecs_script_function_node_t*)node); case EcsAstAwait: return flecs_script_type_check_expr(t, &((ecs_script_await_t*)node)->expr, NULL); case EcsAstTry: return flecs_script_type_try(t, (ecs_script_try_t*)node); case EcsAstContinue: if (!t->for_depth) { flecs_script_eval_error(t->v, node, "continue is only allowed inside a loop"); return -1; } return 0; case EcsAstAsync: return flecs_script_type_async(t, (ecs_script_async_t*)node); case EcsAstWhile: return flecs_script_type_while(t, (ecs_script_while_t*)node); case EcsAstAssign: return flecs_script_type_assign(t, (ecs_script_assign_t*)node); } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); } int flecs_script_type_scope( ecs_script_type_visitor_t *t, ecs_script_scope_t *scope, int32_t table, bool push_vars, bool allow_type) { ecs_script_eval_visitor_t *v = t->v; int32_t old_table = t->table; int32_t old_depth = v->base.depth; int32_t old_skipped_vars = ecs_vec_count(&t->skipped_vars); t->table = table; if (push_vars) { v->vars = flecs_script_vars_push( v->vars, &v->r->stack, v->r->allocator); } ecs_vec_clear(&scope->components); ecs_vec_clear(&scope->set_components); ecs_assert(v->base.depth < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); v->base.nodes[v->base.depth ++] = (ecs_script_node_t*)scope; int32_t i, count = ecs_vec_count(&scope->stmts); int result = 0; for (i = 0; i < count; i ++) { ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); ecs_script_node_t *node = stmts[i]; v->base.prev = i ? stmts[i - 1] : NULL; v->base.next = i + 1 < count ? stmts[i + 1] : NULL; ecs_assert(v->base.depth < ECS_SCRIPT_VISIT_MAX_DEPTH, ECS_INTERNAL_ERROR, NULL); v->base.nodes[v->base.depth ++] = node; node->skip = false; result = flecs_script_type_node(t, node, allow_type) == -1 ? -1 : 0; v->base.depth --; if (result) { ecs_vec_clear(&scope->components); ecs_vec_clear(&scope->set_components); break; } if (t->template_scope) { ecs_script_id_t *id = NULL; bool w_expr = false; if (node->kind == EcsAstComponent) { ecs_script_component_t *component = (ecs_script_component_t*)node; id = &component->id; w_expr = component->expr != NULL; } else if (node->kind == EcsAstTag) { id = &((ecs_script_tag_t*)node)->id; } if (id && id->eval && !id->interface && !id->dynamic) { ecs_vec_append_t(&v->base.script->allocator, &scope->components, ecs_id_t)[0] = id->eval; if (w_expr) { ecs_vec_append_t(&v->base.script->allocator, &scope->set_components, ecs_id_t)[0] = id->eval; } } } } v->base.depth = old_depth; if (push_vars) { v->vars = ecs_script_vars_pop(v->vars); } ecs_vec_set_count_t(NULL, &t->skipped_vars, const char*, old_skipped_vars); t->table = old_table; return result; } int flecs_script_visit_type_entity_expr( ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_script_eval_visitor_t *v, ecs_script_entity_t *entity) { if (v && v->type_visitor) { ecs_script_type_visitor_t *t = v->type_visitor; bool old_function_scope = t->function_scope; t->function_scope = false; int result = flecs_script_type_entity(t, entity, false); if (old_function_scope) { entity->symbol = -1; } t->function_scope = old_function_scope; return result; } ecs_script_eval_desc_t eval_desc = { .runtime = desc ? desc->runtime : NULL }; ecs_vec_clear(&flecs_script_impl(script)->unresolved_refs); ecs_vec_clear(&flecs_script_impl(script)->unresolved_component_refs); ecs_script_eval_visitor_t visitor; flecs_script_eval_visit_init( flecs_script_impl(script), &visitor, &eval_desc); if (desc && desc->vars) { flecs_script_eval_push_vars(&visitor, desc->vars); } ecs_script_type_visitor_t t = { .v = &visitor, .table = 0 }; flecs_script_type_init(&t); visitor.type_visitor = &t; int result = flecs_script_type_entity(&t, entity, false); if (flecs_script_type_report_unresolved(&visitor)) { result = -1; } visitor.type_visitor = NULL; flecs_script_type_fini(&t); if (desc && desc->vars) { flecs_script_eval_pop_vars(&visitor); } flecs_script_eval_visit_fini(&visitor, &eval_desc); return result; } int flecs_script_visit_type( ecs_script_eval_visitor_t *v, ecs_script_scope_t *scope) { ecs_vec_clear(&v->base.script->unresolved_refs); ecs_vec_clear(&v->base.script->unresolved_component_refs); ecs_entity_t outer_parent = v->parent; ecs_entity_t outer_module = v->module; ecs_vec_t outer_using = v->r->using; ecs_vec_t outer_annot = v->r->annot; v->r->using = ecs_vec_copy_t( v->r->allocator, &outer_using, ecs_entity_t); v->r->annot = ecs_vec_copy_t( v->r->allocator, &outer_annot, ecs_script_annot_t*); ecs_script_type_visitor_t t = { .v = v, .table = 0 }; flecs_script_type_init(&t); v->type_visitor = &t; int result = flecs_script_type_scope( &t, scope, 0, true, true); v->type_visitor = NULL; if (flecs_script_type_report_unresolved(v)) { result = -1; } flecs_script_type_fini(&t); ecs_vec_fini_t(v->r->allocator, &v->r->using, ecs_entity_t); ecs_vec_fini_t(v->r->allocator, &v->r->annot, ecs_script_annot_t*); v->r->using = outer_using; v->r->annot = outer_annot; v->parent = outer_parent; v->module = outer_module; return result; } #endif #ifdef FLECS_SCRIPT #define flecs_expr_ast_new(parser, T, kind)\ (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) static void* flecs_expr_ast_new_( ecs_parser_t *parser, ecs_size_t size, ecs_expr_node_kind_t kind) { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; ecs_expr_node_t *result = flecs_calloc_w_dbg_info(a, size, "ecs_expr_node_t"); result->kind = kind; result->alloc_size = size; result->pos = parser->pos; return result; } ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type) { ecs_expr_value_node_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_value_node_t); result->ptr = &result->storage.u64; result->node.kind = EcsExprValue; result->node.alloc_size = ECS_SIZEOF(ecs_expr_value_node_t); result->node.pos = node ? node->pos : NULL; result->node.end = node ? node->end : NULL; result->node.type = type; result->node.type_info = ecs_get_type_info(script->world, type); return result; } ecs_expr_variable_t* flecs_expr_variable_from( ecs_script_t *script, ecs_expr_node_t *node, const char *name) { ecs_expr_variable_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); result->name = name; result->sp = -1; result->node.kind = EcsExprVariable; result->node.alloc_size = ECS_SIZEOF(ecs_expr_variable_t); result->node.pos = node ? node->pos : NULL; result->node.end = node ? node->end : NULL; return result; } ecs_expr_member_t* flecs_expr_member_from( ecs_script_t *script, ecs_expr_node_t *node, const char *name) { ecs_expr_member_t *result = flecs_calloc_t( &flecs_script_impl(script)->allocator, ecs_expr_member_t); result->node.kind = EcsExprMember; result->node.alloc_size = ECS_SIZEOF(ecs_expr_member_t); result->node.pos = node->pos; result->node.end = node->end; result->left = node; result->member_name =name; return result; } ecs_expr_swizzle_t* flecs_expr_swizzle_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_expr_node_t *left, const char *name) { ecs_expr_swizzle_t *result = flecs_calloc_t( &flecs_script_impl(script)->allocator, ecs_expr_swizzle_t); result->node.kind = EcsExprSwizzle; result->node.alloc_size = ECS_SIZEOF(ecs_expr_swizzle_t); result->node.pos = node->pos; result->node.end = node->end; result->left = left; result->name = name; return result; } ecs_expr_value_node_t* flecs_expr_bool( ecs_parser_t *parser, bool value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.bool_ = value; result->ptr = &result->storage.bool_; result->node.type = ecs_id(ecs_bool_t); return result; } ecs_expr_value_node_t* flecs_expr_char( ecs_parser_t *parser, const char *value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); if(value[0] == '\\' && value[1] == '\'') { result->storage.char_ = '\''; } else { char ch = 0; const char *ptr = flecs_chrparse(value, &ch); if(!ptr) { flecs_free_t( &parser->script->allocator, ecs_expr_value_node_t, result); return NULL; } result->storage.char_ = ch; } result->ptr = &result->storage.char_; result->node.type = ecs_id(ecs_char_t); return result; } ecs_expr_value_node_t* flecs_expr_int( ecs_parser_t *parser, int64_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.i64 = value; result->ptr = &result->storage.i64; result->node.type = ecs_id(ecs_i64_t); return result; } ecs_expr_value_node_t* flecs_expr_uint( ecs_parser_t *parser, uint64_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.u64 = value; result->ptr = &result->storage.u64; if (value > INT64_MAX) { result->node.type = ecs_id(ecs_u64_t); } else { result->node.type = ecs_id(ecs_i64_t); } return result; } ecs_expr_value_node_t* flecs_expr_float( ecs_parser_t *parser, double value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.f64 = value; result->ptr = &result->storage.f64; result->node.type = ecs_id(ecs_f64_t); return result; } ecs_expr_value_node_t* flecs_expr_string( ecs_parser_t *parser, const char *value) { char *str = ECS_CONST_CAST(char*, value); ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.string = str; result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); if (!flecs_string_escape(str)) { flecs_free_t(&parser->script->allocator, ecs_expr_value_node_t, result); return NULL; } return result; } ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( ecs_parser_t *parser, const char *value) { ecs_expr_interpolated_string_t *result = flecs_expr_ast_new( parser, ecs_expr_interpolated_string_t, EcsExprInterpolatedString); result->value = flecs_strdup(&parser->script->allocator, value); result->buffer = flecs_strdup(&parser->script->allocator, value); result->buffer_size = ecs_os_strlen(result->buffer) + 1; result->node.type = ecs_id(ecs_string_t); ecs_vec_init_t(&parser->script->allocator, &result->fragments, ecs_expr_fragment_t, 0); return result; } ecs_expr_initializer_t* flecs_expr_initializer( ecs_parser_t *parser) { ecs_expr_initializer_t *result = flecs_expr_ast_new( parser, ecs_expr_initializer_t, EcsExprInitializer); ecs_vec_init_t(&parser->script->allocator, &result->elements, ecs_expr_initializer_element_t, 0); return result; } ecs_expr_identifier_t* flecs_expr_identifier( ecs_parser_t *parser, const char *value) { ecs_expr_identifier_t *result = flecs_expr_ast_new( parser, ecs_expr_identifier_t, EcsExprIdentifier); result->value = value; result->symbol = -1; return result; } ecs_expr_variable_t* flecs_expr_variable( ecs_parser_t *parser, const char *value) { ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); result->name = value; result->sp = -1; return result; } ecs_expr_unary_t* flecs_expr_unary( ecs_parser_t *parser) { ecs_expr_unary_t *result = flecs_expr_ast_new( parser, ecs_expr_unary_t, EcsExprUnary); return result; } ecs_expr_binary_t* flecs_expr_binary( ecs_parser_t *parser) { ecs_expr_binary_t *result = flecs_expr_ast_new( parser, ecs_expr_binary_t, EcsExprBinary); return result; } ecs_expr_member_t* flecs_expr_member( ecs_parser_t *parser) { ecs_expr_member_t *result = flecs_expr_ast_new( parser, ecs_expr_member_t, EcsExprMember); return result; } ecs_expr_function_t* flecs_expr_function( ecs_parser_t *parser) { ecs_expr_function_t *result = flecs_expr_ast_new( parser, ecs_expr_function_t, EcsExprFunction); return result; } ecs_expr_element_t* flecs_expr_element( ecs_parser_t *parser) { ecs_expr_element_t *result = flecs_expr_ast_new( parser, ecs_expr_element_t, EcsExprElement); return result; } ecs_expr_has_t* flecs_expr_has( ecs_parser_t *parser) { ecs_expr_has_t *result = flecs_expr_ast_new( parser, ecs_expr_has_t, EcsExprHas); return result; } ecs_expr_match_t* flecs_expr_match( ecs_parser_t *parser) { ecs_expr_match_t *result = flecs_expr_ast_new( parser, ecs_expr_match_t, EcsExprMatch); return result; } ecs_expr_range_t* flecs_expr_range( ecs_parser_t *parser) { ecs_expr_range_t *result = flecs_expr_ast_new( parser, ecs_expr_range_t, EcsExprRange); return result; } ecs_expr_new_t* flecs_expr_new( ecs_parser_t *parser) { ecs_expr_new_t *result = flecs_expr_ast_new( parser, ecs_expr_new_t, EcsExprNew); return result; } ecs_expr_script_t* flecs_expr_script( ecs_parser_t *parser) { ecs_expr_script_t *result = flecs_expr_ast_new( parser, ecs_expr_script_t, EcsExprScript); return result; } bool flecs_expr_explicit_cast_allowed( ecs_world_t *world, ecs_entity_t from, ecs_entity_t to) { if (from == to) { return true; } if (from == ecs_id(ecs_script_template_ref_t) && ecs_has(world, to, EcsStruct)) { return true; } /* Any type can be cast to and from a value */ if (to == ecs_id(ecs_value_t) || from == ecs_id(ecs_value_t)) { return true; } if (to == ecs_id(ecs_script_template_ref_t)) { return from == ecs_id(ecs_entity_t) || from == ecs_id(ecs_string_t); } const EcsType *from_type = ecs_get(world, from, EcsType); const EcsType *to_type = ecs_get(world, to, EcsType); ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); /* Treat opaque types as the types that they're pretending to be */ if (from_type->kind == EcsOpaqueType) { const EcsOpaque *o = ecs_get(world, from, EcsOpaque); ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); from_type = ecs_get(world, o->as_type, EcsType); ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); } if (to_type->kind == EcsOpaqueType) { const EcsOpaque *o = ecs_get(world, to, EcsOpaque); ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); to_type = ecs_get(world, o->as_type, EcsType); ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); } if (from_type->kind != EcsPrimitiveType || to_type->kind != EcsPrimitiveType) { if (from_type->kind == EcsEnumType || from_type->kind == EcsBitmaskType) { if (flecs_expr_is_type_integer(to)) { /* Can cast enums/bitmasks to integers */ return true; } } if (flecs_expr_is_type_integer(from)) { if (to_type->kind == EcsEnumType || to_type->kind == EcsBitmaskType) { /* Can cast integers to enums/bitmasks */ return true; } } if (from_type->kind == EcsStructType && to_type->kind == EcsStructType) { return flecs_struct_is_derived_from(world, from, to); } /* Cannot cast complex types that are not the same */ return false; } return true; } ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *expr, ecs_entity_t type) { if (!flecs_expr_explicit_cast_allowed(script->world, expr->type, type)) { if (type == ecs_id(ecs_script_template_ref_t)) { flecs_expr_visit_error(script, expr, "expected template or struct, got value of type %s", flecs_errstr(ecs_id_str(script->world, expr->type))); return NULL; } flecs_expr_visit_error(script, expr, "invalid cast from %s to %s", flecs_errstr(ecs_id_str(script->world, expr->type)), flecs_errstr_1(ecs_id_str(script->world, type))); return NULL; } ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); result->node.kind = EcsExprCast; result->node.alloc_size = ECS_SIZEOF(ecs_expr_cast_t); if (flecs_expr_is_type_number(expr->type) && flecs_expr_is_type_number(type)) { result->node.kind = EcsExprCastNumber; result->cast = flecs_expr_cast_handler(expr->type, type); } result->node.pos = expr->pos; result->node.end = expr->end; result->node.type = type; result->node.type_info = ecs_get_type_info(script->world, type); ecs_assert(result->node.type_info != NULL, ECS_INTERNAL_ERROR, NULL); result->expr = expr; return result; } int flecs_expr_visit_children( ecs_expr_node_t *node, flecs_expr_visit_action_t action, void *ctx) { switch(node->kind) { case EcsExprValue: case EcsExprGlobalVariable: break; case EcsExprVariable: break; case EcsExprInterpolatedString: { ecs_expr_interpolated_string_t *n = (ecs_expr_interpolated_string_t*)node; ecs_expr_fragment_t *fragments = ecs_vec_first(&n->fragments); int32_t i, count = ecs_vec_count(&n->fragments); for (i = 0; i < count; i ++) { if (action(&fragments[i].expr, ctx)) { return -1; } } for (i = 0; i < count; i ++) { if (action(&fragments[i].format.width, ctx) || action(&fragments[i].format.precision, ctx)) { return -1; } } break; } case EcsExprInitializer: case EcsExprEmptyInitializer: { ecs_expr_initializer_t *n = (ecs_expr_initializer_t*)node; ecs_expr_initializer_element_t *elems = ecs_vec_first(&n->elements); int32_t i, count = ecs_vec_count(&n->elements); for (i = 0; i < count; i ++) { if (action(&elems[i].key, ctx) || action(&elems[i].value, ctx)) { return -1; } } break; } case EcsExprUnary: return action(&((ecs_expr_unary_t*)node)->expr, ctx); case EcsExprBinary: { ecs_expr_binary_t *n = (ecs_expr_binary_t*)node; if (action(&n->left, ctx) || action(&n->right, ctx)) { return -1; } break; } case EcsExprIdentifier: return action(&((ecs_expr_identifier_t*)node)->expr, ctx); case EcsExprFunction: case EcsExprMethod: case EcsExprTemplate: { ecs_expr_function_t *n = (ecs_expr_function_t*)node; ecs_expr_node_t *args = (ecs_expr_node_t*)n->args; if (action(&n->left, ctx) || action(&args, ctx)) { return -1; } n->args = (ecs_expr_initializer_t*)args; break; } case EcsExprMember: return action(&((ecs_expr_member_t*)node)->left, ctx); case EcsExprSwizzle: return action(&((ecs_expr_swizzle_t*)node)->left, ctx); case EcsExprComponent: case EcsExprElement: { ecs_expr_element_t *n = (ecs_expr_element_t*)node; if (action(&n->left, ctx) || action(&n->index, ctx)) { return -1; } break; } case EcsExprHas: { ecs_expr_has_t *n = (ecs_expr_has_t*)node; if (action(&n->left, ctx) || action(&n->first, ctx) || action(&n->second, ctx)) { return -1; } break; } case EcsExprCast: case EcsExprCastNumber: return action(&((ecs_expr_cast_t*)node)->expr, ctx); case EcsExprMatch: { ecs_expr_match_t *n = (ecs_expr_match_t*)node; if (action(&n->expr, ctx)) { return -1; } ecs_expr_match_element_t *elems = ecs_vec_first(&n->elements); int32_t i, count = ecs_vec_count(&n->elements); for (i = 0; i < count; i ++) { if (action(&elems[i].compare, ctx) || action(&elems[i].expr, ctx)) { return -1; } } if (action(&n->any.compare, ctx) || action(&n->any.expr, ctx)) { return -1; } break; } case EcsExprRange: { ecs_expr_range_t *n = (ecs_expr_range_t*)node; if (action(&n->from, ctx) || action(&n->to, ctx)) { return -1; } break; } case EcsExprNew: case EcsExprScript: break; } return 0; } #endif #ifdef FLECS_SCRIPT static const char* flecs_expr_format_skip_space( const char *pos) { while (isspace((unsigned char)pos[0])) { pos ++; } return pos; } static bool flecs_expr_format_is_alignment( char ch) { return ch == '<' || ch == '^' || ch == '>'; } static bool flecs_expr_format_identifier_exists( ecs_parser_t *parser, const ecs_expr_eval_desc_t *desc, const char *start, char *end) { bool is_variable = start[0] == '$'; if (is_variable) { start ++; } char ch = end[0]; end[0] = '\0'; ecs_script_t *script = &parser->script->pub; flecs_script_symbol_t symbol; bool result = !flecs_script_symbol_lookup(script, desc, 0, start, is_variable ? FlecsScriptLookupVariable : FlecsScriptLookupAll, &symbol) && (symbol.kind == FlecsScriptSymbolVariable || symbol.kind == FlecsScriptSymbolGlobalVariable); end[0] = ch; return result; } static void flecs_expr_format_parse_error( ecs_parser_t *parser, const char *message) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, parser->code), "%s", message); } const char* flecs_expr_format_parse( ecs_parser_t *parser, const char *pos, ecs_expr_format_t *format, const ecs_expr_eval_desc_t *desc) { ecs_os_zeromem(format); format->pos = pos; format->fill = ' '; format->alignment = '>'; format->is_present = true; pos = flecs_expr_format_skip_space(pos); if (pos[0] && flecs_expr_format_is_alignment(pos[1])) { format->fill = pos[0]; format->alignment = pos[1]; pos += 2; } else if (flecs_expr_format_is_alignment(pos[0])) { format->alignment = pos[0]; pos ++; } pos = flecs_expr_format_skip_space(pos); if (pos[0] == '+') { format->sign = true; pos ++; } pos = flecs_expr_format_skip_space(pos); if (pos[0] == '0') { format->leading_zero = true; pos ++; } pos = flecs_expr_format_skip_space(pos); bool notation_is_width = false; if ((pos[0] == 'e' || pos[0] == 'E') && flecs_expr_format_skip_space(pos + 1)[0] == '}') { notation_is_width = flecs_expr_format_identifier_exists( parser, desc, pos, ECS_CONST_CAST(char*, pos + 1)); } if (pos[0] && pos[0] != '.' && ((pos[0] != 'e' && pos[0] != 'E') || notation_is_width) && pos[0] != '}') { /* Keep the precision separator out of simple width tokens. More * involved width expressions must be parenthesized. */ char *separator = NULL; if (isdigit((unsigned char)pos[0])) { separator = ECS_CONST_CAST(char*, pos); while (isdigit((unsigned char)separator[0])) { separator ++; } } else if (pos[0] == '$' || isalpha((unsigned char)pos[0]) || pos[0] == '_') { separator = ECS_CONST_CAST(char*, pos); if (separator[0] == '$') { separator ++; } while (isalnum((unsigned char)separator[0]) || separator[0] == '_') { separator ++; } } if (separator && separator[0] != '.' && !isalpha((unsigned char)separator[0])) { separator = NULL; } char separator_ch = separator ? separator[0] : '\0'; if (separator) { separator[0] = '\0'; } const char *expr_end = flecs_script_parse_expr( parser, pos, 0, &format->width); if (separator) { separator[0] = separator_ch; } if (!expr_end) { return NULL; } pos = separator ? separator : expr_end; } pos = flecs_expr_format_skip_space(pos); if (pos[0] == '.') { pos = flecs_expr_format_skip_space(pos + 1); if (!pos[0] || pos[0] == '}') { flecs_expr_format_parse_error( parser, "expected precision expression"); return NULL; } /* Split a notation suffix from a variable token. */ char *notation = NULL; char notation_ch = '\0'; if (pos[0] == '$' || isalnum((unsigned char)pos[0]) || pos[0] == '_') { char *end = ECS_CONST_CAST(char*, pos); if (end[0] == '$') { end ++; } while (isalnum((unsigned char)end[0]) || end[0] == '_') { end ++; } char *last = end - 1; if (last >= pos && (last[0] == 'e' || last[0] == 'E') && flecs_expr_format_skip_space(end)[0] == '}' && !flecs_expr_format_identifier_exists( parser, desc, pos, end)) { notation = last; notation_ch = last[0]; last[0] = '\0'; } } const char *expr_end = flecs_script_parse_expr( parser, pos, 0, &format->precision); if (notation) { notation[0] = notation_ch; } if (!expr_end) { return NULL; } pos = notation ? notation : expr_end; } pos = flecs_expr_format_skip_space(pos); if (pos[0] == 'e' || pos[0] == 'E') { format->notation = pos[0]; pos ++; } pos = flecs_expr_format_skip_space(pos); if (pos[0] != '}') { flecs_expr_format_parse_error( parser, "invalid string format specifier"); return NULL; } return pos; } static void flecs_expr_format_append_fill( ecs_strbuf_t *buf, char fill, int32_t count) { int32_t i; for (i = 0; i < count; i ++) { ecs_strbuf_appendch(buf, fill); } } int flecs_expr_format_value( const ecs_script_t *script, const ecs_expr_node_t *node, const ecs_value_t *value, const ecs_expr_format_t *format, int32_t width, int32_t precision, ecs_strbuf_t *buf) { if (format->width && width < 0) { flecs_expr_visit_error(script, node, "minimum width must not be negative"); return -1; } if (format->width && width > 1024) { flecs_expr_visit_error(script, node, "minimum width must not exceed 1024"); return -1; } if (format->precision && precision < 0) { flecs_expr_visit_error(script, node, "precision must not be negative"); return -1; } if (format->precision && precision > 1024) { flecs_expr_visit_error(script, node, "precision must not exceed 1024"); return -1; } ecs_entity_t type = value->type; char *str = NULL; if (flecs_expr_is_type_float(type)) { double number; if (type == ecs_id(ecs_f32_t)) { number = (double)*(ecs_f32_t*)value->ptr; } else { number = *(ecs_f64_t*)value->ptr; } char printf_fmt[8]; char *fmt_ptr = printf_fmt; fmt_ptr[0] = '%'; fmt_ptr ++; if (format->sign) { fmt_ptr[0] = '+'; fmt_ptr ++; } if (precision >= 0) { fmt_ptr[0] = '.'; fmt_ptr[1] = '*'; fmt_ptr += 2; } fmt_ptr[0] = format->notation ? format->notation : 'f'; fmt_ptr[1] = '\0'; if (precision >= 0) { str = flecs_asprintf(printf_fmt, precision, number); } else { str = flecs_asprintf(printf_fmt, number); } } else if (flecs_expr_is_type_string(type)) { char *value_str = *(char**)value->ptr; str = ecs_os_strdup(value_str ? value_str : "null"); } else if (type == ecs_id(ecs_bool_t)) { str = ecs_os_strdup(*(ecs_bool_t*)value->ptr ? "true" : "false"); } else if (type == ecs_id(ecs_char_t)) { char ch[2] = { *(ecs_char_t*)value->ptr, '\0' }; str = ecs_os_strdup(ch); } else { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, type, value->ptr); if (flecs_expr_is_type_signed_integer(type)) { str = flecs_asprintf(format->sign ? "%+lld" : "%lld", (long long)ecs_meta_get_int(&cur)); } else { ecs_assert(flecs_expr_is_type_unsigned_integer(type), ECS_INTERNAL_ERROR, NULL); str = flecs_asprintf(format->sign ? "+%llu" : "%llu", (unsigned long long)ecs_meta_get_uint(&cur)); } } if (!str) { return -1; } int32_t length = flecs_ito(int32_t, ecs_os_strlen(str)); int32_t padding = width > length ? width - length : 0; if (format->leading_zero && padding) { const char *number_ptr = str; if (number_ptr[0] == '+' || number_ptr[0] == '-') { ecs_strbuf_appendch(buf, number_ptr[0]); number_ptr ++; } flecs_expr_format_append_fill(buf, '0', padding); ecs_strbuf_appendstr(buf, number_ptr); } else if (format->alignment == '<') { ecs_strbuf_appendstr(buf, str); flecs_expr_format_append_fill(buf, format->fill, padding); } else if (format->alignment == '^') { int32_t left = padding / 2; flecs_expr_format_append_fill(buf, format->fill, left); ecs_strbuf_appendstr(buf, str); flecs_expr_format_append_fill(buf, format->fill, padding - left); } else { flecs_expr_format_append_fill(buf, format->fill, padding); ecs_strbuf_appendstr(buf, str); } ecs_os_free(str); return 0; } #endif #ifdef FLECS_SCRIPT #include /* From https://en.cppreference.com/w/c/language/operator_precedence */ static int flecs_expr_precedence[EcsTokLast] = { [EcsTokMember] = 1, [EcsTokBracketOpen] = 1, [EcsTokHasBracketOpen] = 1, [EcsTokNot] = 2, [EcsTokMul] = 3, [EcsTokDiv] = 3, [EcsTokMod] = 3, [EcsTokAdd] = 4, [EcsTokSub] = 4, [EcsTokShiftLeft] = 5, [EcsTokShiftRight] = 5, [EcsTokGt] = 6, [EcsTokGtEq] = 6, [EcsTokLt] = 6, [EcsTokLtEq] = 6, [EcsTokEq] = 7, [EcsTokNeq] = 7, [EcsTokBitwiseAnd] = 8, [EcsTokBitwiseOr] = 10, [EcsTokAnd] = 11, [EcsTokOr] = 12, }; static const char* flecs_script_parse_lhs( ecs_parser_t *parser, const char *pos, ecs_tokenizer_t *tokenizer, ecs_token_kind_t left_oper, ecs_expr_node_t **out); static void flecs_script_parser_expr_free( ecs_parser_t *parser, ecs_expr_node_t *node) { flecs_expr_visit_free(&parser->script->pub, node); } static bool flecs_has_precedence( ecs_token_kind_t first, ecs_token_kind_t second) { if (!flecs_expr_precedence[first]) { return false; } return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } static ecs_entity_t flecs_script_default_lookup( const ecs_world_t *world, const char *name, void *ctx) { (void)ctx; return ecs_lookup(world, name); } static const char* flecs_script_parse_match_elems( ecs_parser_t *parser, const char *pos, ecs_expr_match_t *node) { ecs_allocator_t *a = &parser->script->allocator; bool old_significant_newline = parser->significant_newline; parser->significant_newline = true; ecs_vec_init_t(NULL, &node->elements, ecs_expr_match_element_t, 0); do { ParserBegin; pos = flecs_parser_skip_newlines(parser, pos); LookAhead( case '}': { Consume(); parser->significant_newline = old_significant_newline; EndOfRule; } ) ecs_expr_match_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_match_element_t); ecs_os_zeromem(elem); pos = flecs_script_parse_expr(parser, pos, 0, &elem->compare); if (!pos) { goto error; } Parse_1(':', { pos = flecs_script_parse_expr(parser, pos, 0, &elem->expr); if (!pos) { goto error; } LookAhead_1('}', { Consume(); parser->significant_newline = old_significant_newline; EndOfRule; }) Parse( case ';': case '\n': break; ) }) } while (true); ParserEnd; } const char* flecs_script_parse_initializer( ecs_parser_t *parser, const char *pos, char until, ecs_expr_node_t **node_out) { ecs_expr_initializer_t *node = flecs_expr_initializer(parser); *node_out = (ecs_expr_node_t*)node; ecs_allocator_t *a = &parser->script->allocator; do { ParserBegin; /* End of initializer */ LookAhead( case ']': case ')': case '}': { Consume(); if ((char)lookahead_token.kind != until) { goto unexpected_end; } if (!ecs_vec_count(&node->elements)) { node->node.kind = EcsExprEmptyInitializer; } EndOfRule; }) ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); ecs_os_zeromem(elem); if (until != ']') { LookAheadAny_2(case EcsTokIdentifier: EcsTokKeyword:, case ':': case EcsTokAddAssign: case EcsTokMulAssign:, elem->member = Token(0); elem->operator = lookahead_token.kind == ':' ? 0 : lookahead_token.kind; Consume(); ) } int16_t old_elem_depth = parser->elem_depth; parser->elem_depth = flecs_ito(int16_t, parser->expr_depth + 1); pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); parser->elem_depth = old_elem_depth; if (!pos) { goto error; } if (until == ']') { bool is_key = false; LookAhead_1(':', { Consume(); is_key = true; }) if (is_key) { elem->key = elem->value; elem->value = NULL; parser->elem_depth = flecs_ito(int16_t, parser->expr_depth + 1); pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); parser->elem_depth = old_elem_depth; if (!pos) { goto error; } } } if (until == ']' && !elem->key && ecs_vec_count(&node->elements) == 1) { bool is_range = false; LookAhead_1(EcsTokRange, { Consume(); is_range = true; }) /* [expr .. expr] is a range expression */ if (is_range) { ecs_expr_range_t *range = flecs_expr_range(parser); range->from = elem->value; elem->value = NULL; ecs_vec_remove_last(&node->elements); flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); *node_out = (ecs_expr_node_t*)range; pos = flecs_script_parse_expr(parser, pos, 0, &range->to); if (!pos) { goto error; } Parse_1(']', { EndOfRule; }) } } if (elem->member) { node->is_partial = true; } if (elem->value && elem->value->kind == EcsExprInitializer && ((ecs_expr_initializer_t*)elem->value)->is_partial) { node->is_partial = true; } { bool separated = false; /* Parse next element or end of initializer */ LookAhead( case ',': { Consume(); separated = true; break; } case ']': case ')': case '}': if ((char)lookahead_token.kind == until) { Consume(); EndOfRule; } if (until == '\n' && lookahead_token.kind == '}') { EndOfRule; } Consume(); goto unexpected_end; case ';': case '\n': case '\0': if (until == '\n') { EndOfRule; } if (lookahead_token.kind == '\0') { Consume(); goto unexpected_end; } break; ) if (!separated) { bool newline = false; const char *next = flecs_scan_whitespace_and_comment( parser, pos, &newline); if (!next) { goto error; } if (!newline) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, next), until == '\n' ? "expected ',' or newline" : "expected ',' or newline between elements"); goto error; } } } } while (true); unexpected_end: if (until == '\n') { Error("expected newline"); } Error("expected '%c'", until); error: flecs_script_parser_expr_free(parser, *node_out); *node_out = NULL; return NULL; } static const char* flecs_script_parse_function( ecs_parser_t *parser, const char *pos, ecs_expr_node_t **out) { ecs_expr_function_t *result = flecs_expr_function(parser); result->left = *out; ecs_expr_node_t *args = NULL; pos = flecs_script_parse_initializer(parser, pos, ')', &args); result->args = (ecs_expr_initializer_t*)args; if (!pos) { goto error; } *out = (ecs_expr_node_t*)result; return pos; error: flecs_free_t(&parser->script->allocator, ecs_expr_function_t, result); return NULL; } static ecs_expr_node_t* flecs_script_parse_number( ecs_parser_t *parser, const char *expr, bool is_negative) { int base = 10; if (expr[0] == '0' && (expr[1] == 'x' || expr[1] == 'X')) { base = 16; } else if (expr[0] == '0' && (expr[1] == 'b' || expr[1] == 'B')) { base = 2; } if (base == 10 && (strchr(expr, '.') || strchr(expr, 'e') || strchr(expr, 'E'))) { double v = atof(expr); return (ecs_expr_node_t*)flecs_expr_float(parser, is_negative ? -v : v); } errno = 0; uint64_t v = strtoull(base == 10 ? expr : expr + 2, NULL, base); if (errno == ERANGE) { return NULL; } if (!is_negative) { return (ecs_expr_node_t*)flecs_expr_uint(parser, v); } if (v > (uint64_t)INT64_MAX + 1) { return NULL; } return (ecs_expr_node_t*)flecs_expr_int(parser, (int64_t)(0 - v)); } static const char* flecs_script_parse_rhs( ecs_parser_t *parser, const char *pos, ecs_tokenizer_t *tokenizer, ecs_token_kind_t left_oper, ecs_expr_node_t **out) { const char *last_pos = pos; do { TokenFramePush(); last_pos = pos; bool elem_end = false; if (!parser->significant_newline && parser->elem_depth == parser->expr_depth) { if (!flecs_scan_whitespace_and_comment(parser, pos, &elem_end)) { goto error; } } const char *old_token_cur = parser->token_cur; ecs_token_t lookahead_token; const char *lookahead = flecs_token( parser, pos, &lookahead_token, true); flecs_token_restore(parser, old_token_cur); if (!lookahead) { goto error; } ecs_token_kind_t oper = lookahead_token.kind; bool is_oper = oper != EcsTokNot && flecs_expr_precedence[oper]; if (elem_end && (oper == EcsTokBracketOpen || (oper == EcsTokSub && isdigit((unsigned char)lookahead[0])))) { is_oper = false; } if (is_oper && !flecs_has_precedence(left_oper, oper)) { pos = lookahead; switch(oper) { case EcsTokBracketOpen: { ecs_expr_element_t *result = flecs_expr_element(parser); result->left = *out; *out = (ecs_expr_node_t*)result; pos = flecs_script_parse_expr( parser, pos, 0, &result->index); if (!pos) { goto error; } Parse_1(']', {}); break; } case EcsTokHasBracketOpen: { ecs_expr_has_t *result = flecs_expr_has(parser); result->left = *out; *out = (ecs_expr_node_t*)result; bool is_pair = false; LookAhead_1('(', { Consume(); is_pair = true; }) Parse_1(EcsTokIdentifier, { result->first = (ecs_expr_node_t*) flecs_expr_identifier(parser, t->value); }); if (is_pair) { Parse_1(',', {}); Parse_1(EcsTokIdentifier, { result->second = (ecs_expr_node_t*) flecs_expr_identifier(parser, t->value); }); Parse_1(')', {}); } Parse_1(']', {}); break; } case EcsTokMember: { Parse( case EcsTokIdentifier: EcsTokKeyword: { ecs_expr_member_t *result = flecs_expr_member(parser); result->left = *out; result->member_name = Token(0); *out = (ecs_expr_node_t*)result; if (pos[0] == '(') { pos = flecs_script_parse_function( parser, pos + 1, out); if (!pos) { goto error; } } break; }); break; } default: { ecs_expr_binary_t *result = flecs_expr_binary(parser); result->left = *out; result->operator = oper; *out = (ecs_expr_node_t*)result; pos = flecs_script_parse_lhs(parser, pos, tokenizer, result->operator, &result->right); if (!pos) { goto error; } break; } }; } TokenFramePop(); } while (pos != last_pos); return pos; error: return NULL; } static const char* flecs_script_parse_lhs( ecs_parser_t *parser, const char *pos, ecs_tokenizer_t *tokenizer, ecs_token_kind_t left_oper, ecs_expr_node_t **out) { TokenFramePush(); bool can_have_rhs = true; Parse( case EcsTokNumber: { *out = flecs_script_parse_number(parser, Token(0), false); if (!*out) { Error("integer literal '%s' is out of range", Token(0)); } break; } case EcsTokChar: { *out = (ecs_expr_node_t*)flecs_expr_char(parser, Token(0)); break; } case EcsTokString: { if (flecs_string_is_interpolated(Token(0))) { *out = (ecs_expr_node_t*)flecs_expr_interpolated_string( parser, Token(0)); } else { *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); } break; } case EcsTokIdentifier: { const char *expr = Token(0); if (expr[0] == '$') { *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); } else if (!ecs_os_strcmp(expr, "this")) { *out = (ecs_expr_node_t*)flecs_expr_variable(parser, expr); } else if (!ecs_os_strcmp(expr, "true")) { *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); } else if (!ecs_os_strcmp(expr, "false")) { *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); } else { char *last_elem = strrchr(expr, '.'); if (last_elem && last_elem[1] == '$') { /* Scoped global variable */ ecs_expr_variable_t *v = flecs_expr_variable(parser, expr); ecs_os_memmove(&last_elem[1], &last_elem[2], ecs_os_strlen(&last_elem[2]) + 1); v->node.kind = EcsExprGlobalVariable; *out = (ecs_expr_node_t*)v; } else { /* Entity identifier */ *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); } } if (pos[0] == '(') { pos = flecs_script_parse_function(parser, pos + 1, out); if (!pos) { goto error; } } break; } case EcsTokNot: case EcsTokSub: { ecs_token_kind_t oper = t->kind; if (oper == EcsTokSub && isdigit((unsigned char)pos[0])) { Parse_1(EcsTokNumber, { *out = flecs_script_parse_number(parser, Token(1), true); if (!*out) { Error("integer literal '-%s' is out of range", Token(1)); } }) break; } ecs_expr_unary_t *node = flecs_expr_unary(parser); pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } node->operator = oper; *out = (ecs_expr_node_t*)node; break; } case EcsTokKeywordMatch: { ecs_expr_match_t *node = flecs_expr_match(parser); *out = (ecs_expr_node_t*)node; pos = flecs_script_parse_expr(parser, pos, 0, &node->expr); if (!pos) { goto error; } Parse_1('{', { pos = flecs_script_parse_match_elems(parser, pos, node); if (!pos) { goto error; } }) can_have_rhs = false; break; } case EcsTokKeywordNew: { ecs_expr_new_t *node = flecs_expr_new(parser); *out = (ecs_expr_node_t*)node; int32_t stmt_count = ecs_vec_count(&parser->scope->stmts); bool old_significant_newline = parser->significant_newline; const char *old_stmt_pos = parser->stmt_pos; parser->significant_newline = true; pos = flecs_script_stmt(parser, pos); parser->significant_newline = old_significant_newline; parser->stmt_pos = old_stmt_pos; if (!pos) { goto error; } if ((ecs_vec_count(&parser->scope->stmts) - stmt_count) != 1) { Error("expected entity statement after new"); } ecs_script_node_t *script_node = ecs_vec_last_t( &parser->scope->stmts, ecs_script_node_t*)[0]; if (script_node->kind != EcsAstEntity) { Error("expected entity statement after new"); } ecs_vec_remove_last(&parser->scope->stmts); node->entity = (ecs_script_entity_t*)script_node; can_have_rhs = false; break; } case EcsTokKeywordScript: { ecs_expr_script_t *node = flecs_expr_script(parser); *out = (ecs_expr_node_t*)node; Parse_1('{', { node->script = flecs_script_parse_nested(parser, pos, &pos); if (!node->script) { goto error; } }) can_have_rhs = false; break; } case '(': { pos = flecs_script_parse_expr(parser, pos, 0, out); if (!pos) { goto error; } Parse_1(')', {}) break; } case '{': case '[': { char until = tokenizer->tokens[0].kind == '{' ? '}' : ']'; ecs_expr_node_t *node = NULL; pos = flecs_script_parse_initializer(parser, pos, until, &node); if (!pos) { goto error; } if (until == ']' && (node->kind == EcsExprInitializer || node->kind == EcsExprEmptyInitializer)) { ((ecs_expr_initializer_t*)node)->is_collection = true; } *out = node; can_have_rhs = until == ']'; break; } ) TokenFramePop(); /* Return if this was end of expression, or if the parsed expression cannot * have a right-hand side. */ if (!pos[0] || !can_have_rhs) { return pos; } /* Parse right-hand side of expression if there is one */ return flecs_script_parse_rhs(parser, pos, tokenizer, left_oper, out); error: return NULL; } const char* flecs_script_parse_expr( ecs_parser_t *parser, const char *pos, ecs_token_kind_t left_oper, ecs_expr_node_t **out) { if (parser->expr_depth >= ECS_PARSER_MAX_RECURSION_DEPTH) { ecs_parser_error(parser->name, parser->code, flecs_parser_errpos(parser, pos), "maximum expression nesting depth exceeded"); return NULL; } parser->expr_depth ++; ParserBegin; pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); if (!pos) { parser->expr_depth --; if (out && *out) { flecs_script_parser_expr_free(parser, *out); *out = NULL; } return NULL; } parser->expr_depth --; if (out && *out && !(*out)->end) { (*out)->end = flecs_parser_stmt_end(parser, pos); } return pos; } ecs_script_t* ecs_expr_parse( ecs_world_t *world, const char *expr, const ecs_expr_eval_desc_t *desc) { ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.lookup_action) { priv_desc.lookup_action = flecs_script_default_lookup; } ecs_parser_t parser; ecs_script_t *script = flecs_script_parse_init(world, NULL, expr, &parser); ecs_script_impl_t *impl = flecs_script_impl(script); parser.significant_newline = false; const char *ptr = flecs_script_parse_expr( &parser, script->code, 0, &impl->expr); if (!ptr) { goto error; } impl->next_token = &expr[ptr - script->code]; impl->token_remaining = parser.token_cur; if (flecs_expr_visit_type(script, &impl->expr, &priv_desc)) { goto error; } if (!desc || !desc->disable_folding) { if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { goto error; } } return script; error: ecs_script_free(script); return NULL; } int ecs_expr_eval( const ecs_script_t *script, ecs_value_t *value, const ecs_expr_eval_desc_t *desc) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = flecs_script_impl( /* Safe, won't be writing to script */ ECS_CONST_CAST(ecs_script_t*, script)); ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.lookup_action) { priv_desc.lookup_action = flecs_script_default_lookup; } flecs_script_runtime_error_reset( flecs_script_runtime_get(script->world)); if (flecs_expr_visit_eval(script, impl->expr, &priv_desc, value)) { goto error; } return 0; error: return -1; } const char* ecs_expr_run( ecs_world_t *world, const char *expr, ecs_value_t *value, const ecs_expr_eval_desc_t *desc) { ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.type) { priv_desc.type = value->type; } else if (desc && (value->type != desc->type)) { ecs_throw(ECS_INVALID_PARAMETER, "type of value parameter does not match desc->type"); } ecs_script_t *s = ecs_expr_parse(world, expr, &priv_desc); if (!s) { goto error; } if (ecs_expr_eval(s, value, &priv_desc)) { ecs_script_free(s); goto error; } const char *result = flecs_script_impl(s)->next_token; ecs_script_free(s); return result; error: return NULL; } char* ecs_script_string_interpolate( ecs_world_t *world, const char *str, const ecs_script_vars_t *vars) { if (!flecs_string_is_interpolated(str)) { char *result = ecs_os_strdup(str); if (!flecs_string_escape(result)) { ecs_os_free(result); return NULL; } return result; } char *expr = flecs_asprintf("\"%s\"", str); ecs_expr_eval_desc_t desc = { .vars = vars }; char *r = NULL; if (!ecs_expr_run(world, expr, &ecs_value_ptr(ecs_string_t, &r), &desc)) { ecs_os_free(expr); return NULL; } ecs_os_free(expr); return r; } #endif #ifdef FLECS_SCRIPT static void flecs_expr_value_free( ecs_expr_value_t *v) { const ecs_type_info_t *ti = v->type_info; if (!v->owned || !v->value.ptr) { return; /* Runtime doesn't own value, don't destruct */ } if (ti) { if (ti->hooks.dtor) { flecs_type_info_dtor(v->value.ptr, 1, ti); } flecs_stack_free(v->value.ptr, ti->size); } } void flecs_expr_stack_init( ecs_expr_stack_t *stack) { stack->frame = 0; flecs_stack_init(&stack->stack); } void flecs_expr_stack_fini( ecs_expr_stack_t *stack) { ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); flecs_stack_fini(&stack->stack); } /* Nodes that replace the result pointer with storage the runtime does not own. * Allocating and constructing a buffer for these results is wasted work, and * because the pointer is replaced the buffer is never reachable again: for a * large component that is a stack allocation plus a constructor over the whole * component per evaluation, and an allocation that is never freed. A node in * this list that does need storage on some path must obtain it with * flecs_expr_stack_storage. */ static bool flecs_expr_node_borrows_result( const ecs_expr_node_t *node) { switch(node->kind) { case EcsExprValue: case EcsExprVariable: case EcsExprGlobalVariable: case EcsExprMember: case EcsExprElement: case EcsExprComponent: return true; default: return false; } } ecs_expr_value_t* flecs_expr_stack_result( ecs_expr_stack_t *stack, ecs_expr_node_t *node) { ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(stack->frame > 0, ECS_INTERNAL_ERROR, NULL); int32_t sp = stack->frames[stack->frame - 1].sp ++; ecs_assert(sp < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, "expression nesting is too deep"); ecs_expr_value_t *v = &stack->values[sp]; *v = (ecs_expr_value_t){ .type_info = node->type_info, .value.type = node->type }; if (!flecs_expr_node_borrows_result(node)) { flecs_expr_stack_storage(stack, v); } return v; } void flecs_expr_stack_storage( ecs_expr_stack_t *stack, ecs_expr_value_t *v) { if (v->value.ptr) { return; /* Value already has storage */ } ecs_assert(v->type_info != NULL, ECS_INTERNAL_ERROR, NULL); v->value.ptr = flecs_stack_alloc( &stack->stack, v->type_info->size, v->type_info->alignment); v->owned = true; flecs_type_info_ctor(v->value.ptr, 1, v->type_info); } void flecs_expr_stack_push( ecs_expr_stack_t *stack) { int32_t frame = stack->frame ++; ecs_assert(frame < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, "expression nesting is too deep"); ecs_stack_page_t *page = stack->stack.tail_page; stack->frames[frame].page = page; stack->frames[frame].page_sp = page ? page->sp : 0; if (frame) { stack->frames[frame].sp = stack->frames[frame - 1].sp; } else { stack->frames[frame].sp = 0; } } void flecs_expr_stack_pop( ecs_expr_stack_t *stack) { int32_t frame = -- stack->frame; ecs_assert(frame >= 0, ECS_INTERNAL_ERROR, NULL); int32_t sp, start = 0, end = stack->frames[frame].sp; if (frame) { start = stack->frames[frame - 1].sp; } for (sp = end - 1; sp >= start; sp --) { flecs_expr_value_free(&stack->values[sp]); } ecs_stack_page_t *page = stack->frames[frame].page; stack->stack.tail_page = page ? page : stack->stack.first; if (stack->stack.tail_page) { stack->stack.tail_page->sp = flecs_ito( int16_t, stack->frames[frame].page_sp); } } #endif #ifdef FLECS_SCRIPT #include void flecs_expr_visit_error_( const ecs_script_t *script, const void *node, const char *fmt, ...) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); const ecs_expr_node_t *expr_node = node; const char *code = script->code; const char *pos = expr_node ? expr_node->pos : NULL; if (code && pos && (pos >= code) && (pos <= &code[ecs_os_strlen(code)])) { ecs_parser_error(script->name, code, pos - code, "%s", msg); } else { ecs_parser_error(script->name, NULL, 0, "%s", msg); } ecs_os_free(msg); } flecs_expr_num_class_t flecs_expr_num_class( ecs_entity_t type, ecs_size_t *size) { const flecs_expr_type_info_t *info = flecs_expr_type_info(type); *size = info->size; if (info->floating_point) { return EcsExprNumFloat; } if (info->integer) { return info->signed_integer ? EcsExprNumSigned : EcsExprNumUnsigned; } return EcsExprNumNone; } #define FLECS_EXPR_CAST(S, D)\ static void flecs_expr_cast_##S##_##D(void *dst, const void *src) {\ *(D*)dst = (D)*(const S*)src;\ } #define FLECS_EXPR_CAST_TARGETS(M, S)\ M(S, int8_t)\ M(S, int16_t)\ M(S, int32_t)\ M(S, int64_t)\ M(S, uint8_t)\ M(S, uint16_t)\ M(S, uint32_t)\ M(S, uint64_t)\ M(S, float)\ M(S, double) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, int8_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, int16_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, int32_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, int64_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, uint8_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, uint16_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, uint32_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, uint64_t) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, float) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, double) FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST, bool) #undef FLECS_EXPR_CAST #define FLECS_EXPR_CAST_ENTRY(S, D) flecs_expr_cast_##S##_##D, flecs_expr_cast_t flecs_expr_cast_handler( ecs_entity_t from, ecs_entity_t to) { static const flecs_expr_cast_t handlers[11][10] = { {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, int8_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, int16_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, int32_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, int64_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, uint8_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, uint16_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, uint32_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, uint64_t)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, float)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, double)}, {FLECS_EXPR_CAST_TARGETS(FLECS_EXPR_CAST_ENTRY, bool)}, }; static const int8_t indexes[4][9] = { [EcsExprNumSigned] = { [1] = 0, [2] = 1, [4] = 2, [8] = 3 }, [EcsExprNumUnsigned] = { [1] = 4, [2] = 5, [4] = 6, [8] = 7 }, [EcsExprNumFloat] = { [4] = 8, [8] = 9 } }; ecs_size_t from_size, to_size; flecs_expr_num_class_t from_class = flecs_expr_num_class(from, &from_size); flecs_expr_num_class_t to_class = flecs_expr_num_class(to, &to_size); bool from_bool = from == ecs_id(ecs_bool_t); ecs_assert((from_class || from_bool) && to_class, ECS_INTERNAL_ERROR, NULL); return handlers[from_bool ? 10 : indexes[from_class][from_size]] [indexes[to_class][to_size]]; } #undef FLECS_EXPR_CAST_ENTRY #undef FLECS_EXPR_CAST_TARGETS int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, const ecs_expr_value_t *src) { ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->value.type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->value.ptr != 0, ECS_INTERNAL_ERROR, NULL); if (src->value.type == dst->type) { ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_ptr_copy_w_type_info( world, src->type_info, dst->ptr, src->value.ptr); } else if (src->value.type == ecs_id(ecs_script_template_ref_t) && ecs_has(world, dst->type, EcsStruct)) { const ecs_script_template_ref_t *ref = src->value.ptr; const ecs_type_info_t *ti = ecs_get_type_info(world, dst->type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (ref->value) { ecs_ptr_copy_w_type_info(world, ti, dst->ptr, ref->value); } else { ecs_entity_t type = ref->type ? ref->type : dst->type; void *defaults = ecs_ptr_new(world, type); ecs_ptr_copy_w_type_info(world, ti, dst->ptr, defaults); ecs_ptr_free(world, type, defaults); } } else if (src->value.type == ecs_id(ecs_bool_t) && flecs_expr_is_type_number(dst->type)) { flecs_expr_cast_handler(src->value.type, dst->type)( dst->ptr, src->value.ptr); } else { /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); if (ecs_meta_set_value(&cur, &src->value)) { goto error; } } return 0; error: return -1; } int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_token_kind_t operator) { if (operator == EcsTokNot) { ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); *(bool*)out->ptr = !*(bool*)expr->ptr; } else if (operator == EcsTokSub) { int64_t zero = 0; ecs_value_t left = { .type = expr->type, .ptr = &zero }; return flecs_value_binary(script, NULL, &left, expr, out, EcsTokSub); } else { ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for unary expression"); } return 0; } #define FLECS_VALUE_COMPARE(T)\ case EcsTokEq: *(bool*)out = l == r; return 0;\ case EcsTokNeq: *(bool*)out = l != r; return 0;\ case EcsTokGt: *(bool*)out = l > r; return 0;\ case EcsTokGtEq: *(bool*)out = l >= r; return 0;\ case EcsTokLt: *(bool*)out = l < r; return 0;\ case EcsTokLtEq: *(bool*)out = l <= r; return 0; #define FLECS_VALUE_INTEGER(T)\ case EcsTokMod: *(T*)out = (T)(l % r); return 0;\ case EcsTokBitwiseAnd: *(T*)out = (T)(l & r); return 0;\ case EcsTokBitwiseOr: *(T*)out = (T)(l | r); return 0;\ case EcsTokShiftLeft: *(T*)out = (T)(l << r); return 0;\ case EcsTokShiftRight: *(T*)out = (T)(l >> r); return 0; #define FLECS_VALUE_FLOAT(T) #define FLECS_VALUE_ARITHMETIC(T)\ case EcsTokAdd: case EcsTokAddAssign: *(T*)out = (T)(l + r); return 0;\ case EcsTokSub: *(T*)out = (T)(l - r); return 0;\ case EcsTokMul: case EcsTokMulAssign: *(T*)out = (T)(l * r); return 0;\ case EcsTokDiv: *(T*)out = (T)(l / r); return 0; #define FLECS_VALUE_BINARY(T, ARITHMETIC, INTEGER)\ static int flecs_value_binary_##T(\ const void *left, const void *right, void *out, ecs_token_kind_t operator)\ {\ T l = *(const T*)left, r = *(const T*)right;\ switch (operator) {\ FLECS_VALUE_COMPARE(T)\ ARITHMETIC(T)\ INTEGER(T)\ default: ecs_abort(ECS_INTERNAL_ERROR, "invalid binary operator");\ }\ return -1;\ } FLECS_VALUE_BINARY(ecs_i8_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_i16_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_i32_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_i64_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_u8_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_u16_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_u32_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_u64_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_iptr_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) FLECS_VALUE_BINARY(ecs_uptr_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_INTEGER) #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif FLECS_VALUE_BINARY(ecs_f32_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_FLOAT) FLECS_VALUE_BINARY(ecs_f64_t, FLECS_VALUE_ARITHMETIC, FLECS_VALUE_FLOAT) #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #pragma GCC diagnostic pop #endif FLECS_VALUE_BINARY(ecs_char_t, FLECS_VALUE_FLOAT, FLECS_VALUE_FLOAT) FLECS_VALUE_BINARY(ecs_bool_t, FLECS_VALUE_FLOAT, FLECS_VALUE_FLOAT) FLECS_VALUE_BINARY(ecs_entity_t, FLECS_VALUE_FLOAT, FLECS_VALUE_FLOAT) FLECS_VALUE_BINARY(ecs_id_t, FLECS_VALUE_FLOAT, FLECS_VALUE_FLOAT) FLECS_VALUE_BINARY(ecs_byte_t, FLECS_VALUE_FLOAT, FLECS_VALUE_FLOAT) #undef FLECS_VALUE_BINARY #undef FLECS_VALUE_ARITHMETIC #undef FLECS_VALUE_INTEGER #undef FLECS_VALUE_FLOAT #undef FLECS_VALUE_COMPARE int flecs_value_binary( const ecs_script_t *script, const ecs_expr_node_t *node, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, ecs_token_kind_t operator) { if (operator == EcsTokAddAssign || operator == EcsTokMulAssign) { left = out; } if ((operator == EcsTokDiv || operator == EcsTokMod) && flecs_value_is_0(right)) { flecs_expr_visit_error(script, node, "division by zero"); return -1; } if (operator == EcsTokAnd || operator == EcsTokOr) { bool l = *(bool*)left->ptr, r = *(bool*)right->ptr; *(bool*)out->ptr = operator == EcsTokAnd ? l && r : l || r; return 0; } if (right->type == ecs_id(ecs_string_t)) { char *l = *(char**)left->ptr, *r = *(char**)right->ptr; ecs_assert(operator == EcsTokEq || operator == EcsTokNeq, ECS_INTERNAL_ERROR, NULL); *(bool*)out->ptr = l && r ? (operator == EcsTokEq ? !ecs_os_strcmp(l, r) : ecs_os_strcmp(l, r) != 0) : l == r; return 0; } if (operator == EcsTokAdd || operator == EcsTokSub || operator == EcsTokMul || operator == EcsTokDiv || operator == EcsTokMod || operator == EcsTokBitwiseAnd || operator == EcsTokBitwiseOr || operator == EcsTokShiftLeft || operator == EcsTokShiftRight || operator == EcsTokAddAssign || operator == EcsTokMulAssign) { ecs_assert(out->type == right->type, ECS_INTERNAL_ERROR, NULL); } #define FLECS_VALUE_DISPATCH(T)\ if (right->type == ecs_id(T)) {\ return flecs_value_binary_##T(left->ptr, right->ptr, out->ptr, operator);\ } FLECS_VALUE_DISPATCH(ecs_i8_t) FLECS_VALUE_DISPATCH(ecs_i16_t) FLECS_VALUE_DISPATCH(ecs_i32_t) FLECS_VALUE_DISPATCH(ecs_i64_t) FLECS_VALUE_DISPATCH(ecs_u8_t) FLECS_VALUE_DISPATCH(ecs_u16_t) FLECS_VALUE_DISPATCH(ecs_u32_t) FLECS_VALUE_DISPATCH(ecs_u64_t) FLECS_VALUE_DISPATCH(ecs_iptr_t) FLECS_VALUE_DISPATCH(ecs_uptr_t) FLECS_VALUE_DISPATCH(ecs_f32_t) FLECS_VALUE_DISPATCH(ecs_f64_t) FLECS_VALUE_DISPATCH(ecs_char_t) FLECS_VALUE_DISPATCH(ecs_bool_t) FLECS_VALUE_DISPATCH(ecs_entity_t) FLECS_VALUE_DISPATCH(ecs_id_t) FLECS_VALUE_DISPATCH(ecs_byte_t) #undef FLECS_VALUE_DISPATCH ecs_abort(ECS_INTERNAL_ERROR, "invalid binary operand type"); } bool flecs_string_is_interpolated( const char *value) { const char *ptr = value; for (ptr = strchr(ptr, '$'); ptr; ptr = strchr(ptr + 1, '$')) { if (ptr != value) { if (ptr[-1] == '\\') { continue; /* Escaped */ } } if (isspace((unsigned char)ptr[1]) || !ptr[1]) { continue; /* $ by itself */ } return true; } ptr = value; for (ptr = strchr(ptr, '{'); ptr; ptr = strchr(ptr + 1, '{')) { if (ptr != value) { if (ptr[-1] == '\\') { continue; /* Escaped */ } } return true; } return false; } char* flecs_string_escape( char *str) { const char *ptr; char *out = str, ch; for (ptr = str; ptr[0]; ) { if (ptr[0] == '\\') { if (ptr[1] == '{') { /* Escape string interpolation delimiter */ ch = '{'; ptr += 2; } else if (ptr[1] == '$') { /* Escape string interpolation var */ ch = '$'; ptr += 2; } else { ptr = flecs_chrparse(ptr, &ch); if (!ptr) { ecs_err("invalid escape sequence in string '%s'", str); return NULL; } } } else { ch = ptr[0]; ptr ++; } out[0] = ch; out ++; } out[0] = '\0'; return out + 1; } bool flecs_value_is_0( const ecs_value_t *value) { const flecs_expr_type_info_t *info = flecs_expr_type_info(value->type); if (!(info->integer || info->floating_point || info->kind == EcsBool)) { return true; } uint64_t zero = 0; return !ecs_os_memcmp(value->ptr, &zero, info->size); } ecs_expr_swizzle_t* flecs_expr_expand_swizzle_get( ecs_expr_node_t *node) { if (node->kind == EcsExprIdentifier) { node = ((ecs_expr_identifier_t*)node)->expr; if (!node) { return NULL; } } if (node->kind != EcsExprSwizzle) { return NULL; } ecs_expr_swizzle_t *swizzle = (ecs_expr_swizzle_t*)node; if (!swizzle->expand) { return NULL; } return swizzle; } int flecs_expr_identifier_set( const ecs_script_t *script, const ecs_expr_identifier_t *node, ecs_entity_t entity, const ecs_value_t *out) { if (!entity) { flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); return -1; } ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, out->type, out->ptr); if (ecs_meta_set_entity(&cur, entity)) { flecs_expr_visit_error(script, node, "failed to assign identifier '%s'", node->value); return -1; } return 0; } const ecs_script_var_t* flecs_expr_var_check( const ecs_script_t *script, const ecs_expr_variable_t *node, const ecs_script_var_t *var) { if (!var) { flecs_expr_visit_error(script, node, "unresolved variable '%s'", node->name); return NULL; } if (!var->value.ptr) { flecs_expr_visit_error(script, node, "variable '%s' is not a compile time constant", node->name); return NULL; } ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); return var; } int flecs_expr_element_get( const ecs_script_t *script, const ecs_expr_element_t *node, ecs_type_kind_t kind, const ecs_value_t *expr, const ecs_value_t *index, void **out) { if (kind == EcsMapType) { ecs_map_key_t key = 0; if (flecs_value_blit_u64(script->world, index, &key)) { return -1; } const ecs_map_t *map = expr->ptr; ecs_map_val_t *val = NULL; if (ecs_map_is_init(map)) { val = ecs_map_get(map, key); } if (!val) { char *key_str = ecs_ptr_to_str( script->world, index->type, index->ptr); flecs_expr_visit_error(script, node, "map does not contain key '%s'", key_str); ecs_os_free(key_str); return -1; } *out = node->elem_size > ECS_SIZEOF(ecs_map_val_t) ? (void*)(uintptr_t)val[0] : (void*)val; return 0; } int64_t index_value = *(int64_t*)index->ptr; void *data = expr->ptr; int64_t elem_count = node->elem_count; if (kind == EcsVectorType) { data = ecs_vec_first(expr->ptr); if (!elem_count) { elem_count = ecs_vec_count(expr->ptr); } } if (index_value < 0 || index_value >= elem_count) { flecs_expr_visit_error(script, node, "index %" PRId64 " is out of range for collection (count = %" PRId64 ")", index_value, elem_count); return -1; } *out = ECS_OFFSET(data, node->elem_size * index_value); return 0; } const void* flecs_expr_component_get( const ecs_script_t *script, struct ecs_script_eval_visitor_t *v, const ecs_expr_element_t *node, ecs_entity_t entity, ecs_entity_t component) { ecs_world_t *world = script->world; if (!entity || !ecs_is_alive(world, entity)) { flecs_expr_visit_error(script, node, "cannot read component '%s' from invalid entity", flecs_errstr(ecs_get_path(world, component))); return NULL; } if (node->dyn_input && v && v->script_entity && !v->instance_template) { flecs_script_record_dyn_ref(v->base.script, entity, component, node->dyn_input, false); } const void *ptr = ecs_get_id(world, entity, component); if (!ptr) { flecs_expr_visit_error(script, node, "entity '%s' does not have component '%s'", flecs_errstr(ecs_get_path(world, entity)), flecs_errstr_1(ecs_get_path(world, component))); } return ptr; } bool flecs_expr_has_eval( const ecs_script_t *script, struct ecs_script_eval_visitor_t *v, const ecs_expr_has_t *node, ecs_entity_t entity) { ecs_world_t *world = script->world; if (!entity || !ecs_is_alive(world, entity)) { return false; } if (node->dyn_input && v && v->script_entity && !v->instance_template) { flecs_script_record_dyn_ref(v->base.script, entity, node->id, node->dyn_input, true); } return ecs_has_id(world, entity, node->id); } void* flecs_expr_template_ref_value( const ecs_script_t *script, const ecs_expr_function_t *node, const ecs_script_template_ref_t *ref) { if (!ref->type) { flecs_expr_visit_error(script, node, "template prop '%s' has no value", node->function_name); return NULL; } void *value = ecs_ptr_new(script->world, ref->type); if (ref->value) { ecs_ptr_copy(script->world, ref->type, value, ref->value); } return value; } int flecs_expr_range_eval( ecs_world_t *world, ecs_entity_t type, int32_t from, int32_t to, void *out) { ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, out); if (ecs_meta_push(&cur)) { return -1; } int32_t i, count = to - from; for (i = 0; i < count; i ++) { if (i) { if (ecs_meta_next(&cur)) { return -1; } } if (ecs_meta_set_int(&cur, from + i)) { return -1; } } return ecs_meta_pop(&cur); } ecs_entity_t flecs_expr_script_new( ecs_world_t *world, ecs_script_t *script) { ecs_entity_t e = ecs_new(world); EcsScript *s = ecs_ensure(world, e, EcsScript); s->code = ecs_os_strdup(script->code); s->script = script; flecs_script_impl(script)->refcount ++; ecs_modified(world, e, EcsScript); return e; } int flecs_expr_swizzle_set( ecs_meta_cursor_t *cur, const ecs_expr_swizzle_t *swizzle, const void *src) { int32_t s; for (s = 0; s < swizzle->count; s ++) { if (s) { if (ecs_meta_next(cur)) { return -1; } } ecs_value_t value = { .ptr = ECS_OFFSET(src, swizzle->src[s]), .type = swizzle->node.type }; if (ecs_meta_set_value(cur, &value)) { return -1; } } return 0; } #endif #ifdef FLECS_SCRIPT typedef struct ecs_script_eval_ctx_t { const ecs_script_t *script; ecs_world_t *world; const ecs_expr_eval_desc_t *desc; ecs_expr_stack_t *stack; } ecs_script_eval_ctx_t; static int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out); static inline ecs_expr_value_t* flecs_expr_eval_result( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *literal) { if (node->kind == EcsExprValue) { literal->value = (ecs_value_t){ .type = node->type, .ptr = ((ecs_expr_value_node_t*)node)->ptr }; literal->type_info = node->type_info; literal->owned = false; return literal; } ecs_expr_value_t *result = flecs_expr_stack_result(ctx->stack, node); return flecs_expr_visit_eval_priv(ctx, node, result) ? NULL : result; } static int flecs_expr_value_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_value_node_t *node, ecs_expr_value_t *out) { (void)ctx; out->value.type = node->node.type; out->value.ptr = node->ptr; out->owned = false; return 0; } static int flecs_expr_interpolated_fragment_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *expr, ecs_expr_format_t *format, ecs_strbuf_t *buf) { ecs_expr_value_t *val = flecs_expr_eval_result( ctx, expr, &(ecs_expr_value_t){0}); if (!val) { return -1; } if (format->is_present) { int32_t width = 0; int32_t precision = -1; ecs_expr_node_t *format_exprs[2] = { format->width, format->precision }; int32_t *format_values[2] = { &width, &precision }; int32_t f; for (f = 0; f < 2; f ++) { if (!format_exprs[f]) { continue; } ecs_expr_value_t *format_val = flecs_expr_eval_result( ctx, format_exprs[f], &(ecs_expr_value_t){0}); if (!format_val) { return -1; } ecs_assert(format_val->value.type == ecs_id(ecs_i32_t), ECS_INTERNAL_ERROR, NULL); format_values[f][0] = *(int32_t*)format_val->value.ptr; } return flecs_expr_format_value(ctx->script, expr, &val->value, format, width, precision, buf); } ecs_assert(val->value.type == ecs_id(ecs_string_t), ECS_INTERNAL_ERROR, NULL); const char *str = *(char**)val->value.ptr; if (str) { ecs_strbuf_appendstr(buf, str); } return 0; } static FLECS_SCRIPT_NOINLINE int flecs_expr_interpolated_string_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_interpolated_string_t *node, ecs_expr_value_t *out) { ecs_assert(node->node.type == ecs_id(ecs_string_t), ECS_INTERNAL_ERROR, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; int32_t i, count = ecs_vec_count(&node->fragments); ecs_expr_fragment_t *fragments = ecs_vec_first(&node->fragments); for (i = 0; i < count; i ++) { char *fragment = fragments[i].text; if (fragment) { ecs_strbuf_appendstr(&buf, fragment); } if (fragments[i].expr) { flecs_expr_stack_push(ctx->stack); int result = flecs_expr_interpolated_fragment_eval( ctx, fragments[i].expr, &fragments[i].format, &buf); flecs_expr_stack_pop(ctx->stack); if (result) { ecs_strbuf_reset(&buf); return -1; } } } *(char**)out->value.ptr = ecs_strbuf_get(&buf); out->owned = true; return 0; } static int flecs_expr_initializer_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value, ecs_meta_cursor_t *cur, ecs_size_t value_size); static void flecs_expr_value_move( void *dst, void *src, const ecs_type_info_t *ti) { if (ti->hooks.dtor) { flecs_type_info_move_dtor(dst, src, 1, ti); flecs_type_info_ctor(src, 1, ti); } else { flecs_type_info_move(dst, src, 1, ti); } } static int flecs_expr_initializer_eval_static( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value, ecs_size_t value_size) { flecs_expr_stack_push(ctx->stack); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); if (i) { flecs_expr_stack_pop(ctx->stack); flecs_expr_stack_push(ctx->stack); } if (elem->value->kind == EcsExprInitializer) { ecs_expr_initializer_t *inner = (ecs_expr_initializer_t*)elem->value; void *inner_value = value; ecs_size_t inner_size = value_size; if (inner->is_dynamic) { inner_value = ECS_OFFSET(value, elem->offset); inner_size = inner->node.type_info->size; } if (flecs_expr_initializer_eval(ctx, inner, inner_value, NULL, inner_size)) { goto error; } continue; } ecs_expr_swizzle_t *swizzle = flecs_expr_expand_swizzle_get(elem->value); if (swizzle) { ecs_expr_value_t *left = flecs_expr_eval_result( ctx, swizzle->left, &(ecs_expr_value_t){0}); if (!left) { goto error; } ecs_size_t size = swizzle->elem_size; int32_t s; for (s = 0; s < swizzle->count; s ++) { uintptr_t offset = elem->offset + swizzle->dst[s]; if (flecs_expr_initializer_check(ctx->script, node, swizzle->node.type, offset, size, flecs_uto(uintptr_t, value_size))) { goto error; } ecs_os_memcpy(ECS_OFFSET(value, offset), ECS_OFFSET(left->value.ptr, swizzle->src[s]), size); } continue; } ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, elem->value, &(ecs_expr_value_t){0}); if (!expr) { goto error; } /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ ecs_entity_t type = elem->value->type; if (flecs_expr_initializer_check(ctx->script, node, type, elem->offset, elem->value->type_info->size, flecs_uto(uintptr_t, value_size))) { goto error; } if (!elem->operator) { if (expr->owned) { flecs_expr_value_move(ECS_OFFSET(value, elem->offset), expr->value.ptr, elem->value->type_info); } else { if (ecs_ptr_copy(ctx->world, type, ECS_OFFSET(value, elem->offset), expr->value.ptr)) { goto error; } } } else { ecs_value_t dst = { .type = type, .ptr = ECS_OFFSET(value, elem->offset) }; if (flecs_value_binary( ctx->script, &node->node, NULL, &expr->value, &dst, elem->operator)) { goto error; } } } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval_dynamic( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value, ecs_meta_cursor_t *cur, ecs_size_t value_size) { (void)value_size; flecs_expr_stack_push(ctx->stack); ecs_meta_cursor_t local_cur; if (!cur) { local_cur = ecs_meta_cursor( ctx->world, node->node.type, value); cur = &local_cur; } if (ecs_meta_push(cur)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); if (i) { flecs_expr_stack_pop(ctx->stack); flecs_expr_stack_push(ctx->stack); if (ecs_meta_next(cur)) { goto error; } } if (elem->key) { ecs_expr_value_t *key = flecs_expr_eval_result( ctx, elem->key, &(ecs_expr_value_t){0}); if (!key) { goto error; } if (ecs_meta_key(cur, &key->value)) { goto error; } } else if (elem->member) { if (ecs_meta_member(cur, elem->member)) { goto error; } } if (elem->value->kind == EcsExprInitializer) { if (flecs_expr_initializer_eval_dynamic(ctx, (ecs_expr_initializer_t*)elem->value, value, cur, value_size)) { goto error; } continue; } ecs_expr_swizzle_t *swizzle = flecs_expr_expand_swizzle_get(elem->value); if (swizzle) { ecs_expr_value_t *left = flecs_expr_eval_result( ctx, swizzle->left, &(ecs_expr_value_t){0}); if (!left) { goto error; } if (flecs_expr_swizzle_set(cur, swizzle, left->value.ptr)) { goto error; } continue; } ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, elem->value, &(ecs_expr_value_t){0}); if (!expr) { goto error; } ecs_value_t v_elem_value = { .ptr = expr->value.ptr, .type = expr->value.type }; if (ecs_meta_set_value(cur, &v_elem_value)) { goto error; } } if (ecs_meta_pop(cur)) { goto error; } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value, ecs_meta_cursor_t *cur, ecs_size_t value_size) { if (node->is_dynamic) { return flecs_expr_initializer_eval_dynamic( ctx, node, value, cur, value_size); } else { return flecs_expr_initializer_eval_static(ctx, node, value, value_size); } } static FLECS_SCRIPT_NOINLINE int flecs_expr_initializer_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, ecs_expr_value_t *out) { out->owned = true; const ecs_type_info_t *ti = ecs_get_type_info( ctx->world, node->node.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_expr_initializer_eval( ctx, node, out->value.ptr, NULL, ti->size); } static FLECS_SCRIPT_NOINLINE int flecs_expr_empty_initializer_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, ecs_expr_value_t *out) { if (node->is_dynamic) { ecs_meta_cursor_t cur = ecs_meta_cursor( ctx->world, node->node.type, out->value.ptr); if (ecs_meta_push(&cur)) { return -1; } if (ecs_meta_pop(&cur)) { return -1; } } return 0; } static int flecs_expr_unary_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_unary_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->expr, &(ecs_expr_value_t){0}); if (!expr) { return -1; } return flecs_value_unary( ctx->script, &expr->value, &out->value, node->operator); } static int flecs_expr_binary_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_binary_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *left = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!left) { return -1; } if (!node->vector_count && left->value.type == ecs_id(ecs_bool_t)) { if (node->operator == EcsTokAnd || node->operator == EcsTokOr) { bool lval = *(bool*)left->value.ptr; if ((node->operator == EcsTokAnd) != lval) { *(bool*)out->value.ptr = lval; return 0; } } } ecs_expr_value_t *right = flecs_expr_eval_result( ctx, node->right, &(ecs_expr_value_t){0}); if (!right) { return -1; } return flecs_expr_binary_eval(ctx->script, node, &left->value, &right->value, out->value.ptr, node->operator); } static int flecs_expr_identifier_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_identifier_t *node, ecs_expr_value_t *out) { if (node->expr) { return flecs_expr_visit_eval_priv(ctx, node->expr, out); } ecs_entity_t entity = 0; if (node->symbol != -1) { ecs_script_eval_visitor_t *v = ctx->desc ? ctx->desc->script_visitor : NULL; ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); entity = flecs_script_symbol_entity(v, node->symbol); } else { flecs_script_symbol_t symbol; if (!flecs_script_symbol_lookup(ctx->script, ctx->desc, 0, node->value, FlecsScriptLookupEntity | FlecsScriptLookupDynamic, &symbol)) { entity = symbol.entity; } } return flecs_expr_identifier_set(ctx->script, node, entity, &out->value); } static int flecs_expr_variable_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_variable_t *node, ecs_expr_value_t *out) { ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); ecs_assert(!ctx->desc->script_visitor || ctx->desc->disable_dynamic_variable_binding, ECS_INTERNAL_ERROR, NULL); const ecs_script_var_t *var; if (ctx->desc->disable_dynamic_variable_binding) { var = ecs_script_vars_from_sp(ctx->desc->vars, node->sp); } else { flecs_script_symbol_t symbol; if (flecs_script_symbol_lookup(ctx->script, ctx->desc, 0, node->name, FlecsScriptLookupVariable | FlecsScriptLookupDynamic, &symbol)) { symbol.sp = -1; } var = symbol.sp == -1 ? NULL : ecs_script_vars_from_sp( ctx->desc->vars, symbol.sp); } var = flecs_expr_var_check(ctx->script, node, var); if (!var) { return -1; } out->value = var->value; out->owned = false; return 0; } static int flecs_expr_global_variable_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_variable_t *node, ecs_expr_value_t *out) { ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); ecs_value_t value = flecs_script_global_var_get( ctx->world, node->global, NULL); ecs_assert(value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); ecs_assert(value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); out->value = value; out->owned = false; return 0; } static int flecs_expr_cast_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_cast_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->expr, &(ecs_expr_value_t){0}); if (!expr) { return -1; } if (flecs_value_copy_to(ctx->world, &out->value, expr)) { flecs_expr_visit_error(ctx->script, node, "failed to cast value"); return -1; } out->owned = true; return 0; } static int flecs_expr_cast_number_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_cast_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->expr, &(ecs_expr_value_t){0}); if (!expr) { return -1; } node->cast(out->value.ptr, expr->value.ptr); return 0; } static int flecs_expr_function_args_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, ecs_value_t *args) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, elem->value, &(ecs_expr_value_t){0}); if (!expr) { return -1; } args[i] = expr->value; } return 0; } static FLECS_SCRIPT_NOINLINE int flecs_expr_function_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_function_t *node, ecs_expr_value_t *out) { bool method = node->node.kind == EcsExprMethod; if (method && !node->left) { return 0; } ecs_value_t receiver = {0}; if (method) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!expr) { return -1; } receiver = expr->value; } ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_value_t *argv = NULL; int32_t argc = ecs_vec_count(&node->args->elements); if (argc || method) { argv = ecs_os_alloca_n(ecs_value_t, argc + method); if (method) { argv[0] = receiver; } if (flecs_expr_function_args_visit_eval(ctx, node->args, argv + method)) { return -1; } } if (flecs_expr_function_call(ctx->script, node, argc, argv, &out->value)) { return -1; } out->owned = true; return 0; } static FLECS_SCRIPT_NOINLINE int flecs_expr_template_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_function_t *node, ecs_expr_value_t *out) { ecs_world_t *world = ctx->world; ecs_entity_t tmpl = node->calldata.function; bool is_ref = node->node.type == ecs_id(ecs_script_template_ref_t); bool has_args = node->args && ecs_vec_count(&node->args->elements) != 0; bool has_type = node->args && node->args->node.type_info != NULL; void *value = NULL; ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_expr_template_ref_var(node->left)) { ecs_expr_value_t *ref_value = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!ref_value) { return -1; } const ecs_script_template_ref_t *ref = ref_value->value.ptr; value = flecs_expr_template_ref_value(ctx->script, node, ref); if (!value) { return -1; } tmpl = ref->type; } else if (has_args || has_type || !is_ref) { value = ecs_ptr_new(world, tmpl); if (!value) { flecs_expr_visit_error(ctx->script, node, "failed to create value for template '%s'", node->function_name); return -1; } } if (has_args) { ecs_expr_value_t args_out = { .value = { .type = node->args->node.type, .ptr = value }, .type_info = node->args->node.type_info, .owned = false }; if (flecs_expr_visit_eval_priv( ctx, (ecs_expr_node_t*)node->args, &args_out)) { ecs_ptr_free(world, tmpl, value); return -1; } } if (is_ref) { flecs_script_template_ref_set(world, out->value.ptr, tmpl, value); } else { ecs_ptr_move(world, tmpl, out->value.ptr, value); ecs_ptr_free(world, tmpl, value); } out->owned = true; return 0; } static int flecs_expr_member_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_member_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!expr) { return -1; } out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); out->value.type = node->node.type; out->owned = false; return 0; } static int flecs_expr_swizzle_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_swizzle_t *node, ecs_expr_value_t *out) { ecs_assert(!node->expand, ECS_INTERNAL_ERROR, NULL); ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!expr) { return -1; } ecs_size_t size = node->elem_size; int32_t i; for (i = 0; i < node->count; i ++) { ecs_os_memcpy(ECS_OFFSET(out->value.ptr, i * size), ECS_OFFSET(expr->value.ptr, node->src[i]), size); } out->value.type = node->node.type; out->owned = false; return 0; } static int flecs_expr_element_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!expr) { return -1; } ecs_expr_value_t *index = flecs_expr_eval_result( ctx, node->index, &(ecs_expr_value_t){0}); if (!index) { return -1; } const EcsType *type = ecs_get(ctx->world, expr->value.type, EcsType); ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_expr_element_get(ctx->script, node, type->kind, &expr->value, &index->value, &out->value.ptr)) { return -1; } out->value.type = node->node.type; out->owned = false; return 0; } static FLECS_SCRIPT_NOINLINE int flecs_expr_range_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_range_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *from = flecs_expr_eval_result( ctx, node->from, &(ecs_expr_value_t){0}); if (!from) { return -1; } ecs_expr_value_t *to = flecs_expr_eval_result( ctx, node->to, &(ecs_expr_value_t){0}); if (!to) { return -1; } if (flecs_expr_range_eval(ctx->world, node->node.type, *(int32_t*)from->value.ptr, *(int32_t*)to->value.ptr, out->value.ptr)) { return -1; } out->owned = true; return 0; } static FLECS_SCRIPT_NOINLINE int flecs_expr_match_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_match_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_eval_result( ctx, node->expr, &(ecs_expr_value_t){0}); if (!expr) { return -1; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *compare = flecs_expr_eval_result( ctx, elem->compare, &(ecs_expr_value_t){0}); bool value = false; ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &value }; int r = !compare || flecs_value_binary( ctx->script, &node->node, &expr->value, &compare->value, &result, EcsTokEq); flecs_expr_stack_pop(ctx->stack); if (r) { return -1; } if (value) { break; } } ecs_expr_node_t *branch = i < count ? elems[i].expr : node->any.expr; if (!branch) { char *str = ecs_ptr_to_str( ctx->world, expr->value.type, expr->value.ptr); flecs_expr_visit_error(ctx->script, node, "match value '%s' not handled by case", str); ecs_os_free(str); return -1; } ecs_expr_value_t *result = flecs_expr_eval_result( ctx, branch, &(ecs_expr_value_t){0}); if (!result) { return -1; } ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (result->owned) { flecs_expr_value_move(out->value.ptr, result->value.ptr, node->node.type_info); } else { flecs_type_info_copy(out->value.ptr, result->value.ptr, 1, node->node.type_info); } return 0; } static int flecs_expr_new_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_new_t *node, ecs_expr_value_t *out) { ecs_script_eval_visitor_t *v = ctx->desc ? ctx->desc->script_visitor : NULL; if (v && v->template) { *(ecs_entity_t*)out->value.ptr = 0; out->value.type = ecs_id(ecs_entity_t); return 0; } ecs_entity_t result = 0; if (flecs_script_eval_entity(v, ctx->script, ctx->desc ? ctx->desc->vars : NULL, node->entity, &result)) { return -1; } *(ecs_entity_t*)out->value.ptr = result; out->value.type = ecs_id(ecs_entity_t); return 0; } static int flecs_expr_script_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_script_t *node, ecs_expr_value_t *out) { ecs_script_eval_visitor_t *v = ctx->desc ? ctx->desc->script_visitor : NULL; if (v && v->template) { *(ecs_entity_t*)out->value.ptr = 0; out->value.type = ecs_id(ecs_entity_t); return 0; } *(ecs_entity_t*)out->value.ptr = flecs_expr_script_new( ctx->world, node->script); out->value.type = ecs_id(ecs_entity_t); return 0; } static int flecs_expr_component_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, ecs_expr_value_t *out) { ecs_script_eval_visitor_t *v = ctx->desc ? ctx->desc->script_visitor : NULL; if (v && v->template) { ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); flecs_expr_stack_storage(ctx->stack, out); out->owned = true; return 0; } ecs_expr_value_t *left = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!left) { return -1; } /* Left side of expression must be of entity type */ ecs_assert(left->value.type == ecs_id(ecs_entity_t), ECS_INTERNAL_ERROR, NULL); /* Component must be resolvable at parse time */ ecs_expr_node_t *index = node->index; if (index->kind == EcsExprIdentifier) { index = ((ecs_expr_identifier_t*)index)->expr; } ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; ecs_entity_t component; if (index) { ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); component = ((ecs_expr_value_node_t*)index)->storage.entity; } else { component = node->node.type; } ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value.ptr = ECS_CONST_CAST(void*, flecs_expr_component_get( ctx->script, v, node, entity, component)); out->owned = false; return out->value.ptr ? 0 : -1; } static int flecs_expr_has_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_has_t *node, ecs_expr_value_t *out) { ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_eval_visitor_t *v = ctx->desc ? ctx->desc->script_visitor : NULL; if (v && v->template) { *(bool*)out->value.ptr = false; out->owned = true; return 0; } ecs_expr_value_t *left = flecs_expr_eval_result( ctx, node->left, &(ecs_expr_value_t){0}); if (!left) { return -1; } ecs_assert(left->value.type == ecs_id(ecs_entity_t) || left->value.type == ecs_id(ecs_id_t), ECS_INTERNAL_ERROR, NULL); *(bool*)out->value.ptr = flecs_expr_has_eval( ctx->script, v, node, *(ecs_entity_t*)left->value.ptr); out->owned = true; return 0; } static const bool flecs_expr_eval_has_frame[] = { [EcsExprUnary] = true, [EcsExprBinary] = true, [EcsExprFunction] = true, [EcsExprMethod] = true, [EcsExprTemplate] = true, [EcsExprSwizzle] = true, [EcsExprMatch] = true, [EcsExprRange] = true, [EcsExprCast] = true, [EcsExprCastNumber] = true, [EcsExprScript] = false }; static int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); bool frame = flecs_expr_eval_has_frame[node->kind]; if (frame) { flecs_expr_stack_push(ctx->stack); } int result = 0; switch(node->kind) { case EcsExprValue: result = flecs_expr_value_visit_eval( ctx, (ecs_expr_value_node_t*)node, out); break; case EcsExprInterpolatedString: result = flecs_expr_interpolated_string_visit_eval( ctx, (ecs_expr_interpolated_string_t*)node, out); break; case EcsExprEmptyInitializer: result = flecs_expr_empty_initializer_visit_eval( ctx, (ecs_expr_initializer_t*)node, out); break; case EcsExprInitializer: result = flecs_expr_initializer_visit_eval( ctx, (ecs_expr_initializer_t*)node, out); break; case EcsExprUnary: result = flecs_expr_unary_visit_eval( ctx, (ecs_expr_unary_t*)node, out); break; case EcsExprBinary: result = flecs_expr_binary_visit_eval( ctx, (ecs_expr_binary_t*)node, out); break; case EcsExprIdentifier: result = flecs_expr_identifier_visit_eval( ctx, (ecs_expr_identifier_t*)node, out); break; case EcsExprVariable: result = flecs_expr_variable_visit_eval( ctx, (ecs_expr_variable_t*)node, out); break; case EcsExprGlobalVariable: result = flecs_expr_global_variable_visit_eval( ctx, (ecs_expr_variable_t*)node, out); break; case EcsExprFunction: case EcsExprMethod: result = flecs_expr_function_visit_eval( ctx, (ecs_expr_function_t*)node, out); break; case EcsExprTemplate: result = flecs_expr_template_visit_eval( ctx, (ecs_expr_function_t*)node, out); break; case EcsExprMember: result = flecs_expr_member_visit_eval( ctx, (ecs_expr_member_t*)node, out); break; case EcsExprSwizzle: result = flecs_expr_swizzle_visit_eval( ctx, (ecs_expr_swizzle_t*)node, out); break; case EcsExprElement: result = flecs_expr_element_visit_eval( ctx, (ecs_expr_element_t*)node, out); break; case EcsExprMatch: result = flecs_expr_match_visit_eval( ctx, (ecs_expr_match_t*)node, out); break; case EcsExprRange: result = flecs_expr_range_visit_eval( ctx, (ecs_expr_range_t*)node, out); break; case EcsExprNew: result = flecs_expr_new_visit_eval( ctx, (ecs_expr_new_t*)node, out); break; case EcsExprScript: result = flecs_expr_script_visit_eval( ctx, (ecs_expr_script_t*)node, out); break; case EcsExprComponent: result = flecs_expr_component_visit_eval( ctx, (ecs_expr_element_t*)node, out); break; case EcsExprHas: result = flecs_expr_has_visit_eval( ctx, (ecs_expr_has_t*)node, out); break; case EcsExprCast: result = flecs_expr_cast_visit_eval( ctx, (ecs_expr_cast_t*)node, out); break; case EcsExprCastNumber: result = flecs_expr_cast_number_visit_eval( ctx, (ecs_expr_cast_t*)node, out); break; } if (frame) { flecs_expr_stack_pop(ctx->stack); } return result; } int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, const ecs_expr_eval_desc_t *desc, ecs_value_t *out) { ecs_expr_stack_t *stack = NULL, stack_local; if (desc && desc->runtime) { stack = flecs_script_runtime_expr_stack(desc->runtime); } if (!stack) { stack = &stack_local; flecs_expr_stack_init(stack); } flecs_expr_stack_push(stack); ecs_expr_value_t val_tmp; /* Initializers assign members of the existing value, so they are evaluated * straight into it. Other nodes produce a new value that is move assigned * to the output, so that the old value is destructed. An initializer typed * by a base struct can be evaluated straight into a value of a derived * struct: the base members are a prefix of the derived layout, so member * offsets are the same. */ bool is_initializer = node->kind == EcsExprInitializer || node->kind == EcsExprEmptyInitializer; bool direct = is_initializer && out->type && out->ptr && (out->type == node->type || flecs_struct_is_derived_from( script->world, out->type, node->type)); ecs_script_eval_ctx_t ctx = { .script = script, .world = script->world, .stack = stack, .desc = desc }; int result = -1; ecs_expr_value_t *val = &val_tmp; if (direct) { val_tmp = (ecs_expr_value_t){ .value = *out, .owned = false, .type_info = ecs_get_type_info(script->world, out->type) }; if (flecs_expr_visit_eval_priv(&ctx, node, val)) { goto error; } } else { val = flecs_expr_eval_result(&ctx, node, &val_tmp); if (!val) { goto error; } } if (desc && !out->type) { out->type = desc->type; } if (!out->type) { out->type = node->type; } ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(ctx.world)); if (out->type && !out->ptr) { out->ptr = ecs_ptr_new(world, out->type); } if (val != &val_tmp || out->ptr != val->value.ptr) { if (val->owned && out->type == val->value.type) { flecs_expr_value_move(out->ptr, val->value.ptr, val->type_info); } else if (flecs_value_copy_to(world, out, val)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } result = 0; error: flecs_expr_stack_pop(stack); if (stack == &stack_local) { flecs_expr_stack_fini(stack); } return result; } #endif #ifdef FLECS_SCRIPT typedef struct flecs_expr_fold_ctx_t { ecs_script_t *script; const ecs_expr_eval_desc_t *desc; bool can_fold; } flecs_expr_fold_ctx_t; static int flecs_expr_fold_child( ecs_expr_node_t **node, void *ptr) { flecs_expr_fold_ctx_t *ctx = ptr; if (!*node || !(*node)->type) { return 0; } if (flecs_expr_visit_fold(ctx->script, node, ctx->desc)) { return -1; } ctx->can_fold &= (*node)->kind == EcsExprValue; return 0; } static void flecs_visit_fold_replace( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_expr_node_t *with) { ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); flecs_expr_visit_free(script, *node_ptr); *node_ptr = with; } static void flecs_visit_fold_replace_copy( ecs_script_t *script, ecs_expr_node_t **node_ptr, const void *ptr) { ecs_expr_node_t *node = *node_ptr; ecs_expr_value_node_t *result = flecs_expr_value_from( script, node, node->type); result->ptr = ecs_ptr_new(script->world, node->type); ecs_ptr_copy(script->world, node->type, result->ptr, ptr); flecs_type_info_claim(result->node.type_info); flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } static int flecs_expr_fold_eval( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_node_t *node = *node_ptr; if (node->kind == EcsExprUnary) { ecs_expr_unary_t *unary = (ecs_expr_unary_t*)node; ecs_expr_value_node_t *result = (ecs_expr_value_node_t*)unary->expr; ecs_value_t value = { .type = result->node.type, .ptr = result->ptr }; if (flecs_value_unary(script, &value, &value, unary->operator)) { return -1; } unary->expr = NULL; result->node.pos = node->pos; result->node.end = node->end; flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; } ecs_expr_value_node_t *result = flecs_expr_value_from(script, node, node->type); const ecs_type_info_t *ti = result->node.type_info; if (ti->size > ECS_SIZEOF(result->storage) || ti->hooks.dtor) { result->ptr = ecs_ptr_new_w_type_info(script->world, ti); flecs_type_info_claim(ti); } else { flecs_type_info_ctor(result->ptr, 1, ti); } ecs_value_t value = { .type = node->type, .ptr = result->ptr }; int ret; if (node->kind == EcsExprCastNumber) { ecs_expr_value_node_t *src = (ecs_expr_value_node_t*) ((ecs_expr_cast_t*)node)->expr; ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, node->type, value.ptr); ret = ecs_meta_set_value(&cur, &(ecs_value_t){ .type = src->node.type, .ptr = src->ptr }); if (ret) { flecs_expr_visit_error(script, node, "failed to assign value"); } } else { ret = flecs_expr_visit_eval(script, node, desc, &value); } if (ret) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); return -1; } flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; } static int flecs_expr_binary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } if (!node->vector_count && (node->operator == EcsTokAnd || node->operator == EcsTokOr)) { if (node->left->kind == EcsExprValue && node->left->type == ecs_id(ecs_bool_t)) { ecs_expr_value_node_t *lnode = (ecs_expr_value_node_t*)node->left; bool lval = *(bool*)lnode->ptr; if ((node->operator == EcsTokAnd) != lval) { ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); *(bool*)result->ptr = lval; flecs_visit_fold_replace( script, node_ptr, (ecs_expr_node_t*)result); return 0; } } } if (flecs_expr_visit_fold(script, &node->right, desc)) { goto error; } if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { /* Only folding literals */ return 0; } if (node->vector_count) { return 0; } return flecs_expr_fold_eval(script, node_ptr, desc); error: return -1; } static int flecs_expr_fold_children( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc, bool fold) { flecs_expr_fold_ctx_t ctx = {script, desc, fold}; if (flecs_expr_visit_children(*node_ptr, flecs_expr_fold_child, &ctx)) { return -1; } return ctx.can_fold ? flecs_expr_fold_eval(script, node_ptr, desc) : 0; } static int flecs_expr_initializer_pre_fold( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_expr_eval_desc_t *desc, bool *can_fold) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); if (elem->key) { if (flecs_expr_visit_fold(script, &elem->key, desc)) { goto error; } } /* If this is a nested initializer, don't fold it but instead fold its * values. Because nested initializers are flattened, this ensures that * we'll be using the correct member offsets later. */ if (elem->value->kind == EcsExprInitializer) { if (flecs_expr_initializer_pre_fold( script, (ecs_expr_initializer_t*)elem->value, desc, can_fold)) { goto error; } continue; } if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } if (elem->value->kind != EcsExprValue || elem->operator) { *can_fold = false; } } if (node->is_dynamic) { *can_fold = false; } return 0; error: return -1; } static int flecs_expr_initializer_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc, bool can_fold) { if (flecs_expr_initializer_pre_fold( script, (ecs_expr_initializer_t*)*node_ptr, desc, &can_fold)) { return -1; } return can_fold ? flecs_expr_fold_eval(script, node_ptr, desc) : 0; } int flecs_expr_visit_fold_const( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_node_t *node = *node_ptr; if (node->kind != EcsExprInitializer || !((ecs_expr_initializer_t*)node)->is_partial) { return 0; } return flecs_expr_initializer_visit_fold(script, node_ptr, desc, true); } static int flecs_expr_identifier_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; ecs_expr_node_t *expr = node->expr; if (expr) { node->expr = NULL; if (flecs_expr_visit_fold(script, &expr, desc)) { flecs_expr_visit_free(script, expr); goto error; } flecs_visit_fold_replace(script, node_ptr, expr); } return 0; error: return -1; } static int flecs_expr_variable_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; ecs_script_var_t *var = ecs_script_vars_from_sp( desc->vars, node->sp); /* Should've been caught by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); if (var->is_const && var->value.ptr) { flecs_visit_fold_replace_copy(script, node_ptr, var->value.ptr); } return 0; } static int flecs_expr_global_variable_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; /* A mut variable can change after the expression is compiled, so its value * is always read live. */ if (node->global_component == ecs_id(EcsScriptMutVar)) { return 0; } /* In a template body the node is kept unfolded so its value is read live * from the const variable on every (re)instantiation. */ ecs_script_eval_visitor_t *v = desc->script_visitor; if (v && v->template) { return 0; } /* Global const variables are always const, so we can always fold */ ecs_value_t global_value = flecs_script_global_var_get( script->world, node->global, NULL); if (global_value.ptr) { flecs_visit_fold_replace_copy(script, node_ptr, global_value.ptr); } return 0; } static int flecs_expr_match_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_match_t *node = (ecs_expr_match_t*)*node_ptr; if (flecs_expr_fold_children(script, node_ptr, desc, false)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); if (node->expr->kind != EcsExprValue) { return 0; } ecs_expr_value_node_t *input = (ecs_expr_value_node_t*)node->expr; ecs_value_t input_value = { .type = input->node.type, .ptr = input->ptr }; ecs_expr_node_t **selected = NULL; for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (elem->compare->kind != EcsExprValue) { return 0; } ecs_expr_value_node_t *compare = (ecs_expr_value_node_t*)elem->compare; ecs_value_t compare_value = { .type = compare->node.type, .ptr = compare->ptr }; bool equal = false; ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &equal }; if (flecs_value_binary(script, &node->node, &input_value, &compare_value, &result, EcsTokEq)) { goto error; } if (equal) { selected = &elem->expr; break; } } if (!selected) { if (!node->any.expr) { return 0; } selected = &node->any.expr; } if ((*selected)->kind != EcsExprValue) { return 0; } ecs_expr_node_t *value = *selected; *selected = NULL; flecs_visit_fold_replace(script, node_ptr, value); return 0; error: return -1; } int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; switch(node->kind) { case EcsExprValue: case EcsExprNew: case EcsExprScript: return 0; case EcsExprInterpolatedString: case EcsExprUnary: case EcsExprCast: case EcsExprCastNumber: return flecs_expr_fold_children(script, node_ptr, desc, true); case EcsExprMember: case EcsExprSwizzle: case EcsExprElement: case EcsExprComponent: case EcsExprRange: return flecs_expr_fold_children(script, node_ptr, desc, false); case EcsExprInitializer: case EcsExprEmptyInitializer: return flecs_expr_initializer_visit_fold(script, node_ptr, desc, !((ecs_expr_initializer_t*)node)->is_partial); case EcsExprBinary: return flecs_expr_binary_visit_fold(script, node_ptr, desc); case EcsExprIdentifier: return flecs_expr_identifier_visit_fold(script, node_ptr, desc); case EcsExprVariable: return flecs_expr_variable_visit_fold(script, node_ptr, desc); case EcsExprGlobalVariable: return flecs_expr_global_variable_visit_fold(script, node_ptr, desc); case EcsExprMatch: return flecs_expr_match_visit_fold(script, node_ptr, desc); case EcsExprHas: return flecs_expr_visit_fold( script, &((ecs_expr_has_t*)node)->left, desc); case EcsExprFunction: case EcsExprMethod: { ecs_expr_function_t *function = (ecs_expr_function_t*)node; if (flecs_expr_visit_fold(script, &function->left, desc)) { return -1; } return function->args ? flecs_expr_fold_children(script, (ecs_expr_node_t**)&function->args, desc, false) : 0; } case EcsExprTemplate: { ecs_expr_function_t *function = (ecs_expr_function_t*)node; return function->args ? flecs_expr_visit_fold( script, (ecs_expr_node_t**)&function->args, desc) : 0; } } return 0; } #endif #ifdef FLECS_SCRIPT static int flecs_expr_free_child( ecs_expr_node_t **node, void *ctx) { flecs_expr_visit_free(ctx, *node); return 0; } void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node) { if (!node) { return; } flecs_expr_visit_children(node, flecs_expr_free_child, script); ecs_allocator_t *a = &flecs_script_impl(script)->allocator; switch(node->kind) { case EcsExprValue: { ecs_expr_value_node_t *n = (ecs_expr_value_node_t*)node; if (n->ptr != &n->storage) { ecs_ptr_free_w_type_info(script->world, node->type_info, n->ptr); flecs_type_info_release(node->type_info); } break; } case EcsExprInterpolatedString: { ecs_expr_interpolated_string_t *n = (ecs_expr_interpolated_string_t*)node; ecs_vec_fini_t(a, &n->fragments, ecs_expr_fragment_t); flecs_free_n(a, char, n->buffer_size, n->buffer); flecs_free_n(a, char, n->buffer_size, n->value); break; } case EcsExprInitializer: case EcsExprEmptyInitializer: ecs_vec_fini_t(a, &((ecs_expr_initializer_t*)node)->elements, ecs_expr_initializer_element_t); break; case EcsExprVariable: case EcsExprGlobalVariable: { ecs_expr_variable_t *var = (ecs_expr_variable_t*)node; if (var->owns_name) { flecs_strfree(a, ECS_CONST_CAST(char*, var->name)); } break; } case EcsExprMatch: ecs_vec_fini_t(a, &((ecs_expr_match_t*)node)->elements, ecs_expr_match_element_t); break; case EcsExprNew: { ecs_script_entity_t *entity = ((ecs_expr_new_t*)node)->entity; if (entity) { flecs_script_visit_free_node(script, (ecs_script_node_t*)entity); } break; } case EcsExprScript: { ecs_script_t *nested = ((ecs_expr_script_t*)node)->script; if (nested) { ecs_script_free(nested); } break; } default: break; } flecs_free(a, node->alloc_size, node); } #endif #ifdef FLECS_SCRIPT static ecs_entity_t flecs_expr_ref_entity( const ecs_script_t *script, ecs_expr_node_t *node) { if (!node) { return 0; } if (node->kind == EcsExprValue) { ecs_expr_value_node_t *value = (ecs_expr_value_node_t*)node; if (value->ptr) { return *(ecs_entity_t*)value->ptr; } } else if (node->kind == EcsExprIdentifier) { ecs_expr_identifier_t *identifier = (ecs_expr_identifier_t*)node; ecs_entity_t entity = flecs_expr_ref_entity(script, identifier->expr); if (!entity && identifier->symbol != -1) { ecs_script_impl_t *impl = flecs_script_impl( ECS_CONST_CAST(ecs_script_t*, script)); if (identifier->symbol < ecs_vec_count(&impl->state.symbol_slots)) { entity = ecs_vec_get_t( &impl->state.symbol_slots, ecs_script_symbol_slot_t, identifier->symbol)->entity; } } return entity; } return 0; } static const char* flecs_expr_ref_var_name( ecs_expr_node_t *node) { if (!node) { return NULL; } if (node->kind == EcsExprVariable) { ecs_expr_variable_t *var = (ecs_expr_variable_t*)node; if (var->node.type == ecs_id(ecs_entity_t)) { return var->name; } } return NULL; } typedef struct flecs_expr_ref_ctx_t { const ecs_script_t *script; flecs_expr_ref_action_t action; void *ctx; } flecs_expr_ref_ctx_t; static int flecs_expr_ref_visit( ecs_expr_node_t **node_ptr, void *ptr) { ecs_expr_node_t *node = *node_ptr; if (!node) { return 0; } flecs_expr_ref_ctx_t *ctx = ptr; const ecs_script_t *script = ctx->script; switch(node->kind) { case EcsExprGlobalVariable: { ecs_expr_variable_t *n = (ecs_expr_variable_t*)node; if (n->global && n->global_component) { ecs_script_ref_t ref = { .entity = n->global, .component = n->global_component }; return ctx->action(&ref, NULL, ctx->ctx); } break; } case EcsExprFunction: case EcsExprMethod: { ecs_expr_function_t *n = (ecs_expr_function_t*)node; if (flecs_expr_visit_children(node, flecs_expr_ref_visit, ctx)) { return -1; } if (n->calldata.is.callback == flecs_script_user_function_callback) { const EcsScriptFunction *fn = ecs_get( script->world, n->calldata.function, EcsScriptFunction); if (fn && fn->binding_ctx) { ecs_script_user_function_t *uf = fn->binding_ctx; ecs_script_ref_t *uf_refs = ecs_vec_first(&uf->refs); int32_t i, count = ecs_vec_count(&uf->refs); for (i = 0; i < count; i ++) { if (ctx->action(&uf_refs[i], NULL, ctx->ctx)) { return -1; } } } } return 0; } case EcsExprComponent: case EcsExprHas: { bool is_has = node->kind == EcsExprHas; ecs_expr_node_t *left = is_has ? ((ecs_expr_has_t*)node)->left : ((ecs_expr_element_t*)node)->left; ecs_script_ref_t ref = { .entity = flecs_expr_ref_entity(script, left), .component = is_has ? ((ecs_expr_has_t*)node)->id : node->type, .is_has = is_has }; if (ref.component) { if (!ref.entity) { ref.name = flecs_expr_ref_var_name(left); } if (ctx->action(&ref, ref.entity ? NULL : node, ctx->ctx)) { return -1; } } return flecs_expr_ref_visit(&left, ctx); } default: break; } return flecs_expr_visit_children(node, flecs_expr_ref_visit, ctx); } int flecs_expr_visit_refs( const ecs_script_t *script, ecs_expr_node_t *node, flecs_expr_ref_action_t action, void *ctx) { flecs_expr_ref_ctx_t visitor = {script, action, ctx}; return flecs_expr_ref_visit(&node, &visitor); } #endif #ifdef FLECS_SCRIPT typedef struct ecs_expr_str_visitor_t { const ecs_script_t *script; const ecs_world_t *world; ecs_strbuf_t *buf; int32_t depth; bool failed; bool colors; } ecs_expr_str_visitor_t; static void flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node); static void flecs_expr_color_to_str( ecs_expr_str_visitor_t *v, const char *color) { if (v->colors) ecs_strbuf_appendstr(v->buf, color); } static void flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_value_node_t *node) { flecs_expr_color_to_str(v, ECS_YELLOW); int ret = 0; ecs_entity_t type = node->node.type; if (ecs_has(v->world, type, EcsTypeSerializer)) { ret = ecs_ptr_to_str_buf(v->world, type, node->ptr, v->buf); } else { ecs_strbuf_appendstr(v->buf, "{}"); } flecs_expr_color_to_str(v, ECS_NORMAL); v->failed |= ret != 0; } static void flecs_expr_interpolated_string_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_interpolated_string_t *node) { int32_t i, count = ecs_vec_count(&node->fragments); ecs_expr_fragment_t *fragments = ecs_vec_first(&node->fragments); ecs_strbuf_appendlit(v->buf, "interpolated("); for (i = 0; i < count; i ++) { char *fragment = fragments[i].text; if (i) { ecs_strbuf_appendlit(v->buf, ", "); } if (fragment) { flecs_expr_color_to_str(v, ECS_YELLOW); ecs_strbuf_appendlit(v->buf, "\""); ecs_strbuf_appendstr(v->buf, fragment); ecs_strbuf_appendlit(v->buf, "\""); flecs_expr_color_to_str(v, ECS_NORMAL); } if (fragments[i].expr) { if (fragment) { ecs_strbuf_appendlit(v->buf, ", "); } flecs_expr_node_to_str(v, fragments[i].expr); } } ecs_strbuf_appendlit(v->buf, ")"); } static void flecs_expr_initializer_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_initializer_t *node) { ecs_strbuf_appendlit(v->buf, "{"); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { if (i) { ecs_strbuf_appendstr(v->buf, ", "); } ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member) { ecs_strbuf_appendstr(v->buf, elem->member); ecs_strbuf_appendlit(v->buf, ":"); } if (elem->key) { flecs_expr_node_to_str(v, elem->key); ecs_strbuf_appendlit(v->buf, ":"); } flecs_expr_node_to_str(v, elem->value); } ecs_strbuf_appendlit(v->buf, "}"); } static void flecs_expr_identifier_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_identifier_t *node) { ecs_strbuf_appendlit(v->buf, "@"); ecs_strbuf_appendstr(v->buf, node->value); } static void flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) { flecs_expr_color_to_str(v, ECS_GREEN); ecs_strbuf_appendlit(v->buf, "$"); ecs_strbuf_appendstr(v->buf, node->name); flecs_expr_color_to_str(v, ECS_NORMAL); } static void flecs_expr_function_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_function_t *node) { if (!node->function_name) { flecs_expr_node_to_str(v, node->left); ecs_strbuf_appendlit(v->buf, "("); } else { if (node->left) { flecs_expr_node_to_str(v, node->left); ecs_strbuf_appendlit(v->buf, "."); } ecs_strbuf_append(v->buf, "%s(", node->function_name); } if (node->args) { flecs_expr_node_to_str(v, (ecs_expr_node_t*)node->args); } ecs_strbuf_append(v->buf, ")"); } static void flecs_expr_template_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_function_t *node) { ecs_strbuf_append(v->buf, "%s(", node->function_name); if (node->args) { flecs_expr_node_to_str(v, (ecs_expr_node_t*)node->args); } ecs_strbuf_append(v->buf, ")"); } static void flecs_expr_has_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_has_t *node) { flecs_expr_node_to_str(v, node->left); ecs_strbuf_appendlit(v->buf, "?["); if (node->second) { ecs_strbuf_appendlit(v->buf, "("); flecs_expr_node_to_str(v, node->first); ecs_strbuf_appendlit(v->buf, ", "); flecs_expr_node_to_str(v, node->second); ecs_strbuf_appendlit(v->buf, ")"); } else { flecs_expr_node_to_str(v, node->first); } ecs_strbuf_appendlit(v->buf, "]"); } static void flecs_expr_type_to_str( ecs_expr_str_visitor_t *v, ecs_entity_t type) { flecs_expr_color_to_str(v, ECS_BLUE); const char *name = ecs_get_name(v->world, type); if (name) { ecs_strbuf_appendstr(v->buf, name); } else { char *path = ecs_get_path(v->world, type); ecs_strbuf_appendstr(v->buf, path); ecs_os_free(path); } flecs_expr_color_to_str(v, ECS_NORMAL); ecs_strbuf_appendlit(v->buf, "("); } static void flecs_expr_match_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_match_t *node) { if (node->node.type) { flecs_expr_type_to_str(v, node->node.type); } flecs_expr_color_to_str(v, ECS_BLUE); ecs_strbuf_appendlit(v->buf, "match "); flecs_expr_color_to_str(v, ECS_GREEN); flecs_expr_node_to_str(v, node->expr); ecs_strbuf_appendlit(v->buf, " {\n"); int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_strbuf_appendlit(v->buf, " "); ecs_expr_match_element_t *elem = &elems[i]; flecs_expr_node_to_str(v, elem->compare); ecs_strbuf_appendlit(v->buf, ": "); flecs_expr_node_to_str(v, elem->expr); ecs_strbuf_appendlit(v->buf, "\n"); } ecs_strbuf_appendlit(v->buf, "}"); if (node->node.type) { ecs_strbuf_appendlit(v->buf, ")"); } ecs_strbuf_appendlit(v->buf, "\n"); } static void flecs_expr_new_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_new_t *node) { flecs_expr_color_to_str(v, ECS_BLUE); ecs_strbuf_appendlit(v->buf, "new "); flecs_expr_color_to_str(v, ECS_NORMAL); ecs_assert(node->entity != NULL, ECS_INTERNAL_ERROR, NULL); v->failed |= ecs_script_ast_node_to_buf(v->script, (ecs_script_node_t*)node->entity, v->buf, v->colors, v->depth + 1) != 0; } static void flecs_expr_script_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_script_t *node) { flecs_expr_color_to_str(v, ECS_BLUE); ecs_strbuf_appendlit(v->buf, "script "); flecs_expr_color_to_str(v, ECS_NORMAL); ecs_strbuf_appendlit(v->buf, "{"); ecs_strbuf_appendstr(v->buf, node->script->code); ecs_strbuf_appendlit(v->buf, "}"); } static void flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); if (v->failed) { return; } const char *suffix = NULL; switch(node->kind) { case EcsExprValue: flecs_expr_value_to_str(v, (const ecs_expr_value_node_t*)node); break; case EcsExprInterpolatedString: flecs_expr_interpolated_string_to_str(v, (const ecs_expr_interpolated_string_t*)node); break; case EcsExprInitializer: case EcsExprEmptyInitializer: flecs_expr_initializer_to_str(v, (const ecs_expr_initializer_t*)node); break; case EcsExprUnary: ecs_strbuf_appendstr(v->buf, flecs_token_str( ((const ecs_expr_unary_t*)node)->operator)); flecs_expr_node_to_str(v, ((const ecs_expr_unary_t*)node)->expr); break; case EcsExprBinary: ecs_strbuf_appendlit(v->buf, "("); flecs_expr_node_to_str(v, ((const ecs_expr_binary_t*)node)->left); ecs_strbuf_appendlit(v->buf, " "); ecs_strbuf_appendstr(v->buf, flecs_token_str( ((const ecs_expr_binary_t*)node)->operator)); ecs_strbuf_appendlit(v->buf, " "); flecs_expr_node_to_str(v, ((const ecs_expr_binary_t*)node)->right); suffix = ")"; break; case EcsExprIdentifier: flecs_expr_identifier_to_str(v, (const ecs_expr_identifier_t*)node); break; case EcsExprVariable: case EcsExprGlobalVariable: flecs_expr_variable_to_str(v, (const ecs_expr_variable_t*)node); break; case EcsExprFunction: case EcsExprMethod: flecs_expr_function_to_str(v, (const ecs_expr_function_t*)node); break; case EcsExprTemplate: flecs_expr_template_to_str(v, (const ecs_expr_function_t*)node); break; case EcsExprMember: flecs_expr_node_to_str(v, ((const ecs_expr_member_t*)node)->left); ecs_strbuf_appendlit(v->buf, "."); ecs_strbuf_appendstr(v->buf, ((const ecs_expr_member_t*)node)->member_name); break; case EcsExprSwizzle: flecs_expr_node_to_str(v, ((const ecs_expr_swizzle_t*)node)->left); ecs_strbuf_appendlit(v->buf, "."); ecs_strbuf_appendstr(v->buf, ((const ecs_expr_swizzle_t*)node)->name); break; case EcsExprElement: case EcsExprComponent: flecs_expr_node_to_str(v, ((const ecs_expr_element_t*)node)->left); ecs_strbuf_appendlit(v->buf, "["); flecs_expr_node_to_str(v, ((const ecs_expr_element_t*)node)->index); suffix = "]"; break; case EcsExprHas: flecs_expr_has_to_str(v, (const ecs_expr_has_t*)node); break; case EcsExprMatch: flecs_expr_match_to_str(v, (const ecs_expr_match_t*)node); break; case EcsExprRange: ecs_strbuf_appendlit(v->buf, "["); flecs_expr_node_to_str(v, ((const ecs_expr_range_t*)node)->from); ecs_strbuf_appendlit(v->buf, " .. "); flecs_expr_node_to_str(v, ((const ecs_expr_range_t*)node)->to); suffix = "]"; break; case EcsExprNew: flecs_expr_new_to_str(v, (const ecs_expr_new_t*)node); break; case EcsExprScript: flecs_expr_script_to_str(v, (const ecs_expr_script_t*)node); break; case EcsExprCast: case EcsExprCastNumber: flecs_expr_type_to_str(v, node->type); flecs_expr_node_to_str(v, ((const ecs_expr_cast_t*)node)->expr); suffix = ")"; break; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); } if (suffix) { ecs_strbuf_appendstr(v->buf, suffix); } } void flecs_expr_to_str_buf( const ecs_script_t *script, const ecs_expr_node_t *expr, ecs_strbuf_t *buf, bool colors) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = script->world; ecs_expr_str_visitor_t v = { .script = script, .world = world, .buf = buf, .colors = colors }; flecs_expr_node_to_str(&v, expr); if (v.failed) { ecs_strbuf_reset(buf); } } #endif #ifdef FLECS_SCRIPT static int flecs_expr_visit_type_ex( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc, bool swizzle_expand_allowed); static int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { return flecs_expr_visit_type_ex(script, node_ptr, cur, desc, false); } static void flecs_expr_visit_type_cursor( const ecs_world_t *world, ecs_meta_cursor_t *cur, ecs_entity_t type) { *cur = ecs_meta_cursor(world, type, NULL); } static int flecs_expr_cast_to( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_entity_t type) { if ((*node_ptr)->type == type) { return 0; } ecs_expr_cast_t *cast = flecs_expr_cast(script, *node_ptr, type); if (!cast) { return -1; } *node_ptr = (ecs_expr_node_t*)cast; return 0; } static bool flecs_expr_unresolved_ref( ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_expr_node_t *node, const char *name, flecs_script_unresolved_kind_t kind) { ecs_script_eval_visitor_t *v = desc->script_visitor; if (!v || !v->type_visitor) { return false; } flecs_script_unresolved_ref_add( flecs_script_impl(script), node->pos, name, kind); return true; } static void flecs_expr_unresolved_component_ref( ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_expr_node_t *node, ecs_expr_node_t *left, ecs_id_t component, bool is_has) { ecs_script_eval_visitor_t *v = desc->script_visitor; if (!v || !v->type_visitor) { return; } if (left->kind == EcsExprIdentifier) { left = ((ecs_expr_identifier_t*)left)->expr; } if (!left || left->kind != EcsExprValue || left->type != ecs_id(ecs_entity_t)) { return; } ecs_entity_t src = ((ecs_expr_value_node_t*)left)->storage.entity; if (src && !ecs_has_id(script->world, src, component)) { ecs_script_impl_t *impl = flecs_script_impl(script); ecs_script_unresolved_component_ref_t *ref = ecs_vec_append_t( NULL, &impl->unresolved_component_refs, ecs_script_unresolved_component_ref_t); ref->entity = src; ref->component = component; ref->is_has = is_has; flecs_script_pos_to_line_col(impl->pub.code, node->pos, &ref->line, &ref->column); } } static bool flecs_expr_skip_unknown_unresolved_ref( ecs_script_t *script, const ecs_expr_eval_desc_t *desc, ecs_expr_node_t *node, const char *name, flecs_script_unresolved_kind_t kind) { if (!flecs_script_is_skip_unknown(script)) { return false; } return flecs_expr_unresolved_ref(script, desc, node, name, kind); } static bool flecs_expr_is_skipped_var( ecs_script_t *script, const ecs_expr_eval_desc_t *desc, const char *name) { if (!flecs_script_is_skip_unknown(script)) { return false; } ecs_script_eval_visitor_t *v = desc->script_visitor; if (!v || !v->type_visitor) { return false; } return flecs_script_type_is_skipped_var(v->type_visitor, name); } #define FLECS_EXPR_TYPE_INFO(T, K, I, S, F, E, R) \ if (type == ecs_id(T)) { \ static const flecs_expr_type_info_t info = { \ K, ECS_SIZEOF(T), E, R, I, S, F}; \ return &info; \ } const flecs_expr_type_info_t* flecs_expr_type_info( ecs_entity_t type) { FLECS_EXPR_TYPE_INFO(ecs_bool_t, EcsBool, false, true, false, 1, 1) FLECS_EXPR_TYPE_INFO(ecs_char_t, EcsChar, false, true, false, 2, 1) FLECS_EXPR_TYPE_INFO(ecs_u8_t, EcsU8, true, false, false, 2, 2) FLECS_EXPR_TYPE_INFO(ecs_u16_t, EcsU16, true, false, false, 3, 3) FLECS_EXPR_TYPE_INFO(ecs_u32_t, EcsU32, true, false, false, 4, 4) FLECS_EXPR_TYPE_INFO(ecs_uptr_t, EcsUPtr, true, false, false, 5, 6) FLECS_EXPR_TYPE_INFO(ecs_u64_t, EcsU64, true, false, false, 6, 7) FLECS_EXPR_TYPE_INFO(ecs_i8_t, EcsI8, true, true, false, 7, 1) FLECS_EXPR_TYPE_INFO(ecs_i16_t, EcsI16, true, true, false, 8, 2) FLECS_EXPR_TYPE_INFO(ecs_i32_t, EcsI32, true, true, false, 9, 3) FLECS_EXPR_TYPE_INFO(ecs_iptr_t, EcsIPtr, true, true, false, 10, 5) FLECS_EXPR_TYPE_INFO(ecs_i64_t, EcsI64, true, true, false, 11, 6) FLECS_EXPR_TYPE_INFO(ecs_f32_t, EcsF32, false, false, true, 12, 3) FLECS_EXPR_TYPE_INFO(ecs_f64_t, EcsF64, false, false, true, 13, 4) FLECS_EXPR_TYPE_INFO(ecs_string_t, EcsString, false, false, false, -1, -1) FLECS_EXPR_TYPE_INFO(ecs_entity_t, EcsEntity, false, false, false, -1, -1) static const flecs_expr_type_info_t unknown = { .expressiveness = -1, .storage = -1 }; return &unknown; } #undef FLECS_EXPR_TYPE_INFO bool flecs_expr_is_type_integer( ecs_entity_t type) { return flecs_expr_type_info(type)->integer; } bool flecs_expr_is_type_number( ecs_entity_t type) { const flecs_expr_type_info_t *info = flecs_expr_type_info(type); return info->integer || info->floating_point; } bool flecs_expr_is_type_signed_integer( ecs_entity_t type) { return flecs_expr_type_info(type)->signed_integer; } bool flecs_expr_is_type_unsigned_integer( ecs_entity_t type) { const flecs_expr_type_info_t *info = flecs_expr_type_info(type); return info->integer && !info->signed_integer; } bool flecs_expr_is_type_float( ecs_entity_t type) { return flecs_expr_type_info(type)->floating_point; } bool flecs_expr_is_type_string( ecs_entity_t type) { return type == ecs_id(ecs_string_t); } ecs_size_t flecs_expr_storage_size( ecs_entity_t type) { return flecs_expr_type_info(type)->size; } /* This function returns true if a type can be cast without changing the * precision of the value. It is used to determine a type for operands in a * binary expression in case they are of different types. */ static bool flecs_expr_implicit_cast_allowed( ecs_entity_t from, ecs_entity_t to) { const flecs_expr_type_info_t *from_info = flecs_expr_type_info(from); const flecs_expr_type_info_t *to_info = flecs_expr_type_info(to); if (from_info->expressiveness == -1 || to_info->expressiveness == -1) { return false; } return to_info->expressiveness >= from_info->expressiveness && to_info->storage >= from_info->storage; } static ecs_entity_t flecs_expr_cast_to_lvalue( ecs_entity_t lvalue, ecs_entity_t operand) { if (flecs_expr_implicit_cast_allowed(operand, lvalue)) { return lvalue; } return operand; } static ecs_entity_t flecs_expr_narrow_type( ecs_entity_t lvalue, ecs_expr_node_t *node) { if (node->kind != EcsExprValue) { return node->type; } const flecs_expr_type_info_t *info = flecs_expr_type_info(node->type); if (!info->integer && !info->floating_point) { return node->type; } if (info->floating_point || info->kind == EcsIPtr || info->kind == EcsUPtr) { return lvalue == ecs_id(ecs_f32_t) ? lvalue : node->type; } double value = ecs_meta_ptr_to_float( info->kind, ((ecs_expr_value_node_t*)node)->ptr); if (info->signed_integer) { if (value >= INT8_MIN && value <= INT8_MAX) { return ecs_id(ecs_i8_t); } else if (value >= INT16_MIN && value <= INT16_MAX) { return ecs_id(ecs_i16_t); } else if (value >= INT32_MIN && value <= INT32_MAX) { return ecs_id(ecs_i32_t); } return ecs_id(ecs_i64_t); } if (value <= UINT8_MAX) { return ecs_id(ecs_u8_t); } else if (value <= UINT16_MAX) { return ecs_id(ecs_u16_t); } else if (value <= UINT32_MAX) { return ecs_id(ecs_u32_t); } return ecs_id(ecs_u64_t); } static bool flecs_expr_oper_is_arithmetic( ecs_token_kind_t op) { switch(op) { case EcsTokAdd: case EcsTokSub: case EcsTokMul: case EcsTokDiv: case EcsTokMod: case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokBitwiseAnd: case EcsTokBitwiseOr: return true; default: return false; } } static bool flecs_expr_oper_valid_for_type( ecs_world_t *world, ecs_entity_t type, ecs_token_kind_t op) { switch(op) { case EcsTokAdd: case EcsTokSub: case EcsTokMul: case EcsTokDiv: case EcsTokMod: case EcsTokAddAssign: case EcsTokMulAssign: return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: if (ecs_get(world, type, EcsBitmask) != NULL) { return true; } /* fall through */ case EcsTokShiftLeft: case EcsTokShiftRight: return flecs_expr_is_type_integer(type); case EcsTokEq: case EcsTokNeq: case EcsTokAnd: case EcsTokOr: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: return flecs_expr_is_type_number(type) || (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } static int32_t flecs_script_get_vector_type_data( ecs_world_t *world, ecs_entity_t type, ecs_entity_t *vector_type_out) { const EcsStruct *stype = ecs_get(world, type, EcsStruct); if (!stype) { return 0; } ecs_entity_t vector_type = 0; ecs_member_t *members = ecs_vec_first(&stype->members); int32_t i, count = ecs_vec_count(&stype->members); for (i = 0; i < count; i ++) { ecs_member_t *member = &members[i]; if (!i) { if (!ecs_owns(world, member->type, EcsPrimitive)) { /* Only primitive types can be used in vector ops */ return 0; } vector_type = member->type; } else if (member->type != vector_type) { /* Only structs with members of the same type can be used as vector */ return 0; } } *vector_type_out = vector_type; return count; } static int flecs_expr_type_for_operator( ecs_script_t *script, ecs_expr_node_t *node, /* Only used for error reporting */ ecs_entity_t node_type, ecs_expr_node_t *left, ecs_expr_node_t *right, ecs_token_kind_t operator, ecs_entity_t *operand_type, ecs_entity_t *result_type, int32_t *vector_elem_count) { ecs_world_t *world = script->world; if (operator == EcsTokDiv || operator == EcsTokMod) { if (right->kind == EcsExprValue && flecs_expr_is_type_number(right->type)) { ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; if (flecs_value_is_0(&v)) { flecs_expr_visit_error(script, node, "invalid division by zero"); return -1; } } } switch(operator) { case EcsTokDiv: /* Result type of a division is always a float */ if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ *operand_type = ecs_id(ecs_f64_t); *result_type = ecs_id(ecs_f64_t); } else { *operand_type = left->type; *result_type = left->type; } return 0; case EcsTokMod: /* Mod only accepts integer operands, and results in an integer. We * could disallow doing mod on floating point types, but in practice * that would likely just result in code having to do a manual * conversion to an integer. */ *operand_type = ecs_id(ecs_i64_t); *result_type = ecs_id(ecs_i64_t); return 0; case EcsTokAnd: case EcsTokOr: /* Result type of a conditional operator is always a bool */ *operand_type = ecs_id(ecs_bool_t); *result_type = ecs_id(ecs_bool_t); return 0; case EcsTokEq: case EcsTokNeq: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: /* Result type of comparison operator is always bool, but operand types * should not be cast to bool */ *result_type = ecs_id(ecs_bool_t); break; case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokBitwiseAnd: case EcsTokBitwiseOr: case EcsTokAdd: case EcsTokSub: case EcsTokMul: break; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } /* If one of the types is an entity or id, the other one should be as well */ if (left->type == ecs_id(ecs_entity_t) || right->type == ecs_id(ecs_entity_t)) { *operand_type = ecs_id(ecs_entity_t); goto done; } if (left->type == ecs_id(ecs_id_t) || right->type == ecs_id(ecs_id_t)) { *operand_type = ecs_id(ecs_id_t); goto done; } ecs_entity_t left_type = left->type; const EcsPrimitive *ltype_ptr = ecs_get(world, left_type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { if (ecs_has(world, left_type, EcsBitmask) || ecs_has(world, right->type, EcsBitmask)) { *operand_type = ecs_id(ecs_u32_t); goto done; } const EcsEnum *enum_type = ecs_get(world, left_type, EcsEnum); if (!enum_type) { enum_type = ecs_get(world, right->type, EcsEnum); } if (enum_type) { *operand_type = enum_type->underlying_type; goto done; } if (!ltype_ptr) { ecs_entity_t vector_type = 0; int32_t elem_count = flecs_script_get_vector_type_data( world, left_type, &vector_type); if (elem_count) { /* For vector operations the right operand type must be either a * primitive type or the same type as the left operand. */ if (!rtype_ptr && (left_type != right->type)) { flecs_expr_visit_error(script, node, "invalid types for binary expression (%s, %s)", flecs_errstr(ecs_get_path(world, left->type)), flecs_errstr_1(ecs_get_path(world, right->type))); goto error; } if (*result_type == ecs_id(ecs_bool_t)) { flecs_expr_visit_error(script, node, "comparison operator is not supported for vector " "types (%s, %s)", flecs_errstr(ecs_get_path(world, left->type)), flecs_errstr_1(ecs_get_path(world, right->type))); goto error; } /* This is a binary vector operation, check if it's allowed */ if (!vector_elem_count) { flecs_expr_visit_error(script, node, "binary vector operation is not allowed here (%s, %s)", flecs_errstr(ecs_get_path(world, left->type)), flecs_errstr_1(ecs_get_path(world, right->type))); goto error; } *result_type = left_type; *operand_type = vector_type; *vector_elem_count = elem_count; goto done; } } flecs_expr_visit_error(script, node, "invalid types for binary expression (%s, %s)", flecs_errstr(ecs_get_path(world, left->type)), flecs_errstr_1(ecs_get_path(world, right->type))); goto error; } /* If left and right types are the same, do nothing */ if (left_type == right->type) { if ((operator == EcsTokEq || operator == EcsTokNeq) && flecs_expr_is_type_float(left_type) && left->kind == EcsExprValue && right->kind == EcsExprValue) { flecs_expr_visit_error(script, node, "floating point value is invalid in equality comparison"); goto error; } *operand_type = left->type; goto done; } /* If types are not the same, find the smallest type for literals that can * represent the value without losing precision. */ ecs_entity_t ltype = flecs_expr_narrow_type(node_type, left); ecs_entity_t rtype = flecs_expr_narrow_type(node_type, right); /* If types are not the same, try to implicitly cast to expression type */ ltype = flecs_expr_cast_to_lvalue(node_type, ltype); rtype = flecs_expr_cast_to_lvalue(node_type, rtype); if (ltype == rtype) { *operand_type = ltype; goto done; } if (operator == EcsTokEq || operator == EcsTokNeq) { /* If this is an equality comparison and one of the operands is a bool, * cast the other operand to a bool as well. This ensures that * expressions such as true == 2 evaluate to true. */ if (ltype == ecs_id(ecs_bool_t) || rtype == ecs_id(ecs_bool_t)) { *operand_type = ecs_id(ecs_bool_t); goto done; } bool ltype_is_flt = flecs_expr_is_type_float(ltype); bool rtype_is_flt = flecs_expr_is_type_float(rtype); if (ltype_is_flt || rtype_is_flt) { if (left->kind == EcsExprValue && right->kind == EcsExprValue) { flecs_expr_visit_error(script, node, "floating point value is invalid in equality comparison"); goto error; } if (ltype_is_flt && right->kind == EcsExprValue) { *operand_type = ltype; goto done; } if (rtype_is_flt && left->kind == EcsExprValue) { *operand_type = rtype; goto done; } } } /* If after the implicit cast types are not the same, try to implicitly cast * to the most expressive type. */ if (flecs_expr_type_info(ltype)->expressiveness >= flecs_expr_type_info(rtype)->expressiveness) { if (flecs_expr_implicit_cast_allowed(rtype, ltype)) { *operand_type = ltype; goto done; } } else { if (flecs_expr_implicit_cast_allowed(ltype, rtype)) { *operand_type = rtype; goto done; } } /* If we get here, one or both operands cannot be coerced to the same type * while guaranteeing no loss of precision. Pick the type that's least * likely to cause trouble. */ if (flecs_expr_is_type_number(ltype) && flecs_expr_is_type_number(rtype)) { /* If one of the types is a floating point type, use f64 */ if (flecs_expr_is_type_float(ltype) || flecs_expr_is_type_float(rtype)) { *operand_type = ecs_id(ecs_f64_t); goto done; } /* If one of the types is a signed integer, use i64 */ if (flecs_expr_is_type_signed_integer(ltype) || flecs_expr_is_type_signed_integer(rtype)) { *operand_type = ecs_id(ecs_i64_t); goto done; } } /* If all of that didn't work, give up */ flecs_expr_visit_error(script, node, "incompatible types in expression (%s vs %s)", flecs_errstr(ecs_id_str(world, ltype)), flecs_errstr_1(ecs_id_str(world, rtype))); error: return -1; done: if (operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ *operand_type = ecs_id(ecs_i64_t); } if (*operand_type == ecs_id(ecs_bool_t) && flecs_expr_oper_is_arithmetic(operator) && (!vector_elem_count || !*vector_elem_count)) { *operand_type = ecs_id(ecs_i32_t); } if (!*result_type) { *result_type = *operand_type; } if (ecs_get(script->world, *result_type, EcsBitmask) != NULL) { *operand_type = ecs_id(ecs_u64_t); } return 0; } static int flecs_expr_interpolated_string_visit_type( ecs_script_t *script, ecs_expr_interpolated_string_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_allocator_t *a = &flecs_script_impl(script)->allocator; char *ptr, *frag = NULL; char ch; for (ptr = node->value; (ch = ptr[0]); ) { if (ch == '\\') { ptr ++; if (ptr[0]) { ptr ++; } continue; } if ((ch == '$') && (isspace((unsigned char)ptr[1]) || !ptr[1])) { ptr ++; continue; } if (ch == '$' || ch == '{') { if (!frag) { frag = node->value; } char *frag_end = ptr; ecs_expr_fragment_t *fragment = ecs_vec_append_t( a, &node->fragments, ecs_expr_fragment_t); *fragment = (ecs_expr_fragment_t){.text = frag != ptr ? frag : NULL}; ecs_expr_node_t **result = &fragment->expr; ecs_expr_format_t *format = &fragment->format; if (ch == '$') { char *var_name = ++ ptr; ptr = ECS_CONST_CAST(char*, flecs_tokenizer_identifier( NULL, ptr, NULL)); if (!ptr) { goto error; } ecs_size_t var_name_pos = flecs_ito(int32_t, var_name - node->value); var_name = &node->buffer[var_name_pos]; ecs_size_t var_name_end = flecs_ito(int32_t, ptr - node->value); node->buffer[var_name_end] = '\0'; ecs_expr_variable_t *var = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, var_name); if (!var) { goto error; } *result = (ecs_expr_node_t*)var; } else { ecs_script_impl_t *impl = flecs_script_impl(script); ecs_parser_t parser = { .name = script->name, .code = script->code, .pos = node->node.pos, .fixed_pos = node->node.pos, .script = impl, .scope = impl->root, .significant_newline = false, .token_cur = impl->token_remaining, .token_end = &impl->token_buffer[impl->token_buffer_size] }; ptr = ECS_CONST_CAST(char*, flecs_script_parse_expr( &parser, ptr + 1, 0, result)); if (!ptr) { goto error; } impl->token_remaining = parser.token_cur; if (ptr[0] == ':') { ptr = ECS_CONST_CAST(char*, flecs_expr_format_parse( &parser, ptr + 1, format, desc)); if (!ptr) { goto error; } } if (ptr[0] != '}') { flecs_expr_visit_error(script, node, "expected '}' at end of interpolated expression"); goto error; } ptr ++; } ecs_assert(*result != NULL, ECS_INTERNAL_ERROR, NULL); ecs_expr_eval_desc_t priv_desc = *desc; priv_desc.type = format->is_present ? 0 : ecs_id(ecs_string_t); priv_desc.allow_unresolved_identifiers = true; ecs_meta_cursor_t value_cur = {0}; ecs_meta_cursor_t *result_cur = format->is_present ? &value_cur : cur; if (flecs_expr_visit_type_priv( script, result, result_cur, &priv_desc)) { goto error; } if (format->is_present) { ecs_entity_t value_type = (*result)->type; bool is_float = flecs_expr_is_type_float(value_type); bool is_integer = flecs_expr_is_type_signed_integer(value_type) || flecs_expr_is_type_unsigned_integer(value_type); bool is_string = flecs_expr_is_type_string(value_type); const char *format_error = NULL; if (!is_float && !is_integer && !is_string) { format_error = "format specifiers require a number or " "string value"; } else if (!is_float && format->precision) { format_error = "precision is only supported for f32 and " "f64 values"; } else if (!is_float && format->notation) { format_error = "scientific notation is only supported for " "f32 and f64 values"; } else if (is_string && format->sign) { format_error = "sign is not supported for string values"; } if (format_error) { flecs_expr_visit_error(script, *result, "%s", format_error); goto error; } ecs_expr_node_t **format_values[2] = { &format->width, &format->precision }; int32_t f; for (f = 0; f < 2; f ++) { ecs_expr_node_t **format_value = format_values[f]; if (!format_value[0]) { continue; } ecs_expr_eval_desc_t format_desc = *desc; format_desc.type = 0; ecs_meta_cursor_t format_cur = {0}; if (flecs_expr_visit_type_priv(script, format_value, &format_cur, &format_desc)) { goto error; } if (!flecs_expr_is_type_integer(format_value[0]->type)) { flecs_expr_visit_error(script, format_value[0], "format width and precision must be integers"); goto error; } if (flecs_expr_cast_to( script, format_value, ecs_id(ecs_i32_t))) { goto error; } } } else if (flecs_expr_cast_to( script, result, ecs_id(ecs_string_t))) { goto error; } frag_end[0] = '\0'; frag = ptr; if (!ptr[0]) { break; } } else { ptr ++; } } ecs_assert(frag != NULL, ECS_INTERNAL_ERROR, NULL); if (frag != ptr) { ecs_vec_append_t(a, &node->fragments, ecs_expr_fragment_t)[0] = (ecs_expr_fragment_t){.text = frag}; } return 0; error: return -1; } static int flecs_expr_initializer_collection_check( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur, ecs_entity_t init_type) { ecs_world_t *world = script->world; if (cur) { bool is_collection = ecs_meta_is_collection(cur) || ecs_meta_is_map(cur); if (is_collection != node->is_collection) { flecs_expr_visit_error(script, node, node->is_collection ? "invalid collection literal for non-collection type '%s'" : "invalid object literal for collection type '%s'", flecs_errstr(ecs_get_path(world, init_type))); goto error; } } ecs_entity_t type = node->node.type; if (type) { const EcsOpaque *op = ecs_get(world, type, EcsOpaque); if (op) { type = op->as_type; } const EcsType *ptr = ecs_get(world, type, EcsType); if (ptr) { ecs_type_kind_t kind = ptr->kind; /* Only do the collection check if no cursor is provided. Cursors * also handle inline arrays. */ if (node->is_collection && !cur && kind != EcsArrayType && kind != EcsVectorType && kind != EcsMapType) { flecs_expr_visit_error(script, node, "invalid collection literal for type '%s'", flecs_errstr(ecs_get_path(world, node->node.type))); goto error; } if (!node->is_collection && kind != EcsStructType && kind != EcsValueType) { flecs_expr_visit_error(script, node, "invalid object literal for type '%s'", flecs_errstr(ecs_get_path(world, node->node.type))); goto error; } } } return 0; error: return -1; } /* Dynamic initializers use the cursor API to assign values, and are used for * any type where a simple list of offsets into fields doesn't work. */ static bool flecs_expr_initializer_is_dynamic( ecs_world_t *world, ecs_entity_t type) { const EcsType *t = ecs_get(world, type, EcsType); if (t) { return t->kind == EcsOpaqueType || t->kind == EcsVectorType || t->kind == EcsMapType || t->kind == EcsValueType; } return false; } static int flecs_expr_empty_initializer_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur) { node->node.type = ecs_meta_get_type(cur); if (!node->node.type) { flecs_expr_visit_error(script, node, "unknown type for initializer"); goto error; } node->is_dynamic = node->is_dynamic || flecs_expr_initializer_is_dynamic( script->world, node->node.type); if (ecs_meta_push(cur)) { goto error; } if (flecs_expr_initializer_collection_check( script, node, cur, node->node.type)) { goto error; } if (ecs_meta_pop(cur)) { goto error; } return 0; error: return -1; } static bool flecs_expr_member_swizzle_expand_hint( ecs_meta_cursor_t *cur, const char *name, uint16_t *dst) { int32_t i, count = ecs_os_strlen(name); if (count < 2 || count > FLECS_EXPR_SWIZZLE_MAX) { return false; } ecs_meta_scope_t *scope = &cur->scope[cur->depth]; if (scope->is_collection || scope->is_map || scope->is_value || scope->opaque || !scope->ops) { return false; } int32_t ops_cur = scope->ops_cur; if ((ops_cur + count) > scope->ops_count) { return false; } ecs_meta_op_t *first = &scope->ops[ops_cur]; if (first->kind <= EcsOpPrimitive) { return false; } for (i = 0; i < count; i ++) { ecs_meta_op_t *op = &scope->ops[ops_cur + i]; if (op->kind <= EcsOpPrimitive || op->type != first->type) { return false; } ecs_size_t offset = op->offset - first->offset; if (offset < 0 || offset > UINT16_MAX) { return false; } dst[i] = flecs_ito(uint16_t, offset); } return true; } static int flecs_expr_unify_type( ecs_script_t *script, ecs_expr_node_t *node, ecs_expr_node_t *elem, bool allow_cast, const char *elem_kind, int32_t index, const char *container) { if (!node->type) { node->type = elem->type; return 0; } if (elem->type == node->type) { return 0; } if (flecs_expr_is_type_number(node->type)) { ecs_entity_t result_type = 0, operand_type = 0; if (flecs_expr_type_for_operator(script, node, 0, node, elem, EcsTokAdd, /* Use operator that doesn't change types */ &operand_type, &result_type, NULL)) { return -1; } /* "Accumulate" most expressive type in result node */ node->type = result_type; return 0; } /* If type is not a number it must be castable to the result type */ if (allow_cast && flecs_expr_explicit_cast_allowed( script->world, elem->type, node->type)) { return 0; } flecs_expr_visit_error(script, node, "invalid type for %s %d in %s (got %s, expected %s)", elem_kind, index + 1, container, flecs_errstr(ecs_get_path(script->world, elem->type)), flecs_errstr_1(ecs_get_path(script->world, node->type))); return -1; } /* Infer the type of a collection literal without a target type. The most * expressive element type determines the vector type. */ static int flecs_expr_anonymous_collection_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_expr_eval_desc_t *desc) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); node->node.type = 0; for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->key) { flecs_expr_visit_error(script, node, "missing type for map literal"); goto error; } if (!elem->value) { flecs_expr_visit_error(script, node, "missing value for initializer element"); goto error; } ecs_meta_cursor_t elem_cur; ecs_os_zeromem(&elem_cur); if (flecs_expr_visit_type_priv(script, &elem->value, &elem_cur, desc)) { goto error; } if (flecs_expr_unify_type(script, &node->node, elem->value, false, "element", i, "collection literal")) { goto error; } } ecs_entity_t elem_type = node->node.type; node->node.type = 0; if (!elem_type) { flecs_expr_visit_error(script, node, "cannot infer element type for collection literal"); goto error; } for (i = 0; i < count; i ++) { if (flecs_expr_cast_to(script, &elems[i].value, elem_type)) { goto error; } } ecs_entity_t vector_type = flecs_script_vector_type( script->world, elem_type); if (!vector_type) { flecs_expr_visit_error(script, node, "failed to create vector type for collection literal"); goto error; } node->node.type = vector_type; node->is_dynamic = true; return 0; error: return -1; } static int32_t flecs_expr_inline_elem_count( const ecs_meta_cursor_t *cur) { ecs_meta_cursor_t elem_cur = *cur; int32_t count = 0; int prev_log = ecs_log_set_level(-4); if (!ecs_meta_push(&elem_cur) && ecs_meta_is_collection(&elem_cur)) { count = elem_cur.scope[elem_cur.depth - 1].elem_count; } ecs_log_set_level(prev_log); return count; } static bool flecs_expr_is_inline_array( ecs_world_t *world, const ecs_meta_cursor_t *cur, ecs_entity_t value_type, ecs_entity_t elem_type) { const EcsArray *arr = ecs_get(world, value_type, EcsArray); return arr && arr->type == elem_type && flecs_expr_inline_elem_count(cur) == arr->count; } static int flecs_expr_initializer_visit_type( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)node_ptr[0]; if (!cur || !cur->valid) { if (node->is_collection) { return flecs_expr_anonymous_collection_visit_type( script, node, desc); } flecs_expr_visit_error(script, node, "missing type for initializer"); goto error; } ecs_entity_t type = ecs_meta_get_type(cur); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); /* Opaque types do not have deterministic offsets */ bool is_dynamic = node->is_dynamic || flecs_expr_initializer_is_dynamic(script->world, type); node->is_dynamic = is_dynamic; if (ecs_meta_push(cur)) { goto error; } if (flecs_expr_initializer_collection_check(script, node, cur, type)) { goto error; } bool is_map = ecs_meta_is_map(cur); ecs_entity_t key_type = 0; if (is_map) { const EcsMap *map_type = ecs_get(script->world, type, EcsMap); ecs_assert(map_type != NULL, ECS_INTERNAL_ERROR, NULL); key_type = map_type->key_type; } ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); bool prev_removed = false; for (i = 0; i < count; i ++) { if (i && !prev_removed) { if (ecs_meta_next(cur)) { /* , */ goto error; } } prev_removed = false; ecs_expr_initializer_element_t *elem = &elems[i]; if (!elem->value) { flecs_expr_visit_error(script, node, "missing value for initializer element"); goto error; } if (elem->key) { if (!is_map) { flecs_expr_visit_error(script, node, "invalid key for non-map type '%s'", flecs_errstr(ecs_get_path(script->world, type))); goto error; } ecs_meta_cursor_t key_cur = ecs_meta_cursor( script->world, key_type, NULL); if (flecs_expr_visit_type_priv( script, &elem->key, &key_cur, desc)) { goto error; } if (flecs_expr_cast_to(script, &elem->key, key_type)) { goto error; } } else if (is_map) { flecs_expr_visit_error(script, node, "missing key for map initializer element"); goto error; } if (elem->member) { bool skip_unknown = flecs_script_is_skip_unknown(script); ecs_meta_cursor_t member_cur = *cur; int prev_log = skip_unknown ? ecs_log_set_level(-4) : 0; int dotmember_result = ecs_meta_dotmember(cur, elem->member); if (skip_unknown) { ecs_log_set_level(prev_log); } if (dotmember_result) { /* x: */ if (!skip_unknown) { flecs_expr_visit_error(script, node, "cannot resolve member"); goto error; } *cur = member_cur; flecs_script_skip_unknown_warn(script, elem->member, "skipped value for unresolved member"); flecs_expr_visit_free(script, elem->key); flecs_expr_visit_free(script, elem->value); ecs_vec_remove_ordered_t(&node->elements, ecs_expr_initializer_element_t, i); elems = ecs_vec_first(&node->elements); count --; i --; prev_removed = true; continue; } } /* Check for "member: $" syntax */ if (elem->value->kind == EcsExprVariable) { ecs_expr_variable_t *var = (ecs_expr_variable_t*)elem->value; if (var->name && !var->name[0]) { var->name = ecs_meta_get_member(cur); if (!var->name) { flecs_expr_visit_error(script, node, "cannot deduce variable name: not a member"); goto error; } } } bool swizzle_expand_allowed = !elem->operator && !is_dynamic; if (is_dynamic && elem->value->kind == EcsExprInitializer) { /* A push for a nested initializer may have to allocate elements in * the parent collection value. */ ((ecs_expr_initializer_t*)elem->value)->is_dynamic = true; } ecs_entity_t elem_type = ecs_meta_get_type(cur); ecs_meta_cursor_t elem_cur = *cur; if (elem_type == ecs_id(ecs_value_t) && elem->value->kind != EcsExprInitializer && elem->value->kind != EcsExprEmptyInitializer) { /* When assigning an expression to a value, derive the type of * the value from the expression. */ ecs_os_zeromem(&elem_cur); } if (flecs_expr_visit_type_ex( script, &elem->value, &elem_cur, desc, swizzle_expand_allowed)) { goto error; } if (elem_type == ecs_id(ecs_script_template_ref_t) && elem->value->type == ecs_id(ecs_script_template_ref_t)) { const ecs_expr_variable_t *var = flecs_expr_template_ref_var( elem->value); ecs_script_eval_visitor_t *v = desc ? desc->script_visitor : NULL; ecs_entity_t value_interface = var && v ? flecs_script_template_member_interface(v->template, var->sp) : 0; ecs_entity_t member_interface = flecs_script_template_prop_interface(script->world, cur->scope[cur->depth].type, ecs_meta_get_member(cur)); const char *expected = value_interface && member_interface ? flecs_script_template_interface_error( script->world, value_interface, member_interface) : NULL; if (expected) { flecs_expr_visit_error(script, elem->value, "'%s' passed to template prop '%s' is not %s", var->name, ecs_meta_get_member(cur), expected); goto error; } } ecs_entity_t value_type = elem->value->type; if (value_type != elem_type && (elem->operator || !flecs_expr_is_inline_array( script->world, cur, value_type, elem_type))) { bool single_value = count == 1 && !node->is_collection && !elem->member && !elem->key && !elem->operator; if (single_value && (value_type == type || flecs_struct_is_derived_from(script->world, value_type, type))) { ecs_expr_node_t *value = elem->value; if (flecs_expr_cast_to(script, &value, type)) { goto error; } elem->value = NULL; if (ecs_meta_pop(cur)) { goto error; } flecs_expr_visit_free(script, (ecs_expr_node_t*)node); *node_ptr = value; return 0; } if (single_value && ecs_has(script->world, value_type, EcsStruct) && !flecs_expr_explicit_cast_allowed( script->world, value_type, elem_type)) { flecs_expr_visit_error(script, elem->value, "cannot assign value of type %s to %s", flecs_errstr(ecs_get_path(script->world, value_type)), flecs_errstr_1(ecs_get_path(script->world, type))); goto error; } if (flecs_expr_cast_to(script, &elem->value, elem_type)) { goto error; } } if (elem->operator && !flecs_expr_oper_valid_for_type( script->world, elem_type, elem->operator)) { flecs_expr_visit_error(script, node, "invalid operator for type '%s'", flecs_errstr(ecs_get_path(script->world, elem_type))); goto error; } if (!is_dynamic) { elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); } ecs_expr_swizzle_t *swizzle = flecs_expr_expand_swizzle_get(elem->value); if (swizzle) { int32_t s; for (s = 1; s < swizzle->count; s ++) { if (ecs_meta_next(cur)) { goto error; } } } } node->node.type = type; if (ecs_meta_pop(cur)) { goto error; } return 0; error: return -1; } static int flecs_expr_unary_visit_type( ecs_script_t *script, ecs_expr_unary_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { if (flecs_expr_visit_type_priv(script, &node->expr, cur, desc)) { goto error; } ecs_entity_t type = ecs_id(ecs_bool_t); if (node->operator == EcsTokSub) { type = node->expr->type; if (!flecs_expr_is_type_number(type) && type != ecs_id(ecs_bool_t)) { flecs_expr_visit_error(script, node, "invalid operator - for type '%s'", flecs_errstr(ecs_get_path(script->world, type))); goto error; } if (type == ecs_id(ecs_bool_t) || flecs_expr_is_type_unsigned_integer(type)) { type = ecs_id(ecs_i64_t); } } node->node.type = type; if (flecs_expr_cast_to(script, &node->expr, type)) { goto error; } return 0; error: return -1; } static int flecs_expr_binary_visit_type( ecs_script_t *script, ecs_expr_binary_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { /* Operands must be of this type or cast to it */ ecs_entity_t operand_type = 0; /* Resulting type of binary expression */ ecs_entity_t result_type = 0; /* Number of elements in vector, if vector operation */ int32_t vector_elem_count = 0; /* Operands are visited with a copy of the cursor. Visiting an operand can * move the cursor to the type of the operand, which must not be visible to * the expression that this binary expression is a part of. */ ecs_allocator_t *a = &flecs_script_impl(script)->allocator; ecs_meta_cursor_t *operand_cur = flecs_alloc_n(a, ecs_meta_cursor_t, 2); *operand_cur = *cur; if (operand_cur->valid) { /* Provides a hint to the type visitor. The lvalue type will be used to * reduce the number of casts where possible. */ node->node.type = ecs_meta_get_type(operand_cur); /* If the result of the binary expression is a boolean, it's likely a * conditional expression. We don't want to hint that the operands * of conditional expressions should be cast to booleans. */ if (node->node.type == ecs_id(ecs_bool_t)) { ecs_os_zeromem(operand_cur); } } operand_cur[1] = operand_cur[0]; if (flecs_expr_visit_type_priv(script, &node->left, operand_cur, desc)) { goto error; } if (!node->right) { flecs_expr_visit_error(script, node, "invalid binary expression"); goto error; } if (node->right->kind == EcsExprIdentifier && ecs_get(script->world, node->left->type, EcsEnum) != NULL) { /* If the left hand side is an enum, interpret untyped identifiers * on the right hand side as enum constants of the same type. */ flecs_expr_visit_type_cursor( script->world, &operand_cur[1], node->left->type); } if (flecs_expr_visit_type_priv( script, &node->right, &operand_cur[1], desc)) { goto error; } if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, node->node.type, node->left, node->right, node->operator, &operand_type, &result_type, &vector_elem_count)) { goto error; } ecs_entity_t check_type = operand_type; if (!vector_elem_count && node->operator != EcsTokGt && node->operator != EcsTokGtEq && node->operator != EcsTokLt && node->operator != EcsTokLtEq) { check_type = result_type; } if (!flecs_expr_oper_valid_for_type( script->world, check_type, node->operator)) { flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", flecs_token_str(node->operator), flecs_errstr(ecs_get_path(script->world, check_type))); goto error; } if (!vector_elem_count) { if (flecs_expr_cast_to(script, &node->left, operand_type)) { goto error; } node->vector_type = 0; node->vector_count = 0; } else { node->vector_type = operand_type; node->vector_count = vector_elem_count; } /* If this is a vector operation between the same types, don't try to cast * the right operand to the vector type. */ if ((!vector_elem_count || (node->right->type != node->left->type)) && flecs_expr_cast_to(script, &node->right, operand_type)) { goto error; } node->node.type = result_type; flecs_free_n(a, ecs_meta_cursor_t, 2, operand_cur); return 0; error: flecs_free_n(a, ecs_meta_cursor_t, 2, operand_cur); return -1; } static int flecs_expr_constant_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node) { ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); ecs_meta_cursor_t expr_cur = ecs_meta_cursor( script->world, node->node.type, &result->storage.u64); if (ecs_meta_set_string(&expr_cur, node->value)) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } result->ptr = &result->storage.u64; node->expr = (ecs_expr_node_t*)result; return 0; error: return -1; } static bool flecs_expr_is_entity_type( ecs_world_t *world, ecs_entity_t type, bool *is_opaque) { *is_opaque = false; if (type == ecs_id(ecs_entity_t) || type == ecs_id(ecs_id_t)) { return true; } const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); if (p) { return p->kind == EcsEntity || p->kind == EcsId; } const EcsOpaque *o = ecs_get(world, type, EcsOpaque); if (o) { *is_opaque = true; bool dummy; return flecs_expr_is_entity_type(world, o->as_type, &dummy); } return false; } static int flecs_expr_identifier_variable_member_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc, bool swizzle_expand_allowed) { char *member_sep = strchr(node->value, '.'); while (member_sep) { if (member_sep != node->value && member_sep[-1] == '\\') { member_sep = strchr(member_sep + 1, '.'); continue; } member_sep[0] = '\0'; flecs_script_symbol_t symbol; if (!flecs_script_symbol_lookup(script, desc, 0, node->value, FlecsScriptLookupAll, &symbol)) { if (symbol.kind == FlecsScriptSymbolVariable) { break; } if (symbol.kind == FlecsScriptSymbolGlobalVariable && symbol.entity && flecs_script_global_var_get( script->world, symbol.entity, NULL).ptr) { break; } } member_sep[0] = '.'; member_sep = strchr(member_sep + 1, '.'); } if (!member_sep) { return 1; } char *var_name = flecs_strdup( &flecs_script_impl(script)->allocator, node->value); member_sep[0] = '.'; ecs_expr_variable_t *var_node = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, var_name); var_node->owns_name = true; ecs_expr_member_t *member_node = flecs_expr_member_from( script, (ecs_expr_node_t*)var_node, &member_sep[1]); node->expr = (ecs_expr_node_t*)member_node; if (flecs_expr_visit_type_ex( script, &node->expr, cur, desc, swizzle_expand_allowed)) { goto error; } node->node.type = node->expr->type; return 0; error: flecs_expr_visit_free(script, node->expr); node->expr = NULL; return -1; } static int flecs_expr_parent_member_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_script_eval_visitor_t *v = desc->script_visitor; ecs_script_template_t *template = v ? v->template : NULL; if (!template || !template->parent_type) { return 1; } const char *name = node->value; bool qualified = !ecs_os_strncmp(name, "parent.", 7); if (qualified) { name += 7; } else { const char *dot = strchr(name, '.'); if (dot) { char *root = flecs_strdup(&flecs_script_impl(script)->allocator, name); root[dot - name] = '\0'; bool masked = ecs_script_vars_lookup(desc->vars, root) != NULL; root[dot - name] = '.'; flecs_strfree(&flecs_script_impl(script)->allocator, root); if (masked) { return 1; } } } ecs_entity_t component = 0; if (!flecs_script_template_parent_member( script->world, template, name, &component)) { return 1; } ecs_expr_variable_t *parent = flecs_expr_variable_from( script, &node->node, "#parent"); parent->sp = template->parent_sp; parent->node.type = ecs_id(ecs_entity_t); parent->node.type_info = ecs_get_type_info(script->world, parent->node.type); ecs_expr_element_t *ref = flecs_calloc_t( &flecs_script_impl(script)->allocator, ecs_expr_element_t); ref->node.kind = EcsExprComponent; ref->node.alloc_size = ECS_SIZEOF(ecs_expr_element_t); ref->node.pos = node->node.pos; ref->node.end = node->node.end; ref->node.type = component; ref->left = &parent->node; ecs_expr_value_node_t *index = flecs_expr_value_from( script, &node->node, ecs_id(ecs_entity_t)); index->storage.entity = component; ref->index = &index->node; node->expr = &flecs_expr_member_from(script, &ref->node, name)->node; if (flecs_expr_visit_type_priv(script, &node->expr, cur, desc)) { return -1; } node->node.type = node->expr->type; flecs_script_ref_ensure(&template->dynamic_refs, &(ecs_script_ref_t){ .name = "#parent", .component = component }); return 0; } static int flecs_expr_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc, bool swizzle_expand_allowed) { ecs_world_t *world = script->world; if (node->expr) { flecs_expr_visit_free(script, node->expr); node->expr = NULL; } node->symbol = -1; ecs_entity_t type = node->node.type; if (cur->valid) { type = ecs_meta_get_type(cur); } const EcsType *type_ptr = NULL; if (type) { type_ptr = ecs_get(world, type, EcsType); ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); } if (type_ptr && (type_ptr->kind == EcsEnumType || type_ptr->kind == EcsBitmaskType) && ecs_lookup_child(world, type, node->value) != 0) { /* If the requested type is an enum or bitmask, use the cursor to resolve * the identifier to the correct type constant. This lets us type 'Red' * in places where we expect a value of type Color, instead of * Color.Red. If the identifier doesn't resolve to a constant, fall back * to regular lookup so variables can be used in enum/bitmask * contexts. */ node->node.type = type; return flecs_expr_constant_identifier_visit_type(script, node); } flecs_script_symbol_t symbol; int lookup_result = flecs_script_symbol_lookup( script, desc, 0, node->value, FlecsScriptLookupAll, &symbol); if (!lookup_result && symbol.kind == FlecsScriptSymbolVariable) { ecs_script_var_t *var = ecs_script_vars_from_sp(desc->vars, symbol.sp); ecs_expr_variable_t *var_node = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, node->value); var_node->sp = symbol.sp; node->expr = (ecs_expr_node_t*)var_node; node->node.type = var->value.type; return flecs_expr_visit_type_priv(script, &node->expr, cur, desc); } int parent_result = flecs_expr_parent_member_visit_type( script, node, cur, desc); if (parent_result != 1) { return parent_result; } if (!lookup_result || !ecs_os_strcmp(node->value, "#0")) { ecs_entity_t e = symbol.entity; if (e == EcsAny) { flecs_expr_visit_error(script, node, "cannot use anonymous entity as value"); goto error; } bool is_slot = symbol.kind == FlecsScriptSymbolEntitySlot; ecs_value_t global = {0}; if (e && !is_slot) { global = flecs_script_global_var_get(world, e, NULL); } if (global.ptr) { ecs_expr_variable_t *var_node = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, node->value); node->expr = (ecs_expr_node_t*)var_node; node->node.type = global.type; return flecs_expr_visit_type_priv(script, &node->expr, cur, desc); } bool is_opaque = false; if (!type || type == ecs_id(ecs_value_t)) { type = ecs_id(ecs_entity_t); } else if (!flecs_expr_is_entity_type(world, type, &is_opaque)) { flecs_expr_visit_error(script, node, "cannot cast identifier '%s' to %s", node->value, flecs_errstr(ecs_get_path(world, type))); goto error; } node->node.type = type; if (is_slot) { node->symbol = symbol.slot; return 0; } ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); if (!is_opaque) { result->storage.entity = e; result->ptr = &result->storage.entity; } else { result->ptr = ecs_ptr_new_w_type_info( world, result->node.type_info); flecs_type_info_claim(result->node.type_info); ecs_meta_cursor_t expr_cur = ecs_meta_cursor( world, type, result->ptr); if (ecs_meta_set_entity(&expr_cur, e)) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } } node->expr = (ecs_expr_node_t*)result; return 0; } int var_member_result = flecs_expr_identifier_variable_member_visit_type( script, node, cur, desc, swizzle_expand_allowed); if (var_member_result != 1) { return var_member_result; } /* If unresolved identifiers aren't allowed here, throw error */ if (!desc->allow_unresolved_identifiers || flecs_expr_is_skipped_var(script, desc, node->value)) { if (!flecs_expr_unresolved_ref(script, desc, (ecs_expr_node_t*)node, node->value, FlecsScriptUnresolvedEntity)) { flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); } goto error; } /* Identifier will be resolved at eval time, default to entity */ node->node.type = ecs_id(ecs_entity_t); return 0; error: return -1; } static int flecs_expr_global_variable_resolve( ecs_script_t *script, ecs_expr_variable_t *node, const ecs_expr_eval_desc_t *desc) { ecs_world_t *world = script->world; flecs_script_symbol_t symbol; if (flecs_script_symbol_lookup(script, desc, 0, node->name, FlecsScriptLookupAll, &symbol) || symbol.kind != FlecsScriptSymbolGlobalVariable) { if (!flecs_expr_unresolved_ref(script, desc, (ecs_expr_node_t*)node, node->name, FlecsScriptUnresolvedVariable)) { flecs_expr_visit_error(script, node, "unresolved variable '%s'", node->name); } goto error; } ecs_entity_t global = symbol.entity; ecs_id_t component = 0; ecs_value_t value = flecs_script_global_var_get(world, global, &component); if (!value.ptr) { flecs_expr_visit_error(script, node, "entity '%s' is not a variable", node->name); goto error; } node->node.kind = EcsExprGlobalVariable; node->node.type = value.type; node->global = global; node->global_component = component; return 0; error: return -1; } static int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, const ecs_expr_eval_desc_t *desc) { flecs_script_symbol_t symbol; if (!flecs_script_symbol_lookup(script, desc, 0, node->name, FlecsScriptLookupVariable, &symbol)) { ecs_script_var_t *var = ecs_script_vars_from_sp( desc->vars, symbol.sp); node->sp = symbol.sp; node->node.type = var->value.type; if (!node->node.type) { flecs_expr_visit_error(script, node, "variable '%s' is not initialized", node->name); goto error; } } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; } } return 0; error: return -1; } static int flecs_expr_arguments_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_expr_eval_desc_t *desc, const struct ecs_script_function_t *func_data, ecs_entity_t *vector_type_out) { ecs_script_parameter_t *params = ecs_vec_first(&func_data->params); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); ecs_entity_t vector_type = 0; for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (!elem->value) { flecs_expr_visit_error(script, node, "missing value for function argument"); goto error; } ecs_entity_t argtype = params[i].type; if (argtype != EcsScriptVectorType) { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, argtype, NULL); if (flecs_expr_visit_type_priv(script, &elem->value, &cur, desc)){ goto error; } } else { ecs_meta_cursor_t cur; ecs_os_zeromem(&cur); if (flecs_expr_visit_type_priv(script, &elem->value, &cur, desc)){ goto error; } if (!vector_type) { vector_type = elem->value->type; } argtype = vector_type; /* Check if function provides implementation for type if argument * is of a primitive type. If it doesn't, try to cast. */ const EcsPrimitive *p = ecs_get( script->world, argtype, EcsPrimitive); if (p) { if (!func_data->vector_callbacks[p->kind]) { /* Fall back to types with max expressiveness */ if (func_data->vector_callbacks[EcsF64]) { vector_type = argtype = ecs_id(ecs_f64_t); } else if (func_data->vector_callbacks[EcsI64]) { vector_type = argtype = ecs_id(ecs_i64_t); } else if (func_data->vector_callbacks[EcsU64]) { vector_type = argtype = ecs_id(ecs_u64_t); } else { /* No matching implementation. Error will be caught * later in the code. */ } } } } if (flecs_expr_cast_to(script, &elem->value, argtype)) { goto error; } } *vector_type_out = vector_type; return 0; error: return -1; } static int flecs_expr_populate_vector_calldata( ecs_script_t *script, ecs_expr_function_t *node, const struct ecs_script_function_t *func_data, ecs_entity_t vector_type) { ecs_world_t *world = script->world; const EcsType *type = ecs_get(world, vector_type, EcsType); ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t elem_type = vector_type; int32_t count = 1; switch(type->kind) { case EcsPrimitiveType: break; case EcsStructType: count = flecs_script_get_vector_type_data( world, vector_type, &elem_type); if (!count) { flecs_expr_visit_error(script, node, "type '%s' cannot be passed to vector argument of " "function '%s': all its members must be of the same " "primitive type", flecs_errstr(ecs_get_path(world, vector_type)), node->function_name); return -1; } break; case EcsArrayType: { const EcsArray *array = ecs_get(world, vector_type, EcsArray); elem_type = array->type; count = array->count; break; } case EcsVectorType: elem_type = ecs_get(world, vector_type, EcsVector)->type; count = -1; break; default: flecs_expr_visit_error(script, node, "type '%s' cannot be passed to vector argument of function '%s'", flecs_errstr(ecs_get_path(world, vector_type)), node->function_name); return -1; } const EcsPrimitive *ptype = ecs_get(world, elem_type, EcsPrimitive); if (!ptype) { flecs_expr_visit_error(script, node, "type '%s' cannot be passed to vector argument of " "function '%s': (member) type '%s' is not a primitive type", flecs_errstr(ecs_get_path(world, vector_type)), node->function_name, flecs_errstr_1(ecs_get_path(world, elem_type))); return -1; } ecs_vector_function_callback_t cb = func_data->vector_callbacks[ptype->kind]; if (!cb) { flecs_expr_visit_error(script, node, "function '%s' does not implement " "matching vector operation for type '%s'", node->function_name, flecs_errstr(ecs_get_path(world, elem_type))); return -1; } node->calldata.is.vector_callback = cb; node->calldata.vector_elem_count = count; return 0; } const ecs_expr_variable_t* flecs_expr_template_ref_var( const ecs_expr_node_t *left) { if (!left || left->type != ecs_id(ecs_script_template_ref_t)) { return NULL; } if (left->kind == EcsExprIdentifier) { left = ((const ecs_expr_identifier_t*)left)->expr; } if (left && left->kind == EcsExprVariable) { return (const ecs_expr_variable_t*)left; } return NULL; } static int flecs_expr_template_visit_type( ecs_script_t *script, ecs_expr_function_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_world_t *world = script->world; ecs_entity_t tmpl = 0; const ecs_expr_variable_t *var = flecs_expr_template_ref_var(node->left); if (var) { ecs_script_eval_visitor_t *v = desc ? desc->script_visitor : NULL; tmpl = flecs_script_template_member_interface( v ? v->template : NULL, var->sp); } else { flecs_script_symbol_t symbol; if (flecs_script_symbol_lookup(script, desc, 0, node->function_name, FlecsScriptLookupEntity, &symbol) || !symbol.entity) { flecs_expr_visit_error(script, node, "unresolved template identifier '%s'", node->function_name); goto error; } tmpl = symbol.entity; const EcsScript *tmpl_script = ecs_get(world, tmpl, EcsScript); if (!tmpl_script || !tmpl_script->template_) { flecs_expr_visit_error(script, node, "'%s' is not a template", node->function_name); goto error; } } ecs_entity_t target = cur->valid ? ecs_meta_get_type(cur) : 0; bool is_ref = var || target == ecs_id(ecs_script_template_ref_t); const ecs_type_info_t *ti = tmpl ? ecs_get_type_info(world, tmpl) : NULL; node->node.kind = EcsExprTemplate; node->calldata.function = tmpl; node->node.type = is_ref ? ecs_id(ecs_script_template_ref_t) : tmpl; ecs_expr_initializer_t *args = node->args; ecs_assert(args != NULL, ECS_INTERNAL_ERROR, NULL); args->is_partial = true; int32_t i, count = ecs_vec_count(&args->elements); if (!count) { if (!is_ref && !ti) { flecs_expr_visit_error(script, node, "template '%s' has no props, cannot use as value", node->function_name); goto error; } args->node.type = tmpl; args->node.type_info = ti; return 0; } if (!tmpl) { flecs_expr_visit_error(script, node, "cannot pass arguments to template prop '%s' without interface", node->function_name); goto error; } const EcsStruct *st = ecs_get(world, tmpl, EcsStruct); int32_t member_count = st ? ecs_vec_count(&st->members) : 0; const ecs_member_t *members = st ? ecs_vec_first(&st->members) : NULL; int32_t positional = 0; ecs_expr_initializer_element_t *elems = ecs_vec_first(&args->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member || elem->key) { continue; } if (positional >= member_count) { flecs_expr_visit_error(script, node, "too many arguments for template '%s'", node->function_name); goto error; } elem->member = members[positional ++].name; } ecs_meta_cursor_t args_cur = ecs_meta_cursor(world, tmpl, NULL); if (flecs_expr_visit_type_priv( script, (ecs_expr_node_t**)&node->args, &args_cur, desc)) { goto error; } ecs_assert(node->args->node.type == tmpl, ECS_INTERNAL_ERROR, NULL); return 0; error: return -1; } static int flecs_expr_function_visit_type( ecs_script_t *script, ecs_expr_function_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { bool is_method = false; bool is_split = false; char *last_elem = NULL; const char *func_identifier = NULL; ecs_expr_member_t *replaced_member = NULL; if (node->left->kind == EcsExprIdentifier) { /* If identifier contains '.' separator(s), this is a method call, * otherwise it's a regular function. */ ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; func_identifier = ident->value; last_elem = strrchr(func_identifier, '.'); if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { node->function_name = last_elem + 1; last_elem[0] = '\0'; is_method = true; is_split = true; } else { node->function_name = ident->value; } } else if (node->left->kind == EcsExprMember) { /* This is a method. Just like identifiers, method strings can contain * separators. Split off last separator to get the method. */ ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; last_elem = strrchr(member->member_name, '.'); if (!last_elem) { node->left = member->left; node->function_name = member->member_name; replaced_member = member; } else { node->function_name = last_elem + 1; last_elem[0] = '\0'; is_split = true; } is_method = true; } /* Left of function expression should not inherit lvalue type, since the * function return type is what's going to be assigned. */ ecs_meta_cursor_t target_cur = *cur; ecs_os_zeromem(cur); if (flecs_expr_visit_type_priv(script, &node->left, cur, desc)) { goto error; } if (!is_method && flecs_expr_template_ref_var(node->left)) { return flecs_expr_template_visit_type( script, node, &target_cur, desc); } ecs_world_t *world = script->world; const struct ecs_script_function_t *func_data = NULL; ecs_entity_t func = 0; /* If this is a method, lookup function entity in scope of type */ if (is_method) { flecs_script_symbol_t symbol; if (!flecs_script_symbol_lookup(script, desc, node->left->type, node->function_name, FlecsScriptLookupEntity | FlecsScriptLookupDynamic, &symbol)) { func = symbol.entity; } if (!func) { const EcsType *type = ecs_get(world, node->left->type, EcsType); if (type && (type->kind == EcsArrayType || type->kind == EcsVectorType || type->kind == EcsMapType)) { ecs_entity_t collection = ecs_lookup( world, "flecs.script.collection"); func = ecs_lookup_child(world, collection, node->function_name); } } if (!func) { /* If identifier could be a function (not a method), try that */ if (func_identifier) { is_method = false; is_split = false; last_elem[0] = '.'; node->function_name = func_identifier; goto try_function; } if (flecs_expr_skip_unknown_unresolved_ref(script, desc, (ecs_expr_node_t*)node, node->function_name, FlecsScriptUnresolvedEntity)) { goto error; } flecs_expr_visit_error(script, node, "unresolved method identifier '%s' for type '%s'", node->function_name, flecs_errstr(ecs_get_path(world, node->left->type))); goto error; } func_data = ecs_get(world, func, EcsScriptMethod); node->node.kind = EcsExprMethod; } try_function: if (!is_method) { flecs_script_symbol_t symbol; if (flecs_script_symbol_lookup(script, desc, 0, node->function_name, FlecsScriptLookupEntity, &symbol) || !symbol.entity) { if (!flecs_expr_skip_unknown_unresolved_ref(script, desc, (ecs_expr_node_t*)node, node->function_name, FlecsScriptUnresolvedEntity)) { flecs_expr_visit_error(script, node, "unresolved function identifier '%s'", node->function_name); } goto error; } func = symbol.entity; const EcsScript *tmpl_script = ecs_get(world, func, EcsScript); if (tmpl_script && tmpl_script->template_) { return flecs_expr_template_visit_type( script, node, &target_cur, desc); } func_data = ecs_get(world, func, EcsScriptFunction); } if (!func_data) { flecs_expr_visit_error(script, node, "entity '%s' is not a valid method", flecs_errstr(ecs_get_path(world, func))); goto error; } node->node.type = func_data->return_type; node->calldata.function = func; node->calldata.is.callback = func_data->callback; #ifdef FLECS_SCRIPT_ASYNC node->calldata.async_callback = func_data->async_callback; node->calldata.async_cancel = func_data->async_cancel; #endif node->calldata.ctx = func_data->ctx; node->calldata.vector_elem_count = 0; int32_t count = ecs_vec_count(&node->args->elements); if (count != ecs_vec_count(&func_data->params)) { flecs_expr_visit_error(script, node, "expected %d arguments, got %d", ecs_vec_count(&func_data->params), count); goto error; } ecs_entity_t vector_type = 0; if (flecs_expr_arguments_visit_type( script, node->args, desc, func_data, &vector_type)) { goto error; } if (vector_type) { if (node->node.type == EcsScriptVectorType) { node->node.type = vector_type; } if (flecs_expr_populate_vector_calldata( script, node, func_data, vector_type)) { goto error; } } else { ecs_assert(node->node.type != EcsScriptVectorType, ECS_INTERNAL_ERROR, NULL); } if (replaced_member) { replaced_member->left = NULL; /* Prevent cleanup */ flecs_expr_visit_free(script, (ecs_expr_node_t*)replaced_member); } return 0; error: /* Undo mutations so the expression can be visited again, which can happen * when a script is evaluated multiple times. */ if (is_split) { last_elem[0] = '.'; } if (replaced_member) { node->left = (ecs_expr_node_t*)replaced_member; node->function_name = NULL; } if (node->node.kind == EcsExprMethod) { node->node.kind = EcsExprFunction; } return -1; } static int flecs_expr_member_try_swizzle( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_entity_t left_type, ecs_entity_t target_type, const uint16_t *expand_dst) { ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; ecs_world_t *world = script->world; ecs_entity_t elem_type = 0; int32_t i, member_count = flecs_script_get_vector_type_data( world, left_type, &elem_type); if (!member_count) { return 1; } const EcsStruct *stype = ecs_get(world, left_type, EcsStruct); ecs_member_t *members = ecs_vec_first(&stype->members); for (i = 0; i < member_count; i ++) { if (!members[i].name[0] || members[i].name[1]) { return 1; } } const char *name = node->member_name; int32_t swizzle_count = ecs_os_strlen(name); if (!swizzle_count || swizzle_count > FLECS_EXPR_SWIZZLE_MAX) { return 1; } uint16_t offsets[FLECS_EXPR_SWIZZLE_MAX]; for (i = 0; i < swizzle_count; i ++) { int32_t m; for (m = 0; m < member_count; m ++) { if (members[m].name[0] == name[i]) { break; } } if (m == member_count) { return 1; } offsets[i] = flecs_ito(uint16_t, members[m].offset); } ecs_entity_t result_type = 0; bool expand = false; if (target_type) { ecs_entity_t target_elem_type = 0; int32_t target_count = flecs_script_get_vector_type_data( world, target_type, &target_elem_type); if (target_count == swizzle_count && target_elem_type == elem_type) { result_type = target_type; } else if (expand_dst && elem_type == target_type) { result_type = target_type; expand = true; } else { flecs_expr_visit_error(script, node, "swizzle '%s' of type '%s' is incompatible with type '%s'", name, flecs_errstr(ecs_get_path(world, left_type)), flecs_errstr_1(ecs_get_path(world, target_type))); return -1; } } else { if (swizzle_count != member_count) { flecs_expr_visit_error(script, node, "cannot determine type for swizzle '%s'", name); return -1; } result_type = left_type; } const ecs_type_info_t *eti = ecs_get_type_info(world, elem_type); ecs_assert(eti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_expr_swizzle_t *swizzle = flecs_expr_swizzle_from( script, (ecs_expr_node_t*)node, node->left, name); swizzle->count = swizzle_count; swizzle->elem_size = eti->size; ecs_os_memcpy_n(swizzle->src, offsets, uint16_t, swizzle_count); swizzle->node.type = result_type; if (expand) { ecs_os_memcpy_n(swizzle->dst, expand_dst, uint16_t, swizzle_count); swizzle->expand = true; } node->left = NULL; flecs_expr_visit_free(script, (ecs_expr_node_t*)node); *node_ptr = (ecs_expr_node_t*)swizzle; return 0; } static int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc, bool swizzle_expand_allowed) { ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; ecs_entity_t target_type = 0; if (cur && cur->valid) { target_type = ecs_meta_get_type(cur); } uint16_t expand_dst[FLECS_EXPR_SWIZZLE_MAX]; bool can_expand = false; if (swizzle_expand_allowed && cur && cur->valid) { can_expand = flecs_expr_member_swizzle_expand_hint( cur, node->member_name, expand_dst); } if (flecs_expr_visit_type_priv(script, &node->left, cur, desc)) { goto error; } ecs_world_t *world = script->world; ecs_entity_t left_type = node->left->type; if (left_type == ecs_id(ecs_script_template_ref_t)) { const ecs_expr_variable_t *var = flecs_expr_template_ref_var( node->left); ecs_script_eval_visitor_t *v = desc ? desc->script_visitor : NULL; ecs_entity_t interface = var ? flecs_script_template_member_interface( v ? v->template : NULL, var->sp) : 0; if (!interface || !ecs_has(world, interface, EcsStruct)) { flecs_expr_visit_error(script, node, "cannot resolve member '%s' on template prop without " "interface", node->member_name); goto error; } if (flecs_expr_cast_to(script, &node->left, interface)) { goto error; } left_type = interface; *cur = ecs_meta_cursor(world, interface, NULL); } const EcsType *type = ecs_get(world, left_type, EcsType); if (!type) { flecs_expr_visit_error(script, node, "cannot resolve member on value of type '%s' " "(missing reflection data)", flecs_errstr(ecs_get_path(world, left_type))); goto error; } if (type->kind != EcsStructType) { flecs_expr_visit_error(script, node, "cannot resolve member on non-struct type '%s'", flecs_errstr(ecs_get_path(world, left_type))); goto error; } if (ecs_meta_push(cur)) { goto error; } int prev_log = ecs_log_set_level(-4); if (ecs_meta_dotmember(cur, node->member_name)) { ecs_log_set_level(prev_log); int swizzle = flecs_expr_member_try_swizzle( script, node_ptr, left_type, target_type, can_expand ? expand_dst : NULL); if (swizzle <= 0) { return swizzle; } if (flecs_expr_skip_unknown_unresolved_ref(script, desc, (ecs_expr_node_t*)node, node->member_name, FlecsScriptUnresolvedEntity)) { goto error; } flecs_expr_visit_error(script, node, "unresolved member '%s' for type '%s'", node->member_name, flecs_errstr(ecs_get_path(world, left_type))); goto error; } ecs_log_set_level(prev_log); node->node.type = ecs_meta_get_type(cur); node->offset = (uintptr_t)ecs_meta_get_ptr(cur); /* Members with an inline array count ("values: i32[3]") report the element * type. Expose the member as an array type so the value can be used as a * collection. */ const EcsType *member_type = ecs_get(world, node->node.type, EcsType); if (!member_type || (member_type->kind != EcsArrayType && member_type->kind != EcsVectorType && member_type->kind != EcsMapType)) { int32_t elem_count = flecs_expr_inline_elem_count(cur); if (elem_count > 0) { ecs_entity_t array_type = flecs_script_array_type( world, node->node.type, elem_count); if (!array_type) { goto error; } node->node.type = array_type; } } return 0; error: return -1; } static int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_world_t *world = script->world; /* Check if this is a component expression */ if (node->index->kind == EcsExprIdentifier) { /* Fetch type of left side of expression to check if it's of an entity * type. Pass in an empty cursor object so we don't fail type checks in * case it's a regular element expression. */ ecs_meta_cursor_t tmp_cur = {0}; if (flecs_expr_visit_type_priv(script, &node->left, &tmp_cur, desc)) { goto error; } ecs_entity_t left_type = node->left->type; if (!ecs_has(world, left_type, EcsType)) { flecs_expr_visit_error(script, node, "cannot use [] on value of type '%s' (missing reflection data)", flecs_errstr(ecs_get_path(world, left_type))); goto error; } const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); if (ptype && ptype->kind == EcsEntity) { ecs_meta_cursor_t index_cur = {0}; if (flecs_expr_visit_type_priv( script, &node->index, &index_cur, desc)) { goto error; } ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->index; flecs_script_symbol_t symbol; if (flecs_script_symbol_lookup(script, desc, 0, ident->value, FlecsScriptLookupEntity, &symbol) || !symbol.entity) { if (!flecs_expr_skip_unknown_unresolved_ref(script, desc, (ecs_expr_node_t*)node, ident->value, FlecsScriptUnresolvedEntity)) { flecs_expr_visit_error(script, node, "unresolved component identifier '%s'", ident->value); } goto error; } node->node.type = symbol.entity; if (!ecs_get_type_info(world, node->node.type)) { if (flecs_expr_skip_unknown_unresolved_ref(script, desc, (ecs_expr_node_t*)node, ident->value, FlecsScriptUnresolvedEntity)) { goto error; } flecs_expr_visit_error(script, node, "cannot use [] with component '%s' " "(missing reflection data)", flecs_errstr(ecs_get_path(world, node->node.type))); goto error; } node->node.kind = EcsExprComponent; flecs_expr_unresolved_component_ref(script, desc, &node->node, node->left, node->node.type, false); return 0; } } if (flecs_expr_visit_type_priv(script, &node->left, cur, desc)) { goto error; } if (!cur->valid) { goto not_a_collection; } if (ecs_meta_push(cur)) { goto not_a_collection; } bool is_map = ecs_meta_is_map(cur); if (!is_map && !ecs_meta_is_collection(cur)) { goto not_a_collection; } ecs_entity_t index_type = ecs_id(ecs_i64_t); ecs_meta_cursor_t index_cur = {0}; if (is_map) { const EcsMap *map_type = ecs_get(world, node->left->type, EcsMap); ecs_assert(map_type != NULL, ECS_INTERNAL_ERROR, NULL); index_type = map_type->key_type; index_cur = ecs_meta_cursor(world, index_type, NULL); } if (flecs_expr_visit_type_priv(script, &node->index, &index_cur, desc) || flecs_expr_cast_to(script, &node->index, index_type)) { goto error; } node->node.type = ecs_meta_get_type(cur); node->elem_size = ecs_get_type_info(world, node->node.type)->size; node->elem_count = is_map ? 0 : cur->scope[cur->depth - 1].elem_count; return 0; not_a_collection: flecs_expr_visit_error(script, node, "invalid usage of [] on non collection/entity type '%s'", flecs_errstr(ecs_get_path(world, node->left->type))); error: return -1; } static int flecs_expr_has_id_elem_name( ecs_script_t *script, ecs_expr_has_t *node, ecs_expr_node_t *elem, const char **name) { if (elem->kind != EcsExprIdentifier) { flecs_expr_visit_error(script, node, "expected component identifier in ?[] expression"); return -1; } *name = ((ecs_expr_identifier_t*)elem)->value; return 0; } static int flecs_expr_has_visit_type( ecs_script_t *script, ecs_expr_has_t *node, const ecs_expr_eval_desc_t *desc) { const char *first_name = NULL, *second_name = NULL; if (flecs_expr_has_id_elem_name(script, node, node->first, &first_name)) { goto error; } if (node->second && flecs_expr_has_id_elem_name( script, node, node->second, &second_name)) { goto error; } ecs_entity_t first = 0; const char *unresolved = NULL; if (flecs_script_id_lookup(script, desc, first_name, second_name, FlecsScriptLookupEntity, &first, &node->id, &unresolved)) { if (!flecs_expr_unresolved_ref(script, desc, (ecs_expr_node_t*)node, unresolved, FlecsScriptUnresolvedEntity)) { flecs_expr_visit_error(script, node, "unresolved component identifier '%s'", unresolved); } goto error; } if (node->left->kind == EcsExprVariable && !((ecs_expr_variable_t*)node->left)->name[0]) { /* Singleton expression. Replace the $ with the entity that stores the * singleton component, which is the first element of the id. */ flecs_expr_visit_free(script, node->left); ecs_expr_value_node_t *left = flecs_expr_value_from( script, (ecs_expr_node_t*)node, ecs_id(ecs_entity_t)); left->storage.entity = first; left->ptr = &left->storage.entity; node->left = (ecs_expr_node_t*)left; } else { ecs_meta_cursor_t left_cur = {0}; if (flecs_expr_visit_type_priv(script, &node->left, &left_cur, desc)) { goto error; } bool is_opaque = false; if (!flecs_expr_is_entity_type( script->world, node->left->type, &is_opaque) || is_opaque) { flecs_expr_visit_error(script, node, "invalid usage of ?[] on non-entity type '%s'", flecs_errstr(ecs_get_path(script->world, node->left->type))); goto error; } } flecs_expr_unresolved_component_ref(script, desc, &node->node, node->left, node->id, true); node->node.type = ecs_id(ecs_bool_t); return 0; error: return -1; } static bool flecs_expr_identifier_is_any( ecs_expr_node_t *node) { if (node->kind == EcsExprIdentifier) { ecs_expr_identifier_t *id = (ecs_expr_identifier_t*)node; if (id->value && !ecs_os_strcmp(id->value, "_")) { return true; } } return false; } static int flecs_expr_match_visit_type( ecs_script_t *script, ecs_expr_match_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); ecs_meta_cursor_t expr_cur; ecs_os_zeromem(&expr_cur); if (flecs_expr_visit_type_priv(script, &node->expr, &expr_cur, desc)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); if (!count) { flecs_expr_visit_error(script, node, "match statement must have at least one case"); goto error; } /* Determine most expressive type of all elements */ node->node.type = ecs_meta_get_type(cur); for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (node->node.type) { expr_cur = ecs_meta_cursor(script->world, node->node.type, NULL); } else { ecs_os_zeromem(&expr_cur); } if (flecs_expr_visit_type_priv(script, &elem->expr, &expr_cur, desc)) { goto error; } if (flecs_expr_unify_type(script, &node->node, elem->expr, true, "case", i, "match")) { goto error; } } /* Loop over elements again, cast values to result type */ for (i = 0; i < count; i ++) { if (flecs_expr_cast_to(script, &elems[i].expr, node->node.type)) { goto error; } } /* If this is an enum type, cast to the underlying type. This is necessary * because the compare operation executed by the match evaluation code isn't * implemented for enums. Bitmasks are always cast to u32. */ ecs_entity_t expr_type = node->expr->type; const EcsEnum *enum_type = ecs_get(script->world, expr_type, EcsEnum); ecs_entity_t compare_type = expr_type; if (enum_type) { compare_type = enum_type->underlying_type; } else if (ecs_has(script->world, expr_type, EcsBitmask)) { compare_type = ecs_id(ecs_u32_t); } if (flecs_expr_cast_to(script, &node->expr, compare_type)) { goto error; } /* Make sure that case values match the input type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (flecs_expr_identifier_is_any(elem->compare)) { if (i != count - 1) { flecs_expr_visit_error(script, node, "any (_) must be the last case in match"); goto error; } node->any.compare = elem->compare; node->any.expr = elem->expr; ecs_vec_remove_last(&node->elements); } else { expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); if (flecs_expr_visit_type_priv( script, &elem->compare, &expr_cur, desc)) { goto error; } if (flecs_expr_cast_to( script, &elem->compare, node->expr->type)) { goto error; } } } return 0; error: return -1; } static int flecs_expr_range_visit_type( ecs_script_t *script, ecs_expr_range_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_expr_node_t **bounds[2] = { &node->from, &node->to }; int32_t i; for (i = 0; i < 2; i ++) { ecs_meta_cursor_t bound_cur = ecs_meta_cursor( script->world, ecs_id(ecs_i32_t), NULL); if (flecs_expr_visit_type_priv(script, bounds[i], &bound_cur, desc) || flecs_expr_cast_to(script, bounds[i], ecs_id(ecs_i32_t))) { goto error; } } ecs_entity_t type = 0; if (cur && cur->valid) { type = ecs_meta_get_type(cur); } if (type && type != ecs_id(ecs_value_t)) { const EcsType *ptr = ecs_get(script->world, type, EcsType); if (!ptr || ptr->kind != EcsVectorType) { flecs_expr_visit_error(script, node, "cannot assign range to non-vector type '%s'", flecs_errstr(ecs_get_path(script->world, type))); goto error; } node->node.type = type; } else { node->node.type = flecs_script_vector_type( script->world, ecs_id(ecs_i32_t)); if (!node->node.type) { flecs_expr_visit_error(script, node, "failed to create vector type for range"); goto error; } } return 0; error: return -1; } static int flecs_expr_new_visit_type( ecs_script_t *script, ecs_expr_new_t *node, const ecs_expr_eval_desc_t *desc) { ecs_script_eval_visitor_t *v = desc->script_visitor; if (flecs_script_visit_type_entity_expr( script, desc, v, node->entity)) { return -1; } node->node.type = ecs_id(ecs_entity_t); return 0; } static int flecs_expr_visit_type_ex( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc, bool swizzle_expand_allowed) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); int r = 0; switch(node->kind) { case EcsExprValue: break; case EcsExprInterpolatedString: r = flecs_expr_interpolated_string_visit_type( script, (ecs_expr_interpolated_string_t*)node, cur, desc); break; case EcsExprEmptyInitializer: r = flecs_expr_empty_initializer_visit_type( script, (ecs_expr_initializer_t*)node, cur); break; case EcsExprInitializer: r = flecs_expr_initializer_visit_type(script, node_ptr, cur, desc); break; case EcsExprUnary: r = flecs_expr_unary_visit_type( script, (ecs_expr_unary_t*)node, cur, desc); break; case EcsExprBinary: r = flecs_expr_binary_visit_type( script, (ecs_expr_binary_t*)node, cur, desc); break; case EcsExprIdentifier: r = flecs_expr_identifier_visit_type( script, (ecs_expr_identifier_t*)node, cur, desc, swizzle_expand_allowed); break; case EcsExprVariable: r = flecs_expr_variable_visit_type( script, (ecs_expr_variable_t*)node, desc); break; case EcsExprGlobalVariable: r = flecs_expr_global_variable_resolve( script, (ecs_expr_variable_t*)node, desc); break; case EcsExprFunction: r = flecs_expr_function_visit_type( script, (ecs_expr_function_t*)node, cur, desc); break; case EcsExprTemplate: r = flecs_expr_template_visit_type( script, (ecs_expr_function_t*)node, cur, desc); break; case EcsExprMember: r = flecs_expr_member_visit_type( script, node_ptr, cur, desc, swizzle_expand_allowed); break; case EcsExprElement: r = flecs_expr_element_visit_type( script, (ecs_expr_element_t*)node, cur, desc); break; case EcsExprHas: r = flecs_expr_has_visit_type(script, (ecs_expr_has_t*)node, desc); break; case EcsExprMatch: r = flecs_expr_match_visit_type( script, (ecs_expr_match_t*)node, cur, desc); break; case EcsExprRange: r = flecs_expr_range_visit_type( script, (ecs_expr_range_t*)node, cur, desc); break; case EcsExprNew: r = flecs_expr_new_visit_type(script, (ecs_expr_new_t*)node, desc); break; case EcsExprScript: node->type = ecs_id(ecs_entity_t); break; case EcsExprCast: case EcsExprCastNumber: case EcsExprSwizzle: case EcsExprMethod: case EcsExprComponent: break; } if (r) { return -1; } node = *node_ptr; ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); node->type_info = ecs_get_type_info(script->world, node->type); ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); /* Every node produces a value of its own type. Rebase the cursor on that * type so that members and elements resolved on the value get an offset * that is relative to the value. */ flecs_expr_visit_type_cursor(script->world, cur, node->type); return 0; } int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_node_t *node = *node_ptr; if (node->kind == EcsExprEmptyInitializer) { node->type = desc->type; if (node->type) { ecs_expr_initializer_t* init_node = (ecs_expr_initializer_t*)node; init_node->is_dynamic = flecs_expr_initializer_is_dynamic( script->world, node->type); if (flecs_expr_initializer_collection_check( script, init_node, NULL, node->type)) { return -1; } node->type_info = ecs_get_type_info(script->world, node->type); return 0; } } ecs_meta_cursor_t cur = {0}; if (desc->type) { cur = ecs_meta_cursor(script->world, desc->type, NULL); } return flecs_expr_visit_type_priv(script, node_ptr, &cur, desc); } #endif #ifdef FLECS_SCRIPT typedef struct ecs_script_ir_compiler_t { ecs_script_impl_t *script; ecs_world_t *world; ecs_script_ir_t *ir; ecs_script_scope_t *scope; int32_t entry; int32_t reg_count; int32_t reg_floor; int32_t reg_max; int32_t force_depth; } ecs_script_ir_compiler_t; static void flecs_irc_compile_expr( ecs_script_ir_compiler_t *c, ecs_expr_node_t *node, int32_t dst, bool in_place); static void flecs_irc_compile_scope( ecs_script_ir_compiler_t *c, ecs_script_scope_t *scope, uint16_t flags); static int32_t flecs_irc_emit( ecs_script_ir_compiler_t *c, ecs_script_ir_op_kind_t kind, int32_t a, int32_t b, int32_t cc, const void *node) { ecs_script_ir_op_t *op = ecs_vec_append_t( NULL, &c->ir->ops, ecs_script_ir_op_t); op->kind = (uint16_t)kind; op->flags = 0; op->a = a; op->b = b; op->c = cc; op->imm.u64 = 0; op->node = node; return ecs_vec_count(&c->ir->ops) - 1; } static ecs_script_ir_op_t* flecs_irc_op( ecs_script_ir_compiler_t *c, int32_t index) { return ecs_vec_get_t(&c->ir->ops, ecs_script_ir_op_t, index); } static int32_t flecs_irc_pc( ecs_script_ir_compiler_t *c) { return ecs_vec_count(&c->ir->ops); } static int32_t flecs_irc_reg( ecs_script_ir_compiler_t *c) { int32_t reg = c->reg_count ++; if (c->reg_count > c->reg_max) { c->reg_max = c->reg_count; } return reg; } static int32_t flecs_irc_entry_add( ecs_script_ir_compiler_t *c, const void *node, ecs_script_ir_entry_kind_t kind) { const uint64_t *existing = ecs_map_get( &c->ir->entry_index, (uintptr_t)node); if (existing) { return (int32_t)*existing; } int32_t count = ecs_vec_count(&c->ir->entries); ecs_script_ir_entry_t *entry = ecs_vec_append_t( NULL, &c->ir->entries, ecs_script_ir_entry_t); entry->node = node; entry->kind = kind; entry->pc = -1; entry->reg_count = 0; ecs_map_insert(&c->ir->entry_index, (uintptr_t)node, (uint64_t)count); return count; } static int32_t flecs_irc_compile_tmp( ecs_script_ir_compiler_t *c, ecs_expr_node_t *node) { if (!node) { return -1; } int32_t reg = flecs_irc_reg(c); flecs_irc_compile_expr(c, node, reg, false); return reg; } static int32_t flecs_irc_id( ecs_script_ir_compiler_t *c, ecs_script_id_t *id, bool is_component, int32_t component_slot) { int32_t index_reg = flecs_irc_compile_tmp(c, id->index_expr); int32_t first_reg = flecs_irc_compile_tmp(c, id->first_expr); int32_t second_reg = flecs_irc_compile_tmp(c, id->second_expr); ecs_script_ir_id_t *desc = ecs_vec_append_t( NULL, &c->ir->ids, ecs_script_ir_id_t); ecs_os_zeromem(desc); desc->id = id; desc->eval = id->eval; desc->flag = id->flag; desc->first_eval = id->first_eval; desc->second_eval = id->second_eval; desc->first = id->first; desc->second = id->second; desc->first_symbol = id->first_symbol; desc->second_symbol = id->second_symbol; desc->first_sp = id->first_sp; desc->second_sp = id->second_sp; desc->first_reg = first_reg; desc->second_reg = second_reg; desc->index_reg = index_reg; desc->component_slot = component_slot; desc->resolved = id->eval != 0; desc->has_second = id->second != NULL; desc->dynamic = id->first_expr || id->second_expr || id->index_expr || id->first_symbol != -1 || id->second_symbol != -1 || id->first_sp != -1 || id->second_sp != -1 || !id->eval; if (!desc->dynamic && id->eval && is_component) { ecs_component_record_t *cr = flecs_components_ensure( c->world, id->eval); if (cr && cr->type_info) { desc->ti = cr->type_info; desc->needs_set = desc->ti->hooks.on_replace != NULL; } if (!desc->has_second) { desc->visitor = ecs_has(c->world, id->eval, EcsScriptVisitor); } } return ecs_vec_count(&c->ir->ids) - 1; } static bool flecs_irc_id_needs_expr( const ecs_script_id_t *id) { return id->first_expr != NULL || id->second_expr != NULL || id->index_expr != NULL; } static void flecs_irc_compile_initializer( ecs_script_ir_compiler_t *c, ecs_expr_initializer_t *node, int32_t dst); static void flecs_irc_compile_dynamic_initializer( ecs_script_ir_compiler_t *c, ecs_expr_initializer_t *node, int32_t dst) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); flecs_irc_emit(c, EcsIrDynPush, dst, 0, 0, node); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (i) { flecs_irc_emit(c, EcsIrDynNext, dst, 0, 0, node); } if (elem->key) { int32_t key = flecs_irc_compile_tmp(c, elem->key); flecs_irc_emit(c, EcsIrDynKey, key, 0, 0, node); } else if (elem->member) { int32_t op = flecs_irc_emit(c, EcsIrDynMember, 0, 0, 0, node); flecs_irc_op(c, op)->imm.str = elem->member; } if (elem->value->kind == EcsExprInitializer) { flecs_irc_compile_dynamic_initializer( c, (ecs_expr_initializer_t*)elem->value, dst); continue; } ecs_expr_swizzle_t *swizzle = flecs_expr_expand_swizzle_get( elem->value); if (swizzle) { int32_t left = flecs_irc_compile_tmp(c, swizzle->left); int32_t op = flecs_irc_emit(c, EcsIrDynSwizzle, left, 0, 0, node); flecs_irc_op(c, op)->imm.ptr = swizzle; continue; } int32_t value = flecs_irc_compile_tmp(c, elem->value); flecs_irc_emit(c, EcsIrDynSet, value, 0, 0, node); } flecs_irc_emit(c, EcsIrDynPop, dst, 0, 0, node); } static void flecs_irc_compile_static_initializer( ecs_script_ir_compiler_t *c, ecs_expr_initializer_t *node, int32_t dst) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_assert(elem->value != NULL, ECS_INTERNAL_ERROR, NULL); int32_t offset = flecs_uto(int32_t, elem->offset); if (elem->value->kind == EcsExprInitializer) { ecs_expr_initializer_t *inner = (ecs_expr_initializer_t*)elem->value; if (inner->is_dynamic) { int32_t member = flecs_irc_reg(c); flecs_irc_emit(c, EcsIrMember, member, dst, offset, inner); flecs_irc_compile_initializer(c, inner, member); } else { flecs_irc_compile_static_initializer(c, inner, dst); } continue; } ecs_expr_swizzle_t *swizzle = flecs_expr_expand_swizzle_get( elem->value); if (swizzle) { int32_t left = flecs_irc_compile_tmp(c, swizzle->left); int32_t op = flecs_irc_emit( c, EcsIrInitSwizzle, dst, offset, left, node); flecs_irc_op(c, op)->imm.ptr = swizzle; continue; } if (!elem->operator && elem->value->kind == EcsExprValue) { ecs_expr_value_node_t *value = (ecs_expr_value_node_t*)elem->value; int32_t op = flecs_irc_emit( c, EcsIrInitConst, dst, offset, 0, elem->value); flecs_irc_op(c, op)->imm.ptr = value->ptr; continue; } int32_t value = flecs_irc_compile_tmp(c, elem->value); int32_t op = flecs_irc_emit(c, elem->operator ? EcsIrInitOp : EcsIrInitAssign, dst, offset, value, elem->value); flecs_irc_op(c, op)->flags = (uint16_t)elem->operator; flecs_irc_op(c, op)->imm.ptr = node; } } static void flecs_irc_compile_initializer( ecs_script_ir_compiler_t *c, ecs_expr_initializer_t *node, int32_t dst) { if (!node->is_dynamic) { flecs_irc_compile_static_initializer(c, node, dst); return; } flecs_irc_emit(c, EcsIrDynBegin, dst, 0, 0, node); flecs_irc_compile_dynamic_initializer(c, node, dst); flecs_irc_emit(c, EcsIrDynEnd, dst, 0, 0, node); } static void flecs_irc_compile_interpolated_string( ecs_script_ir_compiler_t *c, ecs_expr_interpolated_string_t *node, int32_t dst) { flecs_irc_emit(c, EcsIrStrBegin, dst, 0, 0, node); int32_t i, count = ecs_vec_count(&node->fragments); ecs_expr_fragment_t *fragments = ecs_vec_first(&node->fragments); for (i = 0; i < count; i ++) { char *fragment = fragments[i].text; if (fragment) { int32_t op = flecs_irc_emit(c, EcsIrStrFrag, 0, 0, 0, node); flecs_irc_op(c, op)->imm.str = fragment; } if (!fragments[i].expr) { continue; } ecs_expr_node_t *expr = fragments[i].expr; ecs_expr_format_t *format = &fragments[i].format; if (!format->is_present && expr->kind == EcsExprCast && expr->type == ecs_id(ecs_string_t)) { ecs_expr_node_t *inner = ((ecs_expr_cast_t*)expr)->expr; int32_t size; flecs_expr_num_class_t cls = inner->type == ecs_id(ecs_char_t) ? EcsExprNumChar : flecs_expr_num_class(inner->type, &size); if (cls) { int32_t src = flecs_irc_compile_tmp(c, inner); int32_t op = flecs_irc_emit(c, EcsIrStrCast, src, 0, 0, inner); flecs_irc_op(c, op)->flags = (uint16_t)cls; if (cls != EcsExprNumChar) { flecs_irc_op(c, op)->imm.cast = flecs_expr_cast_handler( inner->type, cls == EcsExprNumSigned ? ecs_id(ecs_i64_t) : cls == EcsExprNumUnsigned ? ecs_id(ecs_u64_t) : ecs_id(ecs_f64_t)); } continue; } } int32_t value = flecs_irc_compile_tmp(c, expr); if (format->is_present) { int32_t width = flecs_irc_compile_tmp(c, format->width); int32_t precision = flecs_irc_compile_tmp(c, format->precision); int32_t op = flecs_irc_emit( c, EcsIrStrFormat, value, width, precision, expr); flecs_irc_op(c, op)->imm.ptr = format; } else { flecs_irc_emit(c, EcsIrStrValue, value, 0, 0, expr); } } flecs_irc_emit(c, EcsIrStrEnd, dst, 0, 0, node); } static int32_t flecs_irc_compile_args( ecs_script_ir_compiler_t *c, ecs_expr_function_t *node, int32_t *count) { int32_t method = node->node.kind == EcsExprMethod; int32_t i, argc = ecs_vec_count(&node->args->elements); int32_t first = c->reg_count; *count = argc + method; for (i = 0; i < *count; i ++) { flecs_irc_reg(c); } if (method) { flecs_irc_compile_expr(c, node->left, first, false); } ecs_expr_initializer_element_t *elems = ecs_vec_first( &node->args->elements); for (i = 0; i < argc; i ++) { flecs_irc_compile_expr(c, elems[i].value, first + method + i, false); } return first; } static void flecs_irc_compile_call( ecs_script_ir_compiler_t *c, ecs_expr_function_t *node, int32_t dst, bool in_place) { int32_t count, first = flecs_irc_compile_args(c, node, &count); int32_t op = flecs_irc_emit(c, EcsIrCall, dst, first, count, node); flecs_irc_op(c, op)->flags = (uint16_t)((in_place ? EcsIrInPlace : 0) | (node->node.kind == EcsExprMethod ? EcsIrCallMethod : 0)); } static void flecs_irc_patch_jumps( ecs_script_ir_compiler_t *c, ecs_vec_t *jumps) { int32_t i, end = flecs_irc_pc(c); int32_t *jump_ops = ecs_vec_first(jumps); for (i = 0; i < ecs_vec_count(jumps); i ++) { flecs_irc_op(c, jump_ops[i])->a = end; } ecs_vec_fini_t(NULL, jumps, int32_t); } static void flecs_irc_compile_match( ecs_script_ir_compiler_t *c, ecs_expr_match_t *node, int32_t dst, bool in_place) { int32_t value = flecs_irc_compile_tmp(c, node->expr); ecs_vec_t jumps; ecs_vec_init_t(NULL, &jumps, int32_t, 0); int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { int32_t compare = flecs_irc_compile_tmp(c, elems[i].compare); int32_t cmp = flecs_irc_emit(c, EcsIrMatchCmp, value, compare, 0, node); flecs_irc_compile_expr(c, elems[i].expr, dst, in_place); ecs_vec_append_t(NULL, &jumps, int32_t)[0] = flecs_irc_emit(c, EcsIrJump, 0, 0, 0, node); flecs_irc_op(c, cmp)->c = flecs_irc_pc(c); } if (node->any.expr) { flecs_irc_compile_expr(c, node->any.expr, dst, in_place); } else { flecs_irc_emit(c, EcsIrMatchFail, value, 0, 0, node); } flecs_irc_patch_jumps(c, &jumps); } static void flecs_irc_compile_binary( ecs_script_ir_compiler_t *c, ecs_expr_binary_t *node, int32_t dst, bool in_place) { uint16_t place = in_place ? EcsIrInPlace : 0; int32_t left = flecs_irc_compile_tmp(c, node->left); int32_t skip = -1; if (!node->vector_count && node->left->type == ecs_id(ecs_bool_t) && (node->operator == EcsTokAnd || node->operator == EcsTokOr)) { skip = flecs_irc_emit(c, node->operator == EcsTokAnd ? EcsIrJumpIfFalse : EcsIrJumpIfTrue, left, 0, dst, node); flecs_irc_op(c, skip)->flags = place; } int32_t right = flecs_irc_compile_tmp(c, node->right); int32_t op = flecs_irc_emit(c, EcsIrBinary, dst, left, right, node); flecs_irc_op(c, op)->flags = (uint16_t)((uint32_t)node->operator | (in_place ? EcsIrBinaryInPlace : 0u)); int32_t size; flecs_expr_num_class_t class = flecs_expr_num_class( node->left->type, &size); if (!node->vector_count && class && (node->node.type == node->left->type || node->node.type == ecs_id(ecs_bool_t))) { static const uint16_t kinds[4][9] = { [EcsExprNumSigned] = { [1] = EcsIrBinaryI8, [2] = EcsIrBinaryI16, [4] = EcsIrBinaryI32, [8] = EcsIrBinaryI64 }, [EcsExprNumUnsigned] = { [1] = EcsIrBinaryU8, [2] = EcsIrBinaryU16, [4] = EcsIrBinaryU32, [8] = EcsIrBinaryU64 }, [EcsExprNumFloat] = { [4] = EcsIrBinaryF32, [8] = EcsIrBinaryF64 } }; flecs_irc_op(c, op)->kind = kinds[class][size]; } if (skip != -1) { flecs_irc_op(c, skip)->b = flecs_irc_pc(c); } } static void flecs_irc_compile_expr( ecs_script_ir_compiler_t *c, ecs_expr_node_t *node, int32_t dst, bool in_place) { ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); uint16_t place = in_place ? EcsIrInPlace : 0; int32_t op; switch(node->kind) { case EcsExprValue: op = flecs_irc_emit(c, EcsIrLoadConst, dst, 0, 0, node); flecs_irc_op(c, op)->imm.ptr = ((ecs_expr_value_node_t*)node)->ptr; return; case EcsExprInterpolatedString: flecs_irc_compile_interpolated_string( c, (ecs_expr_interpolated_string_t*)node, dst); return; case EcsExprInitializer: case EcsExprEmptyInitializer: if (!in_place) { flecs_irc_emit(c, EcsIrInitAlloc, dst, 0, 0, node); } flecs_irc_compile_initializer(c, (ecs_expr_initializer_t*)node, dst); return; case EcsExprUnary: { int32_t src = flecs_irc_compile_tmp( c, ((ecs_expr_unary_t*)node)->expr); op = flecs_irc_emit(c, EcsIrUnary, dst, src, 0, node); flecs_irc_op(c, op)->flags = place; return; } case EcsExprBinary: flecs_irc_compile_binary(c, (ecs_expr_binary_t*)node, dst, in_place); return; case EcsExprIdentifier: { ecs_expr_identifier_t *n = (ecs_expr_identifier_t*)node; if (n->expr) { flecs_irc_compile_expr(c, n->expr, dst, in_place); return; } op = flecs_irc_emit(c, n->symbol != -1 ? EcsIrLoadSymbol : EcsIrLookup, dst, n->symbol, 0, node); flecs_irc_op(c, op)->imm.str = n->value; flecs_irc_op(c, op)->flags = (uint16_t)(place | (node->type == ecs_id(ecs_entity_t) ? EcsIrIdDirect : 0)); return; } case EcsExprVariable: flecs_irc_emit(c, EcsIrLoadVar, dst, ((ecs_expr_variable_t*)node)->sp, 0, node); return; case EcsExprGlobalVariable: { ecs_expr_variable_t *n = (ecs_expr_variable_t*)node; op = flecs_irc_emit(c, EcsIrLoadGlobal, dst, 0, 0, node); flecs_irc_op(c, op)->imm.entity = n->global; flecs_irc_op(c, op)->flags = n->global_component == ecs_id(EcsScriptMutVar) ? EcsIrGlobalMut : 0; return; } case EcsExprFunction: case EcsExprMethod: flecs_irc_compile_call(c, (ecs_expr_function_t*)node, dst, in_place); return; case EcsExprTemplate: { ecs_expr_function_t *n = (ecs_expr_function_t*)node; ecs_expr_node_t *args = (ecs_expr_node_t*)n->args; bool has_type = n->args && n->args->node.type_info != NULL; bool has_args = n->args && ecs_vec_count(&n->args->elements) != 0; bool from_var = flecs_expr_template_ref_var(n->left) != NULL; if (!from_var && node->type != ecs_id(ecs_script_template_ref_t)) { flecs_irc_compile_expr(c, args, dst, in_place); return; } int32_t src = flecs_irc_compile_tmp( c, from_var ? n->left : (has_type ? args : NULL)); op = flecs_irc_emit(c, EcsIrTemplateRef, dst, src, 0, node); flecs_irc_op(c, op)->flags = (uint16_t)(place | (from_var ? EcsIrTemplateRefVar : 0)); flecs_irc_op(c, op)->imm.entity = n->calldata.function; if (from_var && has_args) { int32_t value = flecs_irc_reg(c); flecs_irc_emit(c, EcsIrTemplateRefValue, value, dst, 0, args); flecs_irc_compile_expr(c, args, value, true); } return; } case EcsExprMember: { ecs_expr_member_t *n = (ecs_expr_member_t*)node; int32_t src = flecs_irc_compile_tmp(c, n->left); flecs_irc_emit(c, EcsIrMember, dst, src, flecs_uto(int32_t, n->offset), node); return; } case EcsExprSwizzle: { int32_t src = flecs_irc_compile_tmp( c, ((ecs_expr_swizzle_t*)node)->left); op = flecs_irc_emit(c, EcsIrSwizzle, dst, src, 0, node); flecs_irc_op(c, op)->flags = place; return; } case EcsExprElement: { ecs_expr_element_t *n = (ecs_expr_element_t*)node; int32_t left = flecs_irc_compile_tmp(c, n->left); int32_t index = flecs_irc_compile_tmp(c, n->index); op = flecs_irc_emit(c, EcsIrElement, dst, left, index, node); const EcsType *type = ecs_get(c->world, n->left->type, EcsType); if (type && type->kind == EcsMapType) { flecs_irc_op(c, op)->flags = EcsIrElementMap; } else if (type && type->kind == EcsVectorType) { flecs_irc_op(c, op)->flags = EcsIrElementVector; } return; } case EcsExprComponent: { ecs_expr_element_t *n = (ecs_expr_element_t*)node; int32_t left = flecs_irc_compile_tmp(c, n->left); ecs_expr_node_t *index = n->index; if (index && index->kind == EcsExprIdentifier) { index = ((ecs_expr_identifier_t*)index)->expr; } ecs_entity_t component; if (index) { ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); component = ((ecs_expr_value_node_t*)index)->storage.entity; } else { component = node->type; } op = flecs_irc_emit(c, EcsIrComponentGet, dst, left, 0, node); flecs_irc_op(c, op)->imm.entity = component; return; } case EcsExprHas: { ecs_expr_has_t *n = (ecs_expr_has_t*)node; int32_t left = flecs_irc_compile_tmp(c, n->left); op = flecs_irc_emit(c, EcsIrHas, dst, left, 0, node); flecs_irc_op(c, op)->imm.id = n->id; flecs_irc_op(c, op)->flags = place; return; } case EcsExprCast: case EcsExprCastNumber: { ecs_expr_cast_t *n = (ecs_expr_cast_t*)node; int32_t src = flecs_irc_compile_tmp(c, n->expr); int32_t size; bool number = node->kind == EcsExprCastNumber || (n->expr->type == ecs_id(ecs_bool_t) && flecs_expr_num_class(node->type, &size) != EcsExprNumNone); op = flecs_irc_emit(c, number ? EcsIrCastNumber : EcsIrCast, dst, src, 0, node); flecs_irc_op(c, op)->flags = place; if (number) { flecs_irc_op(c, op)->imm.cast = flecs_expr_cast_handler( n->expr->type, node->type); } return; } case EcsExprMatch: flecs_irc_compile_match(c, (ecs_expr_match_t*)node, dst, in_place); return; case EcsExprRange: { ecs_expr_range_t *n = (ecs_expr_range_t*)node; int32_t from = flecs_irc_compile_tmp(c, n->from); int32_t to = flecs_irc_compile_tmp(c, n->to); op = flecs_irc_emit(c, EcsIrRange, dst, from, to, node); flecs_irc_op(c, op)->flags = place; return; } case EcsExprNew: { int32_t entry = flecs_irc_entry_add( c, ((ecs_expr_new_t*)node)->entity, EcsIrEntryEntity); op = flecs_irc_emit(c, EcsIrNew, dst, entry, 0, node); flecs_irc_op(c, op)->flags = place; return; } case EcsExprScript: op = flecs_irc_emit(c, EcsIrScript, dst, 0, 0, node); flecs_irc_op(c, op)->imm.ptr = ((ecs_expr_script_t*)node)->script; flecs_irc_op(c, op)->flags = place; return; } ecs_abort(ECS_INTERNAL_ERROR, "corrupt expression node kind"); } static bool flecs_irc_expr_borrows( ecs_expr_node_t *expr) { switch(expr->kind) { case EcsExprValue: case EcsExprVariable: case EcsExprGlobalVariable: case EcsExprMember: case EcsExprElement: case EcsExprComponent: return true; case EcsExprIdentifier: { ecs_expr_identifier_t *id = (ecs_expr_identifier_t*)expr; return id->expr ? flecs_irc_expr_borrows(id->expr) : false; } case EcsExprMatch: { ecs_expr_match_t *m = (ecs_expr_match_t*)expr; int32_t i, count = ecs_vec_count(&m->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&m->elements); for (i = 0; i < count; i ++) { if (flecs_irc_expr_borrows(elems[i].expr)) { return true; } } return m->any.expr ? flecs_irc_expr_borrows(m->any.expr) : false; } default: return false; } } static bool flecs_irc_type_matches( ecs_script_ir_compiler_t *c, ecs_expr_node_t *expr, ecs_entity_t type) { if (!type) { return false; } if (flecs_irc_expr_borrows(expr)) { return false; } if (expr->type == type) { return true; } if (expr->kind == EcsExprInitializer || expr->kind == EcsExprEmptyInitializer) { return flecs_struct_is_derived_from(c->world, type, expr->type); } return false; } static int32_t flecs_irc_expr_begin( ecs_script_ir_compiler_t *c, const void *node) { return flecs_irc_emit(c, EcsIrExprBegin, c->reg_count, 0, 0, node); } static void flecs_irc_expr_end( ecs_script_ir_compiler_t *c, int32_t begin, bool emit) { ecs_script_ir_op_t *op = flecs_irc_op(c, begin); op->b = c->reg_count - op->a; if (emit) { flecs_irc_emit(c, EcsIrExprEnd, 0, 0, 0, NULL); } } static void flecs_irc_compile_body( ecs_script_ir_compiler_t *c, ecs_script_scope_t *scope, uint16_t flags) { int32_t floor = c->reg_floor, count = c->reg_count; c->reg_floor = count; flecs_irc_compile_scope(c, scope, flags); c->reg_floor = floor; c->reg_count = count; } static void flecs_irc_compile_id_op( ecs_script_ir_compiler_t *c, const void *node, ecs_script_id_t *id, int32_t component_slot, ecs_script_ir_op_kind_t kind) { bool expr = flecs_irc_id_needs_expr(id); int32_t begin = expr ? flecs_irc_expr_begin(c, node) : -1; int32_t index = flecs_irc_id(c, id, false, component_slot); int32_t op = flecs_irc_emit(c, kind, index, 0, 0, node); if (expr) { bool scope = kind == EcsIrPairScopeEnter; flecs_irc_expr_end(c, begin, !scope); flecs_irc_op(c, op)->flags = scope ? EcsIrEndsExpr : 0; } } static void flecs_irc_compile_tag( ecs_script_ir_compiler_t *c, ecs_script_tag_t *node, bool with) { flecs_irc_compile_id_op(c, node, &node->id, node->component_slot, with ? EcsIrWithTag : EcsIrTag); } static void flecs_irc_compile_component( ecs_script_ir_compiler_t *c, ecs_script_component_t *node, bool with) { bool expr = node->expr || flecs_irc_id_needs_expr(&node->id); int32_t begin = expr ? flecs_irc_expr_begin(c, node) : -1; int32_t id = flecs_irc_id(c, &node->id, true, node->component_slot); const ecs_script_ir_id_t *desc = ecs_vec_get_t( &c->ir->ids, ecs_script_ir_id_t, id); bool partial = false; if (node->expr && node->expr->kind == EcsExprInitializer) { partial = ((ecs_expr_initializer_t*)node->expr)->is_partial; } int32_t visitor = -1; if (!with && !desc->has_second && (desc->visitor || desc->dynamic)) { visitor = flecs_irc_emit(c, EcsIrComponentVisitor, id, 0, 0, node); if (desc->visitor) { flecs_irc_op(c, visitor)->flags = EcsIrVisitorStatic; } } if (node->expr) { int32_t tmp = flecs_irc_reg(c); int32_t op = flecs_irc_emit(c, with ? EcsIrWithComponentBegin : EcsIrComponentBegin, id, tmp, 0, node); flecs_irc_op(c, op)->flags = partial ? EcsIrComponentPartial : 0; bool in_place; if (desc->ti) { in_place = flecs_irc_type_matches( c, node->expr, desc->ti->component); } else { in_place = node->expr->kind == EcsExprInitializer || node->expr->kind == EcsExprEmptyInitializer; } int32_t value = in_place ? tmp : flecs_irc_reg(c); flecs_irc_compile_expr(c, node->expr, value, in_place); flecs_irc_emit(c, with ? EcsIrWithComponentEnd : EcsIrComponentEnd, id, value, tmp, node); } else if (with) { flecs_irc_emit(c, EcsIrWithComponentBegin, id, -1, 0, node); } else { flecs_irc_emit(c, EcsIrComponentEnd, id, -1, -1, node); } if (visitor != -1) { flecs_irc_op(c, visitor)->b = flecs_irc_pc(c); } if (expr) { flecs_irc_expr_end(c, begin, true); } } static void flecs_irc_compile_const( ecs_script_ir_compiler_t *c, ecs_script_var_node_t *node) { int32_t begin = flecs_irc_expr_begin(c, node); int32_t value = flecs_irc_compile_tmp(c, node->expr); int32_t op = flecs_irc_emit(c, EcsIrConstEnd, value, node->computed - 1, 0, node); flecs_irc_op(c, op)->imm.ptr = node->eval_type ? ECS_CONST_CAST(void*, ecs_get_type_info(c->world, node->eval_type)) : NULL; flecs_irc_expr_end(c, begin, true); } static void flecs_irc_compile_await( ecs_script_ir_compiler_t *c, ecs_script_node_t *stmt) { ecs_expr_node_t *expr; uint16_t flags = 0; if (stmt->kind == EcsAstAwait) { expr = ((ecs_script_await_t*)stmt)->expr; } else { expr = ((ecs_script_var_node_t*)stmt)->expr; flags |= EcsIrAwaitVar; } int32_t check = flecs_irc_emit(c, EcsIrAwaitStart, 0, 0, 0, stmt); flecs_irc_op(c, check)->flags = flags; if (!expr || (expr->kind != EcsExprFunction && expr->kind != EcsExprMethod)) { int32_t op = flecs_irc_emit(c, EcsIrAwaitLaunch, -1, 0, 0, stmt); flecs_irc_op(c, op)->flags = flags; } else { ecs_expr_function_t *call = (ecs_expr_function_t*)expr; int32_t begin = flecs_irc_expr_begin(c, stmt); int32_t count, first = flecs_irc_compile_args(c, call, &count); int32_t launch = flecs_irc_emit( c, EcsIrAwaitLaunch, first, count, 0, stmt); flecs_irc_op(c, launch)->flags = flags; flecs_irc_op(c, launch)->imm.ptr = call; flecs_irc_expr_end(c, begin, true); } flecs_irc_op(c, check)->b = flecs_irc_pc(c); int32_t poll = flecs_irc_emit(c, EcsIrAwaitPoll, 0, 0, 0, stmt); flecs_irc_op(c, poll)->flags = flags; } static void flecs_irc_compile_entity( ecs_script_ir_compiler_t *c, ecs_script_entity_t *node) { int32_t begin = node->name_expr ? flecs_irc_expr_begin(c, node) : -1; int32_t name = flecs_irc_compile_tmp(c, node->name_expr); int32_t op = flecs_irc_emit(c, EcsIrEntityEnter, name, 0, 0, node); if (name != -1) { flecs_irc_expr_end(c, begin, false); flecs_irc_op(c, op)->flags = EcsIrEndsExpr; } flecs_irc_compile_body(c, node->scope, EcsIrScopeEntity); flecs_irc_emit(c, EcsIrLeave, EcsIrFrameEntity, 0, 0, node); } static void flecs_irc_compile_pair_scope( ecs_script_ir_compiler_t *c, ecs_script_pair_scope_t *node) { flecs_irc_compile_id_op(c, node, &node->id, -1, EcsIrPairScopeEnter); flecs_irc_compile_body(c, node->scope, 0); flecs_irc_emit(c, EcsIrLeave, EcsIrFramePairScope, 0, 0, node); } static void flecs_irc_compile_with( ecs_script_ir_compiler_t *c, ecs_script_with_t *node) { flecs_irc_emit(c, EcsIrWithEnter, 0, 0, 0, node); ecs_script_node_t **stmts = ecs_vec_first(&node->expressions->stmts); int32_t i, count = ecs_vec_count(&node->expressions->stmts); for (i = 0; i < count; i ++) { ecs_script_node_t *stmt = stmts[i]; c->reg_count = c->reg_floor; if (stmt->skip) { continue; } if (stmt->kind == EcsAstWithTag) { flecs_irc_compile_tag(c, (ecs_script_tag_t*)stmt, true); } else { ecs_assert(stmt->kind == EcsAstWithComponent, ECS_INTERNAL_ERROR, NULL); flecs_irc_compile_component( c, (ecs_script_component_t*)stmt, true); } } flecs_irc_emit(c, EcsIrWithBody, 0, 0, 0, node); flecs_irc_compile_body(c, node->scope, 0); flecs_irc_emit(c, EcsIrLeave, EcsIrFrameWith, 0, 0, node); } static void flecs_irc_compile_if( ecs_script_ir_compiler_t *c, ecs_script_if_t *node) { int32_t begin = flecs_irc_expr_begin(c, node); int32_t cond = flecs_irc_compile_tmp(c, node->expr); flecs_irc_emit(c, EcsIrToBool, cond, 0, 0, node); flecs_irc_expr_end(c, begin, true); int32_t enter = flecs_irc_emit(c, EcsIrIfEnter, 0, 0, 0, node); flecs_irc_compile_body(c, node->if_true, 0); int32_t jump = flecs_irc_emit(c, EcsIrJump, 0, 0, 0, node); flecs_irc_op(c, enter)->b = flecs_irc_pc(c); flecs_irc_compile_body(c, node->if_false, 0); flecs_irc_op(c, jump)->a = flecs_irc_pc(c); flecs_irc_emit(c, EcsIrLeave, EcsIrFrameIf, 0, 0, node); } static void flecs_irc_compile_for( ecs_script_ir_compiler_t *c, ecs_script_for_t *node) { int32_t begin = flecs_irc_expr_begin(c, node); int32_t from = flecs_irc_compile_tmp( c, node->expr ? node->expr : node->from); int32_t to = flecs_irc_compile_tmp(c, node->expr ? NULL : node->to); flecs_irc_expr_end(c, begin, false); flecs_irc_emit(c, EcsIrForEnter, from, to, 0, node); int32_t next = flecs_irc_emit(c, EcsIrForNext, 0, 0, 0, node); c->force_depth ++; flecs_irc_compile_body(c, node->scope, 0); c->force_depth --; flecs_irc_emit(c, EcsIrJump, next, 0, 0, node); flecs_irc_op(c, next)->a = flecs_irc_pc(c); flecs_irc_emit(c, EcsIrLeave, EcsIrFrameFor, 0, 0, node); flecs_irc_emit(c, EcsIrExprEnd, 0, 0, 0, node); } static void flecs_irc_compile_try( ecs_script_ir_compiler_t *c, ecs_script_try_t *node) { int32_t catch_count = ecs_vec_count(&node->catches); int32_t catch_first = ecs_vec_count(&c->ir->catches); int32_t i; ecs_vec_set_count_t(NULL, &c->ir->catches, int32_t, catch_first + catch_count); int32_t enter = flecs_irc_emit( c, EcsIrTryEnter, catch_first, catch_count, 0, node); ecs_vec_t jumps; ecs_vec_init_t(NULL, &jumps, int32_t, 0); flecs_irc_compile_body(c, node->try_scope, 0); ecs_vec_append_t(NULL, &jumps, int32_t)[0] = flecs_irc_emit(c, EcsIrJump, 0, 0, 0, node); for (i = 0; i < catch_count; i ++) { ecs_script_catch_t *catch_ = ecs_vec_get_t( &node->catches, ecs_script_catch_t, i); ecs_vec_get_t(&c->ir->catches, int32_t, catch_first + i)[0] = flecs_irc_pc(c); flecs_irc_compile_body(c, catch_->scope, 0); ecs_vec_append_t(NULL, &jumps, int32_t)[0] = flecs_irc_emit(c, EcsIrJump, 0, 0, 0, node); } flecs_irc_patch_jumps(c, &jumps); flecs_irc_op(c, enter)->c = flecs_irc_pc(c); flecs_irc_emit(c, EcsIrLeave, EcsIrFrameTry, 0, 0, node); } static void flecs_irc_compile_while( ecs_script_ir_compiler_t *c, ecs_script_while_t *node) { flecs_irc_emit(c, EcsIrWhileEnter, 0, 0, 0, node); int32_t cond_pc = flecs_irc_pc(c); int32_t begin = flecs_irc_expr_begin(c, node); int32_t cond = flecs_irc_compile_tmp(c, node->expr); flecs_irc_emit(c, EcsIrToBool, cond, 0, 0, node); flecs_irc_expr_end(c, begin, true); int32_t next = flecs_irc_emit(c, EcsIrWhileNext, 0, 0, 0, node); c->force_depth ++; flecs_irc_compile_body(c, node->scope, 0); c->force_depth --; flecs_irc_emit(c, EcsIrJump, cond_pc, 0, 0, node); flecs_irc_op(c, next)->a = flecs_irc_pc(c); flecs_irc_emit(c, EcsIrLeave, EcsIrFrameWhile, 0, 0, node); } static void flecs_irc_compile_assign( ecs_script_ir_compiler_t *c, ecs_script_assign_t *node) { int32_t begin = flecs_irc_expr_begin(c, node); int32_t value = flecs_irc_compile_tmp(c, node->expr); flecs_irc_emit(c, EcsIrAssign, value, 0, 0, node); flecs_irc_expr_end(c, begin, true); } static void flecs_irc_compile_stmt( ecs_script_ir_compiler_t *c, ecs_script_scope_t *scope, int32_t index) { ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); int32_t count = ecs_vec_count(&scope->stmts); ecs_script_node_t *node = stmts[index]; c->reg_count = c->reg_floor; int32_t marks = node->region - 1; int32_t stmt = -1; if (!c->force_depth || node->skip) { stmt = flecs_irc_emit(c, EcsIrStmt, 0, 0, marks, node); ecs_script_ir_op_t *op = flecs_irc_op(c, stmt); op->imm.u64 = node->input; op->flags = (uint16_t)( (flecs_script_stmt_support(node) ? EcsIrStmtAlways : 0) | (node->skip ? EcsIrStmtSkip : 0)); } if (node->skip) { flecs_irc_op(c, stmt)->b = flecs_irc_pc(c); return; } switch(node->kind) { case EcsAstScope: flecs_irc_compile_scope(c, (ecs_script_scope_t*)node, 0); break; case EcsAstTag: flecs_irc_compile_tag(c, (ecs_script_tag_t*)node, false); break; case EcsAstComponent: flecs_irc_compile_component(c, (ecs_script_component_t*)node, false); break; case EcsAstWithTag: case EcsAstWithComponent: ecs_abort(ECS_INTERNAL_ERROR, "invalid context for with expression"); case EcsAstWith: flecs_irc_compile_with(c, (ecs_script_with_t*)node); break; case EcsAstUsing: flecs_irc_emit(c, EcsIrUsing, 0, 0, 0, node); break; case EcsAstModule: flecs_irc_emit(c, EcsIrModule, 0, 0, 0, node); break; case EcsAstAnnotation: { int32_t op = flecs_irc_emit(c, EcsIrAnnot, 0, 0, 0, node); ecs_script_node_t *next = (index + 1) < count ? stmts[index + 1] : NULL; if (!next) { flecs_irc_op(c, op)->flags = EcsIrAnnotNoTarget; } else if (next->kind != EcsAstEntity && next->kind != EcsAstTemplate && next->kind != EcsAstAnnotation) { flecs_irc_op(c, op)->flags = EcsIrAnnotBadTarget; } break; } case EcsAstTemplate: flecs_irc_entry_add(c, node, EcsIrEntryTemplate); flecs_irc_emit(c, EcsIrTemplate, 0, 0, 0, node); break; case EcsAstProp: break; case EcsAstMut: flecs_irc_emit(c, EcsIrMutCheck, 0, 0, 0, node); break; case EcsAstConst: { ecs_script_var_node_t *n = (ecs_script_var_node_t*)node; if (n->is_await) { flecs_irc_compile_await(c, node); break; } flecs_irc_compile_const(c, n); if (n->computed && stmt != -1) { int32_t jump = flecs_irc_emit(c, EcsIrJump, 0, 0, 0, node); flecs_irc_op(c, stmt)->b = flecs_irc_pc(c); flecs_irc_op(c, stmt)->flags |= EcsIrStmtCached; flecs_irc_emit(c, EcsIrConstCached, n->computed - 1, stmt + 1, 0, node); flecs_irc_op(c, jump)->a = flecs_irc_pc(c); stmt = -1; } break; } case EcsAstExportConst: case EcsAstExportMut: break; case EcsAstEntity: { ecs_script_entity_t *n = (ecs_script_entity_t*)node; if (!n->is_type) { flecs_irc_compile_entity(c, n); break; } flecs_irc_emit(c, EcsIrAnnotClear, 0, 0, 0, node); if (marks != -1) { flecs_irc_emit(c, EcsIrMark, 0, 0, marks, node); } break; } case EcsAstPairScope: flecs_irc_compile_pair_scope(c, (ecs_script_pair_scope_t*)node); break; case EcsAstIf: flecs_irc_compile_if(c, (ecs_script_if_t*)node); break; case EcsAstFor: flecs_irc_compile_for(c, (ecs_script_for_t*)node); break; case EcsAstInclude: break; case EcsAstFunction: flecs_irc_entry_add(c, node, EcsIrEntryFunction); break; case EcsAstAwait: flecs_irc_compile_await(c, node); break; case EcsAstTry: flecs_irc_compile_try(c, (ecs_script_try_t*)node); break; case EcsAstContinue: flecs_irc_emit(c, EcsIrContinue, 0, 0, 0, node); break; case EcsAstAsync: flecs_irc_entry_add(c, node, EcsIrEntryAsync); flecs_irc_emit(c, EcsIrAsync, 0, 0, 0, node); break; case EcsAstWhile: flecs_irc_compile_while(c, (ecs_script_while_t*)node); break; case EcsAstAssign: flecs_irc_compile_assign(c, (ecs_script_assign_t*)node); break; } if (stmt != -1) { flecs_irc_op(c, stmt)->b = flecs_irc_pc(c); } } static int32_t flecs_irc_scope_var_count( ecs_script_scope_t *scope) { ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); int32_t i, count = ecs_vec_count(&scope->stmts), result = 0; for (i = 0; i < count; i ++) { if (stmts[i]->kind == EcsAstConst && !stmts[i]->skip) { result ++; } } return result; } static void flecs_irc_compile_scope( ecs_script_ir_compiler_t *c, ecs_script_scope_t *scope, uint16_t flags) { int32_t enter = flecs_irc_emit(c, EcsIrScopeEnter, scope->scope_slot, flecs_irc_scope_var_count(scope), ecs_vec_count(&scope->components), scope); flecs_irc_op(c, enter)->flags = flags; flecs_irc_op(c, enter)->imm.ptr = ecs_vec_first(&scope->components); scope->parent = c->scope; ecs_script_scope_t *prev = c->scope; c->scope = scope; int32_t i, count = ecs_vec_count(&scope->stmts); int32_t block = -1; bool blocks = count >= 32 && !c->force_depth; for (i = 0; i < count; i ++) { ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); if (blocks && !(i % 32)) { if (block != -1) { flecs_irc_op(c, block)->b = flecs_irc_pc(c); } block = flecs_irc_emit(c, EcsIrStmtBlock, 0, 0, 0, scope); } int32_t stmt = flecs_irc_pc(c); flecs_irc_compile_stmt(c, scope, i); if (block != -1) { const ecs_script_ir_op_t *stmt_op = flecs_irc_op(c, stmt); bool always = stmt_op->flags & (EcsIrStmtAlways | EcsIrStmtCached); if (stmt_op->c != -1) { const ecs_script_region_t *region = ecs_vec_get_t( &c->script->regions, ecs_script_region_t, stmt_op->c); always |= region->for_count != 0; } ecs_script_ir_op_t *block_op = flecs_irc_op(c, block); block_op->imm.u64 |= stmts[i]->input; block_op->c ++; if (always) { block_op->flags |= EcsIrStmtAlways; } } } if (block != -1) { flecs_irc_op(c, block)->b = flecs_irc_pc(c); } c->scope = prev; flecs_irc_emit(c, EcsIrLeave, EcsIrFrameScope, 0, 0, scope); } static void flecs_irc_compile_function( ecs_script_ir_compiler_t *c, ecs_script_function_node_t *node) { ecs_script_scope_t *body = node->body; body->parent = NULL; c->scope = body; int32_t i, count = ecs_vec_count(&body->stmts); for (i = 0; i < count; i ++) { flecs_irc_compile_stmt(c, body, i); } c->scope = NULL; c->reg_count = c->reg_floor; if (node->return_expr) { int32_t begin = flecs_irc_expr_begin(c, node); int32_t value = flecs_irc_compile_tmp(c, node->return_expr); flecs_irc_expr_end(c, begin, false); flecs_irc_emit(c, EcsIrReturn, value, 0, 0, node); } } static void flecs_irc_compile_entry( ecs_script_ir_compiler_t *c, int32_t index) { ecs_script_ir_entry_t *entry = ecs_vec_get_t( &c->ir->entries, ecs_script_ir_entry_t, index); entry->pc = flecs_irc_pc(c); ecs_script_ir_entry_kind_t kind = entry->kind; const void *node = entry->node; c->entry = index; c->reg_count = 0; c->reg_floor = 0; c->reg_max = 0; c->scope = NULL; c->force_depth = 0; switch(kind) { case EcsIrEntryRoot: flecs_irc_compile_scope( c, ECS_CONST_CAST(ecs_script_scope_t*, node), 0); break; case EcsIrEntryTemplate: flecs_irc_compile_scope(c, ((const ecs_script_template_node_t*) node)->scope, 0); break; case EcsIrEntryEntity: flecs_irc_compile_entity( c, ECS_CONST_CAST(ecs_script_entity_t*, node)); break; case EcsIrEntryFunction: flecs_irc_compile_function( c, ECS_CONST_CAST(ecs_script_function_node_t*, node)); break; case EcsIrEntryAsync: flecs_irc_compile_scope( c, ((const ecs_script_async_t*)node)->scope, 0); break; } flecs_irc_emit(c, EcsIrEnd, 0, 0, 0, node); entry = ecs_vec_get_t(&c->ir->entries, ecs_script_ir_entry_t, index); entry->reg_count = c->reg_max; } static void flecs_irc_init( ecs_script_ir_t *ir) { ecs_vec_init_t(NULL, &ir->ops, ecs_script_ir_op_t, 0); ecs_vec_init_t(NULL, &ir->ids, ecs_script_ir_id_t, 0); ecs_vec_init_t(NULL, &ir->catches, int32_t, 0); ecs_vec_init_t(NULL, &ir->entries, ecs_script_ir_entry_t, 0); ecs_map_init(&ir->entry_index, NULL); ir->root_entry = -1; } void flecs_script_ir_free( ecs_script_ir_t *ir) { if (!ir) { return; } ecs_vec_fini_t(NULL, &ir->ops, ecs_script_ir_op_t); ecs_vec_fini_t(NULL, &ir->ids, ecs_script_ir_id_t); ecs_vec_fini_t(NULL, &ir->catches, int32_t); ecs_vec_fini_t(NULL, &ir->entries, ecs_script_ir_entry_t); ecs_map_fini(&ir->entry_index); ecs_os_free(ir); } ecs_script_ir_t* flecs_script_ir_compile( ecs_script_impl_t *script) { ecs_script_ir_t *ir = ecs_os_calloc_t(ecs_script_ir_t); flecs_irc_init(ir); ecs_script_ir_compiler_t c = { .script = script, .world = script->pub.world, .ir = ir }; ir->root_entry = flecs_irc_entry_add(&c, script->root, EcsIrEntryRoot); int32_t i; for (i = 0; i < ecs_vec_count(&ir->entries); i ++) { flecs_irc_compile_entry(&c, i); } return ir; } ecs_script_ir_t* flecs_script_ir_ensure( ecs_script_impl_t *script) { if (!script->ir) { script->ir = flecs_script_ir_compile(script); } return script->ir; } const ecs_script_ir_entry_t* flecs_script_ir_entry( const ecs_script_ir_t *ir, const void *node) { const uint64_t *index = ecs_map_get(&ir->entry_index, (uintptr_t)node); return index ? ecs_vec_get_t( &ir->entries, ecs_script_ir_entry_t, (int32_t)*index) : NULL; } #endif #ifdef FLECS_SCRIPT static const struct { const char *name; const char *operands[3]; } flecs_ir_listing[EcsIrOpKindLast] = { [EcsIrEnd] = {"End", {NULL}}, [EcsIrLeave] = {"Leave", {"frame"}}, [EcsIrJump] = {"Jump", {"target"}}, [EcsIrStmt] = {"Stmt", {NULL, "skip", "region"}}, [EcsIrStmtBlock] = {"StmtBlock", {NULL, "skip", "count"}}, [EcsIrMark] = {"Mark", {NULL, NULL, "region"}}, [EcsIrAnnotClear] = {"AnnotClear", {NULL}}, [EcsIrScopeEnter] = {"ScopeEnter", {"slot", "vars", "add_count"}}, [EcsIrEntityEnter] = {"EntityEnter", {"name"}}, [EcsIrWithEnter] = {"WithEnter", {NULL}}, [EcsIrWithBody] = {"WithBody", {NULL}}, [EcsIrWithTag] = {"WithTag", {"id"}}, [EcsIrWithComponentBegin] = {"WithComponentBegin", {"id", "dst"}}, [EcsIrWithComponentEnd] = {"WithComponentEnd", {"id", "value", "tmp"}}, [EcsIrPairScopeEnter] = {"PairScopeEnter", {"id"}}, [EcsIrIfEnter] = {"IfEnter", {NULL, "else"}}, [EcsIrForEnter] = {"ForEnter", {"from", "to"}}, [EcsIrForNext] = {"ForNext", {"end"}}, [EcsIrContinue] = {"Continue", {NULL}}, [EcsIrWhileEnter] = {"WhileEnter", {NULL}}, [EcsIrWhileNext] = {"WhileNext", {"end"}}, [EcsIrTryEnter] = {"TryEnter", {"catches", "count", "end"}}, [EcsIrAwaitStart] = {"AwaitStart", {NULL, "resume"}}, [EcsIrAwaitLaunch] = {"AwaitLaunch", {"args", "count"}}, [EcsIrAwaitPoll] = {"AwaitPoll", {NULL}}, [EcsIrTag] = {"Tag", {"id"}}, [EcsIrComponentBegin] = {"ComponentBegin", {"id", "dst"}}, [EcsIrComponentEnd] = {"ComponentEnd", {"id", "value", "tmp"}}, [EcsIrComponentVisitor] = {"ComponentVisitor", {"id", "done"}}, [EcsIrUsing] = {"Using", {NULL}}, [EcsIrModule] = {"Module", {NULL}}, [EcsIrAnnot] = {"Annot", {NULL}}, [EcsIrTemplate] = {"Template", {NULL}}, [EcsIrMutCheck] = {"MutCheck", {NULL}}, [EcsIrAsync] = {"Async", {NULL}}, [EcsIrAssign] = {"Assign", {"value"}}, [EcsIrConstEnd] = {"ConstEnd", {"value"}}, [EcsIrConstCached] = {"ConstCached", {"slot", "else"}}, [EcsIrExprBegin] = {"ExprBegin", {"first", "count"}}, [EcsIrExprEnd] = {"ExprEnd", {NULL}}, [EcsIrLoadConst] = {"LoadConst", {"dst"}}, [EcsIrLoadVar] = {"LoadVar", {"dst", "slot"}}, [EcsIrLoadGlobal] = {"LoadGlobal", {"dst"}}, [EcsIrLoadSymbol] = {"LoadSymbol", {"dst", "symbol"}}, [EcsIrLookup] = {"Lookup", {"dst"}}, [EcsIrMember] = {"Member", {"dst", "src", "offset"}}, [EcsIrSwizzle] = {"Swizzle", {"dst", "src"}}, [EcsIrElement] = {"Element", {"dst", "src", "index"}}, [EcsIrComponentGet] = {"ComponentGet", {"dst", "entity"}}, [EcsIrHas] = {"Has", {"dst", "entity"}}, [EcsIrUnary] = {"Unary", {"dst", "src"}}, [EcsIrBinary] = {"Binary", {"dst", "left", "right"}}, [EcsIrBinaryI8] = {"BinaryI8", {"dst", "left", "right"}}, [EcsIrBinaryI16] = {"BinaryI16", {"dst", "left", "right"}}, [EcsIrBinaryI32] = {"BinaryI32", {"dst", "left", "right"}}, [EcsIrBinaryI64] = {"BinaryI64", {"dst", "left", "right"}}, [EcsIrBinaryU8] = {"BinaryU8", {"dst", "left", "right"}}, [EcsIrBinaryU16] = {"BinaryU16", {"dst", "left", "right"}}, [EcsIrBinaryU32] = {"BinaryU32", {"dst", "left", "right"}}, [EcsIrBinaryU64] = {"BinaryU64", {"dst", "left", "right"}}, [EcsIrBinaryF32] = {"BinaryF32", {"dst", "left", "right"}}, [EcsIrBinaryF64] = {"BinaryF64", {"dst", "left", "right"}}, [EcsIrJumpIfFalse] = {"JumpIfFalse", {"condition", "target", "result"}}, [EcsIrJumpIfTrue] = {"JumpIfTrue", {"condition", "target", "result"}}, [EcsIrCast] = {"Cast", {"dst", "src"}}, [EcsIrCastNumber] = {"CastNumber", {"dst", "src"}}, [EcsIrCall] = {"Call", {"dst", "args", "count"}}, [EcsIrInitAlloc] = {"InitAlloc", {"dst"}}, [EcsIrInitConst] = {"InitConst", {"dst", "offset"}}, [EcsIrInitAssign] = {"InitAssign", {"dst", "offset", "src"}}, [EcsIrInitOp] = {"InitOp", {"dst", "offset", "src"}}, [EcsIrInitSwizzle] = {"InitSwizzle", {"dst", "offset", "src"}}, [EcsIrDynBegin] = {"DynBegin", {"dst"}}, [EcsIrDynPush] = {"DynPush", {"dst"}}, [EcsIrDynPop] = {"DynPop", {"dst"}}, [EcsIrDynNext] = {"DynNext", {"dst"}}, [EcsIrDynMember] = {"DynMember", {NULL}}, [EcsIrDynKey] = {"DynKey", {"key"}}, [EcsIrDynSet] = {"DynSet", {"value"}}, [EcsIrDynSwizzle] = {"DynSwizzle", {"value"}}, [EcsIrDynEnd] = {"DynEnd", {"dst"}}, [EcsIrStrBegin] = {"StrBegin", {NULL}}, [EcsIrStrFrag] = {"StrFrag", {NULL}}, [EcsIrStrValue] = {"StrValue", {"value"}}, [EcsIrStrCast] = {"StrCast", {"value"}}, [EcsIrStrFormat] = {"StrFormat", {"value", "width", "precision"}}, [EcsIrStrEnd] = {"StrEnd", {"dst"}}, [EcsIrMatchCmp] = {"MatchCmp", {"value", "compare", "else"}}, [EcsIrMatchFail] = {"MatchFail", {"value"}}, [EcsIrRange] = {"Range", {"dst", "from", "to"}}, [EcsIrNew] = {"New", {"dst", "block"}}, [EcsIrScript] = {"Script", {"dst"}}, [EcsIrTemplateRef] = {"TemplateRef", {"dst", "src"}}, [EcsIrTemplateRefValue] = {"TemplateRefValue", {"dst", "ref"}}, [EcsIrToBool] = {"ToBool", {"value"}}, [EcsIrReturn] = {"Return", {"value"}}, }; const char* flecs_script_ir_op_name( ecs_script_ir_op_kind_t kind) { if (kind < 0 || kind >= EcsIrOpKindLast) { return "?"; } return flecs_ir_listing[kind].name; } static void flecs_irs_id_to_buf( const ecs_script_impl_t *script, const ecs_script_ir_t *ir, int32_t index, ecs_strbuf_t *buf) { const ecs_script_ir_id_t *id = ecs_vec_get_t( &ir->ids, ecs_script_ir_id_t, index); if (!id->dynamic && id->eval) { char *str = ecs_id_str(script->pub.world, id->eval); ecs_strbuf_appendstr(buf, str); ecs_os_free(str); return; } if (id->has_second) { ecs_strbuf_append(buf, "(%s, %s)", id->first, id->second); } else { ecs_strbuf_appendstr(buf, id->first); } if (id->first_symbol != -1) { ecs_strbuf_append(buf, " sym=%d", id->first_symbol); } if (id->first_sp != -1) { ecs_strbuf_append(buf, " sp=%d", id->first_sp); } if (id->first_reg != -1) { ecs_strbuf_append(buf, " r%d", id->first_reg); } if (id->second_symbol != -1) { ecs_strbuf_append(buf, " sym2=%d", id->second_symbol); } if (id->second_sp != -1) { ecs_strbuf_append(buf, " sp2=%d", id->second_sp); } if (id->second_reg != -1) { ecs_strbuf_append(buf, " r%d", id->second_reg); } } static void flecs_irs_payload_to_buf( const ecs_script_impl_t *script, const ecs_script_ir_op_t *op, ecs_strbuf_t *buf) { ecs_world_t *world = script->pub.world; const ecs_expr_node_t *expr = op->node; switch (op->kind) { case EcsIrLoadConst: case EcsIrInitConst: { char *value = ecs_ptr_to_expr(world, expr->type, op->imm.ptr); ecs_strbuf_append(buf, " value=%s", value); ecs_os_free(value); break; } case EcsIrLoadGlobal: case EcsIrComponentGet: case EcsIrHas: { char *id = ecs_id_str(world, op->imm.id); ecs_strbuf_append(buf, " id=%s", id); ecs_os_free(id); break; } case EcsIrLookup: case EcsIrDynMember: case EcsIrStrFrag: ecs_strbuf_append(buf, " text=\"%s\"", op->imm.str); break; case EcsIrStmt: case EcsIrStmtBlock: ecs_strbuf_append(buf, " input=0x%llx", (unsigned long long)op->imm.u64); break; case EcsIrScopeEnter: { const ecs_id_t *ids = op->imm.ptr; for (int32_t i = 0; i < op->c; i ++) { char *id = ecs_id_str(world, ids[i]); ecs_strbuf_append(buf, "%s%s", i ? ", " : " add=", id); ecs_os_free(id); } break; } case EcsIrUnary: ecs_strbuf_append(buf, " operator=%s", flecs_token_str( ((const ecs_expr_unary_t*)op->node)->operator)); break; default: break; } if ((op->kind >= EcsIrBinary && op->kind <= EcsIrBinaryF64) || op->kind == EcsIrInitOp) { ecs_strbuf_append(buf, " operator=%s", flecs_token_str( (ecs_token_kind_t)(op->flags & ~EcsIrBinaryInPlace))); } if (!op->node) { return; } if (op->kind >= EcsIrLoadConst && op->kind < EcsIrToBool && expr->type) { const char *type = ecs_get_name(world, expr->type); if (type) { ecs_strbuf_appendlit(buf, " type="); ecs_strbuf_appendstr(buf, type); } } const char *pos = op->kind == EcsIrStmt ? ((const ecs_script_node_t*)op->node)->pos : NULL; if (pos) { int32_t length = 0; while (length < 80 && pos[length] && pos[length] != '\n') { length ++; } ecs_strbuf_append(buf, " ; %.*s", length, pos); } } void flecs_script_ir_to_buf( const ecs_script_impl_t *script, const ecs_script_ir_t *ir, ecs_strbuf_t *buf) { const ecs_script_ir_op_t *ops = ecs_vec_first(&ir->ops); const ecs_script_ir_entry_t *entries = ecs_vec_first(&ir->entries); int32_t count = ecs_vec_count(&ir->ops); int32_t entry_count = ecs_vec_count(&ir->entries); for (int32_t i = 0; i < ecs_vec_count(&ir->ids); i ++) { ecs_strbuf_append(buf, "id %d: ", i); flecs_irs_id_to_buf(script, ir, i, buf); ecs_strbuf_appendch(buf, '\n'); } for (int32_t i = 0; i < count; i ++) { const ecs_script_ir_op_t *op = &ops[i]; for (int32_t e = 0; e < entry_count; e ++) { if (entries[e].pc == i) { static const char *const kinds[] = { "root", "template", "entity", "fn", "async" }; ecs_strbuf_append(buf, "%s%s block %d (regs=%d)\n", i ? "\n" : "", kinds[entries[e].kind], e, entries[e].reg_count); } } ecs_strbuf_append(buf, "%4d %-18s", i, flecs_script_ir_op_name(op->kind)); const int32_t values[] = {op->a, op->b, op->c}; for (int32_t a = 0; a < 3; a ++) { const char *label = flecs_ir_listing[op->kind].operands[a]; if (label) { ecs_strbuf_appendch(buf, ' '); ecs_strbuf_appendstr(buf, label); ecs_strbuf_appendch(buf, '='); ecs_strbuf_appendint(buf, values[a]); } } if (op->flags) { ecs_strbuf_append(buf, " flags=0x%x", op->flags); } flecs_irs_payload_to_buf(script, op, buf); ecs_strbuf_appendch(buf, '\n'); } } #endif #ifdef FLECS_SCRIPT #include #define flecs_ir_error(vm, node, ...)\ flecs_script_eval_error_(&(vm)->v, (const ecs_script_node_t*)(node), __VA_ARGS__) #define flecs_ir_expr_error(vm, node, ...)\ flecs_expr_visit_error_(&(vm)->v.base.script->pub, node, __VA_ARGS__) #define flecs_ir_reg(vm, index) (&(vm)->regs[(vm)->reg_base + (index)]) #ifdef FLECS_SCRIPT_IR_PROFILE int64_t flecs_ir_profile_ops[EcsIrOpKindLast]; int64_t flecs_ir_profile_stats[EcsIrProfileLast]; #define flecs_ir_prof(kind) flecs_ir_profile_stats[kind] ++ #else #define flecs_ir_prof(kind) #endif typedef struct flecs_ir_owned_t { void *ptr; const ecs_type_info_t *ti; int32_t reg; } flecs_ir_owned_t; #define FLECS_IR_SCRATCH_SIZE (64 * 1024) static void flecs_ir_regs_ensure( ecs_script_ir_vm_t *vm, int32_t count) { if (count <= vm->reg_count) { return; } int32_t new_count = count < 16 ? 16 : count; vm->regs = ecs_os_realloc_n(vm->regs, ecs_script_ir_reg_t, new_count); ecs_os_memset(&vm->regs[vm->reg_count], 0, (new_count - vm->reg_count) * ECS_SIZEOF(ecs_script_ir_reg_t)); vm->reg_count = new_count; } static void* flecs_ir_arena_alloc( ecs_script_ir_vm_t *vm, ecs_script_ir_arena_t *arena, int32_t size, int32_t align) { if (!arena->data) { int32_t initial = vm->can_suspend ? 4096 : FLECS_IR_SCRATCH_SIZE; arena->size = size > initial ? size : initial; arena->data = ecs_os_malloc(arena->size); } int32_t top = (arena->top + (align - 1)) & ~(align - 1); if (top + size <= arena->size) { arena->top = top + size; return arena->data + top; } void *ptr = ecs_os_malloc(size); ecs_vec_append_t(NULL, &arena->heap, void*)[0] = ptr; return ptr; } static void flecs_ir_arena_restore( ecs_script_ir_arena_t *arena, int32_t top, int32_t heap_count) { void **heap = arena->heap.array; for (int32_t i = heap_count; i < arena->heap.count; i ++) { ecs_os_free(heap[i]); } arena->heap.count = heap_count; arena->top = top; } static void flecs_ir_arena_init( ecs_script_ir_arena_t *arena) { arena->data = NULL; arena->size = 0; arena->top = 0; ecs_vec_init_t(NULL, &arena->heap, void*, 0); } static void flecs_ir_arena_fini( ecs_script_ir_arena_t *arena) { ecs_vec_fini_t(NULL, &arena->heap, void*); ecs_os_free(arena->data); arena->data = NULL; } static inline ecs_script_var_t* flecs_ir_var_declare( ecs_script_ir_vm_t *vm) { ecs_script_vars_t *vars = vm->v.vars; ecs_script_var_t *var; if (vars->vars.count < vars->vars.size) { var = &((ecs_script_var_t*)vars->vars.array)[vars->vars.count ++]; } else { var = ecs_vec_append_t(vars->allocator, &vars->vars, ecs_script_var_t); } var->name = NULL; var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; var->sp = vars->vars.count + vars->sp - 1; var->is_const = false; var->owned = false; return var; } static void* flecs_ir_var_alloc( ecs_script_ir_vm_t *vm, const ecs_type_info_t *ti) { void *ptr = flecs_ir_arena_alloc( vm, &vm->vscratch, ti->size, ti->alignment); if (ti->hooks.ctor) { ti->hooks.ctor(ptr, 1, ti); } else { ecs_os_memset(ptr, 0, ti->size); } return ptr; } static void* flecs_ir_reg_alloc_w_init( ecs_script_ir_vm_t *vm, ecs_script_ir_reg_t *reg, const ecs_type_info_t *ti, bool init) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); void *ptr = flecs_ir_arena_alloc( vm, &vm->scratch, ti->size, ti->alignment); if (init) { if (ti->hooks.ctor) { ti->hooks.ctor(ptr, 1, ti); } else { ecs_os_memset(ptr, 0, ti->size); } } if (ti->hooks.dtor) { flecs_ir_owned_t *o = ecs_vec_append_t( NULL, &vm->owned, flecs_ir_owned_t); o->ptr = ptr; o->ti = ti; o->reg = (int32_t)(reg - vm->regs); } reg->value.ptr = ptr; reg->value.type = ti->component; reg->ti = ti; reg->storage = ptr; reg->owned = true; return ptr; } static void* flecs_ir_reg_alloc( ecs_script_ir_vm_t *vm, ecs_script_ir_reg_t *reg, const ecs_type_info_t *ti) { return flecs_ir_reg_alloc_w_init(vm, reg, ti, true); } static void* flecs_ir_reg_alloc_raw( ecs_script_ir_vm_t *vm, ecs_script_ir_reg_t *reg, const ecs_type_info_t *ti) { return flecs_ir_reg_alloc_w_init(vm, reg, ti, false); } static void* flecs_ir_reg_out( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, ecs_script_ir_reg_t *reg) { const ecs_expr_node_t *node = op->node; if (op->flags & EcsIrInPlace) { ecs_assert(reg->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); reg->value.type = node->type; reg->ti = node->type_info; return reg->value.ptr; } return flecs_ir_reg_alloc(vm, reg, node->type_info); } static inline void* flecs_ir_reg_out_raw( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, ecs_script_ir_reg_t *reg) { const ecs_expr_node_t *node = op->node; const ecs_type_info_t *ti = node->type_info; if (op->flags & EcsIrInPlace) { ecs_assert(reg->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); reg->value.type = node->type; reg->ti = ti; return reg->value.ptr; } if (!ti->hooks.dtor && ti->size <= 16) { int32_t top = (vm->scratch.top + 15) & ~15; if (top + 16 <= vm->scratch.size) { void *out = vm->scratch.data + top; vm->scratch.top = top + 16; reg->value.ptr = out; reg->value.type = node->type; reg->ti = ti; reg->storage = out; reg->owned = true; return out; } } return flecs_ir_reg_alloc_raw(vm, reg, ti); } static void flecs_ir_reg_borrow( ecs_script_ir_reg_t *reg, const ecs_expr_node_t *node, ecs_entity_t type, void *ptr) { reg->value.type = type; reg->value.ptr = ptr; reg->ti = node->type_info; reg->owned = false; } static ecs_script_ir_frame_t* flecs_ir_frame_at( const ecs_script_ir_vm_t *vm, int32_t frame_index) { uint32_t index = flecs_ito(uint32_t, frame_index); return &vm->frames[index / ECS_SCRIPT_IR_FRAME_CHUNK_SIZE] [index % ECS_SCRIPT_IR_FRAME_CHUNK_SIZE]; } static ecs_script_ir_frame_t* flecs_ir_frame_push( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_kind_t kind, int32_t pc) { ecs_assert(vm->frame_count < ECS_SCRIPT_IR_MAX_FRAMES, ECS_INTERNAL_ERROR, NULL); uint32_t chunk = (uint32_t)vm->frame_count / ECS_SCRIPT_IR_FRAME_CHUNK_SIZE; if (!vm->frames[chunk]) { vm->frames[chunk] = ecs_os_malloc_n( ecs_script_ir_frame_t, ECS_SCRIPT_IR_FRAME_CHUNK_SIZE); } ecs_script_ir_frame_t *frame = flecs_ir_frame_at(vm, vm->frame_count ++); frame->kind = (int16_t)kind; frame->state = 0; frame->pc = pc; return frame; } static ecs_script_ir_frame_t* flecs_ir_frame_top( ecs_script_ir_vm_t *vm) { ecs_assert(vm->frame_count > 0, ECS_INTERNAL_ERROR, NULL); return flecs_ir_frame_at(vm, vm->frame_count - 1); } static void flecs_ir_expr_release( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_t *frame) { int32_t owned_count = vm->owned.count; if (owned_count > frame->u.expr.owned_count) { flecs_ir_owned_t *owned = ecs_vec_first(&vm->owned); int32_t i; for (i = owned_count - 1; i >= frame->u.expr.owned_count; i --) { ecs_script_ir_reg_t *reg = &vm->regs[owned[i].reg]; if (reg->owned && reg->storage == owned[i].ptr) { flecs_type_info_dtor(owned[i].ptr, 1, owned[i].ti); reg->owned = false; } } ecs_vec_set_count_t(NULL, &vm->owned, flecs_ir_owned_t, frame->u.expr.owned_count); } flecs_ir_arena_restore(&vm->scratch, frame->u.expr.scratch_top, frame->u.expr.heap_count); int32_t strbuf_count = vm->strbufs.count; if (strbuf_count > frame->u.expr.strbuf_count) { while (strbuf_count > frame->u.expr.strbuf_count) { ecs_strbuf_reset(ecs_vec_get_t( &vm->strbufs, ecs_strbuf_t, strbuf_count - 1)); strbuf_count --; } ecs_vec_set_count_t(NULL, &vm->strbufs, ecs_strbuf_t, strbuf_count); } if (vm->cursors.count != frame->u.expr.cursor_count) { ecs_vec_set_count_t(NULL, &vm->cursors, ecs_meta_cursor_t, frame->u.expr.cursor_count); } } static void flecs_ir_expr_begin( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, int32_t pc) { ecs_script_ir_frame_t *frame = flecs_ir_frame_push(vm, EcsIrFrameExpr, pc); frame->u.expr.scratch_top = vm->scratch.top; frame->u.expr.owned_count = vm->owned.count; frame->u.expr.heap_count = vm->scratch.heap.count; frame->u.expr.strbuf_count = vm->strbufs.count; frame->u.expr.cursor_count = vm->cursors.count; if (op->node && ((const ecs_script_node_t*)op->node)->kind == EcsAstConst) { frame->state = 1; } } static void flecs_ir_expr_end( ecs_script_ir_vm_t *vm) { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); ecs_assert(frame->kind == EcsIrFrameExpr, ECS_INTERNAL_ERROR, NULL); flecs_ir_expr_release(vm, frame); vm->frame_count --; } static void flecs_ir_vars_truncate( ecs_script_ir_vm_t *vm, int32_t count, int32_t vscratch_top, int32_t vheap_count) { ecs_script_vars_t *vars = vm->v.vars; int32_t i, cur = vars->vars.count; if (cur > count) { ecs_script_var_t *array = vars->vars.array; for (i = count; i < cur; i ++) { ecs_script_var_t *var = &array[i]; if (var->owned && var->value.ptr && var->type_info && var->type_info->hooks.dtor) { flecs_type_info_dtor(var->value.ptr, 1, var->type_info); } } vars->vars.count = count; } flecs_ir_arena_restore(&vm->vscratch, vscratch_top, vheap_count); } static void flecs_ir_scope_leave( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_t *frame) { ecs_script_eval_visitor_t *v = &vm->v; if (frame->u.scope.var_count != -1) { flecs_ir_vars_truncate(vm, frame->u.scope.var_count, frame->u.scope.vscratch_top, frame->u.scope.vheap_count); } v->parent = frame->u.scope.state.parent; v->scope_slot = frame->u.scope.state.scope_slot; } static void flecs_ir_for_leave( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_t *frame) { flecs_script_for_state_t *state = &frame->u.for_.state; flecs_ir_vars_truncate(vm, state->var_base, frame->u.for_.vscratch_top, frame->u.for_.vheap_count); vm->v.for_slot = state->for_slot; vm->v.force = state->force; } static void flecs_ir_block_leave( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_t *frame) { if (frame->u.block.vars) { flecs_ir_vars_truncate(vm, 0, 0, 0); vm->v.vars = ecs_script_vars_pop(vm->v.vars); frame->u.block.vars = false; } } static void flecs_ir_frame_leave( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_t *frame) { ecs_script_eval_visitor_t *v = &vm->v; switch(frame->kind) { case EcsIrFrameScope: flecs_ir_scope_leave(vm, frame); break; case EcsIrFrameEntity: flecs_script_eval_entity_leave(v, &frame->u.entity); break; case EcsIrFrameWith: flecs_script_eval_with_leave(v, &frame->u.with); break; case EcsIrFramePairScope: flecs_script_eval_pair_scope_leave(v, &frame->u.pair_scope); break; case EcsIrFrameIf: v->force = frame->u.if_.force; break; case EcsIrFrameFor: flecs_ir_for_leave(vm, frame); break; case EcsIrFrameTry: case EcsIrFrameWhile: break; case EcsIrFrameBlock: flecs_ir_block_leave(vm, frame); break; case EcsIrFrameExpr: if (frame->state == 1) { const ecs_script_ir_op_t *op = &vm->ops[frame->pc]; const ecs_script_var_node_t *node = op->node; flecs_ir_error(vm, node, "failed to evaluate expression for const variable '%s'", node->name); } flecs_ir_expr_release(vm, frame); break; } } static void flecs_ir_mark( ecs_script_ir_vm_t *vm, int32_t index); static void flecs_ir_set_dirty( ecs_script_ir_vm_t *vm) { if (vm->dirty) { return; } vm->dirty = true; int32_t i, count = vm->pending_marks.count; const int32_t *marks = vm->pending_marks.array; for (i = 0; i < count; i ++) { flecs_ir_mark(vm, marks[i]); } vm->pending_marks.count = 0; } static inline bool flecs_ir_stmt_runs( const ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_script_eval_visitor_t *v = &vm->v; return !(op->flags & EcsIrStmtSkip) && (v->force || (op->flags & EcsIrStmtAlways) || (op->imm.u64 & v->input) || (v->internal && (v->internal & ((const ecs_script_node_t*)op->node)->internal))); } static inline void flecs_ir_stmt_skip( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { flecs_ir_prof(EcsIrProfileStmtSkipped); if (op->c != -1) { const ecs_script_region_t *region = ecs_vec_get_t( &vm->v.base.script->regions, ecs_script_region_t, op->c); if (vm->dirty || region->for_count) { flecs_ir_mark(vm, op->c); } else { ecs_vec_append_t(NULL, &vm->pending_marks, int32_t)[0] = op->c; } } } static void flecs_ir_mark( ecs_script_ir_vm_t *vm, int32_t index) { if (index < 0) { const ecs_script_ir_op_t *block = &vm->ops[-index - 1]; for (int32_t pc = -index; pc < block->b; pc = vm->ops[pc].b) { int32_t marks = vm->ops[pc].c; if (marks != -1) { flecs_ir_mark(vm, marks); } } return; } ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_region_t *region = ecs_vec_get_t( &v->base.script->regions, ecs_script_region_t, index); flecs_script_state_mark(v->state, region, v->visit); } static ecs_entity_t flecs_ir_lookup_name( ecs_script_ir_vm_t *vm, const char *name) { return flecs_script_lookup_name(&vm->v, name); } static const char* flecs_ir_name( ecs_script_ir_vm_t *vm, int32_t reg) { return reg != -1 ? *(char**)flecs_ir_reg(vm, reg)->value.ptr : NULL; } static inline int flecs_ir_id_get( ecs_script_ir_vm_t *vm, const ecs_script_ir_id_t *desc, const void *node, ecs_id_t *out) { if (!desc->dynamic) { *out = desc->eval; return 0; } if (desc->first_reg == -1 && desc->first_sp == -1 && desc->index_reg == -1) { ecs_entity_t first = desc->first_symbol != -1 ? flecs_script_symbol_entity(&vm->v, desc->first_symbol) : desc->first_eval; if (first && first != EcsWildcard && first != EcsAny) { if (!desc->has_second) { *out = desc->flag | first; return 0; } if (desc->second_reg == -1 && desc->second_sp == -1) { ecs_entity_t second = desc->second_symbol != -1 ? flecs_script_symbol_entity(&vm->v, desc->second_symbol) : desc->second_eval; if (second && second != EcsWildcard && second != EcsAny) { *out = desc->flag | ecs_pair(first, second); return 0; } } } } return flecs_script_eval_id_resolve(&vm->v, node, desc->id, flecs_ir_name(vm, desc->first_reg), flecs_ir_name(vm, desc->second_reg), desc->index_reg != -1 ? *(int32_t*)flecs_ir_reg(vm, desc->index_reg)->value.ptr : 0, out); } static const ecs_script_ir_id_t* flecs_ir_id_desc( ecs_script_ir_vm_t *vm, int32_t index) { return ecs_vec_get_t(&vm->ir->ids, ecs_script_ir_id_t, index); } static void flecs_ir_id_cache( ecs_script_ir_vm_t *vm, const ecs_script_ir_id_t *desc, ecs_id_t id) { ecs_script_ir_id_t *mut = ECS_CONST_CAST(ecs_script_ir_id_t*, desc); mut->cache_id = id; mut->cache_visitor = !ECS_IS_PAIR(id) && ecs_has(vm->v.world, id, EcsScriptVisitor); ecs_component_record_t *cr = flecs_components_get(vm->v.world, id); mut->cache_ti = cr ? cr->type_info : NULL; } static const ecs_type_info_t* flecs_ir_id_type_info( ecs_script_ir_vm_t *vm, const ecs_script_ir_id_t *desc, const void *node, ecs_id_t id) { if (desc->ti) { return desc->ti; } if (desc->cache_id != id) { flecs_ir_id_cache(vm, desc, id); } if (desc->cache_ti) { return desc->cache_ti; } return flecs_script_get_type_info(&vm->v, node, id); } static int flecs_ir_tag( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); ecs_id_t id; if (flecs_ir_id_get(vm, desc, op->node, &id)) { return -1; } flecs_ir_prof(EcsIrProfileTagAdd); return flecs_script_eval_tag_id(&vm->v, op->node, id, desc->resolved); } static int flecs_ir_component_visitor( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, bool *handled) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); const ecs_script_component_t *node = op->node; *handled = false; ecs_id_t id; if (flecs_ir_id_get(vm, desc, node, &id)) { return -1; } const EcsScriptVisitor *visitor = NULL; if (op->flags & EcsIrVisitorStatic) { visitor = ecs_get(v->world, id, EcsScriptVisitor); } else if (!ECS_IS_PAIR(id)) { if (node->id.interface && !ecs_is_alive(v->world, id)) { return 0; } if (desc->cache_id != id) { flecs_ir_id_cache(vm, desc, id); } if (desc->cache_visitor) { visitor = ecs_get(v->world, id, EcsScriptVisitor); } } if (!visitor) { return 0; } v->base.nodes[0] = ECS_CONST_CAST(ecs_script_node_t*, (const void*)node); v->base.depth = 1; int result = flecs_script_eval_component_visit(v, node, id, visitor); v->base.depth = 0; *handled = true; return result; } static int flecs_ir_component_begin( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, ecs_id_t *id_out) { const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); const ecs_script_component_t *node = op->node; if (node->id.interface && !node->id.index_expr) { if (flecs_script_eval_interface_id(&vm->v, node, &node->id, id_out)) { return -1; } } else if (flecs_ir_id_get(vm, desc, node, id_out)) { return -1; } return flecs_script_eval_component_check( &vm->v, node, *id_out, desc->resolved); } static int flecs_ir_init_type_check( ecs_script_ir_vm_t *vm, const ecs_script_ir_id_t *desc, const ecs_script_component_t *node, const ecs_type_info_t *ti) { if (!desc->ti && node->expr && ti->component != node->expr->type && (node->expr->kind == EcsExprInitializer || node->expr->kind == EcsExprEmptyInitializer) && !flecs_struct_is_derived_from(vm->v.world, ti->component, node->expr->type)) { flecs_ir_expr_error(vm, node->expr, "failed to write to output"); return -1; } return 0; } static int flecs_ir_component_alloc( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); const ecs_script_component_t *node = op->node; ecs_id_t id; if (flecs_ir_component_begin(vm, op, &id)) { return -1; } const ecs_type_info_t *ti = flecs_ir_id_type_info(vm, desc, node, id); if (!ti) { return -1; } if (flecs_ir_init_type_check(vm, desc, node, ti)) { return -1; } ecs_script_ir_reg_t *tmp = flecs_ir_reg(vm, op->b); void *ptr = flecs_ir_reg_alloc(vm, tmp, ti); if (node->id.interface && v->id_ref && v->id_ref->value) { if (flecs_script_template_ref_apply(v->world, v->id_ref, ti, ptr)) { return -1; } } else if (op->flags & EcsIrComponentPartial) { ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, id); const void *existing = ecs_get_id(v->world, src, id); if (existing) { ecs_ptr_copy_w_type_info(v->world, ti, ptr, existing); } } return 0; } static int flecs_ir_value_to( ecs_script_ir_vm_t *vm, ecs_script_ir_reg_t *src, ecs_entity_t dst_type, void *dst_ptr, const ecs_type_info_t *dst_ti) { ecs_world_t *world = vm->v.world; if (src->value.ptr == dst_ptr) { return 0; } if (src->value.type == dst_type) { if (src->owned) { flecs_type_info_move_dtor(dst_ptr, src->value.ptr, 1, dst_ti); src->owned = false; } else { flecs_type_info_copy(dst_ptr, src->value.ptr, 1, dst_ti); } return 0; } ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst_type, dst_ptr); if (ecs_meta_set_value(&cur, &src->value)) { return -1; } return 0; } static int flecs_ir_component_end( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); const ecs_script_component_t *node = op->node; ecs_id_t id; if (op->b == -1) { if (flecs_ir_component_begin(vm, op, &id)) { return -1; } return flecs_script_eval_component_add(v, node, id); } if (flecs_ir_id_get(vm, desc, node, &id)) { return -1; } ecs_script_ir_reg_t *tmp = flecs_ir_reg(vm, op->c); const ecs_type_info_t *ti = tmp->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); void *value = tmp->storage; ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); { ecs_script_ir_reg_t *val = flecs_ir_reg(vm, op->b); if (flecs_ir_value_to(vm, val, ti->component, value, ti)) { flecs_ir_expr_error(vm, node->expr, "failed to write to output"); return -1; } } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, id); flecs_ir_prof(EcsIrProfileComponentSet); if (desc->needs_set || (!desc->ti && ti->hooks.on_replace)) { ecs_set_id(v->world, src, id, flecs_itosize(ti->size), value); } else { void *dst = ecs_ensure_id(v->world, src, id, flecs_itosize(ti->size)); flecs_type_info_move_dtor(dst, value, 1, ti); tmp->owned = false; ecs_modified_id(v->world, src, id); } flecs_script_track_component(v, desc->component_slot, id); return 0; } static int flecs_ir_with_tag( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_id_t id; if (flecs_ir_id_get(vm, flecs_ir_id_desc(vm, op->a), op->node, &id)) { return -1; } return flecs_script_eval_with_tag_id(&vm->v, op->node, id); } static int flecs_ir_with_component_begin( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); const ecs_script_component_t *node = op->node; ecs_id_t id; if (flecs_ir_id_get(vm, desc, node, &id)) { return -1; } const ecs_type_info_t *ti = desc->ti; if (!ti) { ti = flecs_script_get_type_info(v, node, id); } ecs_value_t *value = flecs_script_with_append(v, ti); value->type = id; value->ptr = NULL; if (op->b == -1) { return 0; } if (!ti) { return -1; } if (flecs_ir_init_type_check(vm, desc, node, ti)) { return -1; } value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); flecs_type_info_ctor(value->ptr, 1, ti); if (node->id.interface && flecs_script_template_ref_apply( v->world, v->id_ref, ti, value->ptr)) { return -1; } ecs_script_ir_reg_t *tmp = flecs_ir_reg(vm, op->b); tmp->value.type = ti->component; tmp->value.ptr = value->ptr; tmp->ti = ti; tmp->owned = false; return 0; } static int flecs_ir_with_component_end( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_component_t *node = op->node; ecs_script_ir_reg_t *tmp = flecs_ir_reg(vm, op->c); const ecs_type_info_t *ti = tmp->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_value_t *with_value = &ecs_vec_last_t( &v->r->with, ecs_script_with_value_t)->value; ecs_script_ir_reg_t *val = flecs_ir_reg(vm, op->b); if (flecs_ir_value_to(vm, val, ti->component, with_value->ptr, ti)) { flecs_ir_expr_error(vm, node->expr, "failed to write to output"); return -1; } return 0; } static int flecs_ir_reg_to_i32( ecs_script_ir_vm_t *vm, int32_t index, int32_t *out) { ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, index); if (reg->value.type == ecs_id(ecs_i32_t)) { *out = *(int32_t*)reg->value.ptr; return 0; } ecs_meta_cursor_t cur = ecs_meta_cursor( vm->v.world, ecs_id(ecs_i32_t), out); return ecs_meta_set_value(&cur, ®->value); } static int flecs_ir_for_enter( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, flecs_script_for_state_t *state) { ecs_script_eval_visitor_t *v = &vm->v; ecs_script_for_t *node = ECS_CONST_CAST( ecs_script_for_t*, (const void*)op->node); const ecs_value_t *collection = NULL; flecs_script_for_prepare(v, node, state); if (node->expr) { collection = &flecs_ir_reg(vm, op->a)->value; } else if (flecs_ir_reg_to_i32(vm, op->a, &state->from)) { flecs_ir_expr_error(vm, node->from, "failed to write to output"); return -1; } else if (flecs_ir_reg_to_i32(vm, op->b, &state->count)) { flecs_ir_expr_error(vm, node->to, "failed to write to output"); return -1; } if (flecs_script_for_init(v, node, state, collection)) { return -1; } state->vars = v->vars; state->var_base = v->vars->vars.count; int32_t i; for (i = 0; i < node->loop_var_count; i ++) { ecs_script_var_t *var = flecs_ir_var_declare(vm); var->value.type = state->var_types[i]; if (i != state->elem_var || state->kind == FlecsScriptForRange) { const ecs_type_info_t *ti = ecs_get_type_info( v->world, var->value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_ir_var_alloc(vm, ti); var->type_info = ti; var->owned = true; } } v->for_slot = node->for_slot; v->force = true; return 0; } static int flecs_ir_const_end( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_var_node_t *node = op->node; ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); ecs_entity_t type = node->eval_type; ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = op->imm.ptr; if (!ti) { ti = ecs_get_type_info(v->world, type); } if (!ti) { frame->state = 0; flecs_ir_error(vm, node, "invalid type %s for variable '%s'", flecs_errstr(ecs_get_path(v->world, type)), node->name); return -1; } ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, op->a); if (op->b >= 0 && op->b < ecs_vec_count(&v->state->computed)) { ecs_script_computed_t *slot = ecs_vec_get_t( &v->state->computed, ecs_script_computed_t, op->b); const void *src = reg->value.ptr; void *tmp = NULL; if (reg->value.type != type) { tmp = flecs_ir_var_alloc(vm, ti); if (flecs_ir_value_to(vm, reg, type, tmp, ti)) { frame->state = 0; flecs_ir_expr_error(vm, node->expr, "failed to write to output"); return -1; } src = tmp; } flecs_script_computed_store(v, slot, op->b, ti, src); if (tmp && ti->hooks.dtor) { flecs_type_info_dtor(tmp, 1, ti); } frame->state = 0; flecs_script_const_var_init( flecs_ir_var_declare(vm), ti, type, slot->ptr, false); return 0; } void *ptr = flecs_ir_var_alloc(vm, ti); if (reg->value.type == type && !ti->hooks.copy && !ti->hooks.move) { if (ti->size == 8) { ecs_os_memcpy(ptr, reg->value.ptr, 8); } else if (ti->size == 4) { ecs_os_memcpy(ptr, reg->value.ptr, 4); } else if (ti->size == 1) { *(uint8_t*)ptr = *(uint8_t*)reg->value.ptr; } else { ecs_os_memcpy(ptr, reg->value.ptr, ti->size); } } else if (flecs_ir_value_to(vm, reg, type, ptr, ti)) { flecs_ir_expr_error(vm, node->expr, "failed to write to output"); if (ti->hooks.dtor) { flecs_type_info_dtor(ptr, 1, ti); } return -1; } frame->state = 0; flecs_script_const_var_init(flecs_ir_var_declare(vm), ti, type, ptr, true); return 0; } static int flecs_ir_annot( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_annot_t *node = op->node; if (op->flags & EcsIrAnnotNoTarget) { flecs_ir_error(vm, node, "missing target for @%s annotation", node->name); return -1; } if (op->flags & EcsIrAnnotBadTarget) { flecs_ir_error(vm, node, "target of @%s annotation must be an entity or template", node->name); return -1; } ecs_vec_append_t(v->r->allocator, &v->r->annot, ecs_script_annot_t*)[0] = ECS_CONST_CAST( ecs_script_annot_t*, node); return 0; } static int flecs_ir_load_symbol( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, ecs_entity_t entity) { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); if (entity && (op->flags & EcsIrIdDirect)) { *(ecs_entity_t*)flecs_ir_reg_out_raw(vm, op, dst) = entity; return 0; } flecs_ir_reg_out(vm, op, dst); return flecs_expr_identifier_set( &vm->v.base.script->pub, op->node, entity, &dst->value); } static int flecs_ir_assign( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; ecs_script_assign_t *node = ECS_CONST_CAST( ecs_script_assign_t*, (const void*)op->node); ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, op->a); const ecs_type_info_t *ti = ecs_get_type_info(v->world, node->eval_type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); const void *src = reg->value.ptr; void *tmp = NULL; if (reg->value.type != node->eval_type) { tmp = ecs_ptr_new_w_type_info(v->world, ti); if (flecs_ir_value_to(vm, reg, node->eval_type, tmp, ti)) { flecs_ir_expr_error(vm, node->expr, "failed to evaluate expression for assignment to '%s'", node->name); ecs_ptr_free_w_type_info(v->world, ti, tmp); return -1; } src = tmp; } int result = flecs_script_assign_value(v, node, src); if (tmp) { ecs_ptr_free_w_type_info(v->world, ti, tmp); } return result; } static int flecs_ir_load_var( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_variable_t *node = op->node; const ecs_script_vars_t *vars = vm->v.vars; const ecs_script_var_t *var = NULL; if (vars) { int32_t sp = op->b; if (sp >= vars->sp && (sp - vars->sp) < vars->vars.count) { var = &((ecs_script_var_t*)vars->vars.array)[sp - vars->sp]; } else { var = ecs_script_vars_from_sp(vars, sp); } } if (!var || !var->value.ptr) { flecs_expr_var_check(&vm->v.base.script->pub, node, var); return -1; } ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), &node->node, var->value.type, var->value.ptr); return 0; } static int flecs_ir_load_global( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_node_t *node = op->node; ecs_id_t component = (op->flags & EcsIrGlobalMut) ? ecs_id(EcsScriptMutVar) : ecs_id(EcsScriptConstVar); const EcsScriptConstVar *ptr = ecs_get_id( vm->v.world, op->imm.entity, component); ecs_value_t value; if (ptr) { value = ptr->value; } else { value = flecs_script_global_var_get(vm->v.world, op->imm.entity, NULL); } ecs_assert(value.type == node->type, ECS_INTERNAL_ERROR, NULL); ecs_assert(value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), node, value.type, value.ptr); return 0; } static int flecs_ir_element( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_element_t *node = op->node; ecs_type_kind_t kind = EcsArrayType; if (op->flags & EcsIrElementMap) { kind = EcsMapType; } else if (op->flags & EcsIrElementVector) { kind = EcsVectorType; } void *ptr; if (flecs_expr_element_get(&vm->v.base.script->pub, node, kind, &flecs_ir_reg(vm, op->b)->value, &flecs_ir_reg(vm, op->c)->value, &ptr)) { return -1; } flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), &node->node, node->node.type, ptr); return 0; } static int flecs_ir_component_get( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_element_t *node = op->node; const void *ptr = flecs_expr_component_get(&vm->v.base.script->pub, &vm->v, node, *(ecs_entity_t*)flecs_ir_reg(vm, op->b)->value.ptr, op->imm.entity); if (!ptr) { return -1; } flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), &node->node, node->node.type, ECS_CONST_CAST(void*, ptr)); return 0; } static int flecs_ir_has( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_entity_t entity = *(ecs_entity_t*)flecs_ir_reg(vm, op->b)->value.ptr; bool *out = flecs_ir_reg_out_raw(vm, op, dst); *out = flecs_expr_has_eval( &vm->v.base.script->pub, &vm->v, op->node, entity); return 0; } static int flecs_ir_binary( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_binary_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *left = flecs_ir_reg(vm, op->b); ecs_script_ir_reg_t *right = flecs_ir_reg(vm, op->c); ecs_token_kind_t operator = (ecs_token_kind_t)(op->flags & ~EcsIrBinaryInPlace); void *out; if (op->flags & EcsIrBinaryInPlace) { out = dst->value.ptr; dst->value.type = node->node.type; dst->ti = node->node.type_info; } else { out = flecs_ir_reg_alloc_raw(vm, dst, node->node.type_info); } return flecs_expr_binary_eval(&vm->v.base.script->pub, node, &left->value, &right->value, out, operator); } #define FLECS_IR_INTEGER_OPS(T)\ case EcsTokMod:\ if (!r) {\ flecs_ir_expr_error(vm, node, "division by zero");\ return -1;\ }\ *(T*)out = (T)(l % r); return 0;\ case EcsTokBitwiseAnd: *(T*)out = (T)(l & r); return 0;\ case EcsTokBitwiseOr: *(T*)out = (T)(l | r); return 0;\ case EcsTokShiftLeft: *(T*)out = (T)(l << r); return 0;\ case EcsTokShiftRight: *(T*)out = (T)(l >> r); return 0; #define FLECS_IR_FLOAT_OPS(T) #define FLECS_IR_BINARY_TYPED(NAME, T, W, CMP_ONLY_FLOAT, INT_OPS)\ static int NAME(\ ecs_script_ir_vm_t *vm,\ const ecs_script_ir_op_t *op)\ {\ const ecs_expr_binary_t *node = op->node;\ ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a);\ W l = *(T*)flecs_ir_reg(vm, op->b)->value.ptr;\ W r = *(T*)flecs_ir_reg(vm, op->c)->value.ptr;\ ecs_token_kind_t operator =\ (ecs_token_kind_t)(op->flags & ~EcsIrBinaryInPlace);\ void *out;\ if (op->flags & EcsIrBinaryInPlace) {\ out = dst->value.ptr;\ dst->value.type = node->node.type;\ dst->ti = node->node.type_info;\ } else {\ int32_t top = (vm->scratch.top + 7) & ~7;\ if (top + 8 <= vm->scratch.size) {\ out = vm->scratch.data + top;\ vm->scratch.top = top + 8;\ dst->value.ptr = out;\ dst->value.type = node->node.type;\ dst->ti = node->node.type_info;\ dst->storage = out;\ dst->owned = true;\ } else {\ out = flecs_ir_reg_alloc_raw(vm, dst, node->node.type_info);\ }\ }\ switch(operator) {\ INT_OPS(T)\ case EcsTokAdd: *(T*)out = (T)(l + r); return 0;\ case EcsTokSub: *(T*)out = (T)(l - r); return 0;\ case EcsTokMul: *(T*)out = (T)(l * r); return 0;\ case EcsTokDiv:\ if (CMP_ONLY_FLOAT ? ECS_EQZERO(r) : (r == 0)) {\ flecs_ir_expr_error(vm, node, "division by zero");\ return -1;\ }\ *(T*)out = (T)(l / r); return 0;\ case EcsTokEq: *(bool*)out = l == r; return 0;\ case EcsTokNeq: *(bool*)out = l != r; return 0;\ case EcsTokGt: *(bool*)out = l > r; return 0;\ case EcsTokGtEq: *(bool*)out = l >= r; return 0;\ case EcsTokLt: *(bool*)out = l < r; return 0;\ case EcsTokLtEq: *(bool*)out = l <= r; return 0;\ default: break;\ }\ ecs_value_t out_value = { .type = node->node.type, .ptr = out };\ return flecs_value_binary(&vm->v.base.script->pub, &node->node,\ &flecs_ir_reg(vm, op->b)->value, &flecs_ir_reg(vm, op->c)->value,\ &out_value, operator);\ } FLECS_IR_BINARY_TYPED(flecs_ir_binary_i64, int64_t, int64_t, false, FLECS_IR_INTEGER_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_i32, int32_t, int32_t, false, FLECS_IR_INTEGER_OPS) #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif FLECS_IR_BINARY_TYPED(flecs_ir_binary_f64, double, double, true, FLECS_IR_FLOAT_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_f32, float, float, true, FLECS_IR_FLOAT_OPS) #if defined(ECS_TARGET_GNU) || defined(ECS_TARGET_CLANG) #pragma GCC diagnostic pop #endif FLECS_IR_BINARY_TYPED(flecs_ir_binary_i8, int8_t, int64_t, false, FLECS_IR_INTEGER_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_i16, int16_t, int64_t, false, FLECS_IR_INTEGER_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_u8, uint8_t, uint64_t, false, FLECS_IR_INTEGER_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_u16, uint16_t, uint64_t, false, FLECS_IR_INTEGER_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_u32, uint32_t, uint64_t, false, FLECS_IR_INTEGER_OPS) FLECS_IR_BINARY_TYPED(flecs_ir_binary_u64, uint64_t, uint64_t, false, FLECS_IR_INTEGER_OPS) #undef FLECS_IR_BINARY_TYPED #undef FLECS_IR_INTEGER_OPS #undef FLECS_IR_FLOAT_OPS static int flecs_ir_cast_number( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); const ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->b); void *out = flecs_ir_reg_out_raw(vm, op, dst); op->imm.cast(out, src->value.ptr); return 0; } static int flecs_ir_cast( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_cast_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->b); flecs_ir_reg_out(vm, op, dst); ecs_expr_value_t expr = { .value = src->value, .type_info = src->ti, .owned = src->owned }; if (flecs_value_copy_to(vm->v.world, &dst->value, &expr)) { flecs_ir_expr_error(vm, node, "failed to cast value"); return -1; } return 0; } static int flecs_ir_template_ref( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_eval_visitor_t *v = &vm->v; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_template_ref_t *ref = flecs_ir_reg_out(vm, op, dst); ecs_entity_t tmpl = op->imm.entity; void *value = NULL; if (op->flags & EcsIrTemplateRefVar) { const ecs_script_template_ref_t *src_ref = flecs_ir_reg(vm, op->b)->value.ptr; value = flecs_expr_template_ref_value( &v->base.script->pub, op->node, src_ref); if (!value) { return -1; } tmpl = src_ref->type; } else if (op->b != -1) { ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->b); ecs_assert(src->ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->ti->component == tmpl, ECS_INTERNAL_ERROR, NULL); value = ecs_ptr_new(v->world, tmpl); ecs_ptr_copy_w_type_info(v->world, src->ti, value, src->value.ptr); } flecs_script_template_ref_set(v->world, ref, tmpl, value); return 0; } static int flecs_ir_call( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_function_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); flecs_ir_reg_out(vm, op, dst); int32_t i, count = op->c; ecs_value_t *argv = NULL; if (count) { int32_t size = count * ECS_SIZEOF(ecs_value_t); int32_t top = (vm->scratch.top + 7) & ~7; if (top + size <= vm->scratch.size) { argv = (ecs_value_t*)(void*)(vm->scratch.data + top); vm->scratch.top = top + size; } else { argv = flecs_ir_arena_alloc(vm, &vm->scratch, size, 8); } for (i = 0; i < count; i ++) { argv[i] = flecs_ir_reg(vm, op->b + i)->value; } } return flecs_expr_function_call(&vm->v.base.script->pub, node, (op->flags & EcsIrCallMethod) ? count - 1 : count, argv, &dst->value); } static inline int flecs_ir_init_check( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, const void *error_node, ecs_script_ir_reg_t *dst, const ecs_type_info_t *ti) { return flecs_expr_initializer_check(&vm->v.base.script->pub, error_node, ti->component, flecs_ito(uintptr_t, op->b), ti->size, dst->ti ? flecs_uto(uintptr_t, dst->ti->size) : UINTPTR_MAX); } static int flecs_ir_init_const( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_node_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); const ecs_type_info_t *ti = node->type_info; if (flecs_ir_init_check(vm, op, node, dst, ti)) { return -1; } void *ptr = ECS_OFFSET(dst->value.ptr, op->b); if (!ti->hooks.copy) { ecs_os_memcpy(ptr, op->imm.ptr, ti->size); } else { ecs_ptr_copy_w_type_info(vm->v.world, ti, ptr, op->imm.ptr); } return 0; } static int flecs_ir_init_assign( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_node_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->c); const ecs_type_info_t *ti = node->type_info; if (flecs_ir_init_check(vm, op, op->imm.ptr, dst, ti)) { return -1; } void *ptr = ECS_OFFSET(dst->value.ptr, op->b); if (src->owned) { if (!ti->hooks.move_dtor) { ecs_os_memcpy(ptr, src->value.ptr, ti->size); } else { flecs_type_info_move_dtor(ptr, src->value.ptr, 1, ti); } src->owned = false; } else { if (!ti->hooks.copy) { ecs_os_memcpy(ptr, src->value.ptr, ti->size); } else { flecs_type_info_copy(ptr, src->value.ptr, 1, ti); } } return 0; } static int flecs_ir_init_op( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_node_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->c); const ecs_type_info_t *ti = node->type_info; if (flecs_ir_init_check(vm, op, op->imm.ptr, dst, ti)) { return -1; } ecs_value_t out = { .type = node->type, .ptr = ECS_OFFSET(dst->value.ptr, op->b) }; return flecs_value_binary(&vm->v.base.script->pub, op->imm.ptr, NULL, &src->value, &out, (ecs_token_kind_t)op->flags); } static int flecs_ir_init_swizzle( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_swizzle_t *swizzle = op->imm.ptr; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *left = flecs_ir_reg(vm, op->c); ecs_size_t size = swizzle->elem_size; uintptr_t dst_size = dst->ti ? flecs_uto(uintptr_t, dst->ti->size) : UINTPTR_MAX; int32_t s; for (s = 0; s < swizzle->count; s ++) { uintptr_t offset = flecs_ito(uintptr_t, op->b) + swizzle->dst[s]; if (flecs_expr_initializer_check(&vm->v.base.script->pub, op->node, swizzle->node.type, offset, size, dst_size)) { return -1; } ecs_os_memcpy(ECS_OFFSET(dst->value.ptr, offset), ECS_OFFSET(left->value.ptr, swizzle->src[s]), size); } return 0; } static ecs_meta_cursor_t* flecs_ir_cursor( ecs_script_ir_vm_t *vm) { ecs_assert(ecs_vec_count(&vm->cursors) > 0, ECS_INTERNAL_ERROR, NULL); return ecs_vec_last_t(&vm->cursors, ecs_meta_cursor_t); } static int flecs_ir_str_format( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_node_t *expr = op->node; const ecs_expr_format_t *format = op->imm.ptr; ecs_script_ir_reg_t *value = flecs_ir_reg(vm, op->a); int32_t width = 0, precision = -1; if (op->b != -1) { width = *(int32_t*)flecs_ir_reg(vm, op->b)->value.ptr; } if (op->c != -1) { precision = *(int32_t*)flecs_ir_reg(vm, op->c)->value.ptr; } ecs_strbuf_t *buf = ecs_vec_last_t(&vm->strbufs, ecs_strbuf_t); return flecs_expr_format_value(&vm->v.base.script->pub, expr, &value->value, format, width, precision, buf); } static int flecs_ir_range( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_range_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); void *out = flecs_ir_reg_out(vm, op, dst); return flecs_expr_range_eval(vm->v.world, node->node.type, *(int32_t*)flecs_ir_reg(vm, op->b)->value.ptr, *(int32_t*)flecs_ir_reg(vm, op->c)->value.ptr, out); } static int flecs_ir_match_cmp( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { const ecs_expr_match_t *node = op->node; ecs_script_ir_reg_t *value = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *compare = flecs_ir_reg(vm, op->b); bool result = false; ecs_value_t out = { .type = ecs_id(ecs_bool_t), .ptr = &result }; if (flecs_value_binary(&vm->v.base.script->pub, &node->node, &value->value, &compare->value, &out, EcsTokEq)) { return -1; } vm->cond = result; return 0; } static int flecs_ir_script( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); void *out = flecs_ir_reg_out(vm, op, dst); *(ecs_entity_t*)out = flecs_expr_script_new(vm->v.world, op->imm.ptr); dst->value.type = ecs_id(ecs_entity_t); return 0; } #ifdef FLECS_SCRIPT_ASYNC static int flecs_ir_await_launch( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op) { ecs_expr_function_t *call = flecs_script_await_call( &vm->v, op->node, op->imm.ptr); if (!call) { return -1; } int32_t i, count = op->b; ecs_value_t *argv = count ? ecs_os_alloca_n(ecs_value_t, count) : NULL; for (i = 0; i < count; i ++) { argv[i] = flecs_ir_reg(vm, op->a + i)->value; } flecs_script_await_launch(vm->v.world, &vm->async, call, argv); return 0; } static int flecs_ir_await_poll( ecs_script_ir_vm_t *vm, const ecs_script_ir_op_t *op, bool *suspended) { ecs_script_future_t *future = NULL; int result = flecs_script_await_poll(&vm->v, &vm->async, op->node, &future); *suspended = result == 1; if (result) { return result == 1 ? 0 : -1; } if (op->flags & EcsIrAwaitVar) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_value_t *value = &future->value; ecs_script_var_t *var = ecs_script_vars_declare(v->vars, NULL); const ecs_type_info_t *ti = ecs_get_type_info(v->world, value->type); var->value.type = value->type; var->value.ptr = flecs_ir_var_alloc(vm, ti); var->type_info = ti; var->owned = true; ecs_ptr_copy(v->world, value->type, var->value.ptr, value->ptr); } ecs_script_future_release(future); return result; } static bool flecs_ir_try_catch( ecs_script_ir_vm_t *vm, ecs_script_ir_frame_t *frame) { if (frame->kind != EcsIrFrameTry || frame->state != 1) { return false; } const ecs_script_ir_op_t *op = &vm->ops[frame->pc]; int32_t i = flecs_script_catch(&vm->v, &vm->async, op->node); if (i == -1) { return false; } frame->state = 2; vm->pc = ecs_vec_get_t(&vm->ir->catches, int32_t, op->a + i)[0]; return true; } #endif static bool flecs_ir_unwind( ecs_script_ir_vm_t *vm, int32_t base) { while (vm->frame_count > base) { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); #ifdef FLECS_SCRIPT_ASYNC if (flecs_ir_try_catch(vm, frame)) { return true; } #endif if (frame->kind == EcsIrFrameBlock) { return false; } flecs_ir_frame_leave(vm, frame); vm->frame_count --; } return false; } static bool flecs_ir_continue( ecs_script_ir_vm_t *vm) { int32_t i; for (i = vm->frame_count - 1; i >= 0; i --) { int16_t kind = flecs_ir_frame_at(vm, i)->kind; if (kind == EcsIrFrameFor || kind == EcsIrFrameWhile) { break; } if (kind == EcsIrFrameBlock) { return false; } } if (i < 0) { return false; } while (vm->frame_count > i + 1) { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); flecs_ir_frame_leave(vm, frame); vm->frame_count --; } vm->pc = flecs_ir_frame_at(vm, i)->pc; return true; } static flecs_script_run_status_t flecs_ir_exec( ecs_script_ir_vm_t *vm, int32_t base); static int flecs_ir_run_nested( ecs_script_ir_vm_t *vm, int32_t entry_index, ecs_entity_t *out) { const ecs_script_ir_entry_t *entries = ecs_vec_first(&vm->ir->entries); const ecs_script_ir_entry_t *current = &entries[vm->entry]; const ecs_script_ir_entry_t *entry = &entries[entry_index]; int32_t base = vm->frame_count; ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameBlock, vm->pc); frame->u.block.reg_base = vm->reg_base; frame->u.block.entry = vm->entry; frame->u.block.vars = false; vm->reg_base += current->reg_count; flecs_ir_regs_ensure(vm, vm->reg_base + entry->reg_count); vm->entry = entry_index; vm->pc = entry->pc; vm->last_entity = 0; flecs_script_run_status_t status = flecs_ir_exec(vm, base); ecs_assert(vm->frame_count == base, ECS_INTERNAL_ERROR, NULL); if (status != FlecsScriptRunDone) { return -1; } *out = vm->last_entity; return 0; } static void flecs_ir_block_pop( ecs_script_ir_vm_t *vm) { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); ecs_assert(frame->kind == EcsIrFrameBlock, ECS_INTERNAL_ERROR, NULL); flecs_ir_block_leave(vm, frame); vm->pc = frame->pc; vm->reg_base = frame->u.block.reg_base; vm->entry = frame->u.block.entry; vm->frame_count --; } static flecs_script_run_status_t flecs_ir_exec( ecs_script_ir_vm_t *vm, int32_t base) { ecs_script_eval_visitor_t *v = &vm->v; const ecs_script_ir_op_t *ops = vm->ops; while (vm->frame_count > base) { int32_t pc = vm->pc ++; const ecs_script_ir_op_t *op = &ops[pc]; int res = 0; #ifdef FLECS_SCRIPT_IR_PROFILE flecs_ir_profile_ops[op->kind] ++; #endif switch((ecs_script_ir_op_kind_t)op->kind) { case EcsIrEnd: case EcsIrReturn: { if (op->kind == EcsIrReturn) { ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, op->a); ecs_value_t *result = vm->result; if (result) { if (flecs_ir_value_to(vm, reg, result->type, result->ptr, ecs_get_type_info(v->world, result->type))) { flecs_ir_expr_error(vm, ((const ecs_script_function_node_t*)op->node) ->return_expr, "failed to write to output"); res = -1; break; } } flecs_ir_expr_end(vm); } while (vm->frame_count > base + 1) { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); flecs_ir_frame_leave(vm, frame); vm->frame_count --; } flecs_ir_block_pop(vm); return FlecsScriptRunDone; } case EcsIrJump: vm->pc = op->a; break; case EcsIrStmtBlock: if (v->script_entity && !ecs_is_alive(v->world, v->script_entity)) { flecs_ir_unwind(vm, base); flecs_ir_block_pop(vm); return FlecsScriptRunError; } if (!flecs_ir_stmt_runs(vm, op)) { if (vm->dirty) { flecs_ir_mark(vm, -pc - 1); } else { ecs_vec_append_t(NULL, &vm->pending_marks, int32_t)[0] = -pc - 1; } #ifdef FLECS_SCRIPT_IR_PROFILE flecs_ir_profile_stats[EcsIrProfileStmtSkipped] += op->c; #endif vm->pc = op->b; } break; case EcsIrStmt: { if (v->script_entity && !ecs_is_alive(v->world, v->script_entity)) { flecs_ir_unwind(vm, base); flecs_ir_block_pop(vm); return FlecsScriptRunError; } if (flecs_ir_stmt_runs(vm, op)) { flecs_ir_prof(EcsIrProfileStmtRun); break; } if (op->flags & EcsIrStmtCached) { vm->pc = op->b; break; } flecs_ir_stmt_skip(vm, op); int32_t next = op->b; for (;;) { const ecs_script_ir_op_t *next_op = &ops[next]; if (next_op->kind != EcsIrStmt) { vm->pc = next; break; } if (flecs_ir_stmt_runs(vm, next_op)) { flecs_ir_prof(EcsIrProfileStmtRun); vm->pc = next + 1; break; } if (next_op->flags & EcsIrStmtCached) { vm->pc = next_op->b; break; } flecs_ir_stmt_skip(vm, next_op); next = next_op->b; } break; } case EcsIrMark: flecs_ir_mark(vm, op->c); break; case EcsIrAnnotClear: ecs_vec_clear(&v->r->annot); break; case EcsIrScopeEnter: { ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameScope, pc); frame->u.scope.state.parent = v->parent; frame->u.scope.state.scope_slot = v->scope_slot; frame->u.scope.var_count = -1; v->scope_slot = op->a; if (op->a >= 0 && op->a < ecs_vec_count(&v->state->scope_slots)) { ecs_vec_get_t(&v->state->scope_slots, int32_t, op->a)[0] = v->visit; } if (op->b > 0) { frame->u.scope.var_count = v->vars->vars.count; frame->u.scope.vscratch_top = vm->vscratch.top; frame->u.scope.vheap_count = vm->vscratch.heap.count; } if (v->entity && op->c && (v->force || v->entity->created)) { ecs_entity_t src = v->entity->eval; if (src != EcsVariable) { const ecs_id_t *ids = op->imm.ptr; int32_t i, count = op->c; ecs_table_t *table = ecs_get_table(v->world, src); bool missing = table == NULL; for (i = 0; !missing && i < count; i ++) { if (ecs_search(v->world, table, ids[i], NULL) == -1) { missing = true; } } if (missing) { flecs_ir_prof(EcsIrProfileBatchAdd); flecs_script_scope_add_ids(v, src, ECS_CONST_CAST(ecs_script_scope_t*, op->node), ids, count); } else { flecs_ir_prof(EcsIrProfileBatchSkip); } } } if (op->flags & EcsIrScopeEntity) { ecs_assert(v->entity != NULL, ECS_INTERNAL_ERROR, NULL); v->parent = v->entity->eval; } break; } case EcsIrLeave: { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); ecs_assert(frame->kind == op->a, ECS_INTERNAL_ERROR, NULL); if (frame->kind == EcsIrFrameEntity) { vm->last_entity = frame->u.entity.eval; } flecs_ir_frame_leave(vm, frame); vm->frame_count --; break; } case EcsIrEntityEnter: { flecs_script_entity_state_t state; res = flecs_script_eval_entity_enter_w_name(v, ECS_CONST_CAST( ecs_script_entity_t*, (const void*)op->node), op->a != -1 ? &flecs_ir_reg(vm, op->a)->value : NULL, &state); #ifdef FLECS_SCRIPT_IR_PROFILE if (op->a == -1) { flecs_ir_prof(state.created ? EcsIrProfileEntityCreate : EcsIrProfileEntityReuse); } #endif if (op->flags & EcsIrEndsExpr) { flecs_ir_expr_end(vm); } if (!res) { ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameEntity, pc); frame->u.entity = state; v->entity = &frame->u.entity; } break; } case EcsIrWithEnter: { ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameWith, pc); frame->u.with.with_count = ecs_vec_count(&v->r->with); frame->u.with.cursor = flecs_stack_get_cursor(&v->r->stack); frame->u.with.is_with_scope = v->is_with_scope; frame->u.with.force = v->force; break; } case EcsIrWithBody: { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); v->is_with_scope = true; v->force = flecs_script_eval_force( v, op->node, frame->u.with.force); break; } case EcsIrWithTag: res = flecs_ir_with_tag(vm, op); break; case EcsIrWithComponentBegin: res = flecs_ir_with_component_begin(vm, op); break; case EcsIrWithComponentEnd: res = flecs_ir_with_component_end(vm, op); break; case EcsIrPairScopeEnter: { const ecs_script_ir_id_t *desc = flecs_ir_id_desc(vm, op->a); flecs_script_pair_scope_state_t state; res = flecs_script_eval_pair_scope_enter(v, op->node, flecs_ir_name(vm, desc->first_reg), flecs_ir_name(vm, desc->second_reg), &state); if (op->flags & EcsIrEndsExpr) { flecs_ir_expr_end(vm); } if (!res) { ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFramePairScope, pc); frame->u.pair_scope = state; } break; } case EcsIrIfEnter: { flecs_ir_set_dirty(vm); ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameIf, pc); frame->u.if_.force = v->force; v->force = flecs_script_eval_force(v, op->node, v->force); if (!vm->cond) { vm->pc = op->b; } break; } case EcsIrForEnter: { flecs_ir_set_dirty(vm); ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameFor, vm->pc); frame->u.for_.vscratch_top = vm->vscratch.top; frame->u.for_.vheap_count = vm->vscratch.heap.count; res = flecs_ir_for_enter(vm, op, &frame->u.for_.state); if (res) { vm->frame_count --; } break; } case EcsIrForNext: { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); ecs_assert(frame->kind == EcsIrFrameFor, ECS_INTERNAL_ERROR, NULL); flecs_script_for_state_t *fs = &frame->u.for_.state; if (fs->kind == FlecsScriptForRange && fs->index_var == -1) { int32_t value = fs->from + fs->index; if (value >= fs->count) { vm->pc = op->a; } else { *(int32_t*)((ecs_script_var_t*)fs->vars->vars.array) [fs->var_base + fs->elem_var].value.ptr = value; fs->index ++; } } else if (!flecs_script_for_next(fs)) { vm->pc = op->a; } break; } case EcsIrContinue: if (!flecs_ir_continue(vm)) { flecs_ir_error(vm, op->node, "continue is only allowed inside a loop"); res = -1; } break; case EcsIrWhileEnter: flecs_ir_frame_push(vm, EcsIrFrameWhile, vm->pc); break; case EcsIrWhileNext: if (!vm->cond) { vm->pc = op->a; } break; case EcsIrAssign: res = flecs_ir_assign(vm, op); break; case EcsIrTryEnter: { #ifdef FLECS_SCRIPT_ASYNC if (vm->can_suspend && base == 0) { ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameTry, pc); frame->state = 1; break; } #endif flecs_ir_error(vm, op->node, "try requires resumable script execution"); res = -1; break; } case EcsIrAwaitStart: #ifdef FLECS_SCRIPT_ASYNC if (!vm->can_suspend || base != 0) { flecs_ir_error(vm, op->node, "await requires resumable script execution"); res = -1; break; } if (vm->async.future) { vm->pc = op->b; } break; #else flecs_ir_error(vm, op->node, "await requires resumable script execution"); res = -1; break; #endif case EcsIrAwaitLaunch: #ifdef FLECS_SCRIPT_ASYNC res = flecs_ir_await_launch(vm, op); #else res = -1; #endif break; case EcsIrAwaitPoll: { #ifdef FLECS_SCRIPT_ASYNC bool suspended = false; res = flecs_ir_await_poll(vm, op, &suspended); if (suspended) { vm->pc = pc; return FlecsScriptRunSuspended; } #else res = -1; #endif break; } case EcsIrTag: res = flecs_ir_tag(vm, op); break; case EcsIrComponentBegin: res = flecs_ir_component_alloc(vm, op); break; case EcsIrComponentEnd: res = flecs_ir_component_end(vm, op); break; case EcsIrComponentVisitor: { bool handled = false; res = flecs_ir_component_visitor(vm, op, &handled); if (!res && handled) { vm->pc = op->b; } break; } case EcsIrUsing: case EcsIrModule: case EcsIrTemplate: case EcsIrMutCheck: case EcsIrAsync: res = flecs_script_eval_node(&v->base, ECS_CONST_CAST(ecs_script_node_t*, (const void*)op->node)); break; case EcsIrAnnot: res = flecs_ir_annot(vm, op); break; case EcsIrConstEnd: res = flecs_ir_const_end(vm, op); break; case EcsIrConstCached: { ecs_script_computed_t *slot = op->a < ecs_vec_count(&v->state->computed) ? ecs_vec_get_t(&v->state->computed, ecs_script_computed_t, op->a) : NULL; if (!slot || !slot->valid) { vm->pc = op->b; break; } flecs_script_const_var_init(flecs_ir_var_declare(vm), slot->ti, ((const ecs_script_var_node_t*)op->node)->eval_type, slot->ptr, false); break; } case EcsIrExprBegin: flecs_ir_expr_begin(vm, op, pc); break; case EcsIrExprEnd: flecs_ir_expr_end(vm); break; case EcsIrLoadConst: { const ecs_expr_node_t *node = op->node; flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), node, node->type, op->imm.ptr); break; } case EcsIrLoadVar: res = flecs_ir_load_var(vm, op); break; case EcsIrLoadGlobal: res = flecs_ir_load_global(vm, op); break; case EcsIrLoadSymbol: res = flecs_ir_load_symbol(vm, op, flecs_script_symbol_entity(v, op->b)); break; case EcsIrLookup: res = flecs_ir_load_symbol(vm, op, flecs_ir_lookup_name(vm, op->imm.str)); break; case EcsIrMember: { const ecs_expr_node_t *node = op->node; ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->b); flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), node, node->type, ECS_OFFSET(src->value.ptr, op->c)); break; } case EcsIrSwizzle: { const ecs_expr_swizzle_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->b); void *out = flecs_ir_reg_out_raw(vm, op, dst); ecs_size_t size = node->elem_size; int32_t i; for (i = 0; i < node->count; i ++) { ecs_os_memcpy(ECS_OFFSET(out, i * size), ECS_OFFSET(src->value.ptr, node->src[i]), size); } break; } case EcsIrElement: res = flecs_ir_element(vm, op); break; case EcsIrComponentGet: res = flecs_ir_component_get(vm, op); break; case EcsIrHas: res = flecs_ir_has(vm, op); break; case EcsIrUnary: { const ecs_expr_unary_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->b); flecs_ir_reg_out(vm, op, dst); res = flecs_value_unary(&v->base.script->pub, &src->value, &dst->value, node->operator); break; } case EcsIrBinary: res = flecs_ir_binary(vm, op); break; case EcsIrBinaryI8: res = flecs_ir_binary_i8(vm, op); break; case EcsIrBinaryI16: res = flecs_ir_binary_i16(vm, op); break; case EcsIrBinaryI32: res = flecs_ir_binary_i32(vm, op); break; case EcsIrBinaryI64: res = flecs_ir_binary_i64(vm, op); break; case EcsIrBinaryU8: res = flecs_ir_binary_u8(vm, op); break; case EcsIrBinaryU16: res = flecs_ir_binary_u16(vm, op); break; case EcsIrBinaryU32: res = flecs_ir_binary_u32(vm, op); break; case EcsIrBinaryU64: res = flecs_ir_binary_u64(vm, op); break; case EcsIrBinaryF32: res = flecs_ir_binary_f32(vm, op); break; case EcsIrBinaryF64: res = flecs_ir_binary_f64(vm, op); break; case EcsIrJumpIfFalse: case EcsIrJumpIfTrue: { ecs_script_ir_reg_t *src = flecs_ir_reg(vm, op->a); bool value = *(bool*)src->value.ptr; if (value == (op->kind == EcsIrJumpIfTrue)) { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->c); bool *out = flecs_ir_reg_out_raw(vm, op, dst); *out = value; vm->pc = op->b; } break; } case EcsIrCast: res = flecs_ir_cast(vm, op); break; case EcsIrCastNumber: res = flecs_ir_cast_number(vm, op); break; case EcsIrCall: res = flecs_ir_call(vm, op); break; case EcsIrInitAlloc: { const ecs_expr_node_t *node = op->node; flecs_ir_reg_alloc(vm, flecs_ir_reg(vm, op->a), node->type_info); break; } case EcsIrInitConst: res = flecs_ir_init_const(vm, op); break; case EcsIrInitAssign: res = flecs_ir_init_assign(vm, op); break; case EcsIrInitOp: res = flecs_ir_init_op(vm, op); break; case EcsIrInitSwizzle: res = flecs_ir_init_swizzle(vm, op); break; case EcsIrDynBegin: { const ecs_expr_node_t *node = op->node; ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_meta_cursor_t *cur = ecs_vec_append_t( NULL, &vm->cursors, ecs_meta_cursor_t); *cur = ecs_meta_cursor(v->world, node->type, dst->value.ptr); break; } case EcsIrDynPush: res = ecs_meta_push(flecs_ir_cursor(vm)); break; case EcsIrDynPop: res = ecs_meta_pop(flecs_ir_cursor(vm)); break; case EcsIrDynNext: res = ecs_meta_next(flecs_ir_cursor(vm)); break; case EcsIrDynMember: res = ecs_meta_member(flecs_ir_cursor(vm), op->imm.str); break; case EcsIrDynKey: res = ecs_meta_key(flecs_ir_cursor(vm), &flecs_ir_reg(vm, op->a)->value); break; case EcsIrDynSet: res = ecs_meta_set_value(flecs_ir_cursor(vm), &flecs_ir_reg(vm, op->a)->value); break; case EcsIrDynSwizzle: res = flecs_expr_swizzle_set(flecs_ir_cursor(vm), op->imm.ptr, flecs_ir_reg(vm, op->a)->value.ptr); break; case EcsIrDynEnd: ecs_vec_remove_last(&vm->cursors); break; case EcsIrStrBegin: { ecs_strbuf_t *buf = ecs_vec_append_t( NULL, &vm->strbufs, ecs_strbuf_t); *buf = ECS_STRBUF_INIT; break; } case EcsIrStrFrag: ecs_strbuf_appendstr( ecs_vec_last_t(&vm->strbufs, ecs_strbuf_t), op->imm.str); break; case EcsIrStrValue: { ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, op->a); ecs_assert(reg->value.type == ecs_id(ecs_string_t), ECS_INTERNAL_ERROR, NULL); const char *str = *(char**)reg->value.ptr; if (str) { ecs_strbuf_appendstr( ecs_vec_last_t(&vm->strbufs, ecs_strbuf_t), str); } break; } case EcsIrStrCast: { const void *ptr = flecs_ir_reg(vm, op->a)->value.ptr; ecs_strbuf_t *buf = ecs_vec_last_t(&vm->strbufs, ecs_strbuf_t); union { int64_t i; uint64_t u; double f; } value; if (op->flags == EcsExprNumChar) { ecs_strbuf_append(buf, "%c", *(const char*)ptr); break; } op->imm.cast(&value, ptr); if (op->flags == EcsExprNumSigned) { ecs_strbuf_appendint(buf, value.i); } else if (op->flags == EcsExprNumUnsigned) { ecs_strbuf_append(buf, "%" PRIu64, value.u); } else { ecs_strbuf_append(buf, "%f", value.f); } break; } case EcsIrStrFormat: res = flecs_ir_str_format(vm, op); break; case EcsIrStrEnd: { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_strbuf_t *buf = ecs_vec_last_t(&vm->strbufs, ecs_strbuf_t); char *str = ecs_strbuf_get(buf); ecs_vec_remove_last(&vm->strbufs); char **out = flecs_ir_reg_out(vm, op, dst); if (op->flags & EcsIrInPlace) { ecs_os_free(*out); } *out = str; break; } case EcsIrMatchCmp: res = flecs_ir_match_cmp(vm, op); if (!res && !vm->cond) { vm->pc = op->c; } break; case EcsIrMatchFail: { ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, op->a); char *str = ecs_ptr_to_str(v->world, reg->value.type, reg->value.ptr); flecs_ir_expr_error(vm, op->node, "match value '%s' not handled by case", str); ecs_os_free(str); res = -1; break; } case EcsIrRange: res = flecs_ir_range(vm, op); break; case EcsIrNew: { ecs_entity_t result = 0; res = flecs_ir_run_nested(vm, op->b, &result); if (!res) { ecs_script_ir_reg_t *dst = flecs_ir_reg(vm, op->a); ecs_entity_t *out = flecs_ir_reg_out_raw(vm, op, dst); *out = result; dst->value.type = ecs_id(ecs_entity_t); } break; } case EcsIrScript: res = flecs_ir_script(vm, op); break; case EcsIrTemplateRef: res = flecs_ir_template_ref(vm, op); break; case EcsIrTemplateRefValue: { const ecs_expr_node_t *node = op->node; ecs_script_template_ref_t *ref = flecs_ir_reg(vm, op->b)->value.ptr; flecs_ir_reg_borrow(flecs_ir_reg(vm, op->a), node, node->type, ref->value); break; } case EcsIrToBool: { ecs_script_ir_reg_t *reg = flecs_ir_reg(vm, op->a); if (reg->value.type == ecs_id(ecs_bool_t)) { vm->cond = *(bool*)reg->value.ptr; } else { res = flecs_script_value_to_bool( v, op->node, ®->value, &vm->cond); } break; } case EcsIrOpKindLast: ecs_abort(ECS_INTERNAL_ERROR, "corrupt IR instruction"); } if (res) { if (!flecs_ir_unwind(vm, base)) { #ifdef FLECS_SCRIPT_ASYNC if (vm->async.thrown && base == 0) { flecs_script_report_throw(&vm->v, &vm->async); } #endif flecs_ir_block_pop(vm); return FlecsScriptRunError; } } } return FlecsScriptRunDone; } static void flecs_ir_vm_reset( ecs_script_ir_vm_t *vm, ecs_script_ir_t *ir) { vm->ir = ir; vm->ops = ir ? ecs_vec_first(&ir->ops) : NULL; vm->pc = 0; vm->entry = 0; vm->reg_base = 0; vm->scratch.top = 0; vm->vscratch.top = 0; vm->frame_count = 0; vm->last_entity = 0; vm->result = NULL; vm->cond = false; vm->dirty = false; vm->can_suspend = false; #ifdef FLECS_SCRIPT_ASYNC ecs_os_zeromem(&vm->async); #endif } static void flecs_ir_vm_setup( ecs_script_ir_vm_t *vm, ecs_script_ir_t *ir) { vm->regs = NULL; vm->reg_count = 0; ecs_os_memset(vm->frames, 0, sizeof(vm->frames)); flecs_ir_arena_init(&vm->scratch); flecs_ir_arena_init(&vm->vscratch); ecs_vec_init_t(NULL, &vm->owned, flecs_ir_owned_t, 0); ecs_vec_init_t(NULL, &vm->strbufs, ecs_strbuf_t, 0); ecs_vec_init_t(NULL, &vm->cursors, ecs_meta_cursor_t, 0); ecs_vec_init_t(NULL, &vm->pending_marks, int32_t, 0); flecs_ir_vm_reset(vm, ir); } static void flecs_ir_vm_clear( ecs_script_ir_vm_t *vm) { flecs_script_ir_vm_abandon(vm); int32_t i, count = vm->strbufs.count; for (i = 0; i < count; i ++) { ecs_strbuf_reset(ecs_vec_get_t(&vm->strbufs, ecs_strbuf_t, i)); } vm->strbufs.count = 0; vm->cursors.count = 0; vm->pending_marks.count = 0; flecs_ir_arena_restore(&vm->scratch, 0, 0); flecs_ir_arena_restore(&vm->vscratch, 0, 0); vm->owned.count = 0; #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_fini(&vm->async); #endif } static void flecs_ir_vm_teardown( ecs_script_ir_vm_t *vm) { flecs_ir_vm_clear(vm); int32_t i; for (i = 0; i < (int32_t)(sizeof(vm->frames) / sizeof(vm->frames[0])); i ++) { ecs_os_free(vm->frames[i]); } ecs_vec_fini_t(NULL, &vm->strbufs, ecs_strbuf_t); ecs_vec_fini_t(NULL, &vm->cursors, ecs_meta_cursor_t); ecs_vec_fini_t(NULL, &vm->pending_marks, int32_t); flecs_ir_arena_fini(&vm->scratch); flecs_ir_arena_fini(&vm->vscratch); ecs_vec_fini_t(NULL, &vm->owned, flecs_ir_owned_t); ecs_os_free(vm->regs); vm->regs = NULL; vm->reg_count = 0; } static ecs_script_ir_vm_t* flecs_ir_vm_acquire( ecs_script_runtime_t *r, ecs_script_ir_t *ir) { ecs_script_ir_vm_t *vm; if (r && r->ir_vms.count) { vm = ((ecs_script_ir_vm_t**)r->ir_vms.array)[-- r->ir_vms.count]; flecs_ir_vm_reset(vm, ir); } else { vm = ecs_os_malloc_t(ecs_script_ir_vm_t); flecs_ir_vm_setup(vm, ir); } return vm; } static void flecs_ir_vm_release( ecs_script_runtime_t *r, ecs_script_ir_vm_t *vm) { flecs_ir_vm_clear(vm); if (r) { ecs_vec_append_t(NULL, &r->ir_vms, ecs_script_ir_vm_t*)[0] = vm; } else { flecs_ir_vm_teardown(vm); ecs_os_free(vm); } } void flecs_script_ir_vm_pool_fini( ecs_script_runtime_t *r) { int32_t i, count = r->ir_vms.count; ecs_script_ir_vm_t **vms = r->ir_vms.array; for (i = 0; i < count; i ++) { flecs_ir_vm_teardown(vms[i]); ecs_os_free(vms[i]); } ecs_vec_fini_t(NULL, &r->ir_vms, ecs_script_ir_vm_t*); } ecs_script_ir_vm_t* flecs_script_ir_vm_new( ecs_script_impl_t *script, const ecs_script_eval_desc_t *desc) { ecs_script_ir_vm_t *vm = flecs_ir_vm_acquire( desc ? desc->runtime : NULL, flecs_script_ir_ensure(script)); flecs_script_eval_visit_init(script, &vm->v, desc); return vm; } void flecs_script_ir_vm_free( ecs_script_ir_vm_t *vm, const ecs_script_eval_desc_t *desc) { ecs_script_runtime_t *r = desc ? desc->runtime : NULL; flecs_script_eval_visit_fini(&vm->v, desc); flecs_ir_vm_release(r, vm); } void flecs_script_ir_vm_init( ecs_script_ir_vm_t *vm) { flecs_ir_vm_setup(vm, flecs_script_ir_ensure(vm->v.base.script)); } void flecs_script_ir_vm_abandon( ecs_script_ir_vm_t *vm) { while (vm->frame_count) { ecs_script_ir_frame_t *frame = flecs_ir_frame_top(vm); if (frame->kind == EcsIrFrameExpr) { frame->state = 0; } flecs_ir_frame_leave(vm, frame); vm->frame_count --; } } void flecs_script_ir_vm_fini( ecs_script_ir_vm_t *vm, const ecs_script_eval_desc_t *desc) { flecs_ir_vm_teardown(vm); flecs_script_eval_visit_fini(&vm->v, desc); } flecs_script_run_status_t flecs_script_ir_vm_run( ecs_script_ir_vm_t *vm, const ecs_script_ir_entry_t *entry) { if (!vm->ir || !entry) { return FlecsScriptRunError; } flecs_ir_prof(EcsIrProfileRun); if (!vm->frame_count) { const ecs_script_ir_entry_t *entries = ecs_vec_first(&vm->ir->entries); ecs_script_ir_frame_t *frame = flecs_ir_frame_push( vm, EcsIrFrameBlock, -1); frame->u.block.reg_base = 0; frame->u.block.entry = 0; frame->u.block.vars = true; vm->v.vars = flecs_script_vars_push( vm->v.vars, &vm->v.r->stack, vm->v.r->allocator); vm->entry = (int32_t)(entry - entries); vm->reg_base = 0; vm->pc = entry->pc; vm->last_entity = 0; vm->pending_marks.count = 0; flecs_ir_regs_ensure(vm, entry->reg_count); } return flecs_ir_exec(vm, 0); } flecs_script_run_status_t flecs_script_ir_vm_resume( ecs_script_ir_vm_t *vm) { ecs_assert(vm->frame_count > 0, ECS_INTERNAL_ERROR, NULL); return flecs_ir_exec(vm, 0); } const char* flecs_script_ir_vm_stmt_pos( const ecs_script_ir_vm_t *vm) { if (!vm->frame_count || !vm->ops) { return NULL; } const ecs_script_ir_op_t *op = &vm->ops[vm->pc]; if (op->kind == EcsIrAwaitPoll && op->node) { return ((const ecs_script_node_t*)op->node)->pos; } return NULL; } int flecs_script_ir_eval_root( ecs_script_eval_visitor_t *v, ecs_script_impl_t *script) { ecs_script_ir_t *ir = flecs_script_ir_ensure(script); ecs_script_ir_vm_t *vm = flecs_ir_vm_acquire(v->r, ir); vm->v = *v; const ecs_script_ir_entry_t *entry = ecs_vec_get_t( &ir->entries, ecs_script_ir_entry_t, ir->root_entry); flecs_script_run_status_t status = flecs_script_ir_vm_run(vm, entry); int result = -1; if (status == FlecsScriptRunDone) { flecs_script_eval_cleanup(&vm->v, vm->dirty); result = 0; } flecs_ir_vm_release(v->r, vm); return result; } void flecs_script_ir_call_function( const ecs_function_ctx_t *ctx, ecs_script_user_function_t *uf, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { ecs_world_t *world = ctx->world; ecs_script_impl_t *impl = flecs_script_impl(uf->script); bool failed = false; ecs_script_ir_t *ir = flecs_script_ir_ensure(impl); const ecs_script_ir_entry_t *entry = flecs_script_ir_entry(ir, uf->node); if (!entry) { ecs_err("script function has no compiled body"); flecs_script_runtime_get(world)->error = true; return; } ecs_script_runtime_t *runtime = flecs_script_runtime_get(world); ecs_script_eval_desc_t desc = { .runtime = flecs_script_runtime_acquire_call(runtime) }; ecs_script_ir_vm_t *vm = flecs_ir_vm_acquire(desc.runtime, ir); flecs_script_eval_visit_init(impl, &vm->v, &desc); ecs_script_eval_visitor_t *v = &vm->v; flecs_script_user_function_bind(v, uf, argc, argv, false); vm->result = result; if (flecs_script_ir_vm_run(vm, entry) != FlecsScriptRunDone) { failed = true; } v->vars = ecs_script_vars_pop(v->vars); flecs_ir_vm_release(desc.runtime, vm); flecs_script_runtime_release_call(runtime, desc.runtime); if (failed) { flecs_script_runtime_get(world)->error = true; } } #endif #ifdef FLECS_SCRIPT_IR_PROFILE void ecs_script_ir_profile_reset(void) { ecs_os_memset_n(flecs_ir_profile_ops, 0, int64_t, EcsIrOpKindLast); ecs_os_memset_n(flecs_ir_profile_stats, 0, int64_t, EcsIrProfileLast); } static const char* flecs_ir_profile_stat_name(int32_t i) { switch(i) { case EcsIrProfileRun: return "vm runs"; case EcsIrProfileStmtRun: return "statements run"; case EcsIrProfileStmtSkipped: return "statements skipped"; case EcsIrProfileBatchAdd: return "batch adds executed"; case EcsIrProfileBatchSkip: return "batch adds skipped"; case EcsIrProfileEntityCreate: return "entities created"; case EcsIrProfileEntityReuse: return "entities reused"; case EcsIrProfileComponentSet: return "components set"; case EcsIrProfileTagAdd: return "tags added"; default: return "?"; } } char* ecs_script_ir_profile_str(void) { ecs_strbuf_t buf = ECS_STRBUF_INIT; int32_t i; int64_t total = 0; for (i = 0; i < EcsIrOpKindLast; i ++) { total += flecs_ir_profile_ops[i]; } ecs_strbuf_append(&buf, "instructions: %lld\n", (long long)total); for (i = 0; i < EcsIrOpKindLast; i ++) { if (flecs_ir_profile_ops[i]) { ecs_strbuf_append(&buf, " %-20s %10lld %5.1f%%\n", flecs_script_ir_op_name((ecs_script_ir_op_kind_t)i), (long long)flecs_ir_profile_ops[i], 100.0 * (double)flecs_ir_profile_ops[i] / (double)total); } } for (i = 0; i < EcsIrProfileLast; i ++) { ecs_strbuf_append(&buf, "%-22s %10lld\n", flecs_ir_profile_stat_name(i), (long long)flecs_ir_profile_stats[i]); } return ecs_strbuf_get(&buf); } #endif #ifdef FLECS_SCRIPT_EVENT ECS_COMPONENT_DECLARE(EcsScriptMouseEvent); ECS_COMPONENT_DECLARE(EcsScriptKeyboardEvent); typedef enum { FlecsScriptEventPress, FlecsScriptEventDrag, FlecsScriptEventRelease, FlecsScriptEventClick, FlecsScriptEventMove, FlecsScriptEventEnter, FlecsScriptEventLeave, FlecsScriptEventKeyDown, FlecsScriptEventKeyUp, FlecsScriptEventKindCount } flecs_script_event_kind_t; static const char *flecs_script_event_names[] = { "press", "drag", "release", "click", "move", "enter", "leave", "key_down", "key_up" }; typedef struct { ecs_script_future_t *future; ecs_entity_t target; flecs_script_event_kind_t kind; } flecs_script_event_waiter_t; /* Singleton with the pending listeners and the state of the last event */ typedef struct EcsScriptEventState { ecs_vec_t waiters; EcsScriptMouseEvent last; /* Last event, with the deltas it was given */ uint32_t pressed_mask; /* Buttons pressed by the last event */ uint32_t released_mask; /* Buttons released by the last event */ ecs_entity_t pressed; /* Entity that received the last press */ ecs_entity_t focus; /* Receives keyboard events */ ecs_entity_t hover; /* Target of the last event */ ecs_entity_t prev_hover; /* Target of the event before that */ ecs_entity_t last_target; /* Target of the last call */ bool has_last; } EcsScriptEventState; static ECS_COMPONENT_DECLARE(EcsScriptEventState); static ECS_CTOR(EcsScriptEventState, state, { ecs_os_zeromem(state); ecs_vec_init_t(NULL, &state->waiters, flecs_script_event_waiter_t, 0); }) static ECS_DTOR(EcsScriptEventState, state, { flecs_script_event_waiter_t *waiters = ecs_vec_first(&state->waiters); for (int32_t w = 0; w < ecs_vec_count(&state->waiters); w ++) { ecs_script_future_release(waiters[w].future); } ecs_vec_fini_t(NULL, &state->waiters, flecs_script_event_waiter_t); }) static EcsScriptEventState* flecs_script_event_state( ecs_world_t *world) { if (!ecs_id(EcsScriptEventState) || ecs_is_fini(world)) { return NULL; } return ecs_singleton_get_mut(world, EcsScriptEventState); } static void flecs_script_event_wait( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_script_future_t *future) { flecs_script_event_kind_t kind = (flecs_script_event_kind_t)(uintptr_t)ctx->ctx; (void)argc; ecs_entity_t target = *(ecs_entity_t*)argv[0].ptr; EcsScriptEventState *state = flecs_script_event_state(ctx->world); if (!target || !state) { char msg[64]; ecs_os_snprintf(msg, sizeof(msg), "on.%s expects an entity", flecs_script_event_names[kind]); ecs_script_future_reject(future, msg); ecs_script_future_release(future); return; } ecs_vec_append_t(NULL, &state->waiters, flecs_script_event_waiter_t)[0] = (flecs_script_event_waiter_t){ future, target, kind }; } static void flecs_script_event_cancel( const ecs_function_ctx_t *ctx, ecs_script_future_t *future) { EcsScriptEventState *state = flecs_script_event_state(ctx->world); if (!state) { return; } flecs_script_event_waiter_t *waiters = ecs_vec_first(&state->waiters); for (int32_t i = 0; i < ecs_vec_count(&state->waiters); i ++) { if (waiters[i].future == future) { ecs_script_future_release(future); ecs_vec_remove_ordered_t( &state->waiters, flecs_script_event_waiter_t, i); return; } } } /* Resolve the listeners for an event kind on a single entity */ static int32_t flecs_script_event_deliver_one( EcsScriptEventState *state, flecs_script_event_kind_t kind, ecs_entity_t target, ecs_entity_t type, void *value) { int32_t resolved = 0; for (int32_t i = 0; i < ecs_vec_count(&state->waiters); ) { flecs_script_event_waiter_t *w = ecs_vec_get_t( &state->waiters, flecs_script_event_waiter_t, i); if (w->kind != kind || w->target != target) { i ++; continue; } ecs_script_future_t *future = w->future; ecs_vec_remove_ordered_t( &state->waiters, flecs_script_event_waiter_t, i); ecs_script_future_resolve(future, &(ecs_value_t){ .type = type, .ptr = value }); ecs_script_future_release(future); resolved ++; } return resolved; } /* Resolve the listeners for an event kind on an entity and its parents */ static int32_t flecs_script_event_deliver( ecs_world_t *world, EcsScriptEventState *state, flecs_script_event_kind_t kind, ecs_entity_t target, ecs_entity_t type, void *value) { int32_t resolved = 0; for (; target && ecs_is_alive(world, target); target = ecs_get_target(world, target, EcsChildOf, 0)) { resolved += flecs_script_event_deliver_one( state, kind, target, type, value); } return resolved; } static bool flecs_script_event_is_ancestor_or_self( ecs_world_t *world, ecs_entity_t e, ecs_entity_t target) { for (; target && ecs_is_alive(world, target); target = ecs_get_target(world, target, EcsChildOf, 0)) { if (target == e) { return true; } } return false; } /* A call with the same pointer state as the previous call, for the parent of * the previous target, propagates the previous event. */ static bool flecs_script_event_is_propagation( ecs_world_t *world, const EcsScriptEventState *state, const EcsScriptMouseEvent *e) { const EcsScriptMouseEvent *last = &state->last; return state->has_last && e->target && state->last_target && ecs_is_alive(world, state->last_target) && ecs_get_target(world, state->last_target, EcsChildOf, 0) == e->target && ECS_EQ(e->screen_x, last->screen_x) && ECS_EQ(e->screen_y, last->screen_y) && e->button == last->button && e->buttons == last->buttons && e->ctrl == last->ctrl && e->shift == last->shift && e->alt == last->alt && e->meta == last->meta; } bool ecs_script_mouse_event( ecs_world_t *world, const EcsScriptMouseEvent *evt) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(evt != NULL, ECS_INVALID_PARAMETER, NULL); world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); EcsScriptEventState *state = flecs_script_event_state(world); ecs_check(state != NULL, ECS_INVALID_OPERATION, "script event module is not imported"); EcsScriptMouseEvent e = *evt; ecs_entity_t type = ecs_id(EcsScriptMouseEvent); int32_t resolved = 0; if (!flecs_script_event_is_propagation(world, state, &e)) { /* New pointer event: derive what happened from the previous one */ if (ECS_EQZERO(e.delta_x) && ECS_EQZERO(e.delta_y)) { e.delta_x = e.local_x - state->last.local_x; e.delta_y = e.local_y - state->last.local_y; } state->pressed_mask = e.buttons & ~state->last.buttons; state->released_mask = state->last.buttons & ~e.buttons; state->prev_hover = state->hover; state->hover = e.target; if (!state->last.buttons) { state->pressed = 0; } if (state->pressed_mask) { state->pressed = e.target; state->focus = e.target; } state->last = e; state->has_last = true; /* Leave goes to the entities that are no longer under the pointer. * The host doesn't propagate over the previous target, so this walks * the previous parent chain here. */ ecs_entity_t prev = state->prev_hover; for (; prev && ecs_is_alive(world, prev); prev = ecs_get_target(world, prev, EcsChildOf, 0)) { if (!flecs_script_event_is_ancestor_or_self(world, prev, e.target)) { resolved += flecs_script_event_deliver_one( state, FlecsScriptEventLeave, prev, type, &e); } } } else { e.delta_x = state->last.delta_x; e.delta_y = state->last.delta_y; } ecs_entity_t target = e.target; state->last_target = target; if (!target) { goto done; } bool captured = flecs_script_event_is_ancestor_or_self( world, target, state->pressed); bool released = state->released_mask && captured; bool deliver[] = { [FlecsScriptEventPress] = state->pressed_mask != 0, [FlecsScriptEventDrag] = state->pressed_mask || (e.buttons && !state->released_mask && captured), [FlecsScriptEventRelease] = released, [FlecsScriptEventClick] = released && state->hover == state->pressed, [FlecsScriptEventMove] = !e.buttons && !state->released_mask, [FlecsScriptEventEnter] = !flecs_script_event_is_ancestor_or_self( world, target, state->prev_hover) }; for (int32_t k = FlecsScriptEventPress; k <= FlecsScriptEventEnter; k ++) { if (deliver[k]) { resolved += flecs_script_event_deliver_one( state, (flecs_script_event_kind_t)k, target, type, &e); } } done: if (resolved) { ecs_script_tasks_progress(world); } return true; error: return false; } void ecs_script_keyboard_event( ecs_world_t *world, const EcsScriptKeyboardEvent *evt) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(evt != NULL, ECS_INVALID_PARAMETER, NULL); world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); EcsScriptEventState *state = flecs_script_event_state(world); ecs_check(state != NULL, ECS_INVALID_OPERATION, "script event module is not imported"); EcsScriptKeyboardEvent e = *evt; if (flecs_script_event_deliver(world, state, e.down ? FlecsScriptEventKeyDown : FlecsScriptEventKeyUp, state->focus, ecs_id(EcsScriptKeyboardEvent), &e)) { ecs_script_tasks_progress(world); } error: return; } void FlecsScriptEventImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScriptEvent); ECS_IMPORT(world, FlecsScript); ecs_set_name_prefix(world, "EcsScript"); ECS_COMPONENT_DEFINE(world, EcsScriptMouseEvent); ECS_COMPONENT_DEFINE(world, EcsScriptKeyboardEvent); ECS_COMPONENT_DEFINE(world, EcsScriptEventState); ecs_set_hooks(world, EcsScriptEventState, { .ctor = ecs_ctor(EcsScriptEventState), .dtor = ecs_dtor(EcsScriptEventState), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL | ECS_TYPE_HOOK_MOVE_ILLEGAL }); ecs_struct(world, { .entity = ecs_id(EcsScriptMouseEvent), .members = { { .name = "target", .type = ecs_id(ecs_entity_t) }, { .name = "screen_x", .type = ecs_id(ecs_f32_t) }, { .name = "screen_y", .type = ecs_id(ecs_f32_t) }, { .name = "local_x", .type = ecs_id(ecs_f32_t) }, { .name = "local_y", .type = ecs_id(ecs_f32_t) }, { .name = "delta_x", .type = ecs_id(ecs_f32_t) }, { .name = "delta_y", .type = ecs_id(ecs_f32_t) }, { .name = "button", .type = ecs_id(ecs_i32_t) }, { .name = "buttons", .type = ecs_id(ecs_u32_t) }, { .name = "ctrl", .type = ecs_id(ecs_bool_t) }, { .name = "shift", .type = ecs_id(ecs_bool_t) }, { .name = "alt", .type = ecs_id(ecs_bool_t) }, { .name = "meta", .type = ecs_id(ecs_bool_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsScriptKeyboardEvent), .members = { { .name = "key", .type = ecs_id(ecs_string_t) }, { .name = "down", .type = ecs_id(ecs_bool_t) }, { .name = "repeat", .type = ecs_id(ecs_bool_t) }, { .name = "ctrl", .type = ecs_id(ecs_bool_t) }, { .name = "shift", .type = ecs_id(ecs_bool_t) }, { .name = "alt", .type = ecs_id(ecs_bool_t) }, { .name = "meta", .type = ecs_id(ecs_bool_t) } } }); ecs_entity_t on = ecs_entity(world, { .name = "on", .parent = ecs_lookup(world, "flecs.script") }); for (int32_t i = 0; i < FlecsScriptEventKindCount; i ++) { ecs_async_function(world, { .name = flecs_script_event_names[i], .parent = on, .return_type = i < FlecsScriptEventKeyDown ? ecs_id(EcsScriptMouseEvent) : ecs_id(EcsScriptKeyboardEvent), .params = {{ "target", ecs_id(ecs_entity_t) }}, .callback = flecs_script_event_wait, .cancel = flecs_script_event_cancel, .ctx = (void*)(uintptr_t)i }); } ecs_singleton_add(world, EcsScriptEventState); } #endif #ifdef FLECS_SCRIPT_MATH #include typedef struct ecs_script_rng_t { uint64_t x; /* Current state (initialize with seed) */ uint64_t w; /* Weyl sequence increment */ uint64_t s; /* Constant for Weyl sequence */ int32_t refcount; /* Necessary as flecs script doesn't have ref types */ bool initialized; } ecs_script_rng_t; static ecs_script_rng_t* flecs_script_rng_new(void) { ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); result->x = 0; result->w = 0; result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ result->refcount = 1; result->initialized = false; return result; } static void flecs_script_rng_keep(ecs_script_rng_t *rng) { if (!rng) { return; } rng->refcount ++; } static void flecs_script_rng_free(ecs_script_rng_t *rng) { if (!rng) { return; } ecs_assert(rng->refcount > 0, ECS_INTERNAL_ERROR, NULL); if (!--rng->refcount) { ecs_os_free(rng); } } static uint64_t flecs_script_rng_mix(uint64_t x) { x += 0x9e3779b97f4a7c15; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9; x = (x ^ (x >> 27)) * 0x94d049bb133111eb; return x ^ (x >> 31); } static void flecs_script_rng_init(ecs_script_rng_t *rng, uint64_t seed) { if (rng->initialized) { return; } rng->x = flecs_script_rng_mix(seed); rng->w = flecs_script_rng_mix(rng->x); rng->initialized = true; } static uint64_t flecs_script_rng_next(ecs_script_rng_t *rng) { rng->x *= rng->x; rng->x += (rng->w += rng->s); rng->x = (rng->x >> 32) | (rng->x << 32); return rng->x; } ECS_COMPONENT_DECLARE(EcsScriptRng); static ECS_CTOR(EcsScriptRng, ptr, { ptr->seed = 0; ptr->impl = flecs_script_rng_new(); }) static ECS_COPY(EcsScriptRng, dst, src, { flecs_script_rng_keep(src->impl); if (dst->impl != src->impl) { flecs_script_rng_free(dst->impl); } dst->seed = src->seed; dst->impl = src->impl; }) static ECS_MOVE(EcsScriptRng, dst, src, { flecs_script_rng_free(dst->impl); dst->seed = src->seed; dst->impl = src->impl; src->impl = NULL; }) static ECS_DTOR(EcsScriptRng, ptr, { flecs_script_rng_free(ptr->impl); }) static void flecs_script_rng_get_float( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_rng_t *impl = rng->impl; flecs_script_rng_init(impl, rng->seed); uint64_t x = flecs_script_rng_next(rng->impl); double max = *(double*)argv[1].ptr; double *r = result->ptr; if (ECS_EQZERO(max)) { ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); flecs_script_runtime_get(ctx->world)->error = true; } else { *r = (double)x / ((double)UINT64_MAX / max); } } static void flecs_script_rng_get_uint( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_rng_t *impl = rng->impl; flecs_script_rng_init(impl, rng->seed); uint64_t x = flecs_script_rng_next(rng->impl); uint64_t max = *(uint64_t*)argv[1].ptr; uint64_t *r = result->ptr; if (!max) { ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); flecs_script_runtime_get(ctx->world)->error = true; } else { *r = x % max; } } double flecs_lerp( double a, double b, double t) { return a + t * (b - a); } static void flecs_script_min( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)ctx; (void)argc; double a = *(double*)argv[0].ptr; double b = *(double*)argv[1].ptr; *(double*)result->ptr = (a < b) ? a : b; } static void flecs_script_max( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)ctx; (void)argc; double a = *(double*)argv[0].ptr; double b = *(double*)argv[1].ptr; *(double*)result->ptr = (a > b) ? a : b; } #define FLECS_SCRIPT_FOR_EACH_NUMERIC(M)\ M(int8_t) M(int16_t) M(int32_t) M(int64_t)\ M(uint8_t) M(uint16_t) M(uint32_t) M(uint64_t)\ M(float) M(double) #define FLECS_SCRIPT_NUMERIC_CALLBACKS(fn) {\ [EcsI8] = fn##_int8_t, [EcsI16] = fn##_int16_t,\ [EcsI32] = fn##_int32_t, [EcsI64] = fn##_int64_t,\ [EcsU8] = fn##_uint8_t, [EcsU16] = fn##_uint16_t,\ [EcsU32] = fn##_uint32_t, [EcsU64] = fn##_uint64_t,\ [EcsF32] = fn##_float, [EcsF64] = fn##_double } #define FLECS_SCRIPT_FLOAT_CALLBACKS(fn) {\ [EcsF32] = fn##_float, [EcsF64] = fn##_double } #define FLECS_SCRIPT_LERP(type)\ static void flecs_script_lerp_##type(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result,\ int32_t elem_count)\ {\ (void)ctx; (void)argc;\ type *a = argv[0].ptr;\ type *b = argv[1].ptr;\ type t = (type)*(double*)argv[2].ptr;\ type *r = result->ptr;\ for (int i = 0; i < elem_count; i ++) {\ r[i] = a[i] + t * (b[i] - a[i]);\ }\ } FLECS_SCRIPT_LERP(float) FLECS_SCRIPT_LERP(double) #define FLECS_SCRIPT_SMOOTHSTEP(type)\ static void flecs_script_smoothstep_##type(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result,\ int32_t elem_count)\ {\ (void)ctx; (void)argc;\ type *a = argv[0].ptr;\ type *b = argv[1].ptr;\ type t = (type)*(double*)argv[2].ptr;\ type *r = result->ptr;\ for (int i = 0; i < elem_count; i ++) {\ type v = (t - a[i]) / (b[i] - a[i]);\ type x = (v < (type)0) ? (type)0\ : (v > (type)1) ? (type)1\ : v;\ r[i] = x * x * (3 - 2 * x);\ }\ } FLECS_SCRIPT_SMOOTHSTEP(float) FLECS_SCRIPT_SMOOTHSTEP(double) #define FLECS_SCRIPT_CLAMP(type)\ static void flecs_script_clamp_##type(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result,\ int32_t elem_count)\ {\ (void)ctx; (void)argc;\ type *v = argv[0].ptr;\ type min = (type)*(double*)argv[1].ptr;\ type max = (type)*(double*)argv[2].ptr;\ type *r = result->ptr;\ for (int i = 0; i < elem_count; i ++) {\ r[i] = (v[i] < min) ? min\ : (v[i] > max) ? max\ : v[i];\ }\ } FLECS_SCRIPT_FOR_EACH_NUMERIC(FLECS_SCRIPT_CLAMP) typedef struct flecs_math_function_t { const char *name; const char *brief; double (*unary)(double); double (*binary)(double, double); double (*exponent)(double, int); } flecs_math_function_t; #define FLECS_MATH_UNARY(name, expr)\ static double flecs_math_##name(\ double x)\ {\ return expr;\ } FLECS_MATH_UNARY(sqrt, sqrt(x)) FLECS_MATH_UNARY(sqr, x * x) FLECS_MATH_UNARY(ceil, ceil(x)) FLECS_MATH_UNARY(floor, floor(x)) FLECS_MATH_UNARY(round, round(x)) FLECS_MATH_UNARY(abs, fabs(x)) FLECS_MATH_UNARY(log2, log2(x)) static const flecs_math_function_t flecs_math_length = { .unary = flecs_math_sqrt }; #define FLECS_SCRIPT_DOT(type)\ static void flecs_script_dot_##type(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result,\ int32_t elem_count)\ {\ const type *a = argv[0].ptr;\ const type *b = argv[argc - 1].ptr;\ double sum = 0;\ for (int i = 0; i < elem_count; i ++) {\ sum += (double)a[i] * (double)b[i];\ }\ const flecs_math_function_t *fn = ctx->ctx;\ *(double*)result->ptr = fn ? fn->unary(sum) : sum;\ } FLECS_SCRIPT_FOR_EACH_NUMERIC(FLECS_SCRIPT_DOT) #define FLECS_SCRIPT_NORMALIZE(type)\ static void flecs_script_normalize_##type(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result,\ int32_t elem_count)\ {\ (void)ctx; (void)argc;\ const type *v = argv[0].ptr;\ type *r = result->ptr;\ double sum_sq = 0.0;\ for (int i = 0; i < elem_count; i++) {\ double x = (double)v[i];\ sum_sq += x * x;\ }\ if (ECS_EQZERO(sum_sq)) {\ for (int i = 0; i < elem_count; i++) r[i] = (type)0;\ return;\ }\ double inv_len = 1.0 / sqrt(sum_sq);\ for (int i = 0; i < elem_count; i++) {\ r[i] = (type)((double)v[i] * inv_len);\ }\ } FLECS_SCRIPT_NORMALIZE(float) FLECS_SCRIPT_NORMALIZE(double) static const flecs_math_function_t flecs_math_functions[] = { {"cos", "Compute cosine", .unary = cos}, {"sin", "Compute sine", .unary = sin}, {"tan", "Compute tangent", .unary = tan}, {"acos", "Compute arc cosine", .unary = acos}, {"asin", "Compute arc sine", .unary = asin}, {"atan", "Compute arc tangent", .unary = atan}, {"atan2", "Compute arc tangent with two parameters", .binary = atan2}, {"cosh", "Compute hyperbolic cosine", .unary = cosh}, {"sinh", "Compute hyperbolic sine", .unary = sinh}, {"tanh", "Compute hyperbolic tangent", .unary = tanh}, {"acosh", "Compute area hyperbolic cosine", .unary = acosh}, {"asinh", "Compute area hyperbolic sine", .unary = asinh}, {"atanh", "Compute area hyperbolic tangent", .unary = atanh}, {"exp", "Compute exponential function", .unary = exp}, {"ldexp", "Generate value from significand and exponent", .exponent = ldexp}, {"log", "Compute natural logarithm", .unary = log}, {"log10", "Compute common logarithm", .unary = log10}, {"exp2", "Compute binary exponential function", .unary = exp2}, {"log2", "Compute binary logarithm", .unary = flecs_math_log2}, {"pow", "Raise to power", .binary = pow}, {"sqrt", "Compute square root", .unary = flecs_math_sqrt}, {"sqr", "Compute square", .unary = flecs_math_sqr}, {"ceil", "Round up value", .unary = flecs_math_ceil}, {"floor", "Round down value", .unary = flecs_math_floor}, {"round", "Round to nearest", .unary = flecs_math_round}, {"abs", "Compute absolute value", .unary = flecs_math_abs}, }; static void flecs_math_call( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; const flecs_math_function_t *fn = ctx->ctx; double x = *(double*)argv[0].ptr; if (fn->unary) { *(double*)result->ptr = fn->unary(x); } else if (fn->binary) { *(double*)result->ptr = fn->binary(x, *(double*)argv[1].ptr); } else { *(double*)result->ptr = fn->exponent(x, *(int32_t*)argv[1].ptr); } } void FlecsScriptMathImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScriptMath); ECS_IMPORT(world, FlecsScript); /* Constants */ double E = 2.71828182845904523536028747135266250; ecs_const_var(world, { .name = "E", .parent = ecs_id(FlecsScriptMath), .type = ecs_id(ecs_f64_t), .value = &E }); double PI = 3.14159265358979323846264338327950288; ecs_const_var(world, { .name = "PI", .parent = ecs_id(FlecsScriptMath), .type = ecs_id(ecs_f64_t), .value = &PI }); for (int32_t i = 0; i < (int32_t)(sizeof(flecs_math_functions) / sizeof(flecs_math_functions[0])); i ++) { const flecs_math_function_t *fn = &flecs_math_functions[i]; ecs_function_desc_t desc = { .name = fn->name, .parent = ecs_id(FlecsScriptMath), .return_type = ecs_id(ecs_f64_t), .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }}, .callback = flecs_math_call, .ctx = ECS_CONST_CAST(flecs_math_function_t*, fn) }; if (fn->binary || fn->exponent) { desc.params[1].name = "y"; desc.params[1].type = fn->exponent ? ecs_id(ecs_i32_t) : ecs_id(ecs_f64_t); } ecs_entity_t f = ecs_function_init(world, &desc); ecs_doc_set_brief(world, f, fn->brief); } ecs_set_name_prefix(world, "EcsScript"); ECS_COMPONENT_DEFINE(world, EcsScriptRng); ecs_set_hooks(world, EcsScriptRng, { .ctor = ecs_ctor(EcsScriptRng), .move = ecs_move(EcsScriptRng), .copy = ecs_copy(EcsScriptRng), .dtor = ecs_dtor(EcsScriptRng), }); ecs_struct(world, { .entity = ecs_id(EcsScriptRng), .members = { { .name = "seed", .type = ecs_id(ecs_u64_t) } } }); ecs_method(world, { .parent = ecs_id(EcsScriptRng), .name = "f", .return_type = ecs_id(ecs_f64_t), .params = { { .name = "max", .type = ecs_id(ecs_f64_t) } }, .callback = flecs_script_rng_get_float }); ecs_method(world, { .parent = ecs_id(EcsScriptRng), .name = "u", .return_type = ecs_id(ecs_u64_t), .params = { { .name = "max", .type = ecs_id(ecs_u64_t) } }, .callback = flecs_script_rng_get_uint }); ecs_function(world, { .name = "min", .parent = ecs_id(FlecsScriptMath), .return_type = ecs_id(ecs_f64_t), .params = { { .name = "a", .type = ecs_id(ecs_f64_t) }, { .name = "b", .type = ecs_id(ecs_f64_t) }, }, .callback = flecs_script_min }); ecs_function(world, { .name = "max", .parent = ecs_id(FlecsScriptMath), .return_type = ecs_id(ecs_f64_t), .params = { { .name = "a", .type = ecs_id(ecs_f64_t) }, { .name = "b", .type = ecs_id(ecs_f64_t) }, }, .callback = flecs_script_max }); ecs_function(world, { .name = "dot", .parent = ecs_id(FlecsScriptMath), .return_type = ecs_id(ecs_f64_t), .params = { { .name = "a", .type = EcsScriptVectorType }, { .name = "b", .type = EcsScriptVectorType }, }, .vector_callbacks = FLECS_SCRIPT_NUMERIC_CALLBACKS(flecs_script_dot) }); ecs_function(world, { .name = "length", .parent = ecs_id(FlecsScriptMath), .return_type = ecs_id(ecs_f64_t), .params = { { .name = "v", .type = EcsScriptVectorType } }, .vector_callbacks = FLECS_SCRIPT_NUMERIC_CALLBACKS(flecs_script_dot), .ctx = ECS_CONST_CAST(flecs_math_function_t*, &flecs_math_length) }); ecs_function(world, { .name = "length_sq", .parent = ecs_id(FlecsScriptMath), .return_type = ecs_id(ecs_f64_t), .params = { { .name = "v", .type = EcsScriptVectorType } }, .vector_callbacks = FLECS_SCRIPT_NUMERIC_CALLBACKS(flecs_script_dot) }); ecs_function(world, { .name = "normalize", .parent = ecs_id(FlecsScriptMath), .return_type = EcsScriptVectorType, .params = { { .name = "v", .type = EcsScriptVectorType } }, .vector_callbacks = FLECS_SCRIPT_FLOAT_CALLBACKS(flecs_script_normalize) }); ecs_function(world, { .name = "lerp", .parent = ecs_id(FlecsScriptMath), .return_type = EcsScriptVectorType, .params = { { .name = "a", .type = EcsScriptVectorType }, { .name = "b", .type = EcsScriptVectorType }, { .name = "t", .type = ecs_id(ecs_f64_t) } }, .vector_callbacks = FLECS_SCRIPT_FLOAT_CALLBACKS(flecs_script_lerp) }); ecs_function(world, { .name = "smoothstep", .parent = ecs_id(FlecsScriptMath), .return_type = EcsScriptVectorType, .params = { { .name = "a", .type = EcsScriptVectorType }, { .name = "b", .type = EcsScriptVectorType }, { .name = "t", .type = ecs_id(ecs_f64_t) } }, .vector_callbacks = FLECS_SCRIPT_FLOAT_CALLBACKS(flecs_script_smoothstep) }); ecs_function(world, { .name = "clamp", .parent = ecs_id(FlecsScriptMath), .return_type = EcsScriptVectorType, .params = { { .name = "v", .type = EcsScriptVectorType }, { .name = "min", .type = ecs_id(ecs_f64_t) }, { .name = "max", .type = ecs_id(ecs_f64_t) } }, .vector_callbacks = FLECS_SCRIPT_NUMERIC_CALLBACKS(flecs_script_clamp) }); FlecsScriptMathPerlinImport(world); } #endif #ifdef FLECS_SCRIPT_MATH #include static uint8_t flecs_perlin_perm[512]; static uint32_t flecs_perlin_xs32( uint32_t *state) { uint32_t x = *state; x ^= x << 13; x ^= x >> 17; x ^= x << 5; *state = x; return x; } static void flecs_perlin_seed( uint32_t seed) { uint8_t p[256]; for (int i = 0; i < 256; i++) p[i] = (uint8_t)i; uint32_t st = (seed ? seed : 0xA341316Cu); for (int i = 255; i > 0; i--) { uint32_t r = flecs_perlin_xs32(&st); int j = (int)(r % (uint32_t)(i + 1)); uint8_t tmp = p[i]; p[i] = p[j]; p[j] = tmp; } for (int i = 0; i < 256; i++) { flecs_perlin_perm[i] = p[i]; flecs_perlin_perm[i + 256] = p[i]; } } static double flecs_perlin_fade( double t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } static double flecs_perlin_grad2( int hash, double x, double y) { switch (hash & 7) { case 0: return x + y; case 1: return -x + y; case 2: return x - y; case 3: return -x - y; case 4: return x; case 5: return -x; case 6: return y; default: return -y; } } static double flecs_perlin2( double x, double y) { int X = (int)floor(x) & 255; int Y = (int)floor(y) & 255; double xf = x - floor(x); double yf = y - floor(y); double u = flecs_perlin_fade(xf); double v = flecs_perlin_fade(yf); int aa = flecs_perlin_perm[flecs_perlin_perm[X] + Y]; int ab = flecs_perlin_perm[flecs_perlin_perm[X] + Y + 1]; int ba = flecs_perlin_perm[flecs_perlin_perm[X + 1] + Y]; int bb = flecs_perlin_perm[flecs_perlin_perm[X + 1] + Y + 1]; double x1 = flecs_lerp( flecs_perlin_grad2(aa, xf, yf), flecs_perlin_grad2(ba, xf - 1, yf), u); double x2 = flecs_lerp( flecs_perlin_grad2(ab, xf, yf - 1), flecs_perlin_grad2(bb, xf - 1, yf - 1), u); return flecs_lerp(x1, x2, v); } static void flecs_script_perlin2( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; (void)ctx; double x = *(double*)argv[0].ptr; double y = *(double*)argv[1].ptr; *(double*)result->ptr = flecs_perlin2(x, y); } void FlecsScriptMathPerlinImport( ecs_world_t *world) { ecs_entity_t FlecsScriptMath = ecs_lookup(world, "flecs.script.math"); ecs_assert(FlecsScriptMath != 0, ECS_INTERNAL_ERROR, NULL); flecs_perlin_seed(1234); ecs_function(world, { .name = "perlin2", .parent = FlecsScriptMath, .return_type = ecs_id(ecs_f64_t), .params = { { .name = "x", .type = ecs_id(ecs_f64_t) }, { .name = "y", .type = ecs_id(ecs_f64_t) }, }, .callback = flecs_script_perlin2 }); } #endif #ifdef FLECS_SCRIPT_PLATFORM #ifdef ECS_TARGET_EM #include EM_JS(int, flecs_script_platform_mobile, (void), { if (typeof navigator === "undefined") { return 0; } if (navigator.userAgentData && navigator.userAgentData.mobile) { return 1; } var ua = navigator.userAgent || ""; if (/Android|iPhone|iPad|iPod|Mobile|Silk|Tablet/i.test(ua)) { return 1; } if (/Mac/.test(ua) && navigator.maxTouchPoints > 1) { return 1; } if (typeof window !== "undefined" && window.matchMedia && window.matchMedia("(pointer: coarse)").matches && navigator.maxTouchPoints > 0) { return 1; } return 0; }); static bool flecs_script_platform_is_mobile(void) { return flecs_script_platform_mobile() != 0; } #else static bool flecs_script_platform_is_mobile(void) { return false; } #endif #if defined(ECS_TARGET_WINDOWS) #define FLECS_SCRIPT_PLATFORM_OS "windows" #elif defined(ECS_TARGET_ANDROID) #define FLECS_SCRIPT_PLATFORM_OS "android" #elif defined(ECS_TARGET_LINUX) #define FLECS_SCRIPT_PLATFORM_OS "linux" #elif defined(ECS_TARGET_FREEBSD) #define FLECS_SCRIPT_PLATFORM_OS "freebsd" #elif defined(ECS_TARGET_DARWIN) #define FLECS_SCRIPT_PLATFORM_OS "darwin" #elif defined(ECS_TARGET_EM) #define FLECS_SCRIPT_PLATFORM_OS "emscripten" #else #define FLECS_SCRIPT_PLATFORM_OS "unknown" #endif #if defined(ECS_TARGET_MSVC) #define FLECS_SCRIPT_PLATFORM_COMPILER "msvc" #elif defined(ECS_TARGET_CLANG) #define FLECS_SCRIPT_PLATFORM_COMPILER "clang" #elif defined(ECS_TARGET_MINGW) #define FLECS_SCRIPT_PLATFORM_COMPILER "mingw" #elif defined(ECS_TARGET_GNU) #define FLECS_SCRIPT_PLATFORM_COMPILER "gcc" #else #define FLECS_SCRIPT_PLATFORM_COMPILER "unknown" #endif #ifdef ECS_TARGET_POSIX #define FLECS_SCRIPT_PLATFORM_HAS_POSIX true #else #define FLECS_SCRIPT_PLATFORM_HAS_POSIX false #endif #ifdef ECS_TARGET_MINGW #define FLECS_SCRIPT_PLATFORM_HAS_MINGW true #else #define FLECS_SCRIPT_PLATFORM_HAS_MINGW false #endif #ifdef ECS_TARGET_GNU #define FLECS_SCRIPT_PLATFORM_HAS_GNU true #else #define FLECS_SCRIPT_PLATFORM_HAS_GNU false #endif void FlecsScriptPlatformImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScriptPlatform); ECS_IMPORT(world, FlecsScript); char *os = ECS_CONST_CAST(char*, FLECS_SCRIPT_PLATFORM_OS); struct { const char *name; bool value; } flags[] = { { "WINDOWS", !ecs_os_strcmp(os, "windows") }, { "POSIX", FLECS_SCRIPT_PLATFORM_HAS_POSIX }, { "ANDROID", !ecs_os_strcmp(os, "android") }, { "LINUX", !ecs_os_strcmp(os, "linux") }, { "FREEBSD", !ecs_os_strcmp(os, "freebsd") }, { "DARWIN", !ecs_os_strcmp(os, "darwin") }, { "EMSCRIPTEN", !ecs_os_strcmp(os, "emscripten") }, { "MINGW", FLECS_SCRIPT_PLATFORM_HAS_MINGW }, { "GNU", FLECS_SCRIPT_PLATFORM_HAS_GNU }, { "mobile", flecs_script_platform_is_mobile() } }; int32_t i, count = (int32_t)(sizeof(flags) / sizeof(flags[0])); for (i = 0; i < count; i ++) { ecs_const_var(world, { .name = flags[i].name, .parent = ecs_id(FlecsScriptPlatform), .type = ecs_id(ecs_bool_t), .value = &flags[i].value }); } char *compiler = ECS_CONST_CAST(char*, FLECS_SCRIPT_PLATFORM_COMPILER); ecs_const_var(world, { .name = "compiler", .parent = ecs_id(FlecsScriptPlatform), .type = ecs_id(ecs_string_t), .value = &compiler }); ecs_const_var(world, { .name = "os", .parent = ecs_id(FlecsScriptPlatform), .type = ecs_id(ecs_string_t), .value = &os }); } #endif #ifdef FLECS_SCRIPT static ecs_entity_t flecs_script_typecache_get( ecs_world_t *world) { ecs_entity_t result = ecs_lookup(world, "flecs.script.typecache"); if (!result) { ecs_entity_t prev_scope = ecs_set_scope(world, 0); result = ecs_entity(world, { .name = "flecs.script.typecache" }); ecs_add_id(world, result, EcsModule); ecs_set_scope(world, prev_scope); } return result; } static bool flecs_script_typecache_match( ecs_world_t *world, ecs_entity_t type, ecs_entity_t elem_type, int32_t count) { if (count) { const EcsArray *a = ecs_get(world, type, EcsArray); return a && a->type == elem_type && a->count == count; } const EcsVector *v = ecs_get(world, type, EcsVector); return v && v->type == elem_type; } static ecs_entity_t flecs_script_typecache_type( ecs_world_t *world, ecs_entity_t elem_type, int32_t count) { ecs_assert(elem_type != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t typecache = flecs_script_typecache_get(world); char *id_str = flecs_asprintf("#%u_%u", (uint32_t)elem_type, (uint32_t)(elem_type >> 32)); const char *elem_name = ecs_get_name(world, elem_type); if (!elem_name || strchr(elem_name, '.')) { elem_name = id_str; } char *name = NULL; do { ecs_os_free(name); name = count ? flecs_asprintf("array<%s,%d>", elem_name, count) : flecs_asprintf("vector<%s>", elem_name); ecs_entity_t existing = ecs_lookup_child(world, typecache, name); if (!existing) { break; } if (flecs_script_typecache_match(world, existing, elem_type, count)) { ecs_os_free(name); ecs_os_free(id_str); return existing; } if (elem_name == id_str) { ecs_os_free(name); name = NULL; break; } elem_name = id_str; } while (true); ecs_entity_t entity = ecs_entity(world, { .name = name, .parent = typecache }); ecs_os_free(name); ecs_os_free(id_str); if (count) { return ecs_array(world, { .entity = entity, .type = elem_type, .count = count }); } return ecs_vector(world, { .entity = entity, .type = elem_type }); } ecs_entity_t flecs_script_array_type( ecs_world_t *world, ecs_entity_t elem_type, int32_t count) { ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); return flecs_script_typecache_type(world, elem_type, count); } ecs_entity_t flecs_script_vector_type( ecs_world_t *world, ecs_entity_t elem_type) { return flecs_script_typecache_type(world, elem_type, 0); } #endif #ifdef FLECS_SCRIPT typedef struct flecs_script_component_owner_t { ecs_script_entity_t *entity; ecs_script_id_t *id; ecs_script_scope_t *scope; int32_t entity_symbol; int32_t next; } flecs_script_component_owner_t; typedef struct flecs_script_dep_var_t { uint64_t input; uint64_t internal; } flecs_script_dep_var_t; typedef struct flecs_script_dep_ctx_t { ecs_script_eval_visitor_t *v; ecs_script_template_t *template; ecs_vec_t *refs; ecs_vec_t *dynamic_refs; ecs_vec_t vars; ecs_vec_t component_owners; ecs_map_t owner_index; int32_t owner_fuzzy_first; int32_t owner_fuzzy_last; int32_t *input_count; int32_t scope_count; int32_t component_count; int32_t for_count; int32_t computed_count; int32_t member; int32_t entity_symbol; bool no_deps; int32_t conditional; ecs_script_entity_t *entity; ecs_script_scope_t *scope; } flecs_script_dep_ctx_t; static int flecs_script_dep_node( flecs_script_dep_ctx_t *ctx, ecs_script_node_t *node); static int flecs_script_dep_scope( flecs_script_dep_ctx_t *ctx, ecs_script_scope_t *scope); static bool flecs_script_dep_scope_contains( ecs_script_scope_t *scope, ecs_script_scope_t *contains) { while (scope) { if (scope == contains) { return true; } scope = scope->parent; } return false; } static bool flecs_script_dep_scopes_exclusive( ecs_script_scope_t *first, ecs_script_scope_t *second) { while (first) { if (first->alternative && flecs_script_dep_scope_contains( second, first->alternative)) { return true; } first = first->parent; } return false; } static bool flecs_script_dep_id_elem_static( ecs_expr_node_t *expr, int32_t sp) { return !expr && sp == -1; } static bool flecs_script_dep_static_elems_differ( const char *first_name, ecs_entity_t first_eval, int32_t first_symbol, const char *second_name, ecs_entity_t second_eval, int32_t second_symbol) { if (first_symbol != -1 && second_symbol != -1) { return first_symbol != second_symbol; } if (first_eval && second_eval) { return first_eval != second_eval; } if (!first_name || !second_name) { return false; } return ecs_os_strcmp( flecs_script_name_leaf(first_name), flecs_script_name_leaf(second_name)) != 0; } static bool flecs_script_dep_ids_may_match( const ecs_script_id_t *first, const ecs_script_id_t *second) { if (first->flag != second->flag || (first->second != NULL) != (second->second != NULL)) { return false; } if (!first->dynamic && !second->dynamic && first->eval && second->eval) { return first->eval == second->eval; } if (flecs_script_dep_id_elem_static( first->first_expr, first->first_sp) && flecs_script_dep_id_elem_static( second->first_expr, second->first_sp) && flecs_script_dep_static_elems_differ( first->first, first->first_eval, first->first_symbol, second->first, second->first_eval, second->first_symbol)) { return false; } if (first->second && flecs_script_dep_id_elem_static( first->second_expr, first->second_sp) && flecs_script_dep_id_elem_static( second->second_expr, second->second_sp) && flecs_script_dep_static_elems_differ( first->second, first->second_eval, first->second_symbol, second->second, second->second_eval, second->second_symbol)) { return false; } return true; } enum { FlecsScriptDepNameAny = -1, FlecsScriptDepNameInteger = -2 }; typedef struct flecs_script_dep_name_char_t { int16_t value; char fill; } flecs_script_dep_name_char_t; static void flecs_script_dep_name_text( ecs_vec_t *pattern, const char *text) { if (text) { while (text[0]) { ecs_vec_append_t(NULL, pattern, flecs_script_dep_name_char_t)[0] = (flecs_script_dep_name_char_t){ .value = (unsigned char)*text ++ }; } } } static void flecs_script_dep_name_expr( ecs_vec_t *pattern, const ecs_expr_node_t *expr, const ecs_expr_format_t *format) { bool formatted = format && format->is_present; if (!formatted && expr->kind == EcsExprValue && expr->type == ecs_id(ecs_string_t)) { flecs_script_dep_name_text(pattern, *(char**)((const ecs_expr_value_node_t*)expr)->ptr); return; } if (expr->kind == EcsExprCast) { expr = ((const ecs_expr_cast_t*)expr)->expr; } ecs_vec_append_t(NULL, pattern, flecs_script_dep_name_char_t)[0] = (flecs_script_dep_name_char_t){ .value = flecs_expr_is_type_integer(expr->type) ? FlecsScriptDepNameInteger : FlecsScriptDepNameAny, .fill = formatted ? format->fill : 0 }; } static void flecs_script_dep_name_pattern( ecs_vec_t *pattern, const ecs_script_entity_t *entity) { ecs_vec_init_t(NULL, pattern, flecs_script_dep_name_char_t, 0); const ecs_expr_node_t *expr = entity->name_expr; if (!expr) { flecs_script_dep_name_text(pattern, entity->name); } else if (expr->kind == EcsExprInterpolatedString) { const ecs_expr_interpolated_string_t *str = (const ecs_expr_interpolated_string_t*)expr; const ecs_expr_fragment_t *fragments = ecs_vec_first(&str->fragments); int32_t i, count = ecs_vec_count(&str->fragments); for (i = 0; i < count; i ++) { flecs_script_dep_name_text(pattern, fragments[i].text); if (fragments[i].expr) { flecs_script_dep_name_expr(pattern, fragments[i].expr, &fragments[i].format); } } } else { flecs_script_dep_name_expr(pattern, expr, NULL); } } static bool flecs_script_dep_name_accepts( const flecs_script_dep_name_char_t *pattern, int16_t ch) { return pattern->value == FlecsScriptDepNameAny || ch == (unsigned char)pattern->fill || ch == '-' || ch == '+' || (ch >= '0' && ch <= '9'); } static bool flecs_script_dep_names_may_match( const ecs_script_entity_t *first, const ecs_script_entity_t *second) { if (!first->name_expr && !second->name_expr) { return false; } ecs_vec_t first_pattern, second_pattern; flecs_script_dep_name_pattern(&first_pattern, first); flecs_script_dep_name_pattern(&second_pattern, second); const flecs_script_dep_name_char_t *a = ecs_vec_first(&first_pattern); const flecs_script_dep_name_char_t *b = ecs_vec_first(&second_pattern); int32_t a_count = ecs_vec_count(&first_pattern); int32_t b_count = ecs_vec_count(&second_pattern); bool *matches = ecs_os_calloc_n(bool, b_count + 1); int32_t i, j; for (i = a_count; i >= 0; i --) { bool diagonal = false; for (j = b_count; j >= 0; j --) { bool below = matches[j]; int16_t a_ch = i < a_count ? a[i].value : 0; int16_t b_ch = j < b_count ? b[j].value : 0; bool match = (i == a_count && j == b_count) || (a_ch < 0 && below) || (b_ch < 0 && matches[j + 1]); if (a_ch && b_ch) { if (a_ch < 0 && b_ch > 0) { match |= flecs_script_dep_name_accepts(&a[i], b_ch) && matches[j + 1]; } else if (b_ch < 0 && a_ch > 0) { match |= flecs_script_dep_name_accepts(&b[j], a_ch) && below; } else if (a_ch > 0 && b_ch > 0) { match |= a_ch == b_ch && diagonal; } } matches[j] = match; diagonal = below; } } bool result = matches[0]; ecs_os_free(matches); ecs_vec_fini_t(NULL, &first_pattern, flecs_script_dep_name_char_t); ecs_vec_fini_t(NULL, &second_pattern, flecs_script_dep_name_char_t); return result; } static bool flecs_script_dep_entity_matches( ecs_script_entity_t *first, ecs_script_entity_t *second, bool fuzzy) { if (first == second) { return true; } if (!first || !second) { return false; } if (first->symbol != -1 && first->symbol == second->symbol) { return true; } if (!first->name || !second->name || (!fuzzy && (first->name_expr || second->name_expr))) { return false; } if (ecs_os_strcmp(first->name, second->name) && (!fuzzy || !flecs_script_dep_names_may_match(first, second))) { return false; } return flecs_script_dep_entity_matches( first->parent, second->parent, fuzzy); } static bool flecs_script_dep_same_entity( const flecs_script_component_owner_t *owner, const flecs_script_dep_ctx_t *ctx) { if (owner->entity || ctx->entity) { return flecs_script_dep_entity_matches( owner->entity, ctx->entity, true); } return owner->entity_symbol == ctx->entity_symbol; } static bool flecs_script_dep_component_already_owned( const flecs_script_component_owner_t *owner, const flecs_script_dep_ctx_t *ctx) { ecs_script_entity_t *first_entity = owner->entity; ecs_script_entity_t *second_entity = ctx->entity; if (!flecs_script_dep_entity_matches( first_entity, second_entity, false)) { return false; } ecs_script_scope_t *first_scope = owner->scope; ecs_script_scope_t *second_scope = ctx->scope; while (first_entity && second_entity && first_scope == first_entity->scope && second_scope == second_entity->scope) { first_scope = first_scope->parent; second_scope = second_scope->parent; first_entity = first_entity->parent; second_entity = second_entity->parent; } return first_scope == second_scope; } static bool flecs_script_dep_initializer_is_complete( const ecs_world_t *world, ecs_entity_t component, const ecs_expr_initializer_t *initializer) { if (!component) { return false; } ecs_entity_t type = ecs_get_typeid(world, component); if (!type) { return false; } const EcsStruct *st = ecs_get(world, type, EcsStruct); if (!st) { return false; } const ecs_expr_initializer_element_t *elements = ecs_vec_first( &initializer->elements); int32_t i, count = ecs_vec_count(&initializer->elements); if (count != ecs_vec_count(&st->members)) { return false; } for (i = 0; i < count; i ++) { if (!elements[i].member) { return false; } const ecs_expr_node_t *value = elements[i].value; if (value && value->kind == EcsExprInitializer && ((const ecs_expr_initializer_t*)value)->is_partial) { return false; } } return true; } static bool flecs_script_dep_owner_key( const ecs_script_entity_t *entity, int32_t entity_symbol, uint64_t *key_out) { uint64_t key = entity ? 14695981039346656037ull : (uint32_t)entity_symbol; while (entity) { if (!entity->name || entity->name_expr) { return false; } const char *name = entity->name; do { key = (key ^ (unsigned char)name[0]) * 1099511628211ull; } while (*(++ name)); entity = entity->parent; } *key_out = key; return true; } static void flecs_script_dep_owner_append( flecs_script_component_owner_t *owners, int32_t *first, int32_t *last, int32_t index) { if (*first == -1) { *first = index; } else { owners[*last].next = index; } *last = index; } static int32_t flecs_script_dep_owner_next( const flecs_script_component_owner_t *owners, int32_t count, int32_t cur[2], bool scan_all) { if (scan_all) { return cur[0] < count ? cur[0] ++ : -1; } int32_t i = cur[1] != -1 && (cur[0] == -1 || cur[1] < cur[0]); int32_t result = cur[i]; if (result != -1) { cur[i] = owners[result].next; } return result; } static int flecs_script_dep_component_owner( flecs_script_dep_ctx_t *ctx, ecs_script_node_t *node, ecs_script_id_t *id, bool *already_owned) { *already_owned = false; if (!ctx->v->base.script->entity) { return 0; } flecs_script_component_owner_t *owners = ecs_vec_first( &ctx->component_owners); int32_t count = ecs_vec_count(&ctx->component_owners); uint64_t key = 0; bool scan_all = !flecs_script_dep_owner_key( ctx->entity, ctx->entity_symbol, &key); int32_t cur[2] = {0, -1}; if (!scan_all) { ecs_map_val_t *val = ecs_map_get(&ctx->owner_index, key); cur[0] = val ? (int32_t)(*val >> 32) - 1 : -1; cur[1] = ctx->entity ? ctx->owner_fuzzy_first : -1; } int32_t i; while ((i = flecs_script_dep_owner_next( owners, count, cur, scan_all)) != -1) { flecs_script_component_owner_t *owner = &owners[i]; if (!flecs_script_dep_same_entity(owner, ctx) || !flecs_script_dep_ids_may_match(owner->id, id)) { continue; } if (owner->scope == ctx->scope || flecs_script_dep_component_already_owned(owner, ctx)) { *already_owned = true; return 0; } if (flecs_script_dep_scopes_exclusive(owner->scope, ctx->scope)) { continue; } flecs_script_eval_error(ctx->v, node, "component can only be created in one scope or " "mutually exclusive scopes (use if/else branches, or assign " "individual members)"); return -1; } flecs_script_component_owner_t *owner = ecs_vec_append_t( NULL, &ctx->component_owners, flecs_script_component_owner_t); owner->entity = ctx->entity; owner->entity_symbol = ctx->entity_symbol; owner->id = id; owner->scope = ctx->scope; owner->next = -1; owners = ecs_vec_first(&ctx->component_owners); if (scan_all) { flecs_script_dep_owner_append(owners, &ctx->owner_fuzzy_first, &ctx->owner_fuzzy_last, count); } else { ecs_map_val_t *val = ecs_map_ensure(&ctx->owner_index, key); int32_t first = (int32_t)(*val >> 32) - 1; int32_t last = (int32_t)(*val & 0xffffffff) - 1; flecs_script_dep_owner_append(owners, &first, &last, count); *val = ((uint64_t)(uint32_t)(first + 1) << 32) | (uint32_t)(last + 1); } return 0; } static int flecs_script_dep_input_new( flecs_script_dep_ctx_t *ctx, uint64_t *input) { if (*ctx->input_count >= 64) { flecs_script_eval_error(ctx->v, ctx->v->base.script->root, "script cannot have more than 64 reactive inputs"); return -1; } *input = (uint64_t)1 << (*ctx->input_count) ++; return 0; } static void flecs_script_dep_var_get( flecs_script_dep_ctx_t *ctx, int32_t sp, uint64_t *input, uint64_t *internal) { if (sp < 0 || sp >= ecs_vec_count(&ctx->vars)) { return; } flecs_script_dep_var_t *var = ecs_vec_get_t( &ctx->vars, flecs_script_dep_var_t, sp); *input |= var->input; *internal |= var->internal; } static void flecs_script_dep_var_set( flecs_script_dep_ctx_t *ctx, int32_t sp, uint64_t input, uint64_t internal) { if (sp < 0) { return; } ecs_vec_set_min_count_zeromem_t(NULL, &ctx->vars, flecs_script_dep_var_t, sp + 1); ecs_vec_get_t(&ctx->vars, flecs_script_dep_var_t, sp)[0] = (flecs_script_dep_var_t){input, internal}; } typedef struct flecs_script_dep_expr_ctx_t { flecs_script_dep_ctx_t *deps; uint64_t *input; uint64_t *internal; } flecs_script_dep_expr_ctx_t; static int flecs_script_dep_expr_var( ecs_expr_node_t **node_ptr, void *ptr) { ecs_expr_node_t *node = *node_ptr; if (!node) { return 0; } flecs_script_dep_expr_ctx_t *ctx = ptr; if (node->kind == EcsExprVariable) { flecs_script_dep_var_get(ctx->deps, ((ecs_expr_variable_t*)node)->sp, ctx->input, ctx->internal); } else if (node->kind == EcsExprNew) { ecs_script_entity_t *entity = ((ecs_expr_new_t*)node)->entity; if (flecs_script_dep_node(ctx->deps, (ecs_script_node_t*)entity)) { return -1; } *ctx->input |= entity->node.input; *ctx->internal |= entity->node.internal; } return flecs_expr_visit_children(node, flecs_script_dep_expr_var, ctx); } static int flecs_script_dep_expr_ref( const ecs_script_ref_t *value, ecs_expr_node_t *dynamic, void *ptr) { flecs_script_dep_expr_ctx_t *expr = ptr; flecs_script_dep_ctx_t *ctx = expr->deps; ecs_vec_t *refs = value->entity ? ctx->refs : ctx->dynamic_refs; if (refs && (value->entity || value->name)) { ecs_script_ref_t *ref = flecs_script_ref_ensure(refs, value); if (!ref->input && flecs_script_dep_input_new(ctx, &ref->input)) { return -1; } *expr->input |= ref->input; } if (dynamic && ctx->v->script_entity && !ctx->template) { uint64_t input; if (flecs_script_dep_input_new(ctx, &input)) { return -1; } if (dynamic->kind == EcsExprHas) { ((ecs_expr_has_t*)dynamic)->dyn_input = input; } else { ((ecs_expr_element_t*)dynamic)->dyn_input = input; } *expr->input |= input; } return 0; } static int flecs_script_dep_expr( flecs_script_dep_ctx_t *ctx, ecs_expr_node_t *node, ecs_script_node_t *stmt) { if (!node) { return 0; } uint64_t discard = 0, discard_internal = 0; flecs_script_dep_expr_ctx_t expr = {ctx, &stmt->direct_input, &stmt->direct_internal}; /* Statements that must never be reevaluated by a reactive event don't * register refs, so nothing can trigger them. */ if (ctx->no_deps) { expr.input = &discard; expr.internal = &discard_internal; } else if (flecs_expr_visit_refs(&ctx->v->base.script->pub, node, flecs_script_dep_expr_ref, &expr)) { return -1; } return flecs_script_dep_expr_var(&node, &expr); } static int flecs_script_dep_id( flecs_script_dep_ctx_t *ctx, ecs_script_id_t *id, ecs_script_node_t *stmt) { if (flecs_script_dep_expr(ctx, id->first_expr, stmt) || flecs_script_dep_expr(ctx, id->second_expr, stmt) || flecs_script_dep_expr(ctx, id->index_expr, stmt)) { return -1; } flecs_script_dep_var_get(ctx, id->first_sp, &stmt->direct_input, &stmt->direct_internal); flecs_script_dep_var_get(ctx, id->second_sp, &stmt->direct_input, &stmt->direct_internal); flecs_script_dep_var_get(ctx, id->index_sp, &stmt->direct_input, &stmt->direct_internal); return 0; } static void flecs_script_dep_symbol_scope( flecs_script_dep_ctx_t *ctx, int32_t symbol, int32_t scope_slot) { if (symbol < 0) { return; } ecs_vec_t *slots = &ctx->v->base.script->state.symbol_slots; if (symbol >= ecs_vec_count(slots)) { return; } ecs_vec_get_t(slots, ecs_script_symbol_slot_t, symbol)->scope_slot = scope_slot; } static int flecs_script_dep_template( flecs_script_dep_ctx_t *ctx, ecs_script_template_node_t *node); static void flecs_script_dep_region( flecs_script_dep_ctx_t *ctx, ecs_script_node_t *node, int32_t scope_first, int32_t for_first) { node->region = 0; ecs_script_region_t region = { scope_first, ctx->scope_count - scope_first, for_first, ctx->for_count - for_first }; if (!region.scope_count && !region.for_count) { return; } ecs_vec_t *regions = &ctx->v->base.script->regions; int32_t count = ecs_vec_count(regions); if (!count || ecs_os_memcmp(ecs_vec_last_t(regions, ecs_script_region_t), ®ion, ECS_SIZEOF(ecs_script_region_t))) { ecs_vec_append_t(NULL, regions, ecs_script_region_t)[0] = region; count ++; } node->region = count; } static int flecs_script_dep_child( flecs_script_dep_ctx_t *ctx, ecs_script_node_t *node, ecs_script_scope_t *scope, bool conditional) { ctx->conditional += conditional; int result = flecs_script_dep_scope(ctx, scope); ctx->conditional -= conditional; node->input |= scope->node.input; node->internal |= scope->node.internal; return result; } static bool flecs_script_dep_component_partial( flecs_script_dep_ctx_t *ctx, ecs_script_component_t *node) { if (!node->expr || node->expr->kind != EcsExprInitializer) { return false; } ecs_expr_initializer_t *initializer = (ecs_expr_initializer_t*)node->expr; bool partial = initializer->is_partial; if (!partial) { ecs_expr_initializer_element_t *elements = ecs_vec_first(&initializer->elements); int32_t i, count = ecs_vec_count(&initializer->elements); for (i = 0; i < count; i ++) { if (elements[i].member) { partial = true; break; } } } return partial && !flecs_script_dep_initializer_is_complete( ctx->v->world, node->id.eval, initializer); } static int flecs_script_dep_node_impl( flecs_script_dep_ctx_t *ctx, ecs_script_node_t *node) { node->input = 0; node->direct_input = 0; node->internal = 0; node->direct_internal = 0; int32_t scope_slot = ctx->scope ? ctx->scope->scope_slot : -1; switch(node->kind) { case EcsAstScope: return flecs_script_dep_scope(ctx, (ecs_script_scope_t*)node); case EcsAstTag: case EcsAstWithTag: { ecs_script_tag_t *n = (ecs_script_tag_t*)node; if (flecs_script_dep_id(ctx, &n->id, node)) { return -1; } if (node->kind == EcsAstTag) { bool already_owned; if (flecs_script_dep_component_owner( ctx, node, &n->id, &already_owned)) { return -1; } n->component_slot = already_owned ? -1 : ctx->component_count ++; } break; } case EcsAstComponent: case EcsAstWithComponent: { ecs_script_component_t *n = (ecs_script_component_t*)node; if (flecs_script_dep_id(ctx, &n->id, node) || flecs_script_dep_expr(ctx, n->expr, node)) { return -1; } if (node->kind == EcsAstComponent) { bool partial = flecs_script_dep_component_partial(ctx, n); bool already_owned = false; if (!partial && flecs_script_dep_component_owner( ctx, node, &n->id, &already_owned)) { return -1; } n->component_slot = partial || already_owned ? -1 : ctx->component_count ++; } break; } case EcsAstWith: { ecs_script_with_t *n = (ecs_script_with_t*)node; if (flecs_script_dep_scope(ctx, n->expressions)) { return -1; } node->direct_input = n->expressions->node.input; node->direct_internal = n->expressions->node.internal; if (flecs_script_dep_child(ctx, node, n->scope, false)) { return -1; } break; } case EcsAstUsing: case EcsAstAnnotation: case EcsAstInclude: case EcsAstContinue: break; case EcsAstModule: flecs_script_dep_symbol_scope(ctx, ((ecs_script_module_t*)node)->symbol, scope_slot); break; case EcsAstFunction: flecs_script_dep_symbol_scope(ctx, ((ecs_script_function_node_t*)node)->symbol, scope_slot); break; case EcsAstTemplate: { ecs_script_template_node_t *n = (ecs_script_template_node_t*)node; flecs_script_dep_symbol_scope(ctx, n->symbol, scope_slot); if (flecs_script_dep_template(ctx, n)) { return -1; } break; } case EcsAstProp: case EcsAstMut: { ecs_script_var_node_t *n = (ecs_script_var_node_t*)node; if (node->skip) { break; } ecs_script_template_member_t *member; if (n->base_member) { ecs_assert(ctx->template != NULL, ECS_INTERNAL_ERROR, NULL); member = ecs_vec_get_t(&ctx->template->members, ecs_script_template_member_t, n->base_member - 1); } else { if (!ctx->template || ctx->member >= ecs_vec_count(&ctx->template->members)) { flecs_script_eval_error(ctx->v, node, "variable '%s' is not a template member", n->name); return -1; } member = ecs_vec_get_t( &ctx->template->members, ecs_script_template_member_t, ctx->member ++); flecs_script_dep_var_set(ctx, n->sp, member->input, 0); } if (flecs_script_dep_expr(ctx, n->expr, node)) { return -1; } node->input = node->direct_input | member->input; return 0; } case EcsAstConst: case EcsAstExportConst: case EcsAstExportMut: { ecs_script_var_node_t *n = (ecs_script_var_node_t*)node; n->computed = 0; if (node->skip) { break; } bool no_deps = ctx->no_deps; ctx->no_deps = no_deps || node->kind == EcsAstExportMut; int dep_result = flecs_script_dep_expr(ctx, n->expr, node); ctx->no_deps = no_deps; if (dep_result) { return -1; } if (node->kind == EcsAstConst) { bool computed = (ctx->template || ctx->v->base.script->entity) && !ctx->conditional && !n->is_await && ctx->computed_count < 64; if (computed) { int32_t slot = ctx->computed_count ++; n->computed = slot + 1; flecs_script_dep_var_set(ctx, n->sp, 0, (uint64_t)1 << slot); } else { flecs_script_dep_var_set(ctx, n->sp, node->direct_input, node->direct_internal); } } else { flecs_script_dep_symbol_scope(ctx, n->symbol, scope_slot); } break; } case EcsAstEntity: { ecs_script_entity_t *n = (ecs_script_entity_t*)node; flecs_script_dep_symbol_scope(ctx, n->symbol, scope_slot); if (flecs_script_dep_expr(ctx, n->name_expr, node)) { return -1; } flecs_script_dep_var_get(ctx, n->kind_sp, &node->direct_input, &node->direct_internal); int32_t entity_symbol = ctx->entity_symbol; ecs_script_entity_t *entity = ctx->entity; n->parent = entity; ctx->entity_symbol = n->symbol; ctx->entity = n; int result = flecs_script_dep_child(ctx, node, n->scope, false); ctx->entity_symbol = entity_symbol; ctx->entity = entity; if (result) { return -1; } break; } case EcsAstPairScope: { ecs_script_pair_scope_t *n = (ecs_script_pair_scope_t*)node; flecs_script_dep_symbol_scope( ctx, n->id.first_symbol, scope_slot); flecs_script_dep_symbol_scope( ctx, n->id.second_symbol, scope_slot); if (flecs_script_dep_id(ctx, &n->id, node) || flecs_script_dep_child(ctx, node, n->scope, false)) { return -1; } break; } case EcsAstIf: { ecs_script_if_t *n = (ecs_script_if_t*)node; n->if_true->alternative = n->if_false; n->if_false->alternative = n->if_true; if (flecs_script_dep_expr(ctx, n->expr, node) || flecs_script_dep_child(ctx, node, n->if_true, true) || flecs_script_dep_child(ctx, node, n->if_false, true)) { return -1; } break; } case EcsAstFor: { ecs_script_for_t *n = (ecs_script_for_t*)node; if (flecs_script_dep_expr(ctx, n->from, node) || flecs_script_dep_expr(ctx, n->to, node) || flecs_script_dep_expr(ctx, n->expr, node)) { return -1; } n->for_slot = ctx->for_count ++; int32_t i; for (i = 0; i < n->loop_var_count; i ++) { flecs_script_dep_var_set(ctx, n->loop_var_sp[i], node->direct_input, node->direct_internal); } if (flecs_script_dep_child(ctx, node, n->scope, true)) { return -1; } break; } case EcsAstAwait: if (flecs_script_dep_expr(ctx, ((ecs_script_await_t*)node)->expr, node)) { return -1; } break; case EcsAstAsync: if (flecs_script_dep_child(ctx, node, ((ecs_script_async_t*)node)->scope, true)) { return -1; } node->input = ctx->template ? ctx->template->async_input : 0; node->internal = 0; break; case EcsAstWhile: { ecs_script_while_t *n = (ecs_script_while_t*)node; if (flecs_script_dep_expr(ctx, n->expr, node) || flecs_script_dep_child(ctx, node, n->scope, true)) { return -1; } break; } case EcsAstAssign: if (flecs_script_dep_expr(ctx, ((ecs_script_assign_t*)node)->expr, node)) { return -1; } break; case EcsAstTry: { ecs_script_try_t *n = (ecs_script_try_t*)node; if (flecs_script_dep_child(ctx, node, n->try_scope, true)) { return -1; } ecs_script_catch_t *catches = ecs_vec_first(&n->catches); int32_t i, count = ecs_vec_count(&n->catches); for (i = 0; i < count; i ++) { if (flecs_script_dep_child(ctx, node, catches[i].scope, true)) { return -1; } } break; } } node->input |= node->direct_input; node->internal |= node->direct_internal; return 0; } static int flecs_script_dep_node( flecs_script_dep_ctx_t *ctx, ecs_script_node_t *node) { int32_t scope_first = ctx->scope_count, for_first = ctx->for_count; int result = flecs_script_dep_node_impl(ctx, node); if (!result) { flecs_script_dep_region(ctx, node, scope_first, for_first); } return result; } static int flecs_script_dep_scope( flecs_script_dep_ctx_t *ctx, ecs_script_scope_t *scope) { int32_t for_first = ctx->for_count; ecs_script_scope_t *prev_scope = ctx->scope; ctx->scope = scope; scope->scope_slot = ctx->scope_count ++; scope->node.input = 0; scope->node.direct_input = 0; scope->node.internal = 0; scope->node.direct_internal = 0; ecs_script_node_t **stmts = ecs_vec_first(&scope->stmts); int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { if (flecs_script_dep_node(ctx, stmts[i])) { ctx->scope = prev_scope; return -1; } scope->node.input |= stmts[i]->input; scope->node.internal |= stmts[i]->internal; } uint64_t next_input = 0, next_internal = 0; for (i = count - 1; i >= 0; i --) { if (stmts[i]->kind == EcsAstAnnotation) { stmts[i]->input = next_input; stmts[i]->internal = next_internal; scope->node.input |= next_input; scope->node.internal |= next_internal; } else { next_input = stmts[i]->input; next_internal = stmts[i]->internal; } } ctx->scope = prev_scope; flecs_script_dep_region(ctx, &scope->node, scope->scope_slot, for_first); return 0; } static int flecs_script_dep_assign_refs( flecs_script_dep_ctx_t *ctx, ecs_vec_t *refs) { ecs_script_ref_t *array = ecs_vec_first(refs); int32_t i, count = ecs_vec_count(refs); for (i = 0; i < count; i ++) { if (flecs_script_dep_input_new(ctx, &array[i].input)) { return -1; } } return 0; } static int flecs_script_dep_template_init( flecs_script_dep_ctx_t *ctx, flecs_script_dep_ctx_t *outer) { ecs_script_template_t *template = ctx->template; template->input_count = 0; template->async_input = UINT64_MAX; ecs_script_template_member_t *members = ecs_vec_first(&template->members); int32_t i, count = ecs_vec_count(&template->members); for (i = 0; i < count; i ++) { if (flecs_script_dep_input_new(ctx, &members[i].input)) { return -1; } if (members[i].is_mut) { template->async_input &= ~members[i].input; } } if (flecs_script_dep_assign_refs(ctx, &template->refs) || flecs_script_dep_assign_refs(ctx, &template->dynamic_refs)) { return -1; } if (template->parent_type) { const EcsScript *parent = ecs_get(ctx->v->world, template->parent_type, EcsScript); ecs_assert(parent != NULL && parent->template_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_ref_t *refs = ecs_vec_first(&template->dynamic_refs); int32_t ref_count = ecs_vec_count(&template->dynamic_refs); for (i = 0; i < ref_count; i ++) { if (refs[i].name && !ecs_os_strcmp(refs[i].name, "#parent") && refs[i].component == parent->template_->muts.type) { template->async_input &= ~refs[i].input; } } } int32_t *capture_sp = ecs_vec_first(&template->capture_sp); count = ecs_vec_count(&template->capture_sp); ecs_vec_set_count_t(NULL, &template->capture_input, ecs_script_template_capture_t, count); ecs_script_template_capture_t *captures = ecs_vec_first( &template->capture_input); for (i = 0; i < count; i ++) { uint64_t outer_input = 0, outer_internal = 0, input = 0; flecs_script_dep_var_get(outer, capture_sp[i], &outer_input, &outer_internal); if ((outer_input || outer_internal) && flecs_script_dep_input_new(ctx, &input)) { return -1; } captures[i].outer_input = outer_input; captures[i].outer_internal = outer_internal; captures[i].input = input; flecs_script_dep_var_set(ctx, i, input, 0); } for (i = 0; i < template->inherited_count; i ++) { flecs_script_dep_var_set(ctx, members[i].sp, members[i].input, 0); } ctx->member = template->inherited_count; return 0; } static int flecs_script_dep_analyze( flecs_script_dep_ctx_t *ctx, ecs_script_scope_t *scope, flecs_script_dep_ctx_t *outer) { ecs_vec_init_t(NULL, &ctx->vars, flecs_script_dep_var_t, 0); ecs_vec_init_t(NULL, &ctx->component_owners, flecs_script_component_owner_t, 0); ecs_map_init(&ctx->owner_index, NULL); ctx->owner_fuzzy_first = -1; ctx->owner_fuzzy_last = -1; int result = ctx->template ? flecs_script_dep_template_init(ctx, outer) : flecs_script_dep_assign_refs(ctx, ctx->refs); if (!result) { result = flecs_script_dep_scope(ctx, scope); } ecs_vec_fini_t(NULL, &ctx->vars, flecs_script_dep_var_t); ecs_vec_fini_t(NULL, &ctx->component_owners, flecs_script_component_owner_t); ecs_map_fini(&ctx->owner_index); return result; } static int flecs_script_dep_template( flecs_script_dep_ctx_t *outer, ecs_script_template_node_t *node) { ecs_entity_t entity = flecs_script_symbol_entity(outer->v, node->symbol); const EcsScript *script = ecs_get(outer->v->world, entity, EcsScript); if (!script || !script->template_) { flecs_script_eval_error(outer->v, node, "'%s' is not a template", node->name); return -1; } ecs_script_template_t *template = script->template_; int32_t *capture_sp = ecs_vec_first(&template->capture_sp); int32_t i, count = ecs_vec_count(&template->capture_sp); for (i = 0; i < count; i ++) { flecs_script_dep_var_get(outer, capture_sp[i], &node->node.direct_input, &node->node.direct_internal); } flecs_script_dep_ctx_t ctx = { .v = outer->v, .template = template, .refs = &template->refs, .dynamic_refs = &template->dynamic_refs, .input_count = &template->input_count, .entity_symbol = template->root_symbol }; if (flecs_script_dep_analyze(&ctx, template->node->scope, outer)) { return -1; } template->scope_count = ctx.scope_count; template->component_count = ctx.component_count; template->for_count = ctx.for_count; template->computed_count = ctx.computed_count; flecs_script_dep_symbol_scope(&ctx, template->root_symbol, template->node->scope->scope_slot); return 0; } int flecs_script_analyze_dependencies( ecs_script_eval_visitor_t *v, ecs_script_scope_t *scope) { ecs_script_impl_t *impl = v->base.script; impl->input_count = 0; ecs_vec_clear(&impl->regions); flecs_script_dep_ctx_t ctx = { .v = v, .refs = &impl->refs, .input_count = &impl->input_count, .entity_symbol = -1 }; if (flecs_script_dep_analyze(&ctx, scope, NULL)) { return -1; } flecs_script_state_resize(&impl->state, ctx.scope_count, ctx.component_count, ctx.for_count); flecs_script_state_resize_computed(&impl->state, ctx.computed_count); return 0; } #endif #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScriptUpdateEvent); static bool flecs_script_ref_matches( const ecs_script_ref_t *first, const ecs_script_ref_t *second) { return first->entity == second->entity && first->component == second->component && first->is_has == second->is_has && ((!first->name && !second->name) || (first->name && second->name && !ecs_os_strcmp(first->name, second->name))); } ecs_script_ref_t* flecs_script_ref_add( ecs_vec_t *refs, const ecs_script_ref_t *value, bool match_input) { ecs_script_ref_t *array = ecs_vec_first(refs); int32_t i, count = ecs_vec_count(refs); for (i = 0; i < count; i ++) { if (flecs_script_ref_matches(&array[i], value) && (!match_input || array[i].input == value->input)) { return &array[i]; } } ecs_script_ref_t *ref = ecs_vec_append_t(NULL, refs, ecs_script_ref_t); *ref = *value; ref->observer = 0; return ref; } ecs_script_ref_t* flecs_script_ref_ensure( ecs_vec_t *refs, const ecs_script_ref_t *value) { return flecs_script_ref_add(refs, value, false); } static void flecs_script_ref_eval( ecs_world_t *world, ecs_entity_t script, uint64_t input) { EcsScript *s = ecs_ensure(world, script, EcsScript); if (!s || !s->script || !input) { return; } ecs_script_impl_t *impl = flecs_script_impl(s->script); world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); ecs_script_runtime_t *pool = flecs_script_runtime_get(world); impl->refcount ++; do { impl->pending_inputs = 0; impl->evaluating = true; ecs_script_runtime_t *runtime = flecs_script_runtime_acquire_call(pool); ecs_script_eval_desc_t desc = { .runtime = runtime }; ecs_script_eval_result_t result = {0}; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_vec_clear(&impl->run_refs); int rc = flecs_script_eval(s->script, &desc, ecs_pair_t(EcsScript, script), input, &result); ecs_set_scope(world, prev_scope); impl->evaluating = false; s = ecs_is_alive(world, script) ? ecs_ensure(world, script, EcsScript) : NULL; if (s && s->script == &impl->pub && !rc) { flecs_script_update_dyn_observers( world, script, impl, &s->dyn_observers, input); s = ecs_is_alive(world, script) ? ecs_ensure(world, script, EcsScript) : NULL; } ecs_vec_clear(&impl->run_refs); if (s && s->error) { ecs_os_free(s->error); s->error = NULL; } if (s && rc) { s->error = result.error; } else { ecs_os_free(result.error); } flecs_script_runtime_release_call(pool, runtime); if (!s || s->script != &impl->pub || rc) { break; } input = impl->pending_inputs; } while (input); ecs_script_free(&impl->pub); } const char* flecs_script_name_leaf( const char *name) { const char *last = name, *ptr = name; while ((ptr = strchr(ptr, '.'))) { if (ptr == name || ptr[-1] != '\\') { last = ptr + 1; } ptr ++; } return last; } static void flecs_script_resolve_run( ecs_world_t *world, ecs_entity_t script) { const EcsScript *s = ecs_get(world, script, EcsScript); if (!s || !s->code) { return; } if (s->script && flecs_script_impl(s->script)->evaluating) { return; } char *code = ecs_os_strdup(s->code); ecs_script_runtime_t *pool = flecs_script_runtime_get(world); ecs_script_runtime_t *runtime = flecs_script_runtime_acquire_call(pool); ecs_entity_t prev_scope = ecs_set_scope(world, 0); flecs_script_update(world, script, 0, code, runtime); ecs_set_scope(world, prev_scope); flecs_script_runtime_release_call(pool, runtime); ecs_os_free(code); } static bool flecs_script_defer_resolve( ecs_world_t *world, ecs_entity_t script) { ecs_script_runtime_t *runtime = flecs_script_runtime_get(world); if (!runtime->include_depth && !runtime->resolving) { return false; } ecs_vec_t *pending = &runtime->pending_resolves; ecs_entity_t *elems = ecs_vec_first(pending); int32_t i, count = ecs_vec_count(pending); for (i = 0; i < count; i ++) { if (elems[i] == script) { return true; } } ecs_vec_append_t(runtime->allocator, pending, ecs_entity_t)[0] = script; return true; } void flecs_script_pending_resolve_remove( ecs_world_t *world, ecs_entity_t script) { ecs_script_runtime_t *runtime = flecs_script_runtime_get(world); ecs_vec_t *pending = &runtime->pending_resolves; ecs_entity_t *elems = ecs_vec_first(pending); int32_t i, count = ecs_vec_count(pending); for (i = 0; i < count; i ++) { if (elems[i] == script) { ecs_vec_remove_t(pending, ecs_entity_t, i); return; } } } void flecs_script_run_pending_resolves( ecs_world_t *world) { ecs_script_runtime_t *runtime = flecs_script_runtime_get(world); if (runtime->resolving) { return; } runtime->resolving = true; ecs_vec_t *pending = &runtime->pending_resolves; while (ecs_vec_count(pending)) { ecs_entity_t script = ecs_vec_first_t(pending, ecs_entity_t)[0]; ecs_vec_remove_t(pending, ecs_entity_t, 0); if (!ecs_is_alive(world, script)) { continue; } flecs_script_resolve_run(world, script); } runtime->resolving = false; } static void flecs_script_ref_dispatch( ecs_world_t *world, ecs_world_t *stage, ecs_entity_t script, uint64_t input) { if (!ecs_is_alive(world, script)) { return; } const EcsScript *s = ecs_get(world, script, EcsScript); if (!s || !s->code || (input && !s->script)) { return; } if (s->script && flecs_script_impl(s->script)->evaluating) { flecs_script_impl(s->script)->pending_inputs |= input; return; } if (stage) { if (!input && flecs_script_defer_resolve(world, script)) { return; } if (ecs_is_stage(stage)) { EcsScriptUpdateEvent evt = { .script = script, .input = input }; ecs_enqueue(stage, &(ecs_event_desc_t){ .event = ecs_id(EcsScriptUpdateEvent), .entity = EcsAny, .param = &evt }); return; } } if (input) { flecs_script_ref_eval(world, script, input); } else { flecs_script_resolve_run(world, script); } } void flecs_script_ref_on_set( ecs_iter_t *it) { ecs_script_ref_ctx_t *ctx = it->ctx; flecs_script_ref_dispatch(it->world, it->stage, ctx->script, ctx->input); } static void flecs_script_resolve_on_set( ecs_iter_t *it) { ecs_script_ref_ctx_t *ctx = it->ctx; if (ctx->name) { int32_t i; for (i = 0; i < it->count; i ++) { const char *name = ecs_get_name(it->world, it->entities[i]); if (name && !ecs_os_strcmp(name, ctx->name)) { break; } } if (i == it->count) { return; } } flecs_script_ref_dispatch(it->world, it->stage, ctx->script, 0); } static void flecs_script_on_update_event( ecs_iter_t *it) { ecs_assert(ecs_is_stage(it->stage), ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_poly_is(it->world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); EcsScriptUpdateEvent *evt = it->param; flecs_script_ref_dispatch(it->world, NULL, evt->script, evt->input); } static void flecs_script_ref_ctx_free( void *ptr) { ecs_script_ref_ctx_t *ctx = ptr; ecs_os_free(ctx->name); ecs_os_free(ctx); } static ecs_entity_t flecs_script_create_ref_observer( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance, const ecs_script_ref_t *ref, ecs_iter_action_t callback) { ecs_entity_t prev_scope = ecs_set_scope(world, script); ecs_observer_desc_t desc = { .events = { EcsOnSet }, .callback = ref->is_resolve ? flecs_script_resolve_on_set : callback }; ecs_script_ref_ctx_t *ctx = ecs_os_malloc_t(ecs_script_ref_ctx_t); ctx->script = script; ctx->instance = instance; ctx->name = NULL; ctx->input = ref->is_resolve ? 0 : ref->input; if (ref->is_resolve && ref->name) { desc.query.terms[0].id = ecs_pair_t(EcsIdentifier, EcsName); if (ref->component) { desc.query.terms[1].id = ref->component; desc.query.terms[2].id = ecs_id(EcsTypeSerializer); desc.query.terms[2].oper = EcsOptional; } ctx->name = ecs_os_strdup(ref->name); } else { desc.query.terms[0].id = ref->component; desc.query.terms[0].src.id = ref->entity; if (ref->is_has) { desc.events[0] = EcsOnAdd; if (!ref->is_resolve) { desc.events[1] = EcsOnRemove; } } } desc.ctx = ctx; desc.ctx_free = flecs_script_ref_ctx_free; ecs_entity_t observer = ecs_observer_init(world, &desc); if (!observer) { flecs_script_ref_ctx_free(ctx); } ecs_set_scope(world, prev_scope); return observer; } void flecs_script_update_ref_observers( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance, ecs_vec_t *refs, ecs_vec_t *observers, ecs_iter_action_t callback) { ecs_vec_init_if_t(observers, ecs_script_ref_t); ecs_script_ref_t *new_refs = ecs_vec_first(refs); int32_t i, new_count = ecs_vec_count(refs); for (i = 0; i < new_count; i ++) { ecs_script_ref_t value = new_refs[i]; value.name = value.is_resolve ? value.name : NULL; ecs_script_ref_t *old_refs = ecs_vec_first(observers); int32_t j, old_count = ecs_vec_count(observers); for (j = i; j < old_count; j ++) { if (old_refs[j].observer && old_refs[j].input == value.input && old_refs[j].is_resolve == value.is_resolve && flecs_script_ref_matches(&old_refs[j], &value)) { break; } } if (j == old_count) { value.observer = flecs_script_create_ref_observer( world, script, instance, &value, callback); value.name = value.name ? ecs_os_strdup(value.name) : NULL; ecs_vec_append_t(NULL, observers, ecs_script_ref_t)[0] = value; old_refs = ecs_vec_first(observers); } value = old_refs[i]; old_refs[i] = old_refs[j]; old_refs[j] = value; } ecs_script_ref_t *old_refs = ecs_vec_first(observers); int32_t old_count = ecs_vec_count(observers); for (i = new_count; i < old_count; i ++) { if (old_refs[i].observer) { ecs_delete(world, old_refs[i].observer); } if (old_refs[i].is_resolve) { ecs_os_free(ECS_CONST_CAST(char*, old_refs[i].name)); } } ecs_vec_set_count_t(NULL, observers, ecs_script_ref_t, new_count); } void flecs_script_record_dyn_ref( ecs_script_impl_t *impl, ecs_entity_t entity, ecs_id_t component, uint64_t input, bool is_has) { flecs_script_ref_add(&impl->run_refs, &(ecs_script_ref_t){ .entity = entity, .component = component, .input = input, .is_has = is_has }, true); } void flecs_script_update_dyn_observers( ecs_world_t *world, ecs_entity_t script, ecs_script_impl_t *impl, ecs_vec_t *observers, uint64_t input) { ecs_vec_t *run_refs = &impl->run_refs; ecs_script_ref_t *new_refs = ecs_vec_first(run_refs); int32_t i, new_count = ecs_vec_count(run_refs); if (!new_count && !ecs_vec_count(observers)) { return; } uint64_t recorded = 0; for (i = 0; i < new_count; i ++) { recorded |= new_refs[i].input; } uint64_t discard = input | recorded; ecs_vec_t refs; ecs_vec_init_t(NULL, &refs, ecs_script_ref_t, new_count); ecs_script_ref_t *old_refs = ecs_vec_first(observers); int32_t old_count = ecs_vec_count(observers); for (i = 0; i < old_count; i ++) { if (old_refs[i].input & discard) { continue; } if (!ecs_is_alive(world, old_refs[i].entity)) { continue; } ecs_script_ref_t *ref = ecs_vec_append_t( NULL, &refs, ecs_script_ref_t); *ref = old_refs[i]; ref->observer = 0; } for (i = 0; i < new_count; i ++) { if (!ecs_is_alive(world, new_refs[i].entity)) { continue; } if (new_refs[i].component != ecs_id(EcsScriptMutVar) && ecs_has_pair(world, new_refs[i].entity, ecs_id(EcsScript), script)) { continue; } ecs_script_ref_t *ref = ecs_vec_append_t( NULL, &refs, ecs_script_ref_t); *ref = new_refs[i]; ref->observer = 0; } flecs_script_update_ref_observers(world, script, 0, &refs, observers, flecs_script_ref_on_set); ecs_vec_fini_t(NULL, &refs, ecs_script_ref_t); } void flecs_script_update_resolve_observers( ecs_world_t *world, ecs_entity_t script, ecs_script_impl_t *impl, ecs_vec_t *observers) { int32_t i, ref_count = ecs_vec_count(&impl->unresolved_refs); ecs_script_unresolved_ref_t *refs = ecs_vec_first( &impl->unresolved_refs); int32_t component_ref_count = ecs_vec_count( &impl->unresolved_component_refs); ecs_script_unresolved_component_ref_t *component_refs = ecs_vec_first( &impl->unresolved_component_refs); ecs_vec_t resolve_refs; ecs_vec_init_t(NULL, &resolve_refs, ecs_script_ref_t, 0); for (i = 0; i < ref_count; i ++) { if (refs[i].kind == FlecsScriptUnresolvedVariable) { continue; } const char *name = flecs_script_name_leaf(refs[i].name); ecs_id_t component = 0; if (refs[i].kind == FlecsScriptUnresolvedComponent) { component = ecs_id(EcsComponent); } flecs_script_ref_ensure(&resolve_refs, &(ecs_script_ref_t){ .name = name, .component = component })->is_resolve = true; } for (i = 0; i < component_ref_count; i ++) { flecs_script_ref_ensure(&resolve_refs, &(ecs_script_ref_t){ .entity = component_refs[i].entity, .component = component_refs[i].component, .is_has = component_refs[i].is_has })->is_resolve = true; } if (!ecs_vec_count(&resolve_refs)) { ecs_script_ref_t *old_refs = ecs_vec_first(observers); int32_t old_count = ecs_vec_count(observers); for (i = 0; i < old_count; i ++) { if (old_refs[i].is_resolve) { ecs_script_ref_t *ref = ecs_vec_append_t( NULL, &resolve_refs, ecs_script_ref_t); *ref = old_refs[i]; ref->observer = 0; } } } flecs_script_update_ref_observers(world, script, 0, &resolve_refs, observers, flecs_script_ref_on_set); ecs_vec_fini_t(NULL, &resolve_refs, ecs_script_ref_t); } void flecs_script_ref_observers_fini( ecs_vec_t *observers) { int32_t i, count = ecs_vec_count(observers); ecs_script_ref_t *refs = ecs_vec_first(observers); for (i = 0; i < count; i ++) { if (refs[i].is_resolve) { ecs_os_free(ECS_CONST_CAST(char*, refs[i].name)); } } ecs_vec_fini_t(NULL, observers, ecs_script_ref_t); } void flecs_script_ref_observers_clear( ecs_world_t *world, ecs_vec_t *observers) { int32_t i, count = ecs_vec_count(observers); ecs_script_ref_t *refs = ecs_vec_first(observers); for (i = 0; i < count; i ++) { if (refs[i].observer && ecs_is_alive(world, refs[i].observer)) { ecs_delete(world, refs[i].observer); } } flecs_script_ref_observers_fini(observers); ecs_vec_init_t(NULL, observers, ecs_script_ref_t, 0); } void flecs_script_refs_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsScriptUpdateEvent); ecs_observer(world, { .entity = ecs_entity(world, { .name = "ScriptUpdateObserver" }), .query.terms = {{ .id = EcsAny }}, .events = { ecs_id(EcsScriptUpdateEvent) }, .callback = flecs_script_on_update_event }); } #endif #ifdef FLECS_SCRIPT static void flecs_script_for_slot_init( ecs_script_for_slot_t *slot) { slot->scope_slot = -1; ecs_vec_init_t(NULL, &slot->entities, ecs_entity_t, 0); ecs_map_init(&slot->named, NULL); } static void flecs_script_for_slot_fini( ecs_script_for_slot_t *slot) { ecs_vec_fini_t(NULL, &slot->entities, ecs_entity_t); ecs_map_iter_t it = ecs_map_iter(&slot->named); while (ecs_map_next(&it)) { ecs_script_for_entry_t *entry = ecs_map_ptr(&it); ecs_vec_fini_t(NULL, &entry->components, ecs_script_for_component_t); ecs_os_free(entry); } ecs_map_fini(&slot->named); } static void flecs_script_for_slots_fini( ecs_vec_t *for_slots) { ecs_script_for_slot_t *slots = ecs_vec_first(for_slots); int32_t i, count = ecs_vec_count(for_slots); for (i = 0; i < count; i ++) { flecs_script_for_slot_fini(&slots[i]); } ecs_vec_fini_t(NULL, for_slots, ecs_script_for_slot_t); } static void flecs_script_for_slots_init( ecs_vec_t *for_slots, int32_t count) { flecs_script_for_slots_fini(for_slots); ecs_vec_init_t(NULL, for_slots, ecs_script_for_slot_t, count); ecs_vec_set_count_t(NULL, for_slots, ecs_script_for_slot_t, count); ecs_script_for_slot_t *slots = ecs_vec_first(for_slots); int32_t i; for (i = 0; i < count; i ++) { flecs_script_for_slot_init(&slots[i]); } } static void flecs_script_for_slot_mark( ecs_script_for_slot_t *slot, int32_t visit) { ecs_map_iter_t it = ecs_map_iter(&slot->named); while (ecs_map_next(&it)) { ecs_script_for_entry_t *entry = ecs_map_ptr(&it); entry->visit = visit; ecs_script_for_component_t *components = ecs_vec_first(&entry->components); int32_t c, component_count = ecs_vec_count(&entry->components); for (c = 0; c < component_count; c ++) { components[c].visit = visit; } } } void flecs_script_slots_reset( ecs_vec_t *slots, ecs_size_t size, int32_t count) { ecs_vec_set_count(NULL, slots, size, count); if (count) { ecs_os_memset(ecs_vec_first(slots), 0, size * count); } } void flecs_script_state_init( ecs_script_state_t *state) { ecs_vec_init_t(NULL, &state->symbol_slots, ecs_script_symbol_slot_t, 0); ecs_vec_init_t(NULL, &state->component_slots, ecs_script_component_slot_t, 0); ecs_vec_init_t(NULL, &state->scope_slots, int32_t, 0); ecs_vec_init_t(NULL, &state->for_slots, ecs_script_for_slot_t, 0); ecs_vec_init_t(NULL, &state->computed, ecs_script_computed_t, 0); state->visit = 0; state->initialized = false; } void flecs_script_state_clear_computed( ecs_script_state_t *state) { ecs_script_computed_t *slots = ecs_vec_first(&state->computed); for (int32_t i = 0; i < ecs_vec_count(&state->computed); i ++) { if (slots[i].ptr) { if (slots[i].ti && slots[i].ti->hooks.dtor) { flecs_type_info_dtor(slots[i].ptr, 1, slots[i].ti); } ecs_os_free(slots[i].ptr); } slots[i] = (ecs_script_computed_t){0}; } } void flecs_script_state_fini( ecs_script_state_t *state) { flecs_script_state_clear_computed(state); ecs_vec_fini_t(NULL, &state->computed, ecs_script_computed_t); ecs_vec_fini_t(NULL, &state->symbol_slots, ecs_script_symbol_slot_t); ecs_vec_fini_t(NULL, &state->component_slots, ecs_script_component_slot_t); ecs_vec_fini_t(NULL, &state->scope_slots, int32_t); flecs_script_for_slots_fini(&state->for_slots); } void flecs_script_state_resize_computed( ecs_script_state_t *state, int32_t count) { flecs_script_state_clear_computed(state); flecs_script_slots_reset_t(&state->computed, ecs_script_computed_t, count); } void flecs_script_state_resize( ecs_script_state_t *state, int32_t scope_count, int32_t component_count, int32_t for_count) { flecs_script_slots_reset_t(&state->scope_slots, int32_t, scope_count); flecs_script_slots_reset_t(&state->component_slots, ecs_script_component_slot_t, component_count); flecs_script_for_slots_init(&state->for_slots, for_count); } int32_t flecs_script_state_next( ecs_script_state_t *state) { if (state->visit == INT32_MAX) { flecs_script_slots_reset_t(&state->scope_slots, int32_t, ecs_vec_count(&state->scope_slots)); state->visit = 0; } return ++ state->visit; } void flecs_script_state_mark( ecs_script_state_t *state, const ecs_script_region_t *region, int32_t visit) { int32_t scope_count = ecs_vec_count(&state->scope_slots); int32_t *scopes = ecs_vec_first(&state->scope_slots); for (int32_t i = 0; i < region->scope_count; i ++) { int32_t slot = region->scope_first + i; if (slot >= 0 && slot < scope_count) { scopes[slot] = visit; } } int32_t for_count = ecs_vec_count(&state->for_slots); for (int32_t i = 0; i < region->for_count; i ++) { int32_t slot = region->for_first + i; if (slot >= 0 && slot < for_count) { flecs_script_for_slot_mark(ecs_vec_get_t( &state->for_slots, ecs_script_for_slot_t, slot), visit); } } } void flecs_script_for_slot_purge( ecs_world_t *world, ecs_script_for_slot_t *slot, int32_t visit) { ecs_map_iter_t it = ecs_map_iter(&slot->named); while (ecs_map_next(&it)) { ecs_entity_t entity = ecs_map_key(&it); ecs_script_for_entry_t *entry = ecs_map_ptr(&it); bool alive = ecs_is_alive(world, entity); if (alive && entry->visit == visit) { ecs_script_for_component_t *components = ecs_vec_first(&entry->components); int32_t c, component_count = ecs_vec_count(&entry->components); for (c = component_count - 1; c >= 0; c --) { if (components[c].visit != visit) { ecs_remove_id(world, entity, components[c].component); ecs_vec_remove_t(&entry->components, ecs_script_for_component_t, c); } } continue; } if (alive) { ecs_delete(world, entity); } ecs_vec_fini_t(NULL, &entry->components, ecs_script_for_component_t); ecs_map_remove_free(&slot->named, entity); slot->cache_entity = 0; slot->cache_entry = NULL; } } void flecs_script_for_slot_clear( ecs_world_t *world, ecs_script_for_slot_t *slot, bool delete_named) { ecs_entity_t *array = ecs_vec_first(&slot->entities); int32_t i, count = ecs_vec_count(&slot->entities); for (i = 0; i < count; i ++) { if (ecs_is_alive(world, array[i])) { ecs_delete(world, array[i]); } } ecs_vec_clear(&slot->entities); if (delete_named) { flecs_script_for_slot_purge(world, slot, -1); } } void flecs_script_for_slot_track( ecs_world_t *world, ecs_script_for_slot_t *slot, ecs_entity_t entity, int32_t visit, bool *named) { if (ecs_get_name(world, entity)) { ecs_script_for_entry_t *entry = ecs_map_ensure_alloc_t( &slot->named, ecs_script_for_entry_t, entity); ecs_vec_init_if_t(&entry->components, ecs_script_for_component_t); entry->visit = visit; slot->cache_entity = entity; slot->cache_entry = entry; *named = true; return; } *named = false; ecs_entity_t *array = ecs_vec_first(&slot->entities); int32_t i, count = ecs_vec_count(&slot->entities); for (i = 0; i < count; i ++) { if (array[i] == entity) { return; } } ecs_vec_append_t(NULL, &slot->entities, ecs_entity_t)[0] = entity; } void flecs_script_for_slot_track_component( ecs_script_for_slot_t *slot, ecs_entity_t entity, ecs_id_t component, int32_t visit) { ecs_script_for_entry_t *entry; if (slot->cache_entity == entity) { entry = slot->cache_entry; } else { entry = ecs_map_get_deref(&slot->named, ecs_script_for_entry_t, entity); if (!entry) { return; } slot->cache_entity = entity; slot->cache_entry = entry; } ecs_script_for_component_t *components = ecs_vec_first(&entry->components); int32_t i, count = ecs_vec_count(&entry->components); for (i = 0; i < count; i ++) { if (components[i].component == component) { components[i].visit = visit; return; } } ecs_script_for_component_t *elem = ecs_vec_append_t( NULL, &entry->components, ecs_script_for_component_t); elem->component = component; elem->visit = visit; } #endif #ifdef FLECS_SCRIPT typedef struct EcsScriptTemplateInstanceUpdateEvent { ecs_entity_t template_entity; ecs_entity_t instance; uint64_t input; } EcsScriptTemplateInstanceUpdateEvent; static ECS_TAG_DECLARE(EcsScriptTemplateFlushEvent); static ECS_COMPONENT_DECLARE(EcsScriptTemplateInstanceUpdateEvent); ECS_COMPONENT_DECLARE(EcsScriptTemplateRoot); ECS_DECLARE(EcsScriptTemplate); ECS_DECLARE(EcsScriptTemplateManual); ECS_DECLARE(EcsScriptTemplatePending); ECS_COMPONENT_DECLARE(ecs_script_template_ref_t); static void flecs_script_template_ref_clear( ecs_world_t *world, ecs_script_template_ref_t *ref) { if (ref->value) { ecs_ptr_free(world, ref->type, ref->value); ref->value = NULL; } } void flecs_script_template_ref_set( ecs_world_t *world, ecs_script_template_ref_t *ref, ecs_entity_t type, void *value) { flecs_script_template_ref_clear(world, ref); ref->type = type; ref->value = value; } int flecs_script_template_ref_apply( ecs_world_t *world, const ecs_script_template_ref_t *ref, const ecs_type_info_t *ti, void *dst) { if (!ref || !ref->value) { return 0; } if (ref->type != ti->component) { return -1; } ecs_ptr_copy_w_type_info(world, ti, dst, ref->value); return 0; } static void flecs_script_template_ref_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.ctx; ecs_script_template_ref_t *dst = dst_ptr; const ecs_script_template_ref_t *src = src_ptr; int32_t i; for (i = 0; i < count; i ++) { void *value = NULL; if (src[i].value) { value = ecs_ptr_new(world, src[i].type); ecs_ptr_copy(world, src[i].type, value, src[i].value); } flecs_script_template_ref_set(world, &dst[i], src[i].type, value); } } static void flecs_script_template_ref_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.ctx; ecs_script_template_ref_t *dst = dst_ptr; ecs_script_template_ref_t *src = src_ptr; int32_t i; for (i = 0; i < count; i ++) { flecs_script_template_ref_set( world, &dst[i], src[i].type, src[i].value); src[i].value = NULL; } } static void flecs_script_template_ref_dtor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.ctx; ecs_script_template_ref_t *refs = ptr; int32_t i; for (i = 0; i < count; i ++) { flecs_script_template_ref_clear(world, &refs[i]); } } static int flecs_script_template_ref_value_cmp( const ecs_world_t *world, ecs_entity_t type, const void *a, const void *b) { if (a == b) { return 0; } if (!a) { return -1; } if (!b) { return 1; } const ecs_type_info_t *ti = ecs_get_type_info(world, type); if (!ti) { return 0; } if (ti->hooks.cmp && !(ti->hooks.flags & ECS_TYPE_HOOK_CMP_ILLEGAL)) { return flecs_type_info_cmp(a, b, ti); } if (ti->hooks.equals && !(ti->hooks.flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL)) { return flecs_type_info_equals(a, b, ti) ? 0 : 1; } return ecs_os_memcmp(a, b, ti->size); } static int flecs_script_template_ref_cmp( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { const ecs_script_template_ref_t *a = a_ptr; const ecs_script_template_ref_t *b = b_ptr; if (a->type != b->type) { return a->type < b->type ? -1 : 1; } return flecs_script_template_ref_value_cmp( ti->hooks.ctx, a->type, a->value, b->value); } static bool flecs_script_template_ref_equals( const void *a_ptr, const void *b_ptr, const ecs_type_info_t *ti) { return flecs_script_template_ref_cmp(a_ptr, b_ptr, ti) == 0; } static int flecs_script_template_ref_serialize( const ecs_serializer_t *ser, const void *ptr) { const ecs_script_template_ref_t *ref = ptr; return ser->value(ser, ecs_id(ecs_entity_t), &ref->type); } static void flecs_script_template_ref_assign_entity( void *ptr, ecs_world_t *world, ecs_entity_t entity) { flecs_script_template_ref_set(world, ptr, entity, NULL); } static ecs_script_template_t* flecs_script_template_get( const ecs_world_t *world, ecs_entity_t template_entity) { if (!template_entity || !ecs_is_alive(world, template_entity)) { return NULL; } const EcsScript *script = ecs_get(world, template_entity, EcsScript); return script ? script->template_ : NULL; } static const ecs_script_template_member_t* flecs_script_template_member( const ecs_script_template_t *template, int32_t index, bool mut) { if (!template) { return NULL; } const ecs_script_template_member_t *members = ecs_vec_first( &template->members); int32_t i, count = ecs_vec_count(&template->members); for (i = 0; i < count; i ++) { if (members[i].is_mut == mut && members[i].index == index) { return &members[i]; } } return NULL; } static const ecs_script_template_member_t* flecs_script_template_member_by_sp( const ecs_script_template_t *template, int32_t sp) { if (!template) { return NULL; } const ecs_script_template_member_t *members = ecs_vec_first( &template->members); int32_t i, count = ecs_vec_count(&template->members); for (i = 0; i < count; i ++) { if (members[i].sp == sp) { return &members[i]; } } return NULL; } static void flecs_script_template_root_tasks_free( EcsScriptTemplateRoot *root) { (void)root; #ifdef FLECS_SCRIPT_ASYNC flecs_script_async_tasks_free(&root->tasks); ecs_vec_clear(&root->async_blocks); #endif } static void flecs_script_template_root_fini( EcsScriptTemplateRoot *root) { flecs_script_template_root_tasks_free(root); ecs_vec_fini_t(NULL, &root->tasks, void*); ecs_vec_fini_t(NULL, &root->async_blocks, ecs_script_async_block_t); flecs_script_state_fini(&root->state); ecs_vec_fini_t(NULL, &root->observers, ecs_script_ref_t); } static void flecs_script_template_root_reset( EcsScriptTemplateRoot *root) { ecs_os_zeromem(root); ecs_vec_init_t(NULL, &root->observers, ecs_script_ref_t, 0); ecs_vec_init_t(NULL, &root->tasks, void*, 0); ecs_vec_init_t(NULL, &root->async_blocks, ecs_script_async_block_t, 0); flecs_script_state_init(&root->state); } static ECS_CTOR(EcsScriptTemplateRoot, ptr, { flecs_script_template_root_reset(ptr); }) static ECS_MOVE(EcsScriptTemplateRoot, dst, src, { flecs_script_template_root_fini(dst); *dst = *src; flecs_script_template_root_reset(src); }) static ECS_DTOR(EcsScriptTemplateRoot, ptr, { flecs_script_template_root_fini(ptr); }) static void flecs_script_template_root_init( EcsScriptTemplateRoot *root, ecs_script_template_t *template) { if (root->generation == template->generation) { return; } root->generation = template->generation; flecs_script_state_resize_computed(&root->state, template->computed_count); flecs_script_slots_reset_t(&root->state.symbol_slots, ecs_script_symbol_slot_t, template->symbol_count); ecs_script_symbol_slot_t *dst = ecs_vec_first(&root->state.symbol_slots); ecs_script_symbol_slot_t *src = ecs_vec_first( &template->script->state.symbol_slots); int32_t i; for (i = 0; i < template->symbol_count; i ++) { dst[i].scope_slot = src[template->symbol_offset + i].scope_slot; } flecs_script_state_resize(&root->state, template->scope_count, template->component_count, template->for_count); } static ecs_entity_t flecs_script_template_slot_src( const ecs_script_state_t *state, const ecs_script_component_slot_t *slot) { if (slot->entity_slot < 0 || slot->entity_slot >= ecs_vec_count(&state->symbol_slots)) { return 0; } ecs_entity_t e = ecs_vec_get_t(&state->symbol_slots, ecs_script_symbol_slot_t, slot->entity_slot)->entity; if (e == EcsVariable) { return ECS_IS_PAIR(slot->component) ? ECS_PAIR_FIRST(slot->component) : slot->component & ECS_COMPONENT_MASK; } return e; } static bool flecs_script_template_chain_has( const ecs_world_t *world, ecs_entity_t template_entity, ecs_entity_t needle) { while (template_entity) { if (template_entity == needle) { return true; } ecs_script_template_t *t = flecs_script_template_get( world, template_entity); if (!t) { break; } template_entity = t->base; } return false; } static bool flecs_script_template_root_next( const ecs_world_t *world, const ecs_table_t *table, int32_t *col, ecs_entity_t *template_entity) { ecs_id_t found = 0; if (!table) { return false; } *col = ecs_search_offset(world, table, *col + 1, ecs_pair(ecs_id(EcsScriptTemplateRoot), EcsWildcard), &found); if (*col == -1) { return false; } *template_entity = ecs_pair_second(world, found); return true; } static bool flecs_script_template_base_in_use( const ecs_world_t *world, ecs_entity_t instance, ecs_entity_t removed, ecs_entity_t base) { ecs_table_t *table = ecs_get_table(world, instance); ecs_entity_t other; int32_t col = -1; while (flecs_script_template_root_next(world, table, &col, &other)) { if (!flecs_script_template_chain_has(world, removed, other) && flecs_script_template_chain_has(world, other, base)) { return true; } } return false; } typedef struct ecs_script_template_claims_t { ecs_map_t entities; ecs_map_t components; ecs_vec_t states; } ecs_script_template_claims_t; static void flecs_script_template_claims_add( ecs_script_template_claims_t *claims, const ecs_script_state_t *state) { const ecs_script_symbol_slot_t *syms = ecs_vec_first(&state->symbol_slots); int32_t i, count = ecs_vec_count(&state->symbol_slots); for (i = 0; i < count; i ++) { if (syms[i].entity) { ecs_map_ensure(&claims->entities, syms[i].entity); } } const ecs_script_component_slot_t *comps = ecs_vec_first( &state->component_slots); count = ecs_vec_count(&state->component_slots); for (i = 0; i < count; i ++) { if (!comps[i].component) { continue; } ecs_entity_t src = flecs_script_template_slot_src(state, &comps[i]); if (!src) { continue; } ecs_vec_t *ids = ecs_map_ensure_alloc_t( &claims->components, ecs_vec_t, src); ecs_vec_init_if_t(ids, ecs_id_t); ecs_vec_append_t(NULL, ids, ecs_id_t)[0] = comps[i].component; } ecs_vec_append_t(NULL, &claims->states, const ecs_script_state_t*)[0] = state; } static void flecs_script_template_claims_fini( ecs_script_template_claims_t *claims) { ecs_map_iter_t it = ecs_map_iter(&claims->components); while (ecs_map_next(&it)) { ecs_vec_t *ids = ecs_map_ptr(&it); ecs_vec_fini_t(NULL, ids, ecs_id_t); ecs_os_free(ids); } ecs_map_fini(&claims->components); ecs_map_fini(&claims->entities); ecs_vec_fini_t(NULL, &claims->states, const ecs_script_state_t*); } static bool flecs_script_template_claims_for( const ecs_script_template_claims_t *claims, ecs_entity_t entity, ecs_id_t component) { const ecs_script_state_t **states = ecs_vec_first(&claims->states); int32_t s, state_count = ecs_vec_count(&claims->states); for (s = 0; s < state_count; s ++) { const ecs_script_for_slot_t *slots = ecs_vec_first( &states[s]->for_slots); int32_t f, for_count = ecs_vec_count(&states[s]->for_slots); for (f = 0; f < for_count; f ++) { ecs_script_for_entry_t *entry = ecs_map_get_deref( &slots[f].named, ecs_script_for_entry_t, entity); if (!entry) { continue; } if (!component) { return true; } const ecs_script_for_component_t *comps = ecs_vec_first( &entry->components); int32_t c, count = ecs_vec_count(&entry->components); for (c = 0; c < count; c ++) { if (comps[c].component == component) { return true; } } } } return false; } static bool flecs_script_template_claims_entity( const ecs_script_template_claims_t *claims, ecs_entity_t entity) { if (ecs_map_get(&claims->entities, entity)) { return true; } return flecs_script_template_claims_for(claims, entity, 0); } static bool flecs_script_template_claims_component( const ecs_script_template_claims_t *claims, ecs_entity_t src, ecs_id_t component) { ecs_vec_t *ids = ecs_map_get_deref( &claims->components, ecs_vec_t, src); if (ids) { const ecs_id_t *array = ecs_vec_first(ids); int32_t i, count = ecs_vec_count(ids); for (i = 0; i < count; i ++) { if (array[i] == component) { return true; } } } return flecs_script_template_claims_for(claims, src, component); } static void flecs_script_template_for_slot_release( ecs_world_t *stage, ecs_script_for_slot_t *slot, const ecs_script_template_claims_t *claims) { ecs_map_iter_t it = ecs_map_iter(&slot->named); while (ecs_map_next(&it)) { ecs_entity_t entity = ecs_map_key(&it); if (!flecs_script_template_claims_entity(claims, entity)) { continue; } ecs_script_for_entry_t *entry = ecs_map_ptr(&it); const ecs_script_for_component_t *comps = ecs_vec_first( &entry->components); int32_t c, count = ecs_vec_count(&entry->components); for (c = 0; c < count; c ++) { if (!flecs_script_template_claims_component( claims, entity, comps[c].component)) { ecs_remove_id(stage, entity, comps[c].component); } } ecs_vec_fini_t(NULL, &entry->components, ecs_script_for_component_t); ecs_map_remove_free(&slot->named, entity); slot->cache_entity = 0; slot->cache_entry = NULL; } } static void flecs_script_template_root_release_shared( ecs_world_t *stage, ecs_entity_t instance, ecs_entity_t template_entity, EcsScriptTemplateRoot *root) { const ecs_world_t *world = ecs_get_world(stage); if (!instance || !ecs_is_alive(world, instance)) { return; } ecs_table_t *table = ecs_get_table(world, instance); ecs_entity_t other; int32_t col = -1; bool has_claims = false; ecs_script_template_claims_t claims; while (flecs_script_template_root_next(world, table, &col, &other)) { if (!other) { continue; } if (flecs_script_template_chain_has(world, other, template_entity) || flecs_script_template_chain_has(world, template_entity, other)) { continue; } const EcsScriptTemplateRoot *sib = ecs_get_pair(world, instance, EcsScriptTemplateRoot, other); if (!sib) { continue; } if (!has_claims) { ecs_map_init(&claims.entities, NULL); ecs_map_init(&claims.components, NULL); ecs_vec_init_t(NULL, &claims.states, const ecs_script_state_t*, 0); has_claims = true; } flecs_script_template_claims_add(&claims, &sib->state); } if (!has_claims) { return; } ecs_script_symbol_slot_t *syms = ecs_vec_first(&root->state.symbol_slots); int32_t i, count = ecs_vec_count(&root->state.symbol_slots); for (i = 0; i < count; i ++) { if (syms[i].entity && flecs_script_template_claims_entity(&claims, syms[i].entity)) { syms[i].scope_slot = -1; } } ecs_script_component_slot_t *comps = ecs_vec_first( &root->state.component_slots); count = ecs_vec_count(&root->state.component_slots); for (i = 0; i < count; i ++) { if (!comps[i].component) { continue; } ecs_entity_t src = flecs_script_template_slot_src( &root->state, &comps[i]); if (src && flecs_script_template_claims_component( &claims, src, comps[i].component)) { comps[i].scope_slot = -1; } } ecs_script_for_slot_t *for_slots = ecs_vec_first(&root->state.for_slots); count = ecs_vec_count(&root->state.for_slots); for (i = 0; i < count; i ++) { flecs_script_template_for_slot_release(stage, &for_slots[i], &claims); } flecs_script_template_claims_fini(&claims); } static void flecs_script_template_root_clear( ecs_world_t *world, ecs_script_template_t *template, EcsScriptTemplateRoot *root, ecs_entity_t instance, ecs_entity_t template_entity) { flecs_script_template_root_release_shared( world, instance, template_entity, root); ecs_script_state_t state = root->state; flecs_script_template_root_tasks_free(root); flecs_script_state_clear_computed(&root->state); ecs_script_for_slot_t *for_slot_array = ecs_vec_first(&state.for_slots); int32_t i, count = ecs_vec_count(&state.for_slots); for (i = 0; i < count; i ++) { flecs_script_for_slot_clear(world, &for_slot_array[i], true); } int32_t root_symbol = template->root_symbol - template->symbol_offset; if (root_symbol >= 0 && root_symbol < ecs_vec_count(&state.symbol_slots)) { ecs_vec_get_t(&state.symbol_slots, ecs_script_symbol_slot_t, root_symbol)->scope_slot = -1; } flecs_script_slots_reset_t(&state.scope_slots, int32_t, ecs_vec_count(&state.scope_slots)); ecs_script_eval_visitor_t v = { .base.script = template->script, .world = world, .state = &state, .instance_template = template, .visit = 1 }; flecs_script_eval_cleanup(&v, true); } static void flecs_script_template_root_on_remove( ecs_iter_t *it) { ecs_world_t *world = it->stage; EcsScriptTemplateRoot *roots = ecs_field_w_size( it, sizeof(EcsScriptTemplateRoot), 0); int32_t i; for (i = 0; i < it->count; i ++) { flecs_script_ref_observers_clear(world, &roots[i].observers); } } static void flecs_script_template_root_remove( ecs_iter_t *it) { ecs_world_t *world = it->stage; ecs_entity_t template_entity = ecs_pair_second( it->world, ecs_field_id(it, 0)); ecs_script_template_t *template = flecs_script_template_get( world, template_entity); if (!template) { return; } EcsScriptTemplateRoot *roots = ecs_field_w_size( it, sizeof(EcsScriptTemplateRoot), 0); int32_t i; for (i = 0; i < it->count; i ++) { flecs_script_template_root_clear(world, template, &roots[i], it->entities[i], template_entity); } } /* Template component ctor to initialize prop or mut default values */ static void flecs_script_template_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti, bool mut) { ecs_world_t *world = ti->hooks.ctx; ecs_entity_t component = ti->component; ecs_entity_t template_entity = mut ? ecs_get_parent(world, component) : component; /* Zero-initialize memory so that hooks can safely run destructors. */ flecs_default_ctor(ptr, count, ti); ecs_script_template_t *template = flecs_script_template_get( world, template_entity); if (!template) { ecs_err("template component '%s' has no template, cannot construct", ti->name); return; } ecs_script_template_vars_t *vars = mut ? &template->muts : &template->props; if (vars->type != component) { ecs_err("component '%s' does not belong to template '%s'", ti->name, ecs_get_name(world, template_entity)); return; } const EcsStruct *st = ecs_get(world, component, EcsStruct); if (!st) { return; } if (st->members.count != ecs_vec_count(&vars->defaults)) { ecs_err("number of defaults (%d) for template component '%s' does not " "match members (%d), cannot construct", ecs_vec_count(&vars->defaults), ti->name, st->members.count); return; } const ecs_member_t *members = st->members.array; int32_t i, m, member_count = st->members.count; ecs_script_var_t *values = ecs_vec_first(&vars->defaults); for (m = 0; m < member_count; m ++) { const ecs_member_t *member = &members[m]; ecs_script_var_t *value = &values[m]; const ecs_type_info_t *mti = value->type_info; ecs_assert(mti != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < count; i ++) { void *el = ECS_ELEM(ptr, ti->size, i); ecs_ptr_copy_w_type_info(world, mti, ECS_OFFSET(el, member->offset), value->value.ptr); } } } static void flecs_script_template_props_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { flecs_script_template_ctor(ptr, count, ti, false); } static void flecs_script_template_muts_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { flecs_script_template_ctor(ptr, count, ti, true); } void flecs_script_template_pending_fini( ecs_vec_t *pending) { ecs_vec_fini_t(NULL, pending, ecs_script_template_pending_t); } static void flecs_script_template_ref_on_set( ecs_iter_t *it); static void flecs_script_template_instance_ref_on_set( ecs_iter_t *it); static void flecs_script_template_update_instance_observers( ecs_world_t *world, ecs_script_template_t *template, ecs_entity_t template_entity, ecs_entity_t instance, const ecs_script_vars_t *vars) { int32_t i, count = ecs_vec_count(&template->dynamic_refs); ecs_script_ref_t *dynamic_refs = ecs_vec_first(&template->dynamic_refs); ecs_vec_t refs; ecs_vec_init_t(NULL, &refs, ecs_script_ref_t, count); for (i = 0; i < count; i ++) { const ecs_script_var_t *var = ecs_script_vars_lookup( vars, dynamic_refs[i].name); if (!var || var->value.type != ecs_id(ecs_entity_t) || !var->value.ptr) { continue; } ecs_entity_t entity = *(ecs_entity_t*)var->value.ptr; if (!entity) { continue; } flecs_script_ref_ensure(&refs, &(ecs_script_ref_t){ .entity = entity, .component = dynamic_refs[i].component, .input = dynamic_refs[i].input, .is_has = dynamic_refs[i].is_has }); } EcsScriptTemplateRoot *root = ecs_ensure_pair( world, instance, EcsScriptTemplateRoot, template_entity); ecs_assert(root != NULL, ECS_INTERNAL_ERROR, NULL); flecs_script_update_ref_observers(world, template_entity, instance, &refs, &root->observers, flecs_script_template_instance_ref_on_set); ecs_vec_fini_t(NULL, &refs, ecs_script_ref_t); } static ecs_entity_t flecs_script_template_member_type( ecs_world_t *world, const ecs_member_t *member) { if (member->count >= 1) { return flecs_script_array_type(world, member->type, member->count); } return member->type; } const ecs_member_t* flecs_script_template_parent_member( ecs_world_t *world, const ecs_script_template_t *template, const char *name, ecs_entity_t *component) { const ecs_script_template_t *parent = template ? flecs_script_template_get(world, template->parent_type) : NULL; if (!parent) { return NULL; } ecs_entity_t types[2] = {parent->props.type, parent->muts.type}; const char *dot = strchr(name, '.'); ecs_size_t len = dot ? flecs_ito(ecs_size_t, dot - name) : ecs_os_strlen(name); for (int32_t i = 0; i < 2; i ++) { const EcsStruct *st = types[i] ? ecs_get(world, types[i], EcsStruct) : NULL; if (!st) { continue; } const ecs_member_t *members = ecs_vec_first(&st->members); for (int32_t m = 0; m < ecs_vec_count(&st->members); m ++) { if (ecs_os_strlen(members[m].name) == len && !ecs_os_strncmp(members[m].name, name, len)) { *component = types[i]; return &members[m]; } } } return NULL; } static void flecs_script_template_instantiate_vars( ecs_world_t *world, ecs_script_vars_t *vars, const ecs_script_template_t *template, const EcsStruct *props_st, void *props_data, const EcsStruct *muts_st, void *muts_data) { ecs_script_template_member_t *template_members = ecs_vec_first(&template->members); bool named = ecs_vec_count(&template->dynamic_refs) != 0; int32_t m, member_count = ecs_vec_count(&template->members); for (m = 0; m < member_count; m ++) { ecs_script_template_member_t *template_member = &template_members[m]; const EcsStruct *st = template_member->is_mut ? muts_st : props_st; void *data = template_member->is_mut ? muts_data : props_data; ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_member_t *member = ecs_vec_get_t( &st->members, ecs_member_t, template_member->index); ecs_script_var_t *var = ecs_script_vars_declare( vars, named ? member->name : NULL); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); var->value.type = flecs_script_template_member_type(world, member); var->value.ptr = ECS_OFFSET(data, member->offset); var->owned = false; } } static int flecs_script_template_init_muts( ecs_script_eval_visitor_t *v, const ecs_script_template_t *template) { ecs_script_scope_t *scope = template->node->scope; int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { ecs_script_node_t *stmt = ecs_vec_get_t( &scope->stmts, ecs_script_node_t*, i)[0]; if (stmt->kind != EcsAstMut || stmt->skip) { continue; } ecs_script_var_node_t *node = (ecs_script_var_node_t*)stmt; ecs_script_var_t *var = ecs_script_vars_from_sp(v->vars, node->sp); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } } return 0; } static int flecs_script_template_validate_interfaces( ecs_world_t *world, ecs_entity_t template_entity, const ecs_script_template_t *template, const EcsStruct *props_st, const void *props_data, const EcsStruct *muts_st, const void *muts_data) { const ecs_script_template_member_t *members = ecs_vec_first( &template->members); int32_t m, count = ecs_vec_count(&template->members); for (m = 0; m < count; m ++) { const ecs_script_template_member_t *tm = &members[m]; if (!tm->interface) { continue; } const EcsStruct *st = tm->is_mut ? muts_st : props_st; const void *data = tm->is_mut ? muts_data : props_data; if (!st || !data) { continue; } const ecs_member_t *member = ecs_vec_get_t( &st->members, ecs_member_t, tm->index); const void *ptr = ECS_OFFSET(data, member->offset); const ecs_script_template_ref_t *elems = ptr; int32_t e, elem_count = 1; if (tm->is_vector) { elems = ecs_vec_first(ptr); elem_count = ecs_vec_count(ptr); } for (e = 0; e < elem_count; e ++) { ecs_entity_t value = elems[e].type; const char *expected = flecs_script_template_interface_error( world, value, tm->interface); if (!expected) { continue; } const char *name = ecs_get_name(world, template_entity); if (!tm->is_vector && (!value || !ecs_is_alive(world, value))) { ecs_err("missing value for prop '%s' of template '%s': " "expected %s", member->name, name, expected); } else { const char *value_str = value ? flecs_errstr(ecs_get_path(world, value)) : "0"; if (tm->is_vector) { ecs_err("invalid value '%s' at index %d of prop '%s' of " "template '%s': expected %s", value_str, e, member->name, name, expected); } else { ecs_err("invalid value '%s' for prop '%s' of template " "'%s': expected %s", value_str, member->name, name, expected); } } return -1; } } return 0; } static int flecs_script_template_run( ecs_world_t *world, ecs_entity_t template_entity, ecs_script_template_t *template, ecs_entity_t instance, ecs_entity_t parent, EcsScriptTemplateRoot *root, uint64_t input) { ecs_script_impl_t *impl = template->script; ecs_script_runner_t runner; ecs_script_ir_vm_t *vm = NULL; const ecs_script_ir_entry_t *entry = NULL; ecs_script_eval_visitor_t *v; ecs_script_eval_desc_t desc = { .runtime = flecs_script_runtime_get(world) }; if (impl->ir_enabled) { vm = flecs_script_ir_vm_new(impl, &desc); if (vm->ir) { entry = flecs_script_ir_entry(vm->ir, template->node); } v = &vm->v; } else { flecs_script_eval_visit_init(impl, &runner.v, &desc); flecs_script_runner_init(&runner, &runner.v); v = &runner.v; } ecs_vec_t prev_using = v->r->using; ecs_vec_t prev_with = v->r->with; v->r->using = template->using_; ecs_vec_init_t(NULL, &v->r->with, ecs_script_with_value_t, 0); ecs_script_scope_t *scope = template->node->scope; int32_t root_symbol = template->root_symbol - template->symbol_offset; ecs_script_entity_t instance_node = { .node = { .kind = EcsAstEntity, .pos = template->node->node.pos }, .scope = scope, .symbol = template->root_symbol, .non_fragmenting_parent = template->non_fragmenting_parent }; flecs_script_entity_state_t instance_state = { .node = &instance_node, .eval = instance, .symbol = root_symbol }; int32_t visit = flecs_script_state_next(&root->state); ecs_script_state_t state = root->state; ecs_script_symbol_slot_t *root_slot = ecs_vec_get_t( &state.symbol_slots, ecs_script_symbol_slot_t, root_symbol); root_slot->entity = instance; root_slot->scope_slot = scope->scope_slot; v->template_entity = template_entity; v->body_template = template_entity; v->template_instance = instance; v->instance_template = template; v->symbol_offset = template->symbol_offset; v->entity = &instance_state; v->parent = instance; v->state = &state; flecs_script_eval_begin(v, input, visit); int result = -1; for (int32_t a = 0; a < ecs_vec_count(&template->annot); a ++) { ecs_script_annot_t *annot = ecs_vec_get_t( &template->annot, ecs_script_annot_t*, a)[0]; if (flecs_script_apply_annot( v, &instance_node, instance, annot)) { goto done; } } ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v->r->stack, v->r->allocator); vars->parent = template->vars; vars->sp = ecs_vec_count(&template->vars->vars); ecs_script_vars_set_size(vars, ecs_vec_count(&template->members) + 1 + (template->parent_type != 0)); ecs_script_var_t *this_var = ecs_script_vars_declare( vars, ecs_vec_count(&template->dynamic_refs) ? "this" : NULL); this_var->value.type = ecs_id(ecs_entity_t); this_var->value.ptr = &instance; this_var->owned = false; if (template->parent_type) { ecs_script_var_t *parent_var = ecs_script_vars_declare(vars, "#parent"); parent_var->value.type = ecs_id(ecs_entity_t); parent_var->value.ptr = &parent; parent_var->owned = false; } const ecs_type_info_t *ti = template->type_info; const EcsStruct *props_st = ecs_get(world, template_entity, EcsStruct); const EcsStruct *muts_st = template->muts.type ? ecs_get(world, template->muts.type, EcsStruct) : NULL; void *props_data = ECS_CONST_CAST(void*, ecs_get_id(world, instance, template->props.type)); void *muts_data = template->muts.type ? ECS_CONST_CAST(void*, ecs_get_id(world, instance, template->muts.type)) : NULL; void *props_copy = NULL; if (props_data && ti && ti->size) { props_copy = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); ecs_os_memcpy(props_copy, props_data, ti->size); props_data = props_copy; } flecs_script_template_instantiate_vars(world, vars, template, props_st, props_data, muts_st, muts_data); v->vars = vars; if (!state.initialized && template->muts.type) { if (flecs_script_template_init_muts(v, template)) { goto done_vars; } } if (template->has_interface_members) { if (flecs_script_template_validate_interfaces(world, template_entity, template, props_st, props_data, muts_st, muts_data)) { goto done_vars; } } bool run_ok = vm ? entry && flecs_script_ir_vm_run(vm, entry) == FlecsScriptRunDone : flecs_script_runner_run_scope(&runner, scope) == FlecsScriptRunDone; if (!run_ok) { goto done_vars; } flecs_script_eval_cleanup(v, vm ? vm->dirty : true); #ifdef FLECS_SCRIPT_ASYNC if (!vm || vm->dirty) { flecs_script_async_cleanup(v); } #endif root = ecs_ensure_pair(world, instance, EcsScriptTemplateRoot, template_entity); root->state.initialized = true; root->changed = 0; if (ecs_vec_count(&template->dynamic_refs)) { flecs_script_template_update_instance_observers( world, template, template_entity, instance, vars); } result = 0; done_vars: if (props_copy) { flecs_stack_free(props_copy, ti->size); } ecs_script_vars_pop(vars); done: ecs_vec_fini_t(v->r->allocator, &v->r->with, ecs_script_with_value_t); v->r->with = prev_with; v->r->using = prev_using; v->state = &impl->state; if (vm) { flecs_script_ir_vm_free(vm, &desc); } else { flecs_script_runner_fini(&runner, &desc); } return result; } static int flecs_script_template_instantiate_body( ecs_world_t *world, ecs_entity_t template_entity, ecs_script_template_t *template, ecs_entity_t instance, uint64_t input, bool force, bool *ran) { ecs_assert(!ecs_is_stage(world), ECS_INTERNAL_ERROR, NULL); ecs_script_runtime_t *rt = flecs_script_runtime_get(world); if (rt->template_depth >= FLECS_SCRIPT_TEMPLATE_DEPTH_MAX) { ecs_err("too many nested instantiations of template '%s', " "is the template instantiating itself?", ecs_get_name(world, template_entity)); rt->error = true; return -1; } int result = 0; if (!ecs_is_alive(world, instance) || !ecs_has_id(world, instance, template_entity)) { goto done; } if (instance == template_entity) { ecs_err("cannot instantiate template '%s' on itself", ecs_get_name(world, template_entity)); result = -1; goto done; } ecs_entity_t parent = template->parent_type ? ecs_get_parent(world, instance) : 0; if (template->parent_type && (!parent || !ecs_has_id(world, parent, template->parent_type))) { ecs_err("template '%s' requires a parent with template '%s'", ecs_get_name(world, template_entity), ecs_get_name(world, template->parent_type)); result = -1; goto done; } EcsScriptTemplateRoot *root = ecs_ensure_pair( world, instance, EcsScriptTemplateRoot, template_entity); flecs_script_template_root_init(root, template); uint64_t run_input = root->state.initialized ? input : UINT64_MAX; if (!run_input && !force) { goto done; } if ((run_input == UINT64_MAX || (run_input & template->async_input)) && ecs_vec_count(&root->async_blocks)) { flecs_script_template_root_tasks_free(root); } *ran = true; rt->template_depth ++; result = flecs_script_template_run(world, template_entity, template, instance, parent, root, force ? UINT64_MAX : run_input); rt->template_depth --; done: if (ecs_vec_count(&template->refs) && !ecs_vec_count(&template->observers)) { flecs_script_update_ref_observers(world, template_entity, 0, &template->refs, &template->observers, flecs_script_template_ref_on_set); } if (result) { if (!rt->error_name && template->script->pub.name) { rt->error_name = ecs_os_strdup(template->script->pub.name); } rt->error = true; } return result; } static uint64_t flecs_script_template_base_input( const ecs_script_template_t *template, const ecs_script_template_t *base_template, uint64_t input) { if (input == UINT64_MAX) { return input; } uint64_t result = 0; const ecs_script_template_member_t *members = ecs_vec_first( &template->members); int32_t i, count = template->inherited_count; for (i = 0; i < count; i ++) { if (!(members[i].input & input)) { continue; } const ecs_script_template_member_t *base_member = flecs_script_template_member( base_template, members[i].index, members[i].is_mut); if (base_member) { result |= base_member->input; } } return result; } static int flecs_script_template_instantiate_chain( ecs_world_t *world, ecs_entity_t template_entity, ecs_entity_t input_template, ecs_entity_t instance, uint64_t input, bool *ran) { ecs_script_template_t *template = flecs_script_template_get( world, template_entity); if (!template) { ecs_err("template is missing script component"); return -1; } bool has_input = template_entity == input_template; bool base_ran = false; ecs_script_template_t *base_template = flecs_script_template_get( world, template->base); if (base_template) { int result = flecs_script_template_instantiate_chain(world, template->base, has_input ? template->base : input_template, instance, has_input ? flecs_script_template_base_input( template, base_template, input) : input, &base_ran); if (result) { return result; } } if (base_ran) { *ran = true; } return flecs_script_template_instantiate_body(world, template_entity, template, instance, has_input ? input : 0, base_ran, ran); } /* Returns the most derived template stored on the instance that inherits from * the specified template. */ static ecs_entity_t flecs_script_template_instance_template( ecs_world_t *world, ecs_entity_t template_entity, ecs_entity_t instance) { ecs_id_t stored = 0; if (ecs_search(world, ecs_get_table(world, instance), template_entity, &stored) != -1 && stored != template_entity && flecs_script_template_get(world, stored)) { return stored; } return template_entity; } static int flecs_script_template_instantiate( ecs_world_t *world, ecs_entity_t template_entity, ecs_entity_t instance, uint64_t input) { if (!flecs_script_template_get(world, template_entity) || !ecs_is_alive(world, instance) || !ecs_has_id(world, instance, template_entity)) { return 0; } ecs_entity_t instance_template = flecs_script_template_instance_template( world, template_entity, instance); if (ecs_has_id(world, instance_template, EcsScriptTemplateManual)) { ecs_add_pair(world, instance, EcsScriptTemplatePending, instance_template); return 0; } bool ran = false; return flecs_script_template_instantiate_chain(world, instance_template, template_entity, instance, input, &ran); } static int flecs_script_template_instantiate_all( ecs_world_t *world, ecs_entity_t template_entity, uint64_t input) { ecs_vec_t instances; ecs_vec_init_t(NULL, &instances, ecs_entity_t, 0); ecs_iter_t it = ecs_each_id(world, template_entity); while (ecs_each_next(&it)) { ecs_os_memcpy_n(ecs_vec_grow_t(NULL, &instances, ecs_entity_t, it.count), it.entities, ecs_entity_t, it.count); } int result = 0; ecs_entity_t *entities = ecs_vec_first(&instances); int32_t i, count = ecs_vec_count(&instances); for (i = 0; i < count; i ++) { if (flecs_script_template_instantiate( world, template_entity, entities[i], input)) { result = -1; } } ecs_vec_fini_t(NULL, &instances, ecs_entity_t); return result; } int ecs_script_template_update( ecs_world_t *world, ecs_entity_t instance, ecs_entity_t template_entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_is_stage(world) && !ecs_stage_is_readonly(world), ECS_INVALID_OPERATION, NULL); if (!flecs_script_template_get(world, template_entity) || !ecs_is_alive(world, instance) || !ecs_has_id(world, instance, template_entity)) { return -1; } ecs_remove_pair(world, instance, EcsScriptTemplatePending, template_entity); bool ran = false; return flecs_script_template_instantiate_chain(world, flecs_script_template_instance_template( world, template_entity, instance), template_entity, instance, UINT64_MAX, &ran); error: return -1; } static void flecs_script_template_ref_on_set( ecs_iter_t *it) { ecs_script_ref_ctx_t *ctx = it->ctx; if (flecs_script_template_get(it->world, ctx->script)) { flecs_script_template_instantiate_all( it->world, ctx->script, ctx->input); } } static void flecs_script_template_instance_ref_on_set( ecs_iter_t *it) { ecs_script_ref_ctx_t *ctx = it->ctx; if ((it->event == EcsOnAdd || it->event == EcsOnRemove) && ecs_is_stage(it->stage)) { EcsScriptTemplateInstanceUpdateEvent evt = { .template_entity = ctx->script, .instance = ctx->instance, .input = ctx->input }; ecs_enqueue(it->stage, &(ecs_event_desc_t){ .event = ecs_id(EcsScriptTemplateInstanceUpdateEvent), .entity = EcsAny, .param = &evt }); return; } flecs_script_template_instantiate( it->world, ctx->script, ctx->instance, ctx->input); } static void flecs_on_template_instance_update_event( ecs_iter_t *it) { EcsScriptTemplateInstanceUpdateEvent *evt = it->param; ecs_world_t *world = it->world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); flecs_script_template_instantiate( world, evt->template_entity, evt->instance, evt->input); } static void flecs_on_template_flush_event( ecs_iter_t *it) { ecs_assert(ecs_is_stage(it->stage), ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = it->world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_script_runtime_t *rt = flecs_script_runtime_get(world); rt->template_pending_marker = false; if (rt->template_pending_active) { return; } int32_t prev_depth = rt->template_depth; rt->template_pending_active = true; int32_t i; for (i = 0; i < ecs_vec_count(&rt->template_pending); i ++) { ecs_script_template_pending_t p = ecs_vec_get_t( &rt->template_pending, ecs_script_template_pending_t, i)[0]; rt->template_depth = p.depth; flecs_script_template_instantiate( world, p.template_entity, p.entity, p.input); } ecs_vec_clear(&rt->template_pending); rt->template_pending_active = false; rt->template_depth = prev_depth; } /* Template on_set handler to update contents for new property values */ static void flecs_script_template_on_set( ecs_iter_t *it, ecs_entity_t template_entity) { if (it->table->flags & EcsTableIsPrefab) { /* Don't instantiate templates for prefabs */ return; } ecs_script_runtime_t *rt = flecs_script_runtime_get(it->world); bool any = false; int32_t i; for (i = 0; i < it->count; i ++) { const EcsScriptTemplateRoot *root = ecs_get_pair(it->world, it->entities[i], EcsScriptTemplateRoot, template_entity); uint64_t input = root && root->state.initialized ? root->changed : UINT64_MAX; if (!input) { continue; } ecs_vec_append_t(NULL, &rt->template_pending, ecs_script_template_pending_t)[0] = (ecs_script_template_pending_t){ .entity = it->entities[i], .template_entity = template_entity, .input = input, .depth = rt->template_depth }; any = true; } if (!any || rt->template_pending_marker) { return; } rt->template_pending_marker = true; ecs_enqueue(it->stage, &(ecs_event_desc_t){ .event = EcsScriptTemplateFlushEvent, .entity = EcsAny }); } static void flecs_script_template_on_add( ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_entity_t template_entity = ecs_field_id(it, 0); ecs_script_template_t *template = flecs_script_template_get( world, template_entity); if (!template) { return; } for (; template; template = flecs_script_template_get( world, template->base)) { template->refcount += it->count; } if (it->count == 1 && flecs_stage_is_ensure_add( it->world, it->entities[0], template_entity)) { return; } flecs_script_template_on_set(it, template_entity); } static void flecs_script_template_on_replace( ecs_iter_t *it, ecs_entity_t template_entity, bool mut) { ecs_world_t *world = it->world; ecs_script_template_t *template = flecs_script_template_get( world, template_entity); if (!template) { return; } ecs_entity_t component = ecs_field_id(it, 0); const ecs_type_info_t *ti = ecs_get_type_info(world, component); const EcsStruct *st = ecs_get(world, component, EcsStruct); if (!ti || !st) { return; } void *old_data = ecs_field_w_size(it, flecs_itosize(ti->size), 0); void *new_data = ecs_field_w_size(it, flecs_itosize(ti->size), 1); ecs_script_template_member_t *members = ecs_vec_first(&template->members); int32_t i, m, member_count = ecs_vec_count(&template->members); for (i = 0; i < it->count; i ++) { void *old_ptr = ECS_OFFSET(old_data, ti->size * i); void *new_ptr = ECS_OFFSET(new_data, ti->size * i); if (!ecs_os_memcmp(old_ptr, new_ptr, ti->size)) { continue; } EcsScriptTemplateRoot *root = ECS_CONST_CAST(EcsScriptTemplateRoot*, ecs_get_pair(world, it->entities[i], EcsScriptTemplateRoot, template_entity)); if (!root || !root->state.initialized) { continue; } for (m = 0; m < member_count; m ++) { ecs_script_template_member_t *template_member = &members[m]; if (template_member->is_mut != mut) { continue; } const ecs_member_t *member = ecs_vec_get_t( &st->members, ecs_member_t, template_member->index); void *old_member = ECS_OFFSET(old_ptr, member->offset); void *new_member = ECS_OFFSET(new_ptr, member->offset); if (!ecs_os_memcmp(old_member, new_member, member->size)) { continue; } const ecs_type_info_t *member_ti = ecs_get_type_info( world, member->type); ecs_assert(member_ti != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_hooks_t *h = &member_ti->hooks; if ((!h->ctor && !h->copy && !h->move && !h->dtor) || !h->equals || (h->flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL)) { root->changed |= template_member->input; continue; } int32_t e, elem_count = member->count ? member->count : 1; for (e = 0; e < elem_count; e ++) { ecs_size_t elem_offset = member_ti->size * e; if (!flecs_type_info_equals( ECS_OFFSET(old_member, elem_offset), ECS_OFFSET(new_member, elem_offset), member_ti)) { root->changed |= template_member->input; break; } } } } } static void flecs_script_template_on_props_replace( ecs_iter_t *it) { flecs_script_template_on_replace(it, ecs_field_id(it, 0), false); } static void flecs_script_template_on_muts_replace( ecs_iter_t *it) { ecs_entity_t muts_entity = ecs_field_id(it, 0); ecs_entity_t template_entity = ecs_get_parent(it->stage, muts_entity); flecs_script_template_on_replace(it, template_entity, true); } static void flecs_script_template_on_props_set( ecs_iter_t *it) { flecs_script_template_on_set(it, ecs_field_id(it, 0)); } static void flecs_script_template_on_muts_set( ecs_iter_t *it) { ecs_entity_t muts_entity = ecs_field_id(it, 0); ecs_entity_t template_entity = ecs_get_parent(it->stage, muts_entity); flecs_script_template_on_set(it, template_entity); } static void flecs_script_template_on_remove( ecs_iter_t *it) { ecs_world_t *world = it->stage; ecs_entity_t removed = ecs_field_id(it, 0); ecs_script_template_t *template = flecs_script_template_get( world, removed); if (!template) { return; } int32_t i; for (i = 0; i < it->count; i ++) { ecs_remove_pair(world, it->entities[i], EcsScriptTemplatePending, removed); } ecs_entity_t template_entity = removed; do { for (i = 0; i < it->count; i ++) { if (template_entity == removed || !flecs_script_template_base_in_use( ecs_get_world(world), it->entities[i], removed, template_entity)) { ecs_remove_pair(world, it->entities[i], ecs_id(EcsScriptTemplateRoot), template_entity); } } template->refcount -= it->count; if (template->refcount <= 0) { template->refcount = 0; flecs_script_ref_observers_clear(world, &template->observers); } template_entity = template->base; template = flecs_script_template_get(world, template_entity); } while (template); } static int flecs_script_template_override_var( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_template_t *template = v->template; const EcsStruct *st = ecs_get(v->world, template->props.type, EcsStruct); ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_member_t *members = ecs_vec_first_t(&st->members, ecs_member_t); int32_t index; int32_t count = template->inherited_count - template->inherited_mut_count; for (index = 0; index < count; index ++) { if (!ecs_os_strcmp(members[index].name, node->name)) { break; } } ecs_assert(index < count, ECS_INTERNAL_ERROR, NULL); if (!node->expr) { flecs_script_eval_error(v, node, "prop '%s' of template '%s' overrides a base prop and must " "specify a default value", node->name, ecs_get_name(v->world, template->props.type)); return -1; } ecs_entity_t member_type = flecs_script_template_member_type( v->world, &members[index]); const ecs_script_template_member_t *member = flecs_script_template_member(template, index, false); ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); if (node->eval_type != member_type || node->eval_interface != member->interface) { flecs_script_eval_error(v, node, "type of prop '%s' of template '%s' does not match the type " "of the base prop it overrides", node->name, ecs_get_name(v->world, template->props.type)); return -1; } ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } node->sp = var->sp; node->base_member = index + 1; return 0; } int flecs_script_template_eval_var( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node, bool mut) { ecs_script_template_t *template = v->template; ecs_script_template_vars_t *vars = mut ? &template->muts : &template->props; if (ecs_vec_count(&v->vars->vars) > ecs_vec_count(&template->members) - template->inherited_count) { flecs_script_eval_error(v, node, "const variables declared before %s '%s' (props and muts must " "come first)", mut ? "mut" : "prop", node->name); return -1; } if (flecs_script_struct_member_is_inherited( v->world, template->props.type, node->name)) { if (mut) { flecs_script_eval_error(v, node, "mut '%s' of template '%s' is already defined by base type", node->name, ecs_get_name(v->world, template->props.type)); return -1; } return flecs_script_template_override_var(v, node); } if (template->inherited_mut_count && flecs_script_struct_member_is_inherited( v->world, template->muts.type, node->name)) { flecs_script_eval_error(v, node, "%s '%s' of template '%s' is already defined as mut by base type", mut ? "mut" : "prop", node->name, ecs_get_name(v->world, template->props.type)); return -1; } ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); return -1; } ecs_entity_t type = node->eval_type; ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); if (!ti) { return -1; } if (ecs_set_rtt_equals(v->world, type)) { flecs_script_eval_error(v, node, "type '%s' cannot be compared", ti->name); return -1; } ti = ecs_get_type_info(v->world, type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.type = type; var->value.ptr = flecs_stack_calloc( &v->r->stack, ti->size, ti->alignment); var->type_info = ti; var->owned = true; node->sp = var->sp; flecs_type_info_ctor(var->value.ptr, 1, ti); if (node->expr) { if (flecs_script_eval_expr(v, &node->expr, &var->value)) { return -1; } } else if (node->eval_interface && !node->type_is_vector && node->eval_interface != ecs_id(EcsStruct)) { ((ecs_script_template_ref_t*)var->value.ptr)->type = node->eval_interface; } if (node->type_is_vector && node->eval_interface) { const ecs_vec_t *vec = var->value.ptr; const ecs_script_template_ref_t *elems = ecs_vec_first(vec); int32_t e, elem_count = ecs_vec_count(vec); for (e = 0; e < elem_count; e ++) { const char *expected = flecs_script_template_interface_error( v->world, elems[e].type, node->eval_interface); if (expected) { flecs_script_eval_error(v, node, "'%s' at index %d of prop '%s' is not %s", elems[e].type ? flecs_errstr( ecs_get_path(v->world, elems[e].type)) : "0", e, node->name, expected); return -1; } } } ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, &vars->defaults, ecs_script_var_t); value->value.ptr = flecs_calloc_w_dbg_info( &v->base.script->allocator, ti->size, ti->name); value->value.type = type; value->type_info = ti; value->owned = false; /* Not stack storage, freed with the template */ ecs_ptr_copy_w_type_info( v->world, ti, value->value.ptr, var->value.ptr); if (mut && !vars->type) { vars->type = ecs_entity(v->world, { .name = "mut", .parent = template->props.type }); } if (ecs_struct_add_member(v->world, vars->type, &(ecs_member_t){ .name = node->name, .type = var->value.type })) { flecs_script_eval_error(v, node, "failed to add %s '%s' to template type", mut ? "mut" : "prop", node->name); return -1; } ecs_script_template_member_t *member = ecs_vec_append_t( &v->base.script->allocator, &template->members, ecs_script_template_member_t); member->index = ecs_vec_count(&vars->defaults) - 1; member->sp = var->sp; member->input = 0; member->is_mut = mut; member->is_vector = node->type_is_vector; member->interface = node->eval_interface; if (member->interface) { template->has_interface_members = true; } return 0; } static void flecs_script_template_declare_inherited_vars( ecs_script_eval_visitor_t *v, ecs_script_template_vars_t *vars, int32_t count) { if (!count) { return; } const EcsStruct *st = ecs_get(v->world, vars->type, EcsStruct); ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&st->members) >= count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&vars->defaults) >= count, ECS_INTERNAL_ERROR, NULL); const ecs_member_t *members = ecs_vec_first_t(&st->members, ecs_member_t); ecs_script_var_t *defaults = ecs_vec_first(&vars->defaults); int32_t i; for (i = 0; i < count; i ++) { ecs_script_var_t *var = ecs_script_vars_declare( v->vars, members[i].name); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); var->value.type = flecs_script_template_member_type( v->world, &members[i]); var->value.ptr = defaults[i].value.ptr; var->type_info = defaults[i].type_info; var->owned = false; } } static int flecs_script_template_inherit_members( ecs_script_eval_visitor_t *v, ecs_script_template_t *template, ecs_script_template_node_t *node, const ecs_script_template_t *base_template, bool mut) { ecs_world_t *world = v->world; ecs_allocator_t *a = &v->base.script->allocator; ecs_script_template_vars_t *vars = mut ? &template->muts : &template->props; if (!ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = vars->type })) { flecs_script_eval_error(v, node, "failed to inherit members of base '%s' for template '%s'", node->base, node->name); return -1; } const EcsStruct *st = ecs_get(world, vars->type, EcsStruct); ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = flecs_struct_inherited_count(world, vars->type, st); if (!count) { return 0; } const ecs_script_var_t *base_defaults = NULL; if (base_template) { const ecs_script_template_vars_t *base_vars = mut ? &base_template->muts : &base_template->props; if (ecs_vec_count(&base_vars->defaults) != count) { flecs_script_eval_error(v, node, "base template '%s' has %d %s defaults, expected %d", node->base, ecs_vec_count(&base_vars->defaults), mut ? "mut" : "prop", count); return -1; } base_defaults = ecs_vec_first(&base_vars->defaults); } int32_t sp = ecs_vec_count(&template->vars->vars) + 1 + (template->parent_type != 0) + template->inherited_count; const ecs_member_t *members = ecs_vec_first_t(&st->members, ecs_member_t); for (i = 0; i < count; i ++) { ecs_entity_t member_type = flecs_script_template_member_type( world, &members[i]); const ecs_type_info_t *ti = ecs_get_type_info(world, member_type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_set_rtt_equals(world, member_type); ecs_script_var_t *value = ecs_vec_append_t( a, &vars->defaults, ecs_script_var_t); ecs_os_zeromem(value); value->name = members[i].name; value->value.ptr = flecs_calloc_w_dbg_info(a, ti->size, ti->name); value->value.type = member_type; value->type_info = ti; value->owned = false; if (base_defaults) { ecs_ptr_copy_w_type_info(world, ti, value->value.ptr, base_defaults[i].value.ptr); } else { flecs_type_info_ctor(value->value.ptr, 1, ti); } ecs_script_template_member_t *member = ecs_vec_append_t( a, &template->members, ecs_script_template_member_t); member->index = i; member->sp = sp + i; member->input = 0; member->is_mut = mut; const ecs_script_template_member_t *base_member = flecs_script_template_member(base_template, i, mut); member->is_vector = base_member && base_member->is_vector; member->interface = base_member ? base_member->interface : 0; if (member->interface) { template->has_interface_members = true; } } template->inherited_count += count; if (mut) { template->inherited_mut_count = count; } return 0; } static int flecs_script_template_inherit( ecs_script_eval_visitor_t *v, ecs_script_template_t *template, ecs_script_template_node_t *node) { ecs_world_t *world = v->world; ecs_entity_t base = node->eval_base; ecs_entity_t template_entity = template->props.type; ecs_script_template_t *base_template = flecs_script_template_get( world, base); bool has_members = ecs_has(world, base, EcsStruct); if (!has_members && !base_template) { flecs_script_eval_error(v, node, "base '%s' of template '%s' is not a struct type", node->base, node->name); return -1; } if (base == template_entity) { flecs_script_eval_error(v, node, "template '%s' cannot inherit from itself", node->name); return -1; } ecs_add_pair(world, template_entity, EcsIsA, base); if (base_template) { template->base = base; } if (has_members && flecs_script_template_inherit_members( v, template, node, base_template, false)) { return -1; } if (!base_template || !base_template->muts.type) { return 0; } const ecs_script_template_t *cur = base_template; while (cur) { if (cur->muts.type) { ecs_remove_pair(world, template_entity, EcsWith, cur->muts.type); } cur = flecs_script_template_get(world, cur->base); } template->muts.type = ecs_entity(world, { .name = "mut", .parent = template_entity }); ecs_add_pair(world, template->muts.type, EcsIsA, base_template->muts.type); return flecs_script_template_inherit_members( v, template, node, base_template, true); } bool flecs_script_template_interface_accepts( const ecs_world_t *world, ecs_entity_t value, ecs_entity_t interface) { if (interface == ecs_id(EcsStruct)) { return ecs_has(world, value, EcsStruct); } if (value == interface) { return true; } return flecs_script_template_get(world, value) && flecs_struct_is_derived_from(world, value, interface); } char* flecs_script_template_expected_str( const ecs_world_t *world, ecs_entity_t interface) { if (interface == ecs_id(EcsStruct)) { return ecs_os_strdup("a struct type"); } char *path = ecs_get_path(world, interface); char *result = flecs_asprintf("a template derived from '%s'", path); ecs_os_free(path); return result; } const char* flecs_script_template_interface_error( const ecs_world_t *world, ecs_entity_t value, ecs_entity_t interface) { if (value && ecs_is_alive(world, value) && flecs_script_template_interface_accepts(world, value, interface)) { return NULL; } return flecs_errstr_5(flecs_script_template_expected_str(world, interface)); } ecs_entity_t flecs_script_template_prop_interface( const ecs_world_t *world, ecs_entity_t type, const char *member) { const ecs_script_template_t *template = flecs_script_template_get( world, type); const EcsStruct *st = template ? ecs_get(world, type, EcsStruct) : NULL; if (!st || !member) { return 0; } const ecs_member_t *members = ecs_vec_first(&st->members); int32_t i, count = ecs_vec_count(&st->members); for (i = 0; i < count; i ++) { if (!ecs_os_strcmp(members[i].name, member)) { const ecs_script_template_member_t *m = flecs_script_template_member(template, i, false); return m ? m->interface : 0; } } return 0; } ecs_entity_t flecs_script_template_member_interface( const ecs_script_template_t *template, int32_t sp) { const ecs_script_template_member_t *m = flecs_script_template_member_by_sp(template, sp); return m ? m->interface : 0; } bool flecs_script_template_member_is_vector( const ecs_script_template_t *template, int32_t sp) { const ecs_script_template_member_t *m = flecs_script_template_member_by_sp(template, sp); return m && m->is_vector; } static int flecs_script_template_preprocess( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { ecs_script_type_visitor_t *t = v->type_visitor; ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_template_node_t *tnode = template->node; if (tnode->symbol_offset == -1) { tnode->symbol_offset = ecs_vec_count(&v->base.script->state.symbol_slots); } template->symbol_offset = tnode->symbol_offset; if (tnode->root_symbol == -1) { tnode->root_symbol = flecs_script_type_slot_new(t, -1); } template->root_symbol = tnode->root_symbol; int32_t old_table = t->table; bool old_template_scope = t->template_scope; int32_t old_for_depth = t->for_depth; t->for_depth = 0; flecs_script_entity_state_t *old_entity = v->entity; ecs_script_template_t *old_template = v->template; ecs_script_template_t *old_instance_template = v->instance_template; ecs_entity_t old_script_entity = v->script_entity; v->template = template; v->instance_template = template; v->script_entity = 0; ecs_script_entity_t instance_node = { .node = { .kind = EcsAstEntity, .pos = template->node->node.pos }, .symbol = -1, .kind_symbol = -1, .kind_sp = -1 }; flecs_script_entity_state_t instance_state = { .node = &instance_node }; v->entity = &instance_state; t->template_scope = true; ecs_script_vars_t *outer_vars = v->vars; ecs_script_vars_t *type_vars = flecs_script_vars_push( NULL, &v->r->stack, v->r->allocator); type_vars->world = v->world; int32_t i, var_count = ecs_vec_count(&template->vars->vars); ecs_script_var_t *src_vars = ecs_vec_first(&template->vars->vars); for (i = 0; i < var_count; i ++) { ecs_script_var_t *src = &src_vars[i]; ecs_script_var_t *dst = ecs_script_vars_define_id( type_vars, src->name, src->value.type); ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); ecs_ptr_copy(v->world, src->value.type, dst->value.ptr, src->value.ptr); dst->is_const = false; } v->vars = ecs_script_vars_push(type_vars); ecs_script_var_t *this_var = ecs_script_vars_declare(v->vars, "this"); this_var->value.type = ecs_id(ecs_entity_t); if (template->parent_type) { ecs_script_var_t *parent_var = ecs_script_vars_declare(v->vars, "#parent"); parent_var->value.type = ecs_id(ecs_entity_t); template->parent_sp = parent_var->sp; } flecs_script_template_declare_inherited_vars(v, &template->props, template->inherited_count - template->inherited_mut_count); flecs_script_template_declare_inherited_vars(v, &template->muts, template->inherited_mut_count); int32_t table = flecs_script_type_table_new( t, old_table, NULL); int result = flecs_script_type_scope( t, template->node->scope, table, true, false); v->vars = ecs_script_vars_pop(v->vars); ecs_script_vars_pop(v->vars); v->vars = outer_vars; t->template_scope = old_template_scope; t->for_depth = old_for_depth; t->table = old_table; v->entity = old_entity; v->script_entity = old_script_entity; v->instance_template = old_instance_template; v->template = old_template; if (tnode->symbol_count == -1) { tnode->symbol_count = ecs_vec_count( &v->base.script->state.symbol_slots) - template->symbol_offset; } template->symbol_count = tnode->symbol_count; return result; } static void flecs_script_template_hoist_using( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { ecs_allocator_t *a = &v->base.script->allocator; if (v->module) { ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; } int i, count = ecs_vec_count(&v->r->using); for (i = 0; i < count; i ++) { ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } } static void flecs_script_template_hoist_vars( ecs_script_eval_visitor_t *v, ecs_script_template_t *template, ecs_script_vars_t *vars) { int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *src = &src_vars[i]; if (!src->is_const) { continue; } if (ecs_script_vars_lookup(template->vars, src->name)) { /* If variable is masked, don't declare it twice */ continue; } ecs_script_var_t *dst = ecs_script_vars_define_id( template->vars, src->name, src->value.type); ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); if (src->value.ptr) { ecs_ptr_copy(v->world, src->value.type, dst->value.ptr, src->value.ptr); } dst->is_const = true; ecs_vec_append_t(NULL, &template->capture_sp, int32_t)[0] = src->sp; } if (vars->parent) { flecs_script_template_hoist_vars(v, template, vars->parent); } } static ecs_script_template_t* flecs_script_template_init( ecs_script_impl_t *script) { ecs_allocator_t *a = &script->allocator; ecs_script_template_t *result = flecs_calloc_t(a, ecs_script_template_t); result->script = script; ecs_vec_init_t(NULL, &result->props.defaults, ecs_script_var_t, 0); ecs_vec_init_t(NULL, &result->muts.defaults, ecs_script_var_t, 0); ecs_vec_init_t(NULL, &result->members, ecs_script_template_member_t, 0); ecs_vec_init_t(NULL, &result->using_, ecs_entity_t, 0); ecs_vec_init_t(NULL, &result->annot, ecs_script_annot_t*, 0); ecs_vec_init_t(NULL, &result->refs, ecs_script_ref_t, 0); ecs_vec_init_t(NULL, &result->observers, ecs_script_ref_t, 0); ecs_vec_init_t(NULL, &result->dynamic_refs, ecs_script_ref_t, 0); ecs_vec_init_t(NULL, &result->capture_sp, int32_t, 0); ecs_vec_init_t(NULL, &result->capture_input, ecs_script_template_capture_t, 0); result->async_input = UINT64_MAX; result->root_symbol = -1; result->parent_sp = -1; result->generation = 1; result->vars = ecs_script_vars_init(script->pub.world); return result; } void flecs_script_template_fini( ecs_script_impl_t *script, ecs_script_template_t *template) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &script->allocator; ecs_vec_t *defaults[2] = { &template->props.defaults, &template->muts.defaults }; int32_t d, i; for (d = 0; d < 2; d ++) { int32_t count = ecs_vec_count(defaults[d]); ecs_script_var_t *values = ecs_vec_first(defaults[d]); for (i = 0; i < count; i ++) { ecs_script_var_t *value = &values[i]; const ecs_type_info_t *ti = value->type_info; if (ti->hooks.dtor) { flecs_type_info_dtor(value->value.ptr, 1, ti); } flecs_free(a, ti->size, value->value.ptr); } ecs_vec_fini_t(a, defaults[d], ecs_script_var_t); } ecs_vec_fini_t(a, &template->members, ecs_script_template_member_t); ecs_vec_fini_t(a, &template->using_, ecs_entity_t); ecs_vec_fini_t(a, &template->annot, ecs_script_annot_t*); ecs_vec_fini_t(NULL, &template->refs, ecs_script_ref_t); flecs_script_ref_observers_fini(&template->observers); ecs_vec_fini_t(NULL, &template->dynamic_refs, ecs_script_ref_t); ecs_vec_fini_t(NULL, &template->capture_sp, int32_t); ecs_vec_fini_t(NULL, &template->capture_input, ecs_script_template_capture_t); ecs_script_vars_fini(template->vars); flecs_free_t(a, ecs_script_template_t, template); } /* Create new template */ int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *node) { ecs_entity_t template_entity = node->symbol != -1 ? flecs_script_symbol_entity(v, node->symbol) : 0; if (!template_entity) { template_entity = flecs_script_create_entity(v, node->name); if (node->symbol != -1 && template_entity) { flecs_script_symbol_set(v, node->symbol, template_entity); } } if (!template_entity) { return -1; } ecs_script_template_t *template = flecs_script_template_init(v->base.script); template->props.type = template_entity; template->node = node; flecs_script_template_hoist_using(v, template); flecs_script_template_hoist_vars(v, template, v->vars); template->parent_type = node->eval_parent; if (node->base) { const ecs_script_template_t *base_template = flecs_script_template_get( v->world, node->eval_base); if (base_template) { ecs_entity_t inherited_parent = base_template->parent_type; if (inherited_parent && template->parent_type && inherited_parent != template->parent_type) { flecs_script_eval_error(v, node, "parent constraint conflicts with base template '%s'", node->base); goto error; } if (!template->parent_type) { template->parent_type = inherited_parent; } } } if (template->parent_type) { if (!flecs_script_template_get(v->world, template->parent_type)) { flecs_script_eval_error(v, node, "parent constraint must name a template"); goto error; } } if (node->base && flecs_script_template_inherit(v, template, node)) { goto error; } ecs_vec_t outer_annot = v->r->annot; ecs_vec_init_t(v->r->allocator, &v->r->annot, ecs_script_annot_t*, 0); int preprocess_result = flecs_script_template_preprocess(v, template); ecs_vec_fini_t(v->r->allocator, &v->r->annot, ecs_script_annot_t*); v->r->annot = outer_annot; if (preprocess_result) { goto error; } if (!ecs_has(v->world, template_entity, EcsStruct)) { if (!ecs_struct_init(v->world, &(ecs_struct_desc_t){ .entity = template_entity })) { goto error; } } /* If template has mut properties, add those when the template is added */ if (template->muts.type) { ecs_add_id(v->world, template->muts.type, EcsSparse); ecs_add_pair(v->world, template_entity, EcsWith, template->muts.type); } /* Consume annotations, if any */ int32_t i, count = ecs_vec_count(&v->r->annot); if (count) { ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); for (i = 0; i < count ; i ++) { ecs_script_annot_t *annot = annots[i]; if (!ecs_os_strcmp(annot->name, "tree")) { if (ecs_os_strcmp(annot->expr, "Parent") && ecs_os_strcmp(annot->expr, "ChildOf")) { flecs_script_eval_error(v, annot, "invalid value for tree annotation: '%s' (expected " "'Parent' or 'ChildOf')", annot->expr); goto error; } ecs_script_entity_t tree_node = { .node = { .kind = EcsAstEntity, .pos = node->node.pos }, .scope = node->scope }; flecs_script_apply_tree_annot(annot, &tree_node); template->non_fragmenting_parent = tree_node.non_fragmenting_parent; continue; } ecs_vec_append_t(&v->base.script->allocator, &template->annot, ecs_script_annot_t*)[0] = annot; } ecs_vec_clear(&v->r->annot); } template->type_info = ecs_get_type_info(v->world, template_entity); EcsScript *script = ecs_ensure(v->world, template_entity, EcsScript); if (script->script) { if (script->template_) { template->refcount = script->template_->refcount; template->generation = script->template_->generation + 1; flecs_script_ref_observers_clear( v->world, &script->template_->observers); flecs_script_template_fini( flecs_script_impl(script->script), script->template_); } ecs_script_free(script->script); } script->script = &v->base.script->pub; script->template_ = template; ecs_modified(v->world, template_entity, EcsScript); ecs_set_hooks_id(v->world, template_entity, &(ecs_type_hooks_t) { .ctor = flecs_script_template_props_ctor, .on_add = flecs_script_template_on_add, .on_replace = flecs_script_template_on_props_replace, .on_set = flecs_script_template_on_props_set, .on_remove = flecs_script_template_on_remove, .ctx = v->world }); if (template->muts.type) { ecs_set_hooks_id(v->world, template->muts.type, &(ecs_type_hooks_t) { .ctor = flecs_script_template_muts_ctor, .on_replace = flecs_script_template_on_muts_replace, .on_set = flecs_script_template_on_muts_set, .ctx = v->world }); } /* Keep script alive for as long as template is alive */ v->base.script->refcount ++; return 0; error: flecs_script_template_fini(v->base.script, template); return -1; } int flecs_script_template_update_vars( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *node) { ecs_entity_t entity = flecs_script_symbol_entity(v, node->symbol); ecs_script_template_t *template = flecs_script_template_get( v->world, entity); ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(&template->capture_sp); ecs_assert(count == ecs_vec_count(&template->vars->vars), ECS_INTERNAL_ERROR, NULL); int32_t *capture_sp = ecs_vec_first(&template->capture_sp); ecs_script_var_t *dst = ecs_vec_first(&template->vars->vars); for (int32_t i = 0; i < count; i ++) { ecs_script_var_t *src = ecs_script_vars_from_sp( v->vars, capture_sp[i]); if (!src || !src->value.ptr) { flecs_script_eval_error(v, node, "variable '%s' captured by template has no value", dst[i].name); return -1; } if (src->value.type != dst[i].value.type) { flecs_script_eval_error(v, node, "variable '%s' captured by template has type %s, expected %s", dst[i].name, flecs_errstr(ecs_get_path(v->world, src->value.type)), flecs_errstr_1(ecs_get_path(v->world, dst[i].value.type))); return -1; } ecs_ptr_copy(v->world, src->value.type, dst[i].value.ptr, src->value.ptr); } /* The inputs of a template are numbered independently from the inputs of * the script that declares it. Translate the inputs of the enclosing scope * to the inputs the template body uses for its captured variables. */ uint64_t input = UINT64_MAX; if (!v->force) { input = 0; ecs_script_template_capture_t *captures = ecs_vec_first( &template->capture_input); int32_t capture_count = ecs_vec_count(&template->capture_input); for (int32_t i = 0; i < capture_count; i ++) { if ((captures[i].outer_input & v->input) || (captures[i].outer_internal & v->internal)) { input |= captures[i].input; } } } return flecs_script_template_instantiate_all(v->world, entity, input); } void flecs_script_template_import( ecs_world_t *world) { ECS_TAG_DEFINE(world, EcsScriptTemplateFlushEvent); ECS_COMPONENT_DEFINE(world, EcsScriptTemplateInstanceUpdateEvent); ECS_COMPONENT_DEFINE(world, EcsScriptTemplateRoot); ECS_COMPONENT_DEFINE(world, ecs_script_template_ref_t); ecs_set_hooks(world, ecs_script_template_ref_t, { .ctor = flecs_default_ctor, .copy = flecs_script_template_ref_copy, .move = flecs_script_template_ref_move, .dtor = flecs_script_template_ref_dtor, .cmp = flecs_script_template_ref_cmp, .equals = flecs_script_template_ref_equals, .ctx = world }); ecs_opaque(world, { .entity = ecs_id(ecs_script_template_ref_t), .type.as_type = ecs_id(ecs_entity_t), .type.serialize = flecs_script_template_ref_serialize, .type.assign_entity = flecs_script_template_ref_assign_entity }); ECS_TAG_DEFINE(world, EcsScriptTemplate); ECS_TAG_DEFINE(world, EcsScriptTemplateManual); ECS_TAG_DEFINE(world, EcsScriptTemplatePending); ecs_add_id(world, EcsScriptTemplatePending, EcsPairIsTag); ecs_add_pair(world, EcsScriptTemplatePending, EcsOnInstantiate, EcsDontInherit); ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); #ifdef FLECS_CONSTRAINT_TRAITS ecs_add_id(world, EcsScriptTemplate, EcsRelationship); #endif ecs_set_hooks(world, EcsScriptTemplateRoot, { .ctor = ecs_ctor(EcsScriptTemplateRoot), .move = ecs_move(EcsScriptTemplateRoot), .dtor = ecs_dtor(EcsScriptTemplateRoot), .on_remove = flecs_script_template_root_on_remove, .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_add_pair(world, ecs_id(EcsScriptTemplateRoot), EcsOnInstantiate, EcsDontInherit); ecs_observer(world, { .entity = ecs_entity(world, { .name = "TemplateRootRemoveObserver" }), .query.terms = {{ .id = ecs_pair(ecs_id(EcsScriptTemplateRoot), EcsWildcard) }}, .events = { EcsOnRemove }, .callback = flecs_script_template_root_remove }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "TemplateFlushObserver" }), .query.terms = {{ .id = EcsAny }}, .events = { EcsScriptTemplateFlushEvent }, .callback = flecs_on_template_flush_event }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "TemplateInstanceUpdateObserver" }), .query.terms = {{ .id = EcsAny }}, .events = { ecs_id(EcsScriptTemplateInstanceUpdateEvent) }, .callback = flecs_on_template_instance_update_event }); } #endif