/** * @file bootstrap.c * @brief Bootstrap entities in the flecs.core namespace. * * Before the ECS storage can be used, core entities such first need to be * initialized. For example, components in Flecs are stored as entities in the * ECS storage itself with an EcsComponent component, but before this component * can be stored, the component itself needs to be initialized. * * The bootstrap code uses lower-level APIs to initialize the data structures. * After bootstrap is completed, regular ECS operations can be used to create * entities and components. * * The bootstrap file also includes several lifecycle hooks and observers for * builtin features, such as relationship properties and hooks for keeping the * entity name administration in sync with the (Identifier, Name) component. */ #include "flecs.h" /** * @file private_api.h * @brief Private functions. */ #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H /** * @file private_types.h * @brief Private types. */ #ifndef FLECS_PRIVATE_TYPES_H #define FLECS_PRIVATE_TYPES_H #ifndef __MACH__ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif #include #include #include /** * @file datastructures/entity_index.h * @brief Entity index data structure. * * The entity index stores the table, row for an entity id. */ #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS) #define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1) typedef struct ecs_entity_index_page_t { ecs_record_t records[FLECS_ENTITY_PAGE_SIZE]; } ecs_entity_index_page_t; typedef struct ecs_entity_index_t { ecs_vec_t dense; ecs_vec_t pages; int32_t alive_count; uint64_t max_id; ecs_block_allocator_t page_allocator; ecs_allocator_t *allocator; } ecs_entity_index_t; /** Initialize entity index. */ void flecs_entity_index_init( ecs_allocator_t *allocator, ecs_entity_index_t *index); /** Deinitialize entity index. */ void flecs_entity_index_fini( ecs_entity_index_t *index); /* Get entity (must exist/must be alive) */ ecs_record_t* flecs_entity_index_get( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (must exist/may not be alive) */ ecs_record_t* flecs_entity_index_get_any( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (may not exist/must be alive) */ ecs_record_t* flecs_entity_index_try_get( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (may not exist/may not be alive) */ ecs_record_t* flecs_entity_index_try_get_any( const ecs_entity_index_t *index, uint64_t entity); /** Ensure entity exists. */ ecs_record_t* flecs_entity_index_ensure( ecs_entity_index_t *index, uint64_t entity); /* Remove entity */ void flecs_entity_index_remove( ecs_entity_index_t *index, uint64_t entity); /* Set generation of entity */ void flecs_entity_index_set_generation( ecs_entity_index_t *index, uint64_t entity); /* Get current generation of entity */ uint64_t flecs_entity_index_get_generation( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is alive */ bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is valid */ bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity exists */ bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity); /* Create or recycle entity id */ uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index); /* Bulk create or recycle new entity ids */ uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count); /* Set size of index */ void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size); /* Return number of entities in index */ int32_t flecs_entity_index_count( const ecs_entity_index_t *index); /* Return number of allocated entities in index */ int32_t flecs_entity_index_size( const ecs_entity_index_t *index); /* Return number of not alive entities in index */ int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index); /* Clear entity index */ void flecs_entity_index_clear( ecs_entity_index_t *index); /* Return number of alive entities in index */ const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index); void flecs_entity_index_copy( ecs_entity_index_t *dst, const ecs_entity_index_t *src); void flecs_entity_index_restore( ecs_entity_index_t *dst, const ecs_entity_index_t *src); #define ecs_eis(world) (&((world)->store.entity_index)) #define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world)) #define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world)) #define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity) #define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity) #define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity) #define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity) #define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity) #define flecs_entities_set_generation(world, entity) flecs_entity_index_set_generation(ecs_eis(world), entity) #define flecs_entities_get_generation(world, entity) flecs_entity_index_get_generation(ecs_eis(world), entity) #define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) #define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) #define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) #define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) #define flecs_entities_max_id(world) (ecs_eis(world)->max_id) #define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) #define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) #define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) #define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) #define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) #define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) #define flecs_entities_copy(dst, src) flecs_entity_index_copy(dst, src) #define flecs_entities_restore(dst, src) flecs_entity_index_restore(dst, src) #endif /** * @file datastructures/stack_allocator.h * @brief Stack allocator. */ #ifndef FLECS_STACK_ALLOCATOR_H #define FLECS_STACK_ALLOCATOR_H /** Stack allocator for quick allocation of small temporary values */ #define ECS_STACK_PAGE_SIZE (4096) typedef struct ecs_stack_page_t { void *data; struct ecs_stack_page_t *next; int16_t sp; uint32_t id; } ecs_stack_page_t; typedef struct ecs_stack_t { ecs_stack_page_t first; ecs_stack_page_t *tail_page; ecs_stack_cursor_t *tail_cursor; #ifdef FLECS_DEBUG int32_t cursor_count; #endif } ecs_stack_t; FLECS_DBG_API void flecs_stack_init( ecs_stack_t *stack); FLECS_DBG_API void flecs_stack_fini( ecs_stack_t *stack); FLECS_DBG_API void* flecs_stack_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align); #define flecs_stack_alloc_t(stack, T)\ flecs_stack_alloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_stack_alloc_n(stack, T, count)\ flecs_stack_alloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) FLECS_DBG_API void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align); #define flecs_stack_calloc_t(stack, T)\ flecs_stack_calloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_stack_calloc_n(stack, T, count)\ flecs_stack_calloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) FLECS_DBG_API void flecs_stack_free( void *ptr, ecs_size_t size); #define flecs_stack_free_t(ptr, T)\ flecs_stack_free(ptr, ECS_SIZEOF(T)) #define flecs_stack_free_n(ptr, T, count)\ flecs_stack_free(ptr, ECS_SIZEOF(T) * count) void flecs_stack_reset( ecs_stack_t *stack); FLECS_DBG_API ecs_stack_cursor_t* flecs_stack_get_cursor( ecs_stack_t *stack); FLECS_DBG_API void flecs_stack_restore_cursor( ecs_stack_t *stack, ecs_stack_cursor_t *cursor); #endif /** * @file bitset.h * @brief Bitset data structure. */ #ifndef FLECS_BITSET_H #define FLECS_BITSET_H #ifdef __cplusplus extern "C" { #endif typedef struct ecs_bitset_t { uint64_t *data; int32_t count; ecs_size_t size; } ecs_bitset_t; /** Initialize bitset. */ FLECS_DBG_API void flecs_bitset_init( ecs_bitset_t *bs); /** Deinialize bitset. */ FLECS_DBG_API void flecs_bitset_fini( ecs_bitset_t *bs); /** Add n elements to bitset. */ FLECS_DBG_API void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count); /** Ensure element exists. */ FLECS_DBG_API void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count); /** Set element. */ FLECS_DBG_API void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value); /** Get element. */ FLECS_DBG_API bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem); /** Return number of elements. */ FLECS_DBG_API int32_t flecs_bitset_count( const ecs_bitset_t *bs); /** Remove from bitset. */ FLECS_DBG_API void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem); /** Swap values in bitset. */ FLECS_DBG_API void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b); #ifdef __cplusplus } #endif #endif /** * @file switch_list.h * @brief Interleaved linked list for storing mutually exclusive values. */ #ifndef FLECS_SWITCH_LIST_H #define FLECS_SWITCH_LIST_H typedef struct ecs_switch_header_t { int32_t element; /* First element for value */ int32_t count; /* Number of elements for value */ } ecs_switch_header_t; typedef struct ecs_switch_node_t { int32_t next; /* Next node in list */ int32_t prev; /* Prev node in list */ } ecs_switch_node_t; struct ecs_switch_t { ecs_map_t hdrs; /* map */ ecs_vec_t nodes; /* vec */ ecs_vec_t values; /* vec */ }; /** Init new switch. */ FLECS_DBG_API void flecs_switch_init( ecs_switch_t* sw, ecs_allocator_t *allocator, int32_t elements); /** Fini switch. */ FLECS_DBG_API void flecs_switch_fini( ecs_switch_t *sw); /** Remove all values. */ FLECS_DBG_API void flecs_switch_clear( ecs_switch_t *sw); /** Add element to switch, initialize value to 0 */ FLECS_DBG_API void flecs_switch_add( ecs_switch_t *sw); /** Set number of elements in switch list */ FLECS_DBG_API void flecs_switch_set_count( ecs_switch_t *sw, int32_t count); /** Get number of elements */ FLECS_DBG_API int32_t flecs_switch_count( ecs_switch_t *sw); /** Ensure that element exists. */ FLECS_DBG_API void flecs_switch_ensure( ecs_switch_t *sw, int32_t count); /** Add n elements. */ FLECS_DBG_API void flecs_switch_addn( ecs_switch_t *sw, int32_t count); /** Set value of element. */ FLECS_DBG_API void flecs_switch_set( ecs_switch_t *sw, int32_t element, uint64_t value); /** Remove element. */ FLECS_DBG_API void flecs_switch_remove( ecs_switch_t *sw, int32_t element); /** Get value for element. */ FLECS_DBG_API uint64_t flecs_switch_get( const ecs_switch_t *sw, int32_t element); /** Swap element. */ FLECS_DBG_API void flecs_switch_swap( ecs_switch_t *sw, int32_t elem_1, int32_t elem_2); /** Get vector with all values. Use together with count(). */ FLECS_DBG_API ecs_vec_t* flecs_switch_values( const ecs_switch_t *sw); /** Return number of different values. */ FLECS_DBG_API int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value); /** Return first element for value. */ FLECS_DBG_API int32_t flecs_switch_first( const ecs_switch_t *sw, uint64_t value); /** Return next element for value. Use with first(). */ FLECS_DBG_API int32_t flecs_switch_next( const ecs_switch_t *sw, int32_t elem); #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif /** * @file table.h * @brief Table storage implementation. */ #ifndef FLECS_TABLE_H #define FLECS_TABLE_H /** * @file table_graph.h * @brief Table graph types and functions. */ #ifndef FLECS_TABLE_GRAPH_H #define FLECS_TABLE_GRAPH_H /** Cache of added/removed components for non-trivial edges between tables */ #define ECS_TABLE_DIFF_INIT { .added = {0}} /** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to * conserve memory on table edges (a type doesn't have the size field), whereas * a vec for the builder is more convenient to use & has allocator support. */ typedef struct ecs_table_diff_builder_t { ecs_vec_t added; ecs_vec_t removed; } ecs_table_diff_builder_t; typedef struct ecs_table_diff_t { ecs_type_t added; /* Components added between tables */ ecs_type_t removed; /* Components removed between tables */ } ecs_table_diff_t; /** Edge linked list (used to keep track of incoming edges) */ typedef struct ecs_graph_edge_hdr_t { struct ecs_graph_edge_hdr_t *prev; struct ecs_graph_edge_hdr_t *next; } ecs_graph_edge_hdr_t; /** Single edge. */ typedef struct ecs_graph_edge_t { ecs_graph_edge_hdr_t hdr; ecs_table_t *from; /* Edge source table */ ecs_table_t *to; /* Edge destination table */ ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ ecs_id_t id; /* Id associated with edge */ } ecs_graph_edge_t; /* Edges to other tables. */ typedef struct ecs_graph_edges_t { ecs_graph_edge_t *lo; /* Small array optimized for low edges */ ecs_map_t *hi; /* Map for hi edges (map) */ } ecs_graph_edges_t; /* Table graph node */ typedef struct ecs_graph_node_t { /* Outgoing edges */ ecs_graph_edges_t add; ecs_graph_edges_t remove; /* Incoming edges (next = add edges, prev = remove edges) */ ecs_graph_edge_hdr_t refs; } ecs_graph_node_t; /* Find table by adding id to current table */ ecs_table_t *flecs_table_traverse_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); /* Find table by removing id from current table */ ecs_table_t *flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); /* Cleanup incoming and outgoing edges for table */ void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table); /* Table diff builder, used to build id lists that indicate the difference in * ids between two tables. */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder); void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src); void flecs_table_diff_build( ecs_world_t *world, ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, int32_t removed_offset); void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff); #endif /* Table event type for notifying tables of world events */ typedef enum ecs_table_eventkind_t { EcsTableTriggersForId, EcsTableNoTriggersForId, } ecs_table_eventkind_t; typedef struct ecs_table_event_t { ecs_table_eventkind_t kind; /* Query event */ ecs_query_t *query; /* Component info event */ ecs_entity_t component; /* Event match */ ecs_entity_t event; /* If the nubmer of fields gets out of hand, this can be turned into a union * but since events are very temporary objects, this works for now and makes * initializing an event a bit simpler. */ } ecs_table_event_t; /** Infrequently accessed data not stored inline in ecs_table_t */ typedef struct ecs_table__t { uint64_t hash; /* Type hash */ int32_t lock; /* Prevents modifications */ int32_t traversable_count; /* Traversable relationship targets in table */ uint16_t generation; /* Used for table cleanup */ int16_t record_count; /* Table record count including wildcards */ struct ecs_table_record_t *records; /* Array with table records */ ecs_hashmap_t *name_index; /* Cached pointer to name index */ ecs_switch_t *sw_columns; /* Switch columns */ ecs_bitset_t *bs_columns; /* Bitset columns */ int16_t sw_count; int16_t sw_offset; int16_t bs_count; int16_t bs_offset; int16_t ft_offset; } ecs_table__t; /** Table column */ typedef struct ecs_column_t { ecs_vec_t data; /* Vector with component data */ ecs_id_t id; /* Component id */ ecs_type_info_t *ti; /* Component type info */ ecs_size_t size; /* Component size */ } ecs_column_t; /** Table data */ struct ecs_data_t { ecs_vec_t entities; /* Entity ids */ ecs_vec_t records; /* Ptrs to records in entity index */ ecs_column_t *columns; /* Component data */ }; /** A table is the Flecs equivalent of an archetype. Tables store all entities * with a specific set of components. Tables are automatically created when an * entity has a set of components not previously observed before. When a new * table is created, it is automatically matched with existing queries */ struct ecs_table_t { uint64_t id; /* Table id in sparse set */ ecs_flags32_t flags; /* Flags for testing table properties */ int16_t column_count; /* Number of components (excluding tags) */ ecs_type_t type; /* Vector with component ids */ ecs_data_t data; /* Component storage */ ecs_graph_node_t node; /* Graph node */ int32_t *dirty_state; /* Keep track of changes in columns */ int32_t *column_map; /* Map type index <-> column * - 0..count(T): type index -> column * - count(T)..count(C): column -> type index */ ecs_table__t *_; /* Infrequently accessed table metadata */ }; /* Init table */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from); /** Copy type. */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src); /** Free type. */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type); /** Find or create table for a set of components */ ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type); /* Initialize columns for data */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from a table. */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table); /* Reset a table to its initial state */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from the table. Do not invoke OnRemove systems */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table); /* Clear table data. Don't call OnRemove handlers. */ void flecs_table_clear_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data); /* Return number of entities in data */ int32_t flecs_table_data_count( const ecs_data_t *data); /* Add a new entry to the table for the specified entity */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_record_t *record, bool construct, bool on_add); /* Delete an entity from the table. */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct); /* Make sure table records are in correct table cache list */ bool flecs_table_records_update_empty( ecs_table_t *table); /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, int32_t new_index, ecs_table_t *old_table, int32_t old_index, bool construct); /* Grow table with specified number of records. Populate table with entities, * starting from specified entity id. */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count, const ecs_entity_t *ids); /* Set table to a fixed size. Useful for preallocating memory in advance. */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count); /* Shrink table to contents */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table); /* Get dirty state for table columns */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table); /* Initialize root table */ void flecs_init_root_table( ecs_world_t *world); /* Unset components in table */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table); /* Replace data */ void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data); /* Merge data of one table into another table */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *new_table, ecs_table_t *old_table, ecs_data_t *new_data, ecs_data_t *old_data); void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2); void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component); void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_table_event_t *event); void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table); int32_t flecs_table_column_to_union_index( const ecs_table_t *table, int32_t column); /* Increase observer count of table */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value); #endif /* Used in id records to keep track of entities used with id flags */ extern const ecs_entity_t EcsFlag; #define ECS_MAX_JOBS_PER_WORKER (16) /* Magic number for a flecs object */ #define ECS_OBJECT_MAGIC (0x6563736f) /* Tags associated with poly for (Poly, tag) components */ #define ecs_world_t_tag invalid #define ecs_stage_t_tag invalid #define ecs_query_t_tag EcsQuery #define ecs_rule_t_tag EcsQuery #define ecs_table_t_tag invalid #define ecs_filter_t_tag EcsQuery #define ecs_observer_t_tag EcsObserver /* Mixin kinds */ typedef enum ecs_mixin_kind_t { EcsMixinWorld, EcsMixinEntity, EcsMixinObservable, EcsMixinIterable, EcsMixinDtor, EcsMixinMax } ecs_mixin_kind_t; /* The mixin array contains pointers to mixin members for different kinds of * flecs objects. This allows the API to retrieve data from an object regardless * of its type. Each mixin array is only stored once per type */ struct ecs_mixins_t { const char *type_name; /* Include name of mixin type so debug code doesn't * need to know about every object */ ecs_size_t elems[EcsMixinMax]; }; /* Mixin tables */ extern ecs_mixins_t ecs_world_t_mixins; extern ecs_mixins_t ecs_stage_t_mixins; extern ecs_mixins_t ecs_filter_t_mixins; extern ecs_mixins_t ecs_query_t_mixins; extern ecs_mixins_t ecs_trigger_t_mixins; extern ecs_mixins_t ecs_observer_t_mixins; /* Types that have no mixins */ #define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) /* Scope for flecs internals, like observers used for builtin features */ extern const ecs_entity_t EcsFlecsInternals; /** Type used for internal string hashmap */ typedef struct ecs_hashed_string_t { char *value; ecs_size_t length; uint64_t hash; } ecs_hashed_string_t; /** Must appear as first member in payload of table cache */ typedef struct ecs_table_cache_hdr_t { struct ecs_table_cache_t *cache; ecs_table_t *table; struct ecs_table_cache_hdr_t *prev, *next; bool empty; } ecs_table_cache_hdr_t; /** Linked list of tables in table cache */ typedef struct ecs_table_cache_list_t { ecs_table_cache_hdr_t *first; ecs_table_cache_hdr_t *last; int32_t count; } ecs_table_cache_list_t; /** Table cache */ typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_table_cache_list_t tables; ecs_table_cache_list_t empty_tables; } ecs_table_cache_t; /* Sparse query term */ typedef struct flecs_switch_term_t { ecs_switch_t *sw_column; ecs_entity_t sw_case; int32_t signature_column_index; } flecs_switch_term_t; /* Bitset query term */ typedef struct flecs_bitset_term_t { ecs_bitset_t *bs_column; int32_t column_index; } flecs_bitset_term_t; typedef struct flecs_flat_monitor_t { int32_t table_state; int32_t monitor; } flecs_flat_monitor_t; /* Flat table term */ typedef struct flecs_flat_table_term_t { int32_t field_index; /* Iterator field index */ ecs_term_t *term; ecs_vec_t monitor; } flecs_flat_table_term_t; /* Entity filter. This filters the entities of a matched table, for example when * it has disabled components or union relationships (switch). */ typedef struct ecs_entity_filter_t { ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */ ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */ ecs_vec_t ft_terms; /* Terms with components from flattened tree */ int32_t flat_tree_column; } ecs_entity_filter_t; typedef struct ecs_entity_filter_iter_t { ecs_entity_filter_t *entity_filter; ecs_iter_t *it; int32_t *columns; ecs_table_t *prev; ecs_table_range_t range; int32_t bs_offset; int32_t sw_offset; int32_t sw_smallest; int32_t flat_tree_offset; int32_t target_count; } ecs_entity_filter_iter_t; /** Table match data. * Each table matched by the query is represented by a ecs_query_table_match_t * instance, which are linked together in a list. A table may match a query * multiple times (due to wildcard queries) with different columns being matched * by the query. */ struct ecs_query_table_match_t { ecs_query_table_match_t *next, *prev; ecs_table_t *table; /* The current table. */ int32_t offset; /* Starting point in table */ int32_t count; /* Number of entities to iterate in table */ int32_t *columns; /* Mapping from query fields to table columns */ int32_t *storage_columns; /* Mapping from query fields to storage columns */ ecs_id_t *ids; /* Resolved (component) ids for current table */ ecs_entity_t *sources; /* Subjects (sources) of ids */ ecs_vec_t refs; /* Cached components for non-this terms */ uint64_t group_id; /* Value used to organize tables in groups */ int32_t *monitor; /* Used to monitor table for changes */ ecs_entity_filter_t *entity_filter; /* Entity specific filters */ /* Next match in cache for same table (includes empty tables) */ ecs_query_table_match_t *next_match; }; /** Table record type for query table cache. A query only has one per table. */ typedef struct ecs_query_table_t { ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ ecs_query_table_match_t *first; /* List with matches for table */ ecs_query_table_match_t *last; /* Last discovered match for table */ uint64_t table_id; int32_t rematch_count; /* Track whether table was rematched */ } ecs_query_table_t; /** Points to the beginning & ending of a query group */ typedef struct ecs_query_table_list_t { ecs_query_table_match_t *first; ecs_query_table_match_t *last; ecs_query_group_info_t info; } ecs_query_table_list_t; /* Query event type for notifying queries of world events */ typedef enum ecs_query_eventkind_t { EcsQueryTableMatch, EcsQueryTableRematch, EcsQueryTableUnmatch, EcsQueryOrphan } ecs_query_eventkind_t; typedef struct ecs_query_event_t { ecs_query_eventkind_t kind; ecs_table_t *table; ecs_query_t *parent_query; } ecs_query_event_t; /* Query level block allocators have sizes that depend on query field count */ typedef struct ecs_query_allocators_t { ecs_block_allocator_t columns; ecs_block_allocator_t ids; ecs_block_allocator_t sources; ecs_block_allocator_t monitors; } ecs_query_allocators_t; /** Query that is automatically matched against tables */ struct ecs_query_t { ecs_header_t hdr; /* Query filter */ ecs_filter_t filter; /* Tables matched with query */ ecs_table_cache_t cache; /* Linked list with all matched non-empty tables, in iteration order */ ecs_query_table_list_t list; /* Contains head/tail to nodes of query groups (if group_by is used) */ ecs_map_t groups; /* Table sorting */ ecs_entity_t order_by_component; ecs_order_by_action_t order_by; ecs_sort_table_action_t sort_table; ecs_vec_t table_slices; int32_t order_by_term; /* Table grouping */ ecs_entity_t group_by_id; ecs_group_by_action_t group_by; ecs_group_create_action_t on_group_create; ecs_group_delete_action_t on_group_delete; void *group_by_ctx; ecs_ctx_free_t group_by_ctx_free; /* Subqueries */ ecs_query_t *parent; ecs_vec_t subqueries; /* Flags for query properties */ ecs_flags32_t flags; /* Monitor generation */ int32_t monitor_generation; int32_t cascade_by; /* Identify cascade column */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ /* User context */ void *ctx; /* User context to pass to callback */ void *binding_ctx; /* Context to be used for language bindings */ ecs_ctx_free_t ctx_free; /** Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /** Callback to free binding_ctx */ /* Mixins */ ecs_iterable_t iterable; ecs_poly_dtor_t dtor; /* Query-level allocators */ ecs_query_allocators_t allocators; }; /** All observers for a specific (component) id */ typedef struct ecs_event_id_record_t { /* Triggers for Self */ ecs_map_t self; /* map */ ecs_map_t self_up; /* map */ ecs_map_t up; /* map */ ecs_map_t observers; /* map */ /* Triggers for SuperSet, SubSet */ ecs_map_t set_observers; /* map */ /* Triggers for Self with non-This subject */ ecs_map_t entity_observers; /* map */ /* Number of active observers for (component) id */ int32_t observer_count; } ecs_event_id_record_t; /* World level allocators are for operations that are not multithreaded */ typedef struct ecs_world_allocators_t { ecs_map_params_t ptr; ecs_map_params_t query_table_list; ecs_block_allocator_t query_table; ecs_block_allocator_t query_table_match; ecs_block_allocator_t graph_edge_lo; ecs_block_allocator_t graph_edge; ecs_block_allocator_t id_record; ecs_block_allocator_t id_record_chunk; ecs_block_allocator_t table_diff; ecs_block_allocator_t sparse_chunk; ecs_block_allocator_t hashmap; /* Temporary vectors used for creating table diff id sequences */ ecs_table_diff_builder_t diff_builder; } ecs_world_allocators_t; /* Stage level allocators are for operations that can be multithreaded */ typedef struct ecs_stage_allocators_t { ecs_stack_t iter_stack; ecs_stack_t deser_stack; ecs_block_allocator_t cmd_entry_chunk; } ecs_stage_allocators_t; /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { EcsOpClone, EcsOpBulkNew, EcsOpAdd, EcsOpRemove, EcsOpSet, EcsOpEmplace, EcsOpMut, EcsOpModified, EcsOpAddModified, EcsOpPath, EcsOpDelete, EcsOpClear, EcsOpOnDeleteAction, EcsOpEnable, EcsOpDisable, EcsOpSkip } ecs_cmd_kind_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / get_mut) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; } ecs_cmd_t; /* Entity specific metadata for command in defer queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; /** A stage is a context that allows for safely using the API from multiple * threads. Stage pointers can be passed to the world argument of API * operations, which causes the operation to be ran on the stage instead of the * world. */ struct ecs_stage_t { ecs_header_t hdr; /* Unique id that identifies the stage */ int32_t id; /* Deferred command queue */ int32_t defer; ecs_vec_t commands; ecs_stack_t defer_stack; /* Temp memory used by deferred commands */ ecs_sparse_t cmd_entries; /* - command combining */ /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ ecs_world_t *world; /* Reference to world */ ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_vec_t post_frame_actions; /* Namespacing */ ecs_entity_t scope; /* Entity of current scope */ ecs_entity_t with; /* Id to add by default to new entities */ ecs_entity_t base; /* Currently instantiated top-level base */ const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Properties */ bool auto_merge; /* Should this stage automatically merge? */ bool async; /* Is stage asynchronous? (write only) */ /* Thread specific allocators */ ecs_stage_allocators_t allocators; ecs_allocator_t allocator; /* Caches for rule creation */ ecs_vec_t variables; ecs_vec_t operations; }; /* Component monitor */ typedef struct ecs_monitor_t { ecs_vec_t queries; /* vector */ bool is_dirty; /* Should queries be rematched? */ } ecs_monitor_t; /* Component monitors */ typedef struct ecs_monitor_set_t { ecs_map_t monitors; /* map */ bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { ecs_id_record_t *idr; ecs_id_t id; ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; } ecs_marked_id_t; typedef struct ecs_store_t { /* Entity lookup */ ecs_entity_index_t entity_index; /* Table lookup by id */ ecs_sparse_t tables; /* sparse */ /* Table lookup by hash */ ecs_hashmap_t table_map; /* hashmap */ /* Root table */ ecs_table_t root; /* Records cache */ ecs_vec_t records; /* Stack of ids being deleted. */ ecs_vec_t marked_ids; /* vector */ /* Entity ids associated with depth (for flat hierarchies) */ ecs_vec_t depth_ids; ecs_map_t entity_to_depth; /* What it says */ } ecs_store_t; /* fini actions */ typedef struct ecs_action_elem_t { ecs_fini_action_t action; void *ctx; } ecs_action_elem_t; typedef struct ecs_pipeline_state_t ecs_pipeline_state_t; /** The world stores and manages all ECS data. An application can have more than * one world, but data is not shared between worlds. */ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ ecs_id_record_t *id_index_lo; ecs_map_t id_index_hi; /* map */ ecs_sparse_t type_info; /* sparse */ /* -- Cached handle to id records -- */ ecs_id_record_t *idr_wildcard; ecs_id_record_t *idr_wildcard_wildcard; ecs_id_record_t *idr_any; ecs_id_record_t *idr_isa_wildcard; ecs_id_record_t *idr_childof_0; ecs_id_record_t *idr_childof_wildcard; ecs_id_record_t *idr_identifier_name; /* -- Mixins -- */ ecs_world_t *self; ecs_observable_t observable; ecs_iterable_t iterable; /* Unique id per generated event used to prevent duplicate notifications */ int32_t event_id; /* Is entity range checking enabled? */ bool range_check_enabled; /* -- Data storage -- */ ecs_store_t store; /* -- Pending table event buffers -- */ ecs_sparse_t *pending_buffer; /* sparse */ ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ ecs_stage_t *stages; /* Stages */ int32_t stage_count; /* Number of stages */ /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ /* -- Time management -- */ ecs_time_t world_start_time; /* Timestamp of simulation start */ ecs_time_t frame_start_time; /* Timestamp of frame start */ ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ /* -- Metrics -- */ ecs_world_info_t info; /* -- World flags -- */ ecs_flags32_t flags; /* Count that increases when component monitors change */ int32_t monitor_generation; /* -- Allocators -- */ ecs_world_allocators_t allocators; /* Static allocation sizes */ ecs_allocator_t allocator; /* Dynamic allocation sizes */ void *ctx; /* Application context */ void *binding_ctx; /* Binding-specific context */ ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ }; #endif /** * @file table_cache.h * @brief Data structure for fast table iteration/lookups. */ #ifndef FLECS_TABLE_CACHE_H_ #define FLECS_TABLE_CACHE_H_ void ecs_table_cache_init( ecs_world_t *world, ecs_table_cache_t *cache); void ecs_table_cache_fini( ecs_table_cache_t *cache); void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result); void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_remove( ecs_table_cache_t *cache, uint64_t table_id, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table); bool ecs_table_cache_set_empty( ecs_table_cache_t *cache, const ecs_table_t *table, bool empty); bool ecs_table_cache_is_empty( const ecs_table_cache_t *cache); #define flecs_table_cache_count(cache) (cache)->tables.count #define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_empty_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_all_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it); #define flecs_table_cache_next(it, T)\ (ECS_CAST(T*, flecs_table_cache_next_(it))) #endif /** * @file id_index.h * @brief Index for looking up tables by (component) id. */ #ifndef FLECS_ID_INDEX_H #define FLECS_ID_INDEX_H /* Payload for id cache */ struct ecs_table_record_t { ecs_table_cache_hdr_t hdr; /* Table cache header */ int16_t index; /* First type index where id occurs in table */ int16_t count; /* Number of times id occurs in table */ int16_t column; /* First column index where id occurs */ }; /* Linked list of id records */ typedef struct ecs_id_record_elem_t { struct ecs_id_record_t *prev, *next; } ecs_id_record_elem_t; typedef struct ecs_reachable_elem_t { const ecs_table_record_t *tr; ecs_record_t *record; ecs_entity_t src; ecs_id_t id; #ifndef NDEBUG ecs_table_t *table; #endif } ecs_reachable_elem_t; typedef struct ecs_reachable_cache_t { int32_t generation; int32_t current; ecs_vec_t ids; /* vec */ } ecs_reachable_cache_t; /* Payload for id index which contains all datastructures for an id. */ struct ecs_id_record_t { /* Cache with all tables that contain the id. Must be first member. */ ecs_table_cache_t cache; /* table_cache */ /* Id of record */ ecs_id_t id; /* Flags for id */ ecs_flags32_t flags; /* Cached pointer to type info for id, if id contains data. */ const ecs_type_info_t *type_info; /* Name lookup index (currently only used for ChildOf pairs) */ ecs_hashmap_t *name_index; /* Lists for all id records that match a pair wildcard. The wildcard id * record is at the head of the list. */ ecs_id_record_elem_t first; /* (R, *) */ ecs_id_record_elem_t second; /* (*, O) */ ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ /* Parent id record. For pair records the parent is the (R, *) record. */ ecs_id_record_t *parent; /* Refcount */ int32_t refcount; /* Keep alive count. This count must be 0 when the id record is deleted. If * it is not 0, an application attempted to delete an id that was still * queried for. */ int32_t keep_alive; /* Cache invalidation counter */ ecs_reachable_cache_t reachable; }; /* Get id record for id */ ecs_id_record_t* flecs_id_record_get( const ecs_world_t *world, ecs_id_t id); /* Get id record for id for searching. * Same as flecs_id_record_get, but replaces (R, *) with (Union, R) if R is a * union relationship. */ ecs_id_record_t* flecs_query_id_record_get( const ecs_world_t *world, ecs_id_t id); /* Ensure id record for id */ ecs_id_record_t* flecs_id_record_ensure( ecs_world_t *world, ecs_id_t id); /* Increase refcount of id record */ void flecs_id_record_claim( ecs_world_t *world, ecs_id_record_t *idr); /* Decrease refcount of id record, delete if 0 */ int32_t flecs_id_record_release( ecs_world_t *world, ecs_id_record_t *idr); /* Release all empty tables in id record */ void flecs_id_record_release_tables( ecs_world_t *world, ecs_id_record_t *idr); /* Set (component) type info for id record */ bool flecs_id_record_set_type_info( ecs_world_t *world, ecs_id_record_t *idr, const ecs_type_info_t *ti); /* Ensure id record has name index */ ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id); ecs_hashmap_t* flecs_id_record_name_index_ensure( ecs_world_t *world, ecs_id_record_t *idr); /* Get name index for id record */ ecs_hashmap_t* flecs_id_name_index_get( const ecs_world_t *world, ecs_id_t id); /* Find table record for id */ ecs_table_record_t* flecs_table_record_get( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id); /* Find table record for id record */ ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table); /* Bootstrap cached id records */ void flecs_init_id_records( ecs_world_t *world); /* Cleanup all id records in world */ void flecs_fini_id_records( ecs_world_t *world); #endif /** * @file observable.h * @brief Functions for sending events. */ #ifndef FLECS_OBSERVABLE_H #define FLECS_OBSERVABLE_H ecs_event_record_t* flecs_event_record_get( const ecs_observable_t *o, ecs_entity_t event); ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event); ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id); ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id); void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id); void flecs_observable_init( ecs_observable_t *observable); void flecs_observable_fini( ecs_observable_t *observable); bool flecs_observers_exist( ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event); void flecs_observer_fini( ecs_observer_t *observer); void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_event_desc_t *desc); bool flecs_default_observer_next_callback( ecs_iter_t *it); void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, int32_t evtx); void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count); #endif /** * @file iter.h * @brief Iterator utilities. */ #ifndef FLECS_ITER_H #define FLECS_ITER_H void flecs_iter_init( const ecs_world_t *world, ecs_iter_t *it, ecs_flags8_t fields); void flecs_iter_validate( ecs_iter_t *it); void flecs_iter_populate_data( ecs_world_t *world, ecs_iter_t *it, ecs_table_t *table, int32_t offset, int32_t count, void **ptrs); bool flecs_iter_next_row( ecs_iter_t *it); bool flecs_iter_next_instanced( ecs_iter_t *it, bool result); void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align); #define flecs_iter_calloc_t(it, T)\ flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_iter_calloc_n(it, T, count)\ flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) void flecs_iter_free( void *ptr, ecs_size_t size); #define flecs_iter_free_t(ptr, T)\ flecs_iter_free(ptr, ECS_SIZEOF(T)) #define flecs_iter_free_n(ptr, T, count)\ flecs_iter_free(ptr, ECS_SIZEOF(T) * count) #endif /** * @file poly.h * @brief Functions for managing poly objects. */ #ifndef FLECS_POLY_H #define FLECS_POLY_H #include /* Initialize poly */ void* ecs_poly_init_( ecs_poly_t *object, int32_t kind, ecs_size_t size, ecs_mixins_t *mixins); #define ecs_poly_init(object, type)\ ecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) /* Deinitialize object for specified type */ void ecs_poly_fini_( ecs_poly_t *object, int32_t kind); #define ecs_poly_fini(object, type)\ ecs_poly_fini_(object, type##_magic) /* Utility functions for creating an object on the heap */ #define ecs_poly_new(type)\ (type*)ecs_poly_init(ecs_os_calloc_t(type), type) #define ecs_poly_free(obj, type)\ ecs_poly_fini(obj, type);\ ecs_os_free(obj) /* Get or create poly component for an entity */ EcsPoly* ecs_poly_bind_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_bind(world, entity, T) \ ecs_poly_bind_(world, entity, T##_tag) void ecs_poly_modified_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_modified(world, entity, T) \ ecs_poly_modified_(world, entity, T##_tag) /* Get poly component for an entity */ const EcsPoly* ecs_poly_bind_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_bind_get(world, entity, T) \ ecs_poly_bind_get_(world, entity, T##_tag) ecs_poly_t* ecs_poly_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_get(world, entity, T) \ ((T*)ecs_poly_get_(world, entity, T##_tag)) /* Utilities for testing/asserting an object type */ #ifndef FLECS_NDEBUG void* ecs_poly_assert_( const ecs_poly_t *object, int32_t type, const char *file, int32_t line); #define ecs_poly_assert(object, type)\ ecs_poly_assert_(object, type##_magic, __FILE__, __LINE__) #define ecs_poly(object, T) ((T*)ecs_poly_assert(object, T)) #else #define ecs_poly_assert(object, type) #define ecs_poly(object, T) ((T*)object) #endif /* Utility functions for getting a mixin from an object */ ecs_iterable_t* ecs_get_iterable( const ecs_poly_t *poly); ecs_observable_t* ecs_get_observable( const ecs_poly_t *object); ecs_poly_dtor_t* ecs_get_dtor( const ecs_poly_t *poly); #endif /** * @file stage.h * @brief Stage functions. */ #ifndef FLECS_STAGE_H #define FLECS_STAGE_H /* Initialize stage data structures */ void flecs_stage_init( ecs_world_t *world, ecs_stage_t *stage); /* Deinitialize stage */ void flecs_stage_fini( ecs_world_t *world, ecs_stage_t *stage); /* Post-frame merge actions */ void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_cmd( ecs_stage_t *stage); bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component); bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value); bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out); bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name); bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity); bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity); bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action); bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component, bool enable); bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t op_kind, ecs_entity_t entity, ecs_entity_t component, ecs_size_t size, void *value, bool need_value); bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage); #endif /** * @file world.h * @brief World-level API. */ #ifndef FLECS_WORLD_H #define FLECS_WORLD_H /* Get current stage */ ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr); /* Get current thread-specific stage from readonly world */ const ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world); /* Get component callbacks */ const ecs_type_info_t *flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component); /* Get or create component callbacks */ ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component); bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li); #define flecs_type_info_init(world, T, ...)\ flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ &(ecs_type_hooks_t)__VA_ARGS__) void flecs_type_info_fini( ecs_type_info_t *ti); void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component); void flecs_eval_component_monitors( ecs_world_t *world); void flecs_monitor_mark_dirty( ecs_world_t *world, ecs_entity_t id); void flecs_monitor_register( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query); void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query); void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event); void flecs_register_table( ecs_world_t *world, ecs_table_t *table); void flecs_unregister_table( ecs_world_t *world, ecs_table_t *table); void flecs_table_set_empty( ecs_world_t *world, ecs_table_t *table); void flecs_delete_table( ecs_world_t *world, ecs_table_t *table); void flecs_process_pending_tables( const ecs_world_t *world); /* Suspend/resume readonly state. To fully support implicit registration of * components, it should be possible to register components while the world is * in readonly mode. It is not uncommon that a component is used first from * within a system, which are often ran while in readonly mode. * * Suspending readonly mode is only allowed when the world is not multithreaded. * When a world is multithreaded, it is not safe to (even temporarily) leave * readonly mode, so a multithreaded application should always explicitly * register components in advance. * * These operations also suspend deferred mode. */ typedef struct ecs_suspend_readonly_state_t { bool is_readonly; bool is_deferred; int32_t defer_count; ecs_entity_t scope; ecs_entity_t with; ecs_vec_t commands; ecs_stack_t defer_stack; ecs_stage_t *stage; } ecs_suspend_readonly_state_t; ecs_world_t* flecs_suspend_readonly( const ecs_world_t *world, ecs_suspend_readonly_state_t *state); void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state); /* Convenience macro's for world allocator */ #define flecs_walloc(world, size)\ flecs_alloc(&world->allocator, size) #define flecs_walloc_n(world, T, count)\ flecs_alloc_n(&world->allocator, T, count) #define flecs_wcalloc(world, size)\ flecs_calloc(&world->allocator, size) #define flecs_wcalloc_n(world, T, count)\ flecs_calloc_n(&world->allocator, T, count) #define flecs_wfree(world, size, ptr)\ flecs_free(&world->allocator, size, ptr) #define flecs_wfree_n(world, T, count, ptr)\ flecs_free_n(&world->allocator, T, count, ptr) #define flecs_wrealloc(world, size_dst, size_src, ptr)\ flecs_realloc(&world->allocator, size_dst, size_src, ptr) #define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) #define flecs_wdup(world, size, ptr)\ flecs_dup(&world->allocator, size, ptr) #define flecs_wdup_n(world, T, count, ptr)\ flecs_dup_n(&world->allocator, T, count, ptr) #endif /** * @file datastructures/name_index.h * @brief Data structure for resolving 64bit keys by string (name). */ #ifndef FLECS_NAME_INDEX_H #define FLECS_NAME_INDEX_H void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator); void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator); bool flecs_name_index_is_init( const ecs_hashmap_t *hm); ecs_hashmap_t* flecs_name_index_new( ecs_world_t *world, ecs_allocator_t *allocator); void flecs_name_index_fini( ecs_hashmap_t *map); void flecs_name_index_free( ecs_hashmap_t *map); ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *dst); ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash); const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t id, uint64_t hash); void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name); #endif //////////////////////////////////////////////////////////////////////////////// //// Bootstrap API //////////////////////////////////////////////////////////////////////////////// /* Bootstrap world */ void flecs_bootstrap( ecs_world_t *world); #define flecs_bootstrap_component(world, id_)\ ecs_component_init(world, &(ecs_component_desc_t){\ .entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ .type.size = sizeof(id_),\ .type.alignment = ECS_ALIGNOF(id_)\ }); #define flecs_bootstrap_tag(world, name)\ ecs_ensure(world, name);\ ecs_add_id(world, name, EcsFinal);\ ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ ecs_set(world, name, EcsComponent, {.size = 0});\ ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ ecs_set_symbol(world, name, #name) /* Bootstrap functions for other parts in the code */ void flecs_bootstrap_hierarchy(ecs_world_t *world); //////////////////////////////////////////////////////////////////////////////// //// Entity API //////////////////////////////////////////////////////////////////////////////// /* Mark an entity as being watched. This is used to trigger automatic rematching * when entities used in system expressions change their components. */ void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag); void flecs_record_add_flag( ecs_record_t *record, uint32_t flag); ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e); void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *diff); void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *type, bool owned); int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table); void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count); void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_id_record_t *table_index, int32_t recur_depth); void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook); //////////////////////////////////////////////////////////////////////////////// //// Query API //////////////////////////////////////////////////////////////////////////////// /* Match table with term */ bool flecs_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, int32_t *match_indices, bool first, ecs_flags32_t iter_flags); /* Match table with filter */ bool flecs_filter_match_table( ecs_world_t *world, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns, ecs_entity_t *sources, int32_t *match_indices, int32_t *matches_left, bool first, int32_t skip_term, ecs_flags32_t iter_flags); ecs_iter_t flecs_filter_iter_w_flags( const ecs_world_t *stage, const ecs_filter_t *filter, ecs_flags32_t flags); void flecs_query_notify( ecs_world_t *world, ecs_query_t *query, ecs_query_event_t *event); ecs_id_t flecs_to_public_id( ecs_id_t id); ecs_id_t flecs_from_public_id( ecs_world_t *world, ecs_id_t id); void flecs_filter_apply_iter_flags( ecs_iter_t *it, const ecs_filter_t *filter); //////////////////////////////////////////////////////////////////////////////// //// Safe(r) integer casting //////////////////////////////////////////////////////////////////////////////// #define FLECS_CONVERSION_ERR(T, value)\ "illegal conversion from value " #value " to type " #T #define flecs_signed_char__ (CHAR_MIN < 0) #define flecs_signed_short__ true #define flecs_signed_int__ true #define flecs_signed_long__ true #define flecs_signed_size_t__ false #define flecs_signed_int8_t__ true #define flecs_signed_int16_t__ true #define flecs_signed_int32_t__ true #define flecs_signed_int64_t__ true #define flecs_signed_intptr_t__ true #define flecs_signed_uint8_t__ false #define flecs_signed_uint16_t__ false #define flecs_signed_uint32_t__ false #define flecs_signed_uint64_t__ false #define flecs_signed_uintptr_t__ false #define flecs_signed_ecs_size_t__ true #define flecs_signed_ecs_entity_t__ false uint64_t flecs_ito_( size_t dst_size, bool dst_signed, bool lt_zero, uint64_t value, const char *err); #ifndef FLECS_NDEBUG #define flecs_ito(T, value)\ (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ (value) < 0,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #define flecs_uto(T, value)\ (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ false,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #else #define flecs_ito(T, value) (T)(value) #define flecs_uto(T, value) (T)(value) #endif #define flecs_itosize(value) flecs_ito(size_t, (value)) #define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) #define flecs_itoi16(value) flecs_ito(int16_t, (value)) #define flecs_itoi32(value) flecs_ito(int32_t, (value)) //////////////////////////////////////////////////////////////////////////////// //// Entity filter //////////////////////////////////////////////////////////////////////////////// void flecs_entity_filter_init( ecs_world_t *world, ecs_entity_filter_t **entity_filter, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns); void flecs_entity_filter_fini( ecs_world_t *world, ecs_entity_filter_t *entity_filter); int flecs_entity_filter_next( ecs_entity_filter_iter_t *it); //////////////////////////////////////////////////////////////////////////////// //// Utilities //////////////////////////////////////////////////////////////////////////////// uint64_t flecs_hash( const void *data, ecs_size_t length); uint64_t flecs_wyhash( const void *data, ecs_size_t length); /* Get next power of 2 */ int32_t flecs_next_pow_of_2( int32_t n); /* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the * entity index */ ecs_record_t flecs_to_row( uint64_t value); /* Get 64bit integer from ecs_record_t */ uint64_t flecs_from_row( ecs_record_t record); /* Convert a symbol name to an entity name by removing the prefix */ const char* flecs_name_from_symbol( ecs_world_t *world, const char *type_name); /* Compare function for entity ids */ int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); bool flecs_name_is_id( const char *name); ecs_entity_t flecs_name_to_id( const ecs_world_t *world, const char *name); /* Convert floating point to string */ char * ecs_ftoa( double f, char * buf, int precision); uint64_t flecs_string_hash( const void *ptr); void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm); void flecs_dump_backtrace( FILE *stream); void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf); bool flecs_isident( char ch); int32_t flecs_search_w_idr( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, ecs_id_t *id_out, ecs_id_record_t *idr); int32_t flecs_search_relation_w_idr( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, ecs_id_record_t *idr); #endif /* -- Identifier Component -- */ static ECS_DTOR(EcsIdentifier, ptr, { ecs_os_strset(&ptr->value, NULL); }) static ECS_COPY(EcsIdentifier, dst, src, { ecs_os_strset(&dst->value, src->value); dst->hash = src->hash; dst->length = src->length; dst->index_hash = src->index_hash; dst->index = src->index; }) static ECS_MOVE(EcsIdentifier, dst, src, { ecs_os_strset(&dst->value, NULL); dst->value = src->value; dst->hash = src->hash; dst->length = src->length; dst->index_hash = src->index_hash; dst->index = src->index; src->value = NULL; src->hash = 0; src->index_hash = 0; src->index = 0; src->length = 0; }) static void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 1); ecs_world_t *world = it->real_world; ecs_entity_t evt = it->event; ecs_id_t evt_id = it->event_id; ecs_entity_t kind = ECS_PAIR_SECOND(evt_id); /* Name, Symbol, Alias */ ecs_id_t pair = ecs_childof(0); ecs_hashmap_t *index = NULL; if (kind == EcsSymbol) { index = &world->symbols; } else if (kind == EcsAlias) { index = &world->aliases; } else if (kind == EcsName) { ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_search(world, it->table, ecs_childof(EcsWildcard), &pair); ecs_assert(pair != 0, ECS_INTERNAL_ERROR, NULL); if (evt == EcsOnSet) { index = flecs_id_name_index_ensure(world, pair); } else { index = flecs_id_name_index_get(world, pair); } } int i, count = it->count; for (i = 0; i < count; i ++) { EcsIdentifier *cur = &ptr[i]; uint64_t hash; ecs_size_t len; const char *name = cur->value; if (cur->index && cur->index != index) { /* If index doesn't match up, the value must have been copied from * another entity, so reset index & cached index hash */ cur->index = NULL; cur->index_hash = 0; } if (cur->value && (evt == EcsOnSet)) { len = cur->length = ecs_os_strlen(name); hash = cur->hash = flecs_hash(name, len); } else { len = cur->length = 0; hash = cur->hash = 0; cur->index = NULL; } if (index) { uint64_t index_hash = cur->index_hash; ecs_entity_t e = it->entities[i]; if (hash != index_hash) { if (index_hash) { flecs_name_index_remove(index, e, index_hash); } if (hash) { flecs_name_index_ensure(index, e, name, len, hash); cur->index_hash = hash; cur->index = index; } } else { /* Name didn't change, but the string could have been * reallocated. Make sure name index points to correct string */ flecs_name_index_update_name(index, e, hash, name); } } } } /* -- Poly component -- */ static ECS_COPY(EcsPoly, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "poly component cannot be copied"); }) static ECS_MOVE(EcsPoly, dst, src, { if (dst->poly && (dst->poly != src->poly)) { ecs_poly_dtor_t *dtor = ecs_get_dtor(dst->poly); ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); dtor[0](dst->poly); } dst->poly = src->poly; src->poly = NULL; }) static ECS_DTOR(EcsPoly, ptr, { if (ptr->poly) { ecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); dtor[0](ptr->poly); } }) /* -- Builtin triggers -- */ static void flecs_assert_relation_unused( ecs_world_t *world, ecs_entity_t rel, ecs_entity_t property) { if (world->flags & (EcsWorldInit|EcsWorldFini)) { return; } ecs_vec_t *marked_ids = &world->store.marked_ids; int32_t i, count = ecs_vec_count(marked_ids); for (i = 0; i < count; i ++) { ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i); if (mid->id == ecs_pair(rel, EcsWildcard)) { /* If id is being cleaned up, no need to throw error as tables will * be cleaned up */ return; } } bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); if (property != EcsUnion) { in_use |= ecs_id_in_use(world, rel); } if (in_use) { char *r_str = ecs_get_fullpath(world, rel); char *p_str = ecs_get_fullpath(world, property); ecs_throw(ECS_ID_IN_USE, "cannot change property '%s' for relationship '%s': already in use", p_str, r_str); ecs_os_free(r_str); ecs_os_free(p_str); } error: return; } static bool flecs_set_id_flag( ecs_id_record_t *idr, ecs_flags32_t flag) { if (!(idr->flags & flag)) { idr->flags |= flag; return true; } return false; } static bool flecs_unset_id_flag( ecs_id_record_t *idr, ecs_flags32_t flag) { if ((idr->flags & flag)) { idr->flags &= ~flag; return true; } return false; } static void flecs_register_id_flag_for_relation( ecs_iter_t *it, ecs_entity_t prop, ecs_flags32_t flag, ecs_flags32_t not_flag, ecs_flags32_t entity_flag) { ecs_world_t *world = it->world; ecs_entity_t event = it->event; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; bool changed = false; if (event == EcsOnAdd) { ecs_id_record_t *idr = flecs_id_record_ensure(world, e); changed |= flecs_set_id_flag(idr, flag); idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); do { changed |= flecs_set_id_flag(idr, flag); } while ((idr = idr->first.next)); if (entity_flag) flecs_add_flag(world, e, entity_flag); } else if (event == EcsOnRemove) { ecs_id_record_t *idr = flecs_id_record_get(world, e); if (idr) changed |= flecs_unset_id_flag(idr, not_flag); idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); if (idr) { do { changed |= flecs_unset_id_flag(idr, not_flag); } while ((idr = idr->first.next)); } } if (changed) { flecs_assert_relation_unused(world, e, prop); } } } static void flecs_register_final(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { char *e_str = ecs_get_fullpath(world, e); ecs_throw(ECS_ID_IN_USE, "cannot change property 'Final' for '%s': already inherited from", e_str); ecs_os_free(e_str); error: continue; } } } static void flecs_register_on_delete(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 1); flecs_register_id_flag_for_relation(it, EcsOnDelete, ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteMask, EcsEntityIsId); } static void flecs_register_on_delete_object(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 1); flecs_register_id_flag_for_relation(it, EcsOnDeleteTarget, ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteObjectMask, EcsEntityIsId); } static void flecs_register_traversable(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdTraversable, EcsIdTraversable, 0); } static void flecs_register_tag(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsTag, EcsIdTag, ~EcsIdTag, 0); /* Ensure that all id records for tag have type info set to NULL */ ecs_world_t *world = it->real_world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (it->event == EcsOnAdd) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); do { if (idr->type_info != NULL) { flecs_assert_relation_unused(world, e, EcsTag); } idr->type_info = NULL; } while ((idr = idr->first.next)); } } } static void flecs_register_exclusive(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsExclusive, EcsIdExclusive, EcsIdExclusive, 0); } static void flecs_register_dont_inherit(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsDontInherit, EcsIdDontInherit, EcsIdDontInherit, 0); } static void flecs_register_always_override(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsAlwaysOverride, EcsIdAlwaysOverride, EcsIdAlwaysOverride, 0); } static void flecs_register_with(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsWith, EcsIdWith, 0, 0); } static void flecs_register_union(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsUnion, EcsIdUnion, 0, 0); } static void flecs_register_slot_of(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_add_id(it->world, it->entities[i], EcsUnion); } } static void flecs_on_symmetric_add_remove(ecs_iter_t *it) { ecs_entity_t pair = ecs_field_id(it, 1); if (!ECS_HAS_ID_FLAG(pair, PAIR)) { /* If relationship was not added as a pair, there's nothing to do */ return; } ecs_world_t *world = it->world; ecs_entity_t rel = ECS_PAIR_FIRST(pair); ecs_entity_t obj = ecs_pair_second(world, pair); ecs_entity_t event = it->event; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t subj = it->entities[i]; if (event == EcsOnAdd) { if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { ecs_add_pair(it->world, obj, rel, subj); } } else { if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { ecs_remove_pair(it->world, obj, rel, subj); } } } } static void flecs_register_symmetric(ecs_iter_t *it) { ecs_world_t *world = it->real_world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t r = it->entities[i]; flecs_assert_relation_unused(world, r, EcsSymmetric); /* Create observer that adds the reverse relationship when R(X, Y) is * added, or remove the reverse relationship when R(X, Y) is removed. */ ecs_observer(world, { .entity = ecs_entity(world, {.add = {ecs_childof(r)}}), .filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, .callback = flecs_on_symmetric_add_remove, .events = {EcsOnAdd, EcsOnRemove} }); } } static void flecs_on_component(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsComponent *c = ecs_field(it, EcsComponent, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; uint32_t component_id = (uint32_t)e; /* Strip generation */ ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, "component id must be smaller than %u", ECS_MAX_COMPONENT_ID); (void)component_id; if (it->event == EcsOnSet) { if (flecs_type_info_init_id( world, e, c[i].size, c[i].alignment, NULL)) { flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); } } else if (it->event == EcsOnRemove) { flecs_type_info_free(world, e); } } } static void flecs_ensure_module_tag(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } } /* -- Iterable mixins -- */ static void flecs_on_event_iterable_init( const ecs_world_t *world, const ecs_poly_t *poly, /* Observable */ ecs_iter_t *it, ecs_term_t *filter) { ecs_iter_poly(world, poly, it, filter); it->event_id = filter->id; } /* -- Bootstrapping -- */ #define flecs_bootstrap_builtin_t(world, table, name)\ flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ ECS_ALIGNOF(name)) static void flecs_bootstrap_builtin( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, const char *symbol, ecs_size_t size, ecs_size_t alignment) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *columns = table->data.columns; ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_ensure(world, entity); record->table = table; int32_t index = flecs_table_append(world, table, entity, record, false, false); record->row = ECS_ROW_TO_RECORD(index, 0); EcsComponent *component = ecs_vec_first(&columns[0].data); component[index].size = size; component[index].alignment = alignment; const char *name = &symbol[3]; /* Strip 'Ecs' */ ecs_size_t symbol_length = ecs_os_strlen(symbol); ecs_size_t name_length = symbol_length - 3; EcsIdentifier *name_col = ecs_vec_first(&columns[1].data); uint64_t name_hash = flecs_hash(name, name_length); name_col[index].value = ecs_os_strdup(name); name_col[index].length = name_length; name_col[index].hash = name_hash; name_col[index].index_hash = 0; name_col[index].index = table->_->name_index; flecs_name_index_ensure( table->_->name_index, entity, name, name_length, name_hash); EcsIdentifier *symbol_col = ecs_vec_first(&columns[2].data); symbol_col[index].value = ecs_os_strdup(symbol); symbol_col[index].length = symbol_length; symbol_col[index].hash = flecs_hash(symbol, symbol_length); symbol_col[index].index_hash = 0; symbol_col[index].index = NULL; } /** Initialize component table. This table is manually constructed to bootstrap * flecs. After this function has been called, the builtin components can be * created. * The reason this table is constructed manually is because it requires the size * and alignment of the EcsComponent and EcsIdentifier components, which haven't * been created yet */ static ecs_table_t* flecs_bootstrap_component_table( ecs_world_t *world) { /* Before creating table, manually set flags for ChildOf/Identifier, as this * can no longer be done after they are in use. */ ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf); idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | EcsIdTraversable | EcsIdTag; /* Initialize id records cached on world */ world->idr_childof_wildcard = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive; idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); idr->flags |= EcsIdDontInherit; world->idr_identifier_name = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); world->idr_childof_0 = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, 0)); ecs_id_t ids[] = { ecs_id(EcsComponent), EcsFinal, ecs_pair_t(EcsIdentifier, EcsName), ecs_pair_t(EcsIdentifier, EcsSymbol), ecs_pair(EcsChildOf, EcsFlecsCore), ecs_pair(EcsOnDelete, EcsPanic) }; ecs_type_t array = { .array = ids, .count = 6 }; ecs_table_t *result = flecs_table_find_or_create(world, &array); ecs_data_t *data = &result->data; /* Preallocate enough memory for initial components */ ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->records, ecs_record_t*, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[0].data, EcsComponent, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[1].data, EcsIdentifier, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[2].data, EcsIdentifier, EcsFirstUserComponentId); return result; } static void flecs_bootstrap_entity( ecs_world_t *world, ecs_entity_t id, const char *name, ecs_entity_t parent) { char symbol[256]; ecs_os_strcpy(symbol, "flecs.core."); ecs_os_strcat(symbol, name); ecs_ensure(world, id); ecs_add_pair(world, id, EcsChildOf, parent); ecs_set_name(world, id, name); ecs_set_symbol(world, id, symbol); ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); if (!parent || parent == EcsFlecsCore) { ecs_assert(ecs_lookup_fullpath(world, name) == id, ECS_INTERNAL_ERROR, NULL); } } void flecs_bootstrap( ecs_world_t *world) { ecs_log_push(); ecs_set_name_prefix(world, "Ecs"); /* Ensure builtin ids are alive */ ecs_ensure(world, ecs_id(EcsComponent)); ecs_ensure(world, EcsFinal); ecs_ensure(world, ecs_id(EcsIdentifier)); ecs_ensure(world, EcsName); ecs_ensure(world, EcsSymbol); ecs_ensure(world, EcsAlias); ecs_ensure(world, EcsChildOf); ecs_ensure(world, EcsFlecs); ecs_ensure(world, EcsFlecsCore); ecs_ensure(world, EcsOnAdd); ecs_ensure(world, EcsOnRemove); ecs_ensure(world, EcsOnSet); ecs_ensure(world, EcsUnSet); ecs_ensure(world, EcsOnDelete); ecs_ensure(world, EcsPanic); ecs_ensure(world, EcsFlag); ecs_ensure(world, EcsIsA); ecs_ensure(world, EcsWildcard); ecs_ensure(world, EcsAny); ecs_ensure(world, EcsTag); /* Register type information for builtin components */ flecs_type_info_init(world, EcsComponent, { .ctor = ecs_default_ctor, .on_set = flecs_on_component, .on_remove = flecs_on_component }); flecs_type_info_init(world, EcsIdentifier, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsIdentifier), .copy = ecs_copy(EcsIdentifier), .move = ecs_move(EcsIdentifier), .on_set = ecs_on_set(EcsIdentifier), .on_remove = ecs_on_set(EcsIdentifier) }); flecs_type_info_init(world, EcsPoly, { .ctor = ecs_default_ctor, .copy = ecs_copy(EcsPoly), .move = ecs_move(EcsPoly), .dtor = ecs_dtor(EcsPoly) }); flecs_type_info_init(world, EcsIterable, { 0 }); flecs_type_info_init(world, EcsTarget, { 0 }); /* Create and cache often used id records on world */ flecs_init_id_records(world); /* Create table for builtin components. This table temporarily stores the * entities associated with builtin components, until they get moved to * other tables once properties are added (see below) */ ecs_table_t *table = flecs_bootstrap_component_table(world); assert(table != NULL); /* Bootstrap builtin components */ flecs_bootstrap_builtin_t(world, table, EcsIdentifier); flecs_bootstrap_builtin_t(world, table, EcsComponent); flecs_bootstrap_builtin_t(world, table, EcsIterable); flecs_bootstrap_builtin_t(world, table, EcsPoly); flecs_bootstrap_builtin_t(world, table, EcsTarget); /* Initialize default entity id range */ world->info.last_component_id = EcsFirstUserComponentId; flecs_entities_max_id(world) = EcsFirstUserEntityId; world->info.min_id = 0; world->info.max_id = 0; /* Make EcsOnAdd, EcsOnSet events iterable to enable .yield_existing */ ecs_set(world, EcsOnAdd, EcsIterable, { .init = flecs_on_event_iterable_init }); ecs_set(world, EcsOnSet, EcsIterable, { .init = flecs_on_event_iterable_init }); /* Register observer for tag property before adding EcsTag */ ecs_observer(world, { .entity = ecs_entity(world, {.add = { ecs_childof(EcsFlecsInternals)}}), .filter.terms[0] = { .id = EcsTag, .src.flags = EcsSelf }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_tag, .yield_existing = true }); /* Populate core module */ ecs_set_scope(world, EcsFlecsCore); flecs_bootstrap_tag(world, EcsName); flecs_bootstrap_tag(world, EcsSymbol); flecs_bootstrap_tag(world, EcsAlias); flecs_bootstrap_tag(world, EcsQuery); flecs_bootstrap_tag(world, EcsObserver); flecs_bootstrap_tag(world, EcsModule); flecs_bootstrap_tag(world, EcsPrivate); flecs_bootstrap_tag(world, EcsPrefab); flecs_bootstrap_tag(world, EcsSlotOf); flecs_bootstrap_tag(world, EcsDisabled); flecs_bootstrap_tag(world, EcsEmpty); /* Initialize builtin modules */ ecs_set_name(world, EcsFlecs, "flecs"); ecs_add_id(world, EcsFlecs, EcsModule); ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); ecs_set_name(world, EcsFlecsCore, "core"); ecs_add_id(world, EcsFlecsCore, EcsModule); ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); ecs_set_name(world, EcsFlecsInternals, "internals"); ecs_add_id(world, EcsFlecsInternals, EcsModule); /* Self check */ ecs_record_t *r = flecs_entities_get(world, EcsFlecs); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL); (void)r; /* Initialize builtin entities */ flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore); flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); /* Component/relationship properties */ flecs_bootstrap_tag(world, EcsTransitive); flecs_bootstrap_tag(world, EcsReflexive); flecs_bootstrap_tag(world, EcsSymmetric); flecs_bootstrap_tag(world, EcsFinal); flecs_bootstrap_tag(world, EcsDontInherit); flecs_bootstrap_tag(world, EcsAlwaysOverride); flecs_bootstrap_tag(world, EcsTag); flecs_bootstrap_tag(world, EcsUnion); flecs_bootstrap_tag(world, EcsExclusive); flecs_bootstrap_tag(world, EcsAcyclic); flecs_bootstrap_tag(world, EcsTraversable); flecs_bootstrap_tag(world, EcsWith); flecs_bootstrap_tag(world, EcsOneOf); flecs_bootstrap_tag(world, EcsOnDelete); flecs_bootstrap_tag(world, EcsOnDeleteTarget); flecs_bootstrap_tag(world, EcsRemove); flecs_bootstrap_tag(world, EcsDelete); flecs_bootstrap_tag(world, EcsPanic); flecs_bootstrap_tag(world, EcsFlatten); flecs_bootstrap_tag(world, EcsDefaultChildComponent); /* Builtin predicates */ flecs_bootstrap_tag(world, EcsPredEq); flecs_bootstrap_tag(world, EcsPredMatch); flecs_bootstrap_tag(world, EcsPredLookup); flecs_bootstrap_tag(world, EcsScopeOpen); flecs_bootstrap_tag(world, EcsScopeClose); /* Builtin relationships */ flecs_bootstrap_tag(world, EcsIsA); flecs_bootstrap_tag(world, EcsChildOf); flecs_bootstrap_tag(world, EcsDependsOn); /* Builtin events */ flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); flecs_bootstrap_entity(world, EcsUnSet, "UnSet", EcsFlecsCore); flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); /* Tag relationships (relationships that should never have data) */ ecs_add_id(world, EcsIsA, EcsTag); ecs_add_id(world, EcsChildOf, EcsTag); ecs_add_id(world, EcsSlotOf, EcsTag); ecs_add_id(world, EcsDependsOn, EcsTag); ecs_add_id(world, EcsFlatten, EcsTag); ecs_add_id(world, EcsDefaultChildComponent, EcsTag); ecs_add_id(world, EcsUnion, EcsTag); ecs_add_id(world, EcsFlag, EcsTag); ecs_add_id(world, EcsWith, EcsTag); /* Exclusive properties */ ecs_add_id(world, EcsChildOf, EcsExclusive); ecs_add_id(world, EcsOnDelete, EcsExclusive); ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); /* Sync properties of ChildOf and Identifier with bootstrapped flags */ ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); ecs_add_id(world, EcsChildOf, EcsAcyclic); ecs_add_id(world, EcsChildOf, EcsTraversable); ecs_add_id(world, EcsChildOf, EcsDontInherit); ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); /* Create triggers in internals scope */ ecs_set_scope(world, EcsFlecsInternals); /* Term used to also match prefabs */ ecs_term_t match_prefab = { .id = EcsPrefab, .oper = EcsOptional, .src.flags = EcsSelf }; /* Register observers for components/relationship properties. Most observers * set flags on an id record when a property is added to a component, which * allows for quick property testing in various operations. */ ecs_observer(world, { .filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_final }); ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete }); ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete_object }); ecs_observer(world, { .filter.terms = { { .id = EcsTraversable, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_traversable }); ecs_observer(world, { .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_exclusive }); ecs_observer(world, { .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_symmetric }); ecs_observer(world, { .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_dont_inherit }); ecs_observer(world, { .filter.terms = {{ .id = EcsAlwaysOverride, .src.flags = EcsSelf } }, .events = {EcsOnAdd}, .callback = flecs_register_always_override }); ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_with }); ecs_observer(world, { .filter.terms = {{ .id = EcsUnion, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_union }); /* Entities used as slot are marked as exclusive to ensure a slot can always * only point to a single entity. */ ecs_observer(world, { .filter.terms = { { .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_slot_of }); /* Define observer to make sure that adding a module to a child entity also * adds it to the parent. */ ecs_observer(world, { .filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, .events = {EcsOnAdd}, .callback = flecs_ensure_module_tag }); /* Set scope back to flecs core */ ecs_set_scope(world, EcsFlecsCore); /* Traversable relationships are always acyclic */ ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); /* Transitive relationships are always Traversable */ ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); /* DontInherit components */ ecs_add_id(world, EcsPrefab, EcsDontInherit); /* Acyclic/Traversable components */ ecs_add_id(world, EcsIsA, EcsTraversable); ecs_add_id(world, EcsDependsOn, EcsTraversable); ecs_add_id(world, EcsWith, EcsAcyclic); /* Transitive relationships */ ecs_add_id(world, EcsIsA, EcsTransitive); ecs_add_id(world, EcsIsA, EcsReflexive); /* Exclusive properties */ ecs_add_id(world, EcsSlotOf, EcsExclusive); ecs_add_id(world, EcsOneOf, EcsExclusive); ecs_add_id(world, EcsFlatten, EcsExclusive); /* Private properties */ ecs_add_id(world, ecs_id(EcsPoly), EcsPrivate); ecs_add_id(world, ecs_id(EcsIdentifier), EcsPrivate); ecs_add_id(world, EcsChildOf, EcsPrivate); ecs_add_id(world, EcsIsA, EcsPrivate); /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_hierarchy(world); ecs_set_scope(world, 0); ecs_set_name_prefix(world, NULL); ecs_log_pop(); } /** * @file entity.c * @brief Entity API. * * This file contains the implementation for the entity API, which includes * creating/deleting entities, adding/removing/setting components, instantiating * prefabs, and several other APIs for retrieving entity data. * * The file also contains the implementation of the command buffer, which is * located here so it can call functions private to the compilation unit. */ #include static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **c_info, bool move, int32_t *row_out, ecs_table_diff_t *diff); typedef struct { const ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; static flecs_component_ptr_t flecs_get_component_w_index( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); ecs_column_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = column->ti, .ptr = ecs_vec_get(&column->data, column->size, row) }; error: return (flecs_component_ptr_t){0}; } static flecs_component_ptr_t flecs_get_component_ptr( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_table_record_t *tr = flecs_table_record_get(world, table, id); if (!tr || (tr->column == -1)) { ecs_check(tr == NULL, ECS_NOT_A_COMPONENT, NULL); return (flecs_component_ptr_t){0}; } return flecs_get_component_w_index(table, tr->column, row); error: return (flecs_component_ptr_t){0}; } static void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id) { return flecs_get_component_ptr(world, table, row, id).ptr; } void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_id_record_t *table_index, int32_t recur_depth) { /* Cycle detected in IsA relationship */ ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); /* Table (and thus entity) does not have component, look for base */ if (!(table->flags & EcsTableHasIsA)) { return NULL; } /* Exclude Name */ if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { return NULL; } /* Table should always be in the table index for (IsA, *), otherwise the * HasBase flag should not have been set */ ecs_table_record_t *tr_isa = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; void *ptr = NULL; do { ecs_id_t pair = ids[i ++]; ecs_entity_t base = ecs_pair_second(world, pair); ecs_record_t *r = flecs_entities_get(world, base); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); table = r->table; if (!table) { continue; } const ecs_table_record_t *tr = flecs_id_record_get_table(table_index, table); if (!tr || tr->column == -1) { ptr = flecs_get_base_component(world, table, id, table_index, recur_depth + 1); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); ptr = flecs_get_component_w_index(table, tr->column, row).ptr; } } while (!ptr && (i < end)); return ptr; error: return NULL; } static void flecs_instantiate_slot( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance, ecs_entity_t slot_of, ecs_entity_t slot, ecs_entity_t child) { if (base == slot_of) { /* Instance inherits from slot_of, add slot to instance */ ecs_add_pair(world, instance, slot, child); } else { /* Slot is registered for other prefab, travel hierarchy * upwards to find instance that inherits from slot_of */ ecs_entity_t parent = instance; int32_t depth = 0; do { if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { const char *name = ecs_get_name(world, slot); if (name == NULL) { char *slot_of_str = ecs_get_fullpath(world, slot_of); ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " "slot (slots must be named)", slot_of_str); ecs_os_free(slot_of_str); return; } /* The 'slot' variable is currently pointing to a child (or * grandchild) of the current base. Find the original slot by * looking it up under the prefab it was registered. */ if (depth == 0) { /* If the current instance is an instance of slot_of, just * lookup the slot by name, which is faster than having to * create a relative path. */ slot = ecs_lookup_child(world, slot_of, name); } else { /* If the slot is more than one level away from the slot_of * parent, use a relative path to find the slot */ char *path = ecs_get_path_w_sep(world, parent, child, ".", NULL); slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", NULL, false); ecs_os_free(path); } if (slot == 0) { char *slot_of_str = ecs_get_fullpath(world, slot_of); char *slot_str = ecs_get_fullpath(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } ecs_add_pair(world, parent, slot, child); break; } depth ++; } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); if (parent == 0) { char *slot_of_str = ecs_get_fullpath(world, slot_of); char *slot_str = ecs_get_fullpath(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } } error: return; } static ecs_table_t* flecs_find_table_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_add(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_remove(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static void flecs_instantiate_children( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count, ecs_table_t *child_table) { if (!ecs_table_count(child_table)) { return; } ecs_type_t type = child_table->type; ecs_data_t *child_data = &child_table->data; ecs_entity_t slot_of = 0; ecs_entity_t *ids = type.array; int32_t type_count = type.count; /* Instantiate child table for each instance */ /* Create component array for creating the table */ ecs_type_t components = { .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) }; void **component_data = ecs_os_alloca_n(void*, type_count + 1); /* Copy in component identifiers. Find the base index in the component * array, since we'll need this to replace the base with the instance id */ int j, i, childof_base_index = -1, pos = 0; for (i = 0; i < type_count; i ++) { ecs_id_t id = ids[i]; /* If id has DontInherit flag don't inherit it, except for the name * and ChildOf pairs. The name is preserved so applications can lookup * the instantiated children by name. The ChildOf pair is replaced later * with the instance parent. */ if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && ECS_PAIR_FIRST(id) != EcsChildOf) { if (id == EcsUnion) { /* This should eventually be handled by the DontInherit property * but right now there is no way to selectively apply it to * EcsUnion itself: it would also apply to (Union, *) pairs, * which would make all union relationships uninheritable. * * The reason this is explicitly skipped is so that slot * instances don't all end up with the Union property. */ continue; } ecs_table_record_t *tr = &child_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdDontInherit) { continue; } } /* If child is a slot, keep track of which parent to add it to, but * don't add slot relationship to child of instance. If this is a child * of a prefab, keep the SlotOf relationship intact. */ if (!(table->flags & EcsTableIsPrefab)) { if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); slot_of = ecs_pair_second(world, id); continue; } } /* Keep track of the element that creates the ChildOf relationship with * the prefab parent. We need to replace this element to make sure the * created children point to the instance and not the prefab */ if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == base)) { childof_base_index = pos; } int32_t storage_index = ecs_table_type_to_column_index(child_table, i); if (storage_index != -1) { ecs_vec_t *column = &child_data->columns[storage_index].data; component_data[pos] = ecs_vec_first(column); } else { component_data[pos] = NULL; } components.array[pos] = id; pos ++; } /* Table must contain children of base */ ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); /* If children are added to a prefab, make sure they are prefabs too */ if (table->flags & EcsTableIsPrefab) { components.array[pos] = EcsPrefab; component_data[pos] = NULL; pos ++; } components.count = pos; /* Instantiate the prefab child table for each new instance */ ecs_entity_t *instances = ecs_vec_first(&table->data.entities); int32_t child_count = ecs_vec_count(&child_data->entities); bool has_union = child_table->flags & EcsTableHasUnion; for (i = row; i < count + row; i ++) { ecs_entity_t instance = instances[i]; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *i_table = NULL; /* Replace ChildOf element in the component array with instance id */ components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); /* Find or create table */ for (j = 0; j < components.count; j ++) { i_table = flecs_find_table_add( world, i_table, components.array[j], &diff); } ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == components.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself * which would cause infinite recursion. */ ecs_entity_t *children = ecs_vec_first(&child_data->entities); #ifdef FLECS_DEBUG for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); } #else /* Bit of boilerplate to ensure that we don't get warnings about the * error label not being used. */ ecs_check(true, ECS_INVALID_OPERATION, NULL); #endif /* Create children */ int32_t child_row; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, &components, child_count, component_data, false, &child_row, &table_diff); flecs_table_diff_builder_fini(world, &diff); /* If children have union relationships, initialize */ if (has_union) { ecs_table__t *meta = child_table->_; ecs_assert(meta != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->_ != NULL, ECS_INTERNAL_ERROR, NULL); int32_t u, u_count = meta->sw_count; for (u = 0; u < u_count; u ++) { ecs_switch_t *src_sw = &meta->sw_columns[i]; ecs_switch_t *dst_sw = &i_table->_->sw_columns[i]; ecs_vec_t *v_src_values = flecs_switch_values(src_sw); ecs_vec_t *v_dst_values = flecs_switch_values(dst_sw); uint64_t *src_values = ecs_vec_first(v_src_values); uint64_t *dst_values = ecs_vec_first(v_dst_values); for (j = 0; j < child_count; j ++) { dst_values[j] = src_values[j]; } } } /* If children are slots, add slot relationships to parent */ if (slot_of) { for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_entity_t i_child = i_children[j]; flecs_instantiate_slot(world, base, instance, slot_of, child, i_child); } } /* If prefab child table has children itself, recursively instantiate */ for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; flecs_instantiate(world, child, i_table, child_row + j, 1); } } error: return; } void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count) { ecs_record_t *record = flecs_entities_get_any(world, base); ecs_table_t *base_table = record->table; if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { /* Don't instantiate children from base entities that aren't prefabs */ return; } ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_instantiate_children( world, base, table, row, count, tr->hdr.table); } } } static void flecs_set_union( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *ids) { ecs_id_t *array = ids->array; int32_t i, id_count = ids->count; for (i = 0; i < id_count; i ++) { ecs_id_t id = array[i]; if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); if (!idr) { continue; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->index - table->_->sw_offset; ecs_switch_t *sw = &table->_->sw_columns[column]; ecs_entity_t union_case = 0; union_case = ecs_pair_second(world, id); int32_t r; for (r = 0; r < count; r ++) { flecs_switch_set(sw, row + r, union_case); } } } } static void flecs_notify_on_add( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *added, ecs_flags32_t flags) { ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); if (added->count) { ecs_flags32_t table_flags = table->flags; if (table_flags & EcsTableHasUnion) { flecs_set_union(world, table, row, count, added); } if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasTraversable)) { flecs_emit(world, world, &(ecs_event_desc_t){ .event = EcsOnAdd, .ids = added, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world, .flags = flags }); } } } void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *removed) { ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); if (removed->count && (table->flags & (EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasTraversable))) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = removed, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world }); } } static void flecs_update_name_index( ecs_world_t *world, ecs_table_t *src, ecs_table_t *dst, int32_t offset, int32_t count) { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); if (!(dst->flags & EcsTableHasName)) { /* If destination table doesn't have a name, we don't need to update the * name index. Even if the src table had a name, the on_remove hook for * EcsIdentifier will remove the entity from the index. */ return; } ecs_hashmap_t *src_index = src->_->name_index; ecs_hashmap_t *dst_index = dst->_->name_index; if ((src_index == dst_index) || (!src_index && !dst_index)) { /* If the name index didn't change, the entity still has the same parent * so nothing needs to be done. */ return; } EcsIdentifier *names = ecs_table_get_pair(world, dst, EcsIdentifier, EcsName, offset); ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i; ecs_entity_t *entities = ecs_vec_get_t( &dst->data.entities, ecs_entity_t, offset); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; EcsIdentifier *name = &names[i]; uint64_t index_hash = name->index_hash; if (index_hash) { flecs_name_index_remove(src_index, e, index_hash); } const char *name_str = name->value; if (name_str) { ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); flecs_name_index_ensure( dst_index, e, name_str, name->length, name->hash); name->index = dst_index; } } } static ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append(world, table, entity, record, ctor, true); record->table = table; record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); ecs_assert(ecs_vec_count(&table->data.entities) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); return record; } static void flecs_move_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_table_t *src_table = record->table; int32_t src_row = ECS_RECORD_TO_ROW(record->row); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&src_table->data.entities) > src_row, ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); /* Append new row to destination table */ int32_t dst_row = flecs_table_append(world, dst_table, entity, record, false, false); /* Invoke remove actions for removed components */ flecs_notify_on_remove( world, src_table, dst_table, src_row, 1, &diff->removed); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); /* Update entity index & delete old data after running remove actions */ record->table = dst_table; record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); flecs_table_delete(world, src_table, src_row, false); flecs_notify_on_add( world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); flecs_update_name_index(world, src_table, dst_table, dst_row, 1); error: return; } static void flecs_delete_entity( ecs_world_t *world, ecs_record_t *record, ecs_table_diff_t *diff) { ecs_table_t *table = record->table; int32_t row = ECS_RECORD_TO_ROW(record->row); /* Invoke remove actions before deleting */ flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); flecs_table_delete(world, table, row, true); } /* Updating component monitors is a relatively expensive operation that only * happens for entities that are monitored. The approach balances the amount of * processing between the operation on the entity vs the amount of work that * needs to be done to rematch queries, as a simple brute force approach does * not scale when there are many tables / queries. Therefore we need to do a bit * of bookkeeping that is more intelligent than simply flipping a flag */ static void flecs_update_component_monitor_w_array( ecs_world_t *world, ecs_type_t *ids) { if (!ids) { return; } int i; for (i = 0; i < ids->count; i ++) { ecs_entity_t id = ids->array[i]; if (ECS_HAS_ID_FLAG(id, PAIR)) { flecs_monitor_mark_dirty(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); } flecs_monitor_mark_dirty(world, id); } } static void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed) { flecs_update_component_monitor_w_array(world, added); flecs_update_component_monitor_w_array(world, removed); } static void flecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool construct, ecs_flags32_t evt_flags) { ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); flecs_journal_begin(world, EcsJournalMove, entity, &diff->added, &diff->removed); ecs_table_t *src_table = NULL; int is_trav = 0; if (record) { src_table = record->table; is_trav = (record->row & EcsEntityIsTraversable) != 0; } if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a * table, this suggests that a union relationship could have changed. */ if (src_table) { flecs_notify_on_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags); } flecs_journal_end(); return; } if (src_table) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_move_entity(world, entity, record, dst_table, diff, construct, evt_flags); } else { flecs_delete_entity(world, record, diff); record->table = NULL; } flecs_table_traversable_add(src_table, -is_trav); } else { flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_new_entity(world, entity, record, dst_table, diff, construct, evt_flags); } } /* If the entity is being watched, it is being monitored for changes and * requires rematching systems when components are added or removed. This * ensures that systems that rely on components from containers or prefabs * update the matched tables when the application adds or removes a * component from, for example, a container. */ if (is_trav) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } if ((!src_table || !src_table->type.count) && world->range_check_enabled) { ecs_check(!world->info.max_id || entity <= world->info.max_id, ECS_OUT_OF_RANGE, 0); ecs_check(entity >= world->info.min_id, ECS_OUT_OF_RANGE, 0); } error: flecs_journal_end(); return; } static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **component_data, bool is_move, int32_t *row_out, ecs_table_diff_t *diff) { int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); } if (!table) { return entities; } ecs_type_t type = table->type; if (!type.count) { return entities; } ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; component_array.array = type.array; component_array.count = type.count; } ecs_data_t *data = &table->data; int32_t row = flecs_table_appendn(world, table, data, count, entities); /* Update entity index. */ int i; ecs_record_t **records = ecs_vec_first(&data->records); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); r->table = table; r->row = ECS_ROW_TO_RECORD(row + i, 0); records[row + i] = r; } flecs_defer_begin(world, &world->stages[0]); flecs_notify_on_add(world, table, NULL, row, count, &diff->added, (component_data == NULL) ? 0 : EcsEventNoOnSet); if (component_data) { int32_t c_i; for (c_i = 0; c_i < component_ids->count; c_i ++) { void *src_ptr = component_data[c_i]; if (!src_ptr) { continue; } /* Find component in storage type */ ecs_entity_t id = component_ids->array[c_i]; const ecs_table_record_t *tr = flecs_table_record_get( world, table, id); ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, "id is not a component"); ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, "id is not a component"); ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, "ids cannot be wildcards"); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; ecs_type_info_t *ti = column->ti; int32_t size = column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(&column->data, size, row); ecs_copy_t copy; ecs_move_t move; if (is_move && (move = ti->hooks.move)) { move(ptr, src_ptr, count, ti); } else if (!is_move && (copy = ti->hooks.copy)) { copy(ptr, src_ptr, count, ti); } else { ecs_os_memcpy(ptr, src_ptr, size * count); } }; int32_t j, storage_count = table->column_count; for (j = 0; j < storage_count; j ++) { ecs_type_t set_type = { .array = &table->data.columns[j].id, .count = 1 }; flecs_notify_on_set(world, table, row, count, &set_type, true); } } flecs_defer_end(world, &world->stages[0]); if (row_out) { *row_out = row; } if (sparse_count) { entities = flecs_entities_ids(world); return &entities[sparse_count]; } else { return entities; } } static void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_id_t id, bool construct) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = record->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, record, dst_table, &diff, construct, EcsEventNoOnSet); /* No OnSet, this function is only called from * functions that are about to set the component. */ } static void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *src_table = r->table; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = r->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static flecs_component_ptr_t flecs_get_mut( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r) { flecs_component_ptr_t dst = {0}; ecs_poly_assert(world, ecs_world_t); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check((id & ECS_COMPONENT_MASK) == id || ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); if (r->table) { dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); if (dst.ptr) { return dst; } } /* If entity didn't have component yet, add it */ flecs_add_id_w_record(world, entity, r, id, true); /* Flush commands so the pointer we're fetching is stable */ flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table->column_count != 0, ECS_INTERNAL_ERROR, NULL); dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); error: return dst; } void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook) { ecs_iter_t it = { .field_count = 1}; it.entities = entities; flecs_iter_init(world, &it, flecs_iter_cache_all); it.world = world; it.real_world = world; it.table = table; it.ptrs[0] = ptr; it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); it.ids[0] = id; it.event = event; it.event_id = id; it.ctx = ti->hooks.ctx; it.binding_ctx = ti->hooks.binding_ctx; it.count = count; it.offset = row; flecs_iter_validate(&it); hook(&it); ecs_iter_fini(&it); } void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *ids, bool owned) { ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_data_t *data = &table->data; ecs_entity_t *entities = ecs_vec_get_t( &data->entities, ecs_entity_t, row); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((row + count) <= ecs_vec_count(&data->entities), ECS_INTERNAL_ERROR, NULL); if (owned) { int i; for (i = 0; i < ids->count; i ++) { ecs_id_t id = ids->array[i]; const ecs_table_record_t *tr = flecs_table_record_get(world, table, id); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; const ecs_type_info_t *ti = column->ti; ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_vec_t *c = &column->data; void *ptr = ecs_vec_get(c, column->size, row); flecs_invoke_hook(world, table, count, row, entities, ptr, id, ti, EcsOnSet, on_set); } } } /* Run OnSet notifications */ if (table->flags & EcsTableHasOnSet && ids->count) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = ids, .table = table, .offset = row, .count = count, .observable = world }); } } void flecs_record_add_flag( ecs_record_t *record, uint32_t flag) { if (flag == EcsEntityIsTraversable) { if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { flecs_table_traversable_add(table, 1); } } } record->row |= flag; } void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) { ecs_record_t *record = flecs_entities_get_any(world, entity); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(record, flag); } /* -- Public functions -- */ bool ecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, const ecs_type_t *added, const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); ecs_table_t *src_table = NULL; if (!record) { record = flecs_entities_get(world, entity); src_table = record->table; } ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; if (added) { diff.added = *added; } if (removed) { diff.removed = *removed; } ecs_defer_begin(world); flecs_commit(world, entity, record, table, &diff, true, 0); ecs_defer_end(world); return src_table != table; error: return false; } ecs_entity_t ecs_set_with( ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_id_t prev = stage->with; stage->with = id; return prev; error: return 0; } ecs_id_t ecs_get_with( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->with; error: return 0; } ecs_entity_t ecs_new_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); /* It is possible that the world passed to this function is a stage, so * make sure we have the actual world. Cast away const since this is one of * the few functions that may modify the world while it is in readonly mode, * since it is thread safe (uses atomic inc when in threading mode) */ ecs_world_t *unsafe_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); ecs_entity_t entity; if (stage->async || (unsafe_world->flags & EcsWorldMultiThreaded)) { /* When using an async stage or world is in multithreading mode, make * sure OS API has threading functions initialized */ ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, NULL); /* Can't atomically increase number above max int */ ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, ECS_INVALID_OPERATION, NULL); entity = (ecs_entity_t)ecs_os_ainc( (int32_t*)&flecs_entities_max_id(unsafe_world)); } else { entity = flecs_entities_new_id(unsafe_world); } ecs_assert(!unsafe_world->info.max_id || ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, ECS_OUT_OF_RANGE, NULL); flecs_journal(world, EcsJournalNew, entity, 0, 0); return entity; error: return 0; } ecs_entity_t ecs_new_low_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* It is possible that the world passed to this function is a stage, so * make sure we have the actual world. Cast away const since this is one of * the few functions that may modify the world while it is in readonly mode, * but only if single threaded. */ ecs_world_t *unsafe_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); if (unsafe_world->flags & EcsWorldReadonly) { /* Can't issue new comp id while iterating when in multithreaded mode */ ecs_check(ecs_get_stage_count(world) <= 1, ECS_INVALID_WHILE_READONLY, NULL); } ecs_entity_t id = 0; if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { do { id = unsafe_world->info.last_component_id ++; } while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); } if (!id || id >= FLECS_HI_COMPONENT_ID) { /* If the low component ids are depleted, return a regular entity id */ id = ecs_new_id(unsafe_world); } else { flecs_entities_ensure(world, id); } ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); return id; error: return 0; } ecs_entity_t ecs_new_w_id( ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new_id(world); ecs_id_t ids[3]; ecs_type_t to_add = { .array = ids, .count = 0 }; if (id) { ids[to_add.count ++] = id; } ecs_id_t with = stage->with; if (with) { ids[to_add.count ++] = with; } ecs_entity_t scope = stage->scope; if (scope) { if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); } } if (to_add.count) { if (flecs_defer_add(stage, entity, to_add.array[0])) { int i; for (i = 1; i < to_add.count; i ++) { flecs_defer_add(stage, entity, to_add.array[i]); } return entity; } int32_t i, count = to_add.count; ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); for (i = 0; i < count; i ++) { table = flecs_find_table_add( world, table, to_add.array[i], &diff); } ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); ecs_record_t *r = flecs_entities_get(world, entity); flecs_new_entity(world, entity, r, table, &table_diff, true, true); flecs_table_diff_builder_fini(world, &diff); } else { if (flecs_defer_cmd(stage)) { return entity; } flecs_entities_ensure(world, entity); } flecs_defer_end(world, stage); return entity; error: return 0; } ecs_entity_t ecs_new_w_table( ecs_world_t *world, ecs_table_t *table) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_diff_t table_diff = { .added = table->type }; flecs_new_entity(world, entity, r, table, &table_diff, true, true); return entity; error: return 0; } #ifdef FLECS_PARSER /* Traverse table graph by either adding or removing identifiers parsed from the * passed in expression. */ static ecs_table_t *flecs_traverse_from_expr( ecs_world_t *world, ecs_table_t *table, const char *name, const char *expr, ecs_table_diff_builder_t *diff, bool replace_and, bool *error) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (!(term.first.flags & (EcsSelf|EcsUp))) { term.first.flags = EcsSelf; } if (!(term.second.flags & (EcsSelf|EcsUp))) { term.second.flags = EcsSelf; } if (!(term.src.flags & (EcsSelf|EcsUp))) { term.src.flags = EcsSelf; } if (ecs_term_finalize(world, &term)) { ecs_term_fini(&term); if (error) { *error = true; } return NULL; } if (!ecs_id_is_valid(world, term.id)) { ecs_term_fini(&term); ecs_parser_error(name, expr, (ptr - expr), "invalid term for add expression"); return NULL; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ table = flecs_find_table_add(world, table, term.id, diff); } ecs_term_fini(&term); } if (!ptr) { if (error) { *error = true; } return NULL; } } return table; } /* Add/remove components based on the parsed expression. This operation is * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ static void flecs_defer_from_expr( ecs_world_t *world, ecs_entity_t entity, const char *name, const char *expr, bool is_add, bool replace_and) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (ecs_term_finalize(world, &term)) { return; } if (!ecs_id_is_valid(world, term.id)) { ecs_term_fini(&term); ecs_parser_error(name, expr, (ptr - expr), "invalid term for add expression"); return; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ if (is_add) { ecs_add_id(world, entity, term.id); } else { ecs_remove_id(world, entity, term.id); } } ecs_term_fini(&term); } } } #endif /* If operation is not deferred, add components by finding the target * table and moving the entity towards it. */ static int flecs_traverse_add( ecs_world_t *world, ecs_entity_t result, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { table = flecs_find_table_add(world, table, ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { bool should_add = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); if ((!desc->id && desc->name) || (name && !name_assigned)) { /* If name is added to entity, pass scope to add_path instead * of adding it to the table. The provided name may have nested * elements, in which case the parent provided here is not the * parent the entity will end up with. */ should_add = false; } } if (should_add) { table = flecs_find_table_add(world, table, id, &diff); } } /* Find destination table */ /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { table = flecs_find_table_add( world, table, ecs_pair(EcsChildOf, scope), &diff); } if (with) { table = flecs_find_table_add(world, table, with, &diff); } } /* Add components from the 'add_expr' expression */ if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { #ifdef FLECS_PARSER bool error = false; table = flecs_traverse_from_expr( world, table, name, desc->add_expr, &diff, true, &error); if (error) { flecs_table_diff_builder_fini(world, &diff); return -1; } #else ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } /* Commit entity to destination table */ if (src_table != table) { flecs_defer_begin(world, &world->stages[0]); ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, result, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, &world->stages[0]); } /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); ecs_assert(ecs_get_name(world, result) != NULL, ECS_INTERNAL_ERROR, NULL); } if (desc->symbol && desc->symbol[0]) { const char *sym = ecs_get_symbol(world, result); if (sym) { ecs_assert(!ecs_os_strcmp(desc->symbol, sym), ECS_INCONSISTENT_NAME, desc->symbol); } else { ecs_set_symbol(world, result, desc->symbol); } } flecs_table_diff_builder_fini(world, &diff); return 0; } /* When in deferred mode, we need to add/remove components one by one using * the regular operations. */ static void flecs_deferred_add_remove( ecs_world_t *world, ecs_entity_t entity, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); } if (with) { ecs_add_id(world, entity, with); } } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { bool defer = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); if (name && (!desc->id || !name_assigned)) { /* New named entities are created by temporarily going out of * readonly mode to ensure no duplicates are created. */ defer = false; } } if (defer) { ecs_add_id(world, entity, id); } } /* Add components from the 'add_expr' expression */ if (desc->add_expr) { #ifdef FLECS_PARSER flecs_defer_from_expr(world, entity, name, desc->add_expr, true, true); #else ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } int32_t thread_count = ecs_get_stage_count(world); /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } /* Set symbol */ if (desc->symbol) { const char *sym = ecs_get_symbol(world, entity); if (!sym || ecs_os_strcmp(sym, desc->symbol)) { if (thread_count <= 1) { /* See above */ ecs_suspend_readonly_state_t state; ecs_world_t *real_world = flecs_suspend_readonly(world, &state); ecs_set_symbol(world, entity, desc->symbol); flecs_resume_readonly(real_world, &state); } else { ecs_set_symbol(world, entity, desc->symbol); } } } } ecs_entity_t ecs_entity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t scope = stage->scope; ecs_id_t with = ecs_get_with(world); ecs_entity_t result = desc->id; const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name) { if (!name[0]) { name = NULL; } else if (flecs_name_is_id(name)){ ecs_entity_t id = flecs_name_to_id(world, name); if (!id) { return 0; } if (result && (id != result)) { ecs_err("name id conflicts with provided id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool flecs_new_entity = false; bool name_assigned = false; /* Remove optional prefix from name. Entity names can be derived from * language identifiers, such as components (typenames) and systems * function names). Because C does not have namespaces, such identifiers * often encode the namespace as a prefix. * To ensure interoperability between C and C++ (and potentially other * languages with namespacing) the entity must be stored without this prefix * and with the proper namespace, which is what the name_prefix is for */ const char *prefix = world->info.name_prefix; if (name && prefix) { ecs_size_t len = ecs_os_strlen(prefix); if (!ecs_os_strncmp(name, prefix, len) && (isupper(name[len]) || name[len] == '_')) { if (name[len] == '_') { name = name + len + 1; } else { name = name + len; } } } /* Find or create entity */ if (!result) { if (name) { /* If add array contains a ChildOf pair, use it as scope instead */ const ecs_id_t *ids = desc->add; ecs_id_t id; int32_t i = 0; while ((i < FLECS_ID_DESC_MAX) && (id = ids[i ++])) { if (ECS_HAS_ID_FLAG(id, PAIR) && (ECS_PAIR_FIRST(id) == EcsChildOf)) { scope = ECS_PAIR_SECOND(id); } } result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new_id(world); } flecs_new_entity = true; ecs_assert(ecs_get_type(world, result) == NULL, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure provided id is either alive or revivable */ ecs_ensure(world, result); name_assigned = ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName); if (name && name_assigned) { /* If entity has name, verify that name matches. The name provided * to the function could either have been relative to the current * scope, or fully qualified. */ char *path; ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { /* Fully qualified name was provided, so make sure to * compare with fully qualified name */ path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); } else { /* Relative name was provided, so make sure to compare with * relative name */ if (!sep || sep[0]) { path = ecs_get_path_w_sep(world, scope, result, sep, ""); } else { /* Safe, only freed when sep is valid */ path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); } } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); if (!sep || sep[0]) { ecs_os_free(path); } return 0; } if (!sep || sep[0]) { ecs_os_free(path); } } } } ecs_assert(name_assigned == ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); if (stage->defer) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, flecs_new_entity, name_assigned)) { return 0; } } return result; error: return 0; } const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); const ecs_entity_t *entities = desc->entities; int32_t count = desc->count; int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { int i; for (i = 0; i < count; i ++) { ecs_ensure(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &table_diff); flecs_table_diff_builder_fini(world, &diff); } else { ecs_table_diff_t diff = { .added.array = table->type.array, .added.count = table->type.count }; ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &diff); } if (!sparse_count) { return entities; } else { /* Refetch entity ids, in case the underlying array was reallocated */ entities = flecs_entities_ids(world); return &entities[sparse_count]; } error: return NULL; } static void flecs_check_component( ecs_world_t *world, ecs_entity_t result, const EcsComponent *ptr, ecs_size_t size, ecs_size_t alignment) { if (ptr->size != size) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); ecs_os_free(path); } } ecs_entity_t ecs_component_init( ecs_world_t *world, const ecs_component_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); /* If existing entity is provided, check if it is already registered as a * component and matches the size/alignment. This can prevent having to * suspend readonly mode, and increases the number of scenarios in which * this function can be called in multithreaded mode. */ ecs_entity_t result = desc->entity; if (result && ecs_is_alive(world, result)) { const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); if (const_ptr) { flecs_check_component(world, result, const_ptr, desc->type.size, desc->type.alignment); return result; } } ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; if (!result) { result = ecs_new_low_id(world); } else { ecs_ensure(world, result); new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); if (!ptr->size) { ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; if (!new_component || ptr->size != desc->type.size) { if (!ptr->size) { ecs_trace("#[green]tag#[reset] %s created", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s created", ecs_get_name(world, result)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } ecs_modified(world, result, EcsComponent); if (desc->type.size && !ecs_id_in_use(world, result) && !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) { ecs_set_hooks_id(world, result, &desc->type.hooks); } if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { world->info.last_component_id = result + 1; } /* Ensure components cannot be deleted */ ecs_add_pair(world, result, EcsOnDelete, EcsPanic); flecs_resume_readonly(world, &readonly_state); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); return result; error: return 0; } const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, ecs_id_t id, int32_t count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { return ids; } ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); if (id) { table = flecs_find_table_add(world, table, id, &diff); } ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, stage); return ids; error: return NULL; } void ecs_clear( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, entity)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->table = NULL; if (r->row & EcsEntityIsTraversable) { flecs_table_traversable_add(table, -1); } } flecs_defer_end(world, stage); error: return; } static void flecs_throw_invalid_delete( ecs_world_t *world, ecs_id_t id) { char *id_str = NULL; if (!(world->flags & EcsWorldQuit)) { id_str = ecs_id_str(world, id); ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); } error: ecs_os_free(id_str); } static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, ecs_entity_t action, bool delete_id) { ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, &world->store.marked_ids, ecs_marked_id_t); m->idr = idr; m->id = idr->id; m->action = action; m->delete_id = delete_id; flecs_id_record_claim(world, idr); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id); static void flecs_targets_mark_for_delete( ecs_world_t *world, ecs_table_t *table) { ecs_id_record_t *idr; ecs_entity_t *entities = ecs_vec_first(&table->data.entities); ecs_record_t **records = ecs_vec_first(&table->data.records); int32_t i, count = ecs_vec_count(&table->data.entities); for (i = 0; i < count; i ++) { ecs_record_t *r = records[i]; if (!r) { continue; } /* If entity is not used as id or as relationship target, there won't * be any tables with a reference to it. */ ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; if (!(flags & (EcsEntityIsId|EcsEntityIsTarget))) { continue; } ecs_entity_t e = entities[i]; if (flags & EcsEntityIsId) { if ((idr = flecs_id_record_get(world, e))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE(idr->flags), true); } } if (flags & EcsEntityIsTarget) { if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_TARGET(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_TARGET(idr->flags), true); } } } } static bool flecs_id_is_delete_target( ecs_id_t id, ecs_entity_t action) { if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { /* If no explicit delete action is provided, and the id we're deleting * has the form (*, Target), use OnDeleteTarget action */ return true; } return false; } static ecs_entity_t flecs_get_delete_action( ecs_table_t *table, ecs_table_record_t *tr, ecs_entity_t action, bool delete_target) { ecs_entity_t result = action; if (!result && delete_target) { ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = idr->id; /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ int32_t i = tr->index, count = tr->count; do { ecs_type_t *type = &table->type; ecs_table_record_t *trr = &table->_->records[i]; ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; result = ECS_ID_ON_DELETE_TARGET(idrr->flags); if (result == EcsDelete) { /* Delete takes precedence over Remove */ break; } if (count > 1) { /* If table contains multiple pairs for target they are not * guaranteed to occupy consecutive elements in the table's type * vector, so a linear search is needed to find matches. */ for (++ i; i < type->count; i ++) { if (ecs_id_match(type->array[i], id)) { break; } } /* We should always have as many matching ids as tr->count */ ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); } } while (--count); } return result; } static void flecs_update_monitors_for_delete( ecs_world_t *world, ecs_id_t id) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){id}, .count = 1 }); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id) { if (idr->flags & EcsIdMarkedForDelete) { return; } idr->flags |= EcsIdMarkedForDelete; flecs_marked_id_push(world, idr, action, delete_id); ecs_id_t id = idr->id; bool delete_target = flecs_id_is_delete_target(id, action); /* Mark all tables with the id for delete */ ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableMarkedForDelete) { continue; } ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, delete_target); /* If this is a Delete action, recursively mark ids & tables */ if (cur_action == EcsDelete) { table->flags |= EcsTableMarkedForDelete; ecs_log_push_2(); flecs_targets_mark_for_delete(world, table); ecs_log_pop_2(); } else if (cur_action == EcsPanic) { flecs_throw_invalid_delete(world, id); } } } /* Same for empty tables */ if (flecs_table_cache_empty_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { tr->hdr.table->flags |= EcsTableMarkedForDelete; } } /* Signal query cache monitors */ flecs_update_monitors_for_delete(world, id); /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ if (ecs_id_is_wildcard(id)) { ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *cur = idr; if (ECS_PAIR_SECOND(id) == EcsWildcard) { while ((cur = cur->first.next)) { flecs_update_monitors_for_delete(world, cur->id); } } else { ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); while ((cur = cur->second.next)) { flecs_update_monitors_for_delete(world, cur->id); } } } } static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If there's no id record, there's nothing to delete */ return false; } if (!action) { /* If no explicit action is provided, derive it */ if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { /* Delete actions are determined by the component, or in the case * of a pair by the relationship. */ action = ECS_ID_ON_DELETE(idr->flags); } } if (action == EcsPanic) { /* This id is protected from deletion */ flecs_throw_invalid_delete(world, id); return false; } flecs_id_mark_for_delete(world, idr, action, delete_id); return true; } static void flecs_remove_from_table( ecs_world_t *world, ecs_table_t *table) { ecs_table_diff_t temp_diff; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst_table = table; /* To find the dst table, remove all ids that are marked for deletion */ int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); const ecs_table_record_t *tr; for (i = 0; i < count; i ++) { const ecs_id_record_t *idr = ids[i].idr; if (!(tr = flecs_id_record_get_table(idr, dst_table))) { continue; } t = tr->index; do { ecs_id_t id = dst_table->type.array[t]; dst_table = flecs_table_traverse_remove( world, dst_table, &id, &temp_diff); flecs_table_diff_build_append_table(world, &diff, &temp_diff); } while (dst_table->type.count && (t = ecs_search_offset( world, dst_table, t, idr->id, NULL)) != -1); } ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); if (!dst_table->type.count) { /* If this removes all components, clear table */ flecs_table_clear_entities(world, table); } else { /* Otherwise, merge table into dst_table */ if (dst_table != table) { int32_t table_count = ecs_table_count(table); if (diff.removed.count && table_count) { ecs_log_push_3(); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, table_count, &td.removed); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table, &dst_table->data, &table->data); } } flecs_table_diff_builder_fini(world, &diff); } static bool flecs_on_delete_clear_tables( ecs_world_t *world) { /* Iterate in reverse order so that DAGs get deleted bottom to top */ int32_t i, last = ecs_vec_count(&world->store.marked_ids), first = 0; ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); do { for (i = last - 1; i >= first; i --) { ecs_id_record_t *idr = ids[i].idr; ecs_entity_t action = ids[i].action; /* Empty all tables for id */ { ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if ((action == EcsRemove) || !(table->flags & EcsTableMarkedForDelete)) { flecs_remove_from_table(world, table); } else { ecs_dbg_3( "#[red]delete#[reset] entities from table %u", (uint32_t)table->id); flecs_table_delete_entities(world, table); } } } } /* Run commands so children get notified before parent is deleted */ if (world->stages[0].defer) { flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); } /* User code (from triggers) could have enqueued more ids to delete, * reobtain the array in case it got reallocated */ ids = ecs_vec_first(&world->store.marked_ids); } /* Check if new ids were marked since we started */ int32_t new_last = ecs_vec_count(&world->store.marked_ids); if (new_last != last) { /* Iterate remaining ids */ ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); first = last; last = new_last; } else { break; } } while (true); return true; } static bool flecs_on_delete_clear_ids( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int twice = 2; do { for (i = 0; i < count; i ++) { /* Release normal ids before wildcard ids */ if (ecs_id_is_wildcard(ids[i].id)) { if (twice == 2) { continue; } } else { if (twice == 1) { continue; } } ecs_id_record_t *idr = ids[i].idr; bool delete_id = ids[i].delete_id; flecs_id_record_release_tables(world, idr); /* Release the claim taken by flecs_marked_id_push. This may delete the * id record as all other claims may have been released. */ int32_t rc = flecs_id_record_release(world, idr); ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); (void)rc; /* If rc is 0, the id was likely deleted by a nested delete_with call * made by an on_remove handler/OnRemove observer */ if (rc) { if (delete_id) { /* If id should be deleted, release initial claim. This happens when * a component, tag, or part of a pair is deleted. */ flecs_id_record_release(world, idr); } else { /* If id should not be deleted, unmark id record for deletion. This * happens when all instances *of* an id are deleted, for example * when calling ecs_remove_all or ecs_delete_with. */ idr->flags &= ~EcsIdMarkedForDelete; } } } } while (-- twice); return true; } static void flecs_on_delete( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { /* Cleanup can happen recursively. If a cleanup action is already in * progress, only append ids to the marked_ids. The topmost cleanup * frame will handle the actual cleanup. */ int32_t count = ecs_vec_count(&world->store.marked_ids); /* Make sure we're evaluating a consistent list of non-empty tables */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); /* Only perform cleanup if we're the first stack frame doing it */ if (!count && ecs_vec_count(&world->store.marked_ids)) { ecs_dbg_2("#[red]delete#[reset]"); ecs_log_push_2(); /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); /* All marked tables are empty, ensure they're in the right list */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); /* Verify deleted ids are no longer in use */ #ifdef FLECS_DEBUG ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int32_t i; count = ecs_vec_count(&world->store.marked_ids); for (i = 0; i < count; i ++) { ecs_assert(!ecs_id_in_use(world, ids[i].id), ECS_INTERNAL_ERROR, NULL); } #endif ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); /* Ids are deleted, clear stack */ ecs_vec_clear(&world->store.marked_ids); ecs_log_pop_2(); } } void ecs_delete_with( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { return; } flecs_on_delete(world, id, EcsDelete, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_remove_all( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { return; } flecs_on_delete(world, id, EcsRemove, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_delete( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_delete(stage, entity)) { return; } ecs_record_t *r = flecs_entities_try(world, entity); if (r) { flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); ecs_table_t *table; if (row_flags) { if (row_flags & EcsEntityIsTarget) { flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); r->idr = NULL; } if (row_flags & EcsEntityIsId) { flecs_on_delete(world, entity, 0, true); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); } if (row_flags & EcsEntityIsTraversable) { table = r->table; if (table) { flecs_table_traversable_add(table, -1); } } /* Merge operations before deleting entity */ flecs_defer_end(world, stage); flecs_defer_begin(world, stage); } table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->row = 0; r->table = NULL; } flecs_entities_remove(world, entity); flecs_journal_end(); } flecs_defer_end(world, stage); error: return; } void ecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); flecs_add_id(world, entity, id); error: return; } void ecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), ECS_INVALID_PARAMETER, NULL); flecs_remove_id(world, entity, id); error: return; } void ecs_override_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_add_id(world, entity, ECS_OVERRIDE | id); error: return; } ecs_entity_t ecs_clone( ecs_world_t *world, ecs_entity_t dst, ecs_entity_t src, bool copy_value) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!dst) { dst = ecs_new_id(world); } if (flecs_defer_clone(stage, dst, src, copy_value)) { return dst; } ecs_record_t *src_r = flecs_entities_get(world, src); ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = src_r->table; if (!src_table) { goto done; } ecs_type_t src_type = src_table->type; ecs_table_diff_t diff = { .added = src_type }; ecs_record_t *dst_r = flecs_entities_get(world, dst); flecs_new_entity(world, dst, dst_r, src_table, &diff, true, true); int32_t row = ECS_RECORD_TO_ROW(dst_r->row); if (copy_value) { flecs_table_move(world, dst, src, src_table, row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); int32_t i, count = src_table->column_count; for (i = 0; i < count; i ++) { ecs_type_t type = { .array = &src_table->data.columns[i].id, .count = 1 }; flecs_notify_on_set(world, src_table, row, 1, &type, true); } } done: flecs_defer_end(world, stage); return dst; error: return 0; } const void* ecs_get_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_stage_from_readonly_world(world)->async == false, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = r->table; if (!table) { return NULL; } ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return NULL; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return flecs_get_base_component(world, table, id, idr, 0); } else { ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_w_index(table, tr->column, row).ptr; error: return NULL; } void* ecs_get_mut_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set( world, stage, EcsOpMut, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_get_mut(world, entity, id, r).ptr; ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return result; error: return NULL; } void* ecs_get_mut_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); return flecs_defer_set( world, stage, EcsOpSet, entity, id, 0, NULL, true); error: return NULL; } static ecs_record_t* flecs_access_begin( ecs_world_t *stage, ecs_entity_t entity, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); const ecs_world_t *world = ecs_get_world(stage); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table; if (!(table = r->table)) { return NULL; } int32_t count = ecs_os_ainc(&table->_->lock); (void)count; if (write) { ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); } return r; error: return NULL; } static void flecs_access_end( const ecs_record_t *r, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); int32_t count = ecs_os_adec(&r->table->_->lock); (void)count; if (write) { ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); } ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); error: return; } ecs_record_t* ecs_write_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, true); } void ecs_write_end( ecs_record_t *r) { flecs_access_end(r, true); } const ecs_record_t* ecs_read_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, false); } void ecs_read_end( const ecs_record_t *r) { flecs_access_end(r, false); } ecs_entity_t ecs_record_get_entity( const ecs_record_t *record) { ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = record->table; if (!table) { return 0; } return ecs_vec_get_t(&table->data.entities, ecs_entity_t, ECS_RECORD_TO_ROW(record->row))[0]; error: return 0; } const void* ecs_record_get_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } bool ecs_record_has_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); if (r->table) { return ecs_table_has_id(world, r->table, id); } return false; } void* ecs_record_get_mut_id( ecs_world_t *stage, ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); ecs_check(record != NULL, ECS_INVALID_PARAMETER, "cannot create ref for empty entity"); ecs_ref_t result = { .entity = entity, .id = id, .record = record }; ecs_table_t *table = record->table; if (table) { result.tr = flecs_table_record_get(world, table, id); } return result; error: return (ecs_ref_t){0}; } void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return; } ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, ref->id); if (!tr) { return; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } error: return; } void* ecs_ref_get_id( const ecs_world_t *world, ecs_ref_t *ref, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return NULL; } int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, id); if (!tr) { return NULL; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } int32_t column = tr->column; ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_get_component_w_index(table, column, row).ptr; error: return NULL; } void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, "cannot emplace a component the entity already has"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); flecs_defer_end(world, stage); void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); return ptr; error: return NULL; } static void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; if (!flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } /* If the entity does not have the component, calling ecs_modified is * invalid. The assert needs to happen after the defer statement, as the * entity may not have the component when this function is called while * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } static void flecs_copy_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, EcsOpSet, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); const ecs_type_info_t *ti = dst.ti; ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } } else { ecs_os_memset(dst.ptr, 0, size); } flecs_table_mark_dirty(world, r->table, id); ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } flecs_defer_end(world, stage); error: return; } static void flecs_move_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, cmd_kind, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind != EcsOpEmplace) { /* ctor will have happened by get_mut */ move = ti->hooks.move_dtor; } else { move = ti->hooks.ctor_move_dtor; } if (move) { move(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, id); if (cmd_kind == EcsOpSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } flecs_defer_end(world, stage); error: return; } ecs_entity_t ecs_set_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, size_t size, const void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!entity || ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!entity) { entity = ecs_new_id(world); ecs_entity_t scope = stage->scope; if (scope) { ecs_add_pair(world, entity, EcsChildOf, scope); } } /* Safe to cast away const: function won't modify if move arg is false */ flecs_copy_ptr_w_id(world, stage, entity, id, size, ECS_CONST_CAST(void*, ptr)); return entity; error: return 0; } void ecs_enable_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, bool enable) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_enable(stage, entity, id, enable)) { return; } else { /* Operations invoked by enable/disable should not be deferred */ stage->defer --; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_entity_t bs_id = id | ECS_TOGGLE; ecs_table_t *table = r->table; int32_t index = -1; if (table) { index = ecs_table_get_type_index(world, table, bs_id); } if (index == -1) { ecs_add_id(world, entity, bs_id); ecs_enable_id(world, entity, id, enable); return; } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULl, since entity is stored in the table */ ecs_bitset_t *bs = &table->_->bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); error: return; } bool ecs_is_enabled_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_entity_t bs_id = id | ECS_TOGGLE; int32_t index = ecs_table_get_type_index(world, table, bs_id); if (index == -1) { /* If table does not have TOGGLE column for component, component is * always enabled, if the entity has it */ return ecs_has_id(world, entity, id); } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &table->_->bs_columns[index]; return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get_any(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_id_record_t *idr = flecs_id_record_get(world, id); int32_t column; if (idr) { ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr) { return true; } } if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { return false; } ecs_table_record_t *tr; column = ecs_search_relation(world, table, 0, id, EcsIsA, 0, 0, 0, &tr); if (column == -1) { return false; } table = tr->hdr.table; if ((table->flags & EcsTableHasUnion) && ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) != EcsWildcard) { if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &table->_->sw_columns[ column - table->_->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); uint64_t value = flecs_switch_get(sw, row); return value == ecs_pair_second(world, id); } } return true; error: return false; } bool ecs_owns_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { return (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1); } ecs_entity_t ecs_get_target( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { goto not_found; } ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_id_record_t *idr = flecs_id_record_get(world, wc); const ecs_table_record_t *tr = NULL; if (idr) { tr = flecs_id_record_get_table(idr, table); } if (!tr) { if (table->flags & EcsTableHasUnion) { wc = ecs_pair(EcsUnion, rel); tr = flecs_table_record_get(world, table, wc); if (tr) { ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &table->_->sw_columns[ tr->index - table->_->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_switch_get(sw, row); } } if (!idr || !(idr->flags & EcsIdDontInherit)) { goto look_in_base; } else { return 0; } } else if (table->flags & EcsTableHasTarget) { EcsTarget *tf = ecs_table_get_id(world, table, ecs_pair_t(EcsTarget, rel), ECS_RECORD_TO_ROW(r->row)); if (tf) { return ecs_record_get_entity(tf->target); } } if (index >= tr->count) { index -= tr->count; goto look_in_base; } return ecs_pair_second(world, table->type.array[tr->index + index]); look_in_base: if (table->flags & EcsTableHasIsA) { ecs_table_record_t *tr_isa = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = table->type.array; int32_t i = tr_isa->index, end = (i + tr_isa->count); for (; i < end; i ++) { ecs_id_t isa_pair = ids[i]; ecs_entity_t base = ecs_pair_second(world, isa_pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t t = ecs_get_target(world, base, rel, index); if (t) { return t; } } } not_found: error: return 0; } ecs_entity_t ecs_get_parent( const ecs_world_t *world, ecs_entity_t entity) { return ecs_get_target(world, entity, EcsChildOf, 0); } ecs_entity_t ecs_get_target_for_id( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); if (!id) { return ecs_get_target(world, entity, rel, 0); } world = ecs_get_world(world); ecs_table_t *table = ecs_get_table(world, entity); ecs_entity_t subject = 0; if (rel) { int32_t column = ecs_search_relation( world, table, 0, id, rel, 0, &subject, 0, 0); if (column == -1) { return 0; } } else { entity = 0; /* Don't return entity if id was not found */ if (table) { ecs_id_t *ids = table->type.array; int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { ecs_id_t ent = ids[i]; if (ent & ECS_ID_FLAGS_MASK) { /* Skip ids with pairs, roles since 0 was provided for rel */ break; } if (ecs_has_id(world, ent, id)) { subject = ent; break; } } } } if (subject == 0) { return entity; } else { return subject; } error: return 0; } int32_t ecs_get_depth( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel) { ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return ecs_table_get_depth(world, table, rel); } return 0; error: return -1; } static ecs_entity_t flecs_id_for_depth( ecs_world_t *world, int32_t depth) { ecs_vec_t *depth_ids = &world->store.depth_ids; int32_t i, count = ecs_vec_count(depth_ids); for (i = count; i <= depth; i ++) { ecs_entity_t *el = ecs_vec_append_t( &world->allocator, depth_ids, ecs_entity_t); el[0] = ecs_new_w_pair(world, EcsChildOf, EcsFlecsInternals); ecs_map_val_t *v = ecs_map_ensure(&world->store.entity_to_depth, el[0]); v[0] = flecs_ito(uint64_t, i); } return ecs_vec_get_t(&world->store.depth_ids, ecs_entity_t, depth)[0]; } static int32_t flecs_depth_for_id( ecs_world_t *world, ecs_entity_t id) { ecs_map_val_t *v = ecs_map_get(&world->store.entity_to_depth, id); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_uto(int32_t, v[0]); } static int32_t flecs_depth_for_flat_table( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table->flags & EcsTableHasTarget, ECS_INTERNAL_ERROR, NULL); ecs_id_t id; int32_t col = ecs_search(world, table, ecs_pair(EcsFlatten, EcsWildcard), &id); ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); (void)col; return flecs_depth_for_id(world, ECS_PAIR_SECOND(id)); } static void flecs_flatten( ecs_world_t *world, ecs_entity_t root, ecs_id_t pair, int32_t depth, const ecs_flatten_desc_t *desc) { ecs_id_record_t *idr = flecs_id_record_get(world, pair); if (!idr) { return; } ecs_entity_t depth_id = flecs_id_for_depth(world, depth); ecs_id_t root_pair = ecs_pair(EcsChildOf, root); ecs_id_t tgt_pair = ecs_pair_t(EcsTarget, EcsChildOf); ecs_id_t depth_pair = ecs_pair(EcsFlatten, depth_id); ecs_id_t name_pair = ecs_pair_t(EcsIdentifier, EcsName); ecs_entity_t rel = ECS_PAIR_FIRST(pair); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; int32_t i, count = ecs_table_count(table); bool has_tgt = table->flags & EcsTableHasTarget; flecs_emit_propagate_invalidate(world, table, 0, count); ecs_record_t **records = table->data.records.array; ecs_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[i]->row); if (flags & EcsEntityIsTarget) { flecs_flatten(world, root, ecs_pair(rel, entities[i]), depth + 1, desc); } } ecs_table_diff_t tmpdiff; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst; dst = flecs_table_traverse_add(world, table, &root_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); dst = flecs_table_traverse_add(world, dst, &tgt_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); if (!desc->lose_depth) { if (!has_tgt) { dst = flecs_table_traverse_add(world, dst, &depth_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); } else { int32_t cur_depth = flecs_depth_for_flat_table(world, table); cur_depth += depth; ecs_entity_t e_depth = flecs_id_for_depth(world, cur_depth); ecs_id_t p_depth = ecs_pair(EcsFlatten, e_depth); dst = flecs_table_traverse_add(world, dst, &p_depth, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); } } if (!desc->keep_names) { dst = flecs_table_traverse_remove(world, dst, &name_pair, &tmpdiff); flecs_table_diff_build_append_table(world, &diff, &tmpdiff); } int32_t dst_count = ecs_table_count(dst); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, count, &td.removed); flecs_table_merge(world, dst, table, &dst->data, &table->data); flecs_notify_on_add(world, dst, NULL, dst_count, count, &td.added, 0); flecs_table_diff_builder_fini(world, &diff); EcsTarget *fh = ecs_table_get_id(world, dst, tgt_pair, 0); ecs_assert(fh != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); int32_t remain = count; for (i = dst_count; i < (dst_count + count); i ++) { if (!has_tgt) { fh[i].target = flecs_entities_get_any(world, ECS_PAIR_SECOND(pair)); fh[i].count = remain; remain --; } ecs_assert(fh[i].target != NULL, ECS_INTERNAL_ERROR, NULL); } } } ecs_delete_with(world, pair); flecs_id_record_release(world, idr); } void ecs_flatten( ecs_world_t *world, ecs_id_t pair, const ecs_flatten_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_entity_t rel = ECS_PAIR_FIRST(pair); ecs_entity_t root = ecs_pair_second(world, pair); ecs_flatten_desc_t private_desc = {0}; if (desc) { private_desc = *desc; } ecs_run_aperiodic(world, 0); ecs_defer_begin(world); ecs_id_record_t *idr = flecs_id_record_get(world, pair); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } if (table->flags & EcsTableIsPrefab) { continue; } int32_t i, count = ecs_table_count(table); ecs_record_t **records = table->data.records.array; ecs_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[i]->row); if (flags & EcsEntityIsTarget) { flecs_flatten(world, root, ecs_pair(rel, entities[i]), 1, &private_desc); } } } } ecs_defer_end(world); } static const char* flecs_get_identifier( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); const EcsIdentifier *ptr = ecs_get_pair( world, entity, EcsIdentifier, tag); if (ptr) { return ptr->value; } else { return NULL; } error: return NULL; } const char* ecs_get_name( const ecs_world_t *world, ecs_entity_t entity) { return flecs_get_identifier(world, entity, EcsName); } const char* ecs_get_symbol( const ecs_world_t *world, ecs_entity_t entity) { world = ecs_get_world(world); if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { return flecs_get_identifier(world, entity, EcsSymbol); } else { return NULL; } } static ecs_entity_t flecs_set_identifier( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t tag, const char *name) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { entity = ecs_new_id(world); } if (!name) { ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; } EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (tag == EcsName) { /* Insert command after get_mut, but before the name is potentially * freed. Even though the name is a const char*, it is possible that the * application passed in the existing name of the entity which could * still cause it to be freed. */ flecs_defer_path(stage, 0, entity, name); } ecs_os_strset(&ptr->value, name); ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; error: return 0; } ecs_entity_t ecs_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { if (!entity) { return ecs_entity(world, { .name = name }); } ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_set_identifier(world, stage, entity, EcsName, name); return entity; } ecs_entity_t ecs_set_symbol( ecs_world_t *world, ecs_entity_t entity, const char *name) { return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); } void ecs_set_alias( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_set_identifier(world, NULL, entity, EcsAlias, name); } ecs_id_t ecs_make_pair( ecs_entity_t relationship, ecs_entity_t target) { ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), ECS_INVALID_PARAMETER, "cannot create nested pairs"); return ecs_pair(relationship, target); } bool ecs_is_valid( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* 0 is not a valid entity id */ if (!entity) { return false; } /* Entity identifiers should not contain flag bits */ if (entity & ECS_ID_FLAGS_MASK) { return false; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return false; } /* If entity doesn't exist in the world, the id is valid as long as the * generation is 0. Using a non-existing id with a non-zero generation * requires calling ecs_ensure first. */ if (!ecs_exists(world, entity)) { return ECS_GENERATION(entity) == 0; } /* If id exists, it must be alive (the generation count must match) */ return ecs_is_alive(world, entity); error: return false; } ecs_id_t ecs_strip_generation( ecs_entity_t e) { /* If this is not a pair, erase the generation bits */ if (!(e & ECS_ID_FLAGS_MASK)) { e &= ~ECS_GENERATION_MASK; } return e; } bool ecs_is_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_is_alive(world, entity); error: return false; } ecs_entity_t ecs_get_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { return 0; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); if (flecs_entities_is_alive(world, entity)) { return entity; } /* Make sure id does not have generation. This guards against accidentally * "upcasting" a not alive identifier to a alive one. */ if ((uint32_t)entity != entity) { return 0; } ecs_entity_t current = flecs_entities_get_generation(world, entity); if (!current || !flecs_entities_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_ensure( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); /* The entity index can be mutated while in staged/readonly mode, as long as * the world is not multithreaded. */ ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, NULL); /* Check if a version of the provided id is alive */ ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); if (any == entity) { /* If alive and equal to the argument, there's nothing left to do */ return; } /* If the id is currently alive but did not match the argument, fail */ ecs_check(!any, ECS_INVALID_PARAMETER, NULL); /* Set generation if not alive. The sparse set checks if the provided * id matches its own generation which is necessary for alive ids. This * check would cause ecs_ensure to fail if the generation of the 'entity' * argument doesn't match with its generation. * * While this could've been addressed in the sparse set, this is a rare * scenario that can only be triggered by ecs_ensure. Implementing it here * allows the sparse set to not do this check, which is more efficient. */ flecs_entities_set_generation(world, entity); /* Ensure id exists. The underlying datastructure will verify that the * generation count matches the provided one. */ flecs_entities_ensure(world, entity); error: return; } void ecs_ensure_id( ecs_world_t *world, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); if (flecs_entities_get_generation(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); flecs_entities_ensure(world, r); } if (flecs_entities_get_generation(world, o) == 0) { ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, "second element of pair is not alive"); flecs_entities_ensure(world, o); } } else { flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); } error: return; } bool ecs_exists( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_exists(world, entity); error: return false; } ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); return record->table; error: return NULL; } const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity) { ecs_table_t *table = ecs_get_table(world, entity); if (table) { return &table->type; } return NULL; } const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr && ECS_IS_PAIR(id)) { idr = flecs_id_record_get(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); if (!idr || !idr->type_info) { idr = NULL; } if (!idr) { ecs_entity_t first = ecs_pair_first(world, id); if (!first || !ecs_has_id(world, first, EcsTag)) { idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); if (!idr || !idr->type_info) { idr = NULL; } } } } if (idr) { return idr->type_info; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_type_info_get(world, id); } error: return NULL; } ecs_entity_t ecs_get_typeid( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return ti->component; } error: return 0; } bool ecs_id_is_tag( const ecs_world_t *world, ecs_id_t id) { if (ecs_id_is_wildcard(id)) { /* If id is a wildcard, we can't tell if it's a tag or not, except * when the relationship part of a pair has the Tag property */ if (ECS_HAS_ID_FLAG(id, PAIR)) { if (ECS_PAIR_FIRST(id) != EcsWildcard) { ecs_entity_t rel = ecs_pair_first(world, id); if (ecs_is_valid(world, rel)) { if (ecs_has_id(world, rel, EcsTag)) { return true; } } else { /* During bootstrap it's possible that not all ids are valid * yet. Using ecs_get_typeid will ensure correct values are * returned for only those components initialized during * bootstrap, while still asserting if another invalid id * is provided. */ if (ecs_get_typeid(world, id) == 0) { return true; } } } else { /* If relationship is wildcard id is not guaranteed to be a tag */ } } } else { if (ecs_get_typeid(world, id) == 0) { return true; } } return false; } bool ecs_id_is_union( const ecs_world_t *world, ecs_id_t id) { if (!ECS_IS_PAIR(id)) { return false; } else if (ECS_PAIR_FIRST(id) == EcsUnion) { return true; } else { ecs_entity_t first = ecs_pair_first(world, id); if (ecs_has_id(world, first, EcsUnion)) { return true; } } return false; } int32_t ecs_count_id( const ecs_world_t *world, ecs_entity_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!id) { return 0; } int32_t count = 0; ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = id, .src.flags = EcsSelf, .flags = EcsTermMatchDisabled|EcsTermMatchPrefab }); it.flags |= EcsIterNoData; it.flags |= EcsIterEvalTables; while (ecs_term_next(&it)) { count += it.count; } return count; error: return 0; } void ecs_enable( ecs_world_t *world, ecs_entity_t entity, bool enabled) { if (ecs_has_id(world, entity, EcsPrefab)) { /* If entity is a type, enable/disable all entities in the type */ const ecs_type_t *type = ecs_get_type(world, entity); ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { continue; } ecs_enable(world, id, enabled); } } else { if (enabled) { ecs_remove_id(world, entity, EcsDisabled); } else { ecs_add_id(world, entity, EcsDisabled); } } } bool ecs_defer_begin( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); return flecs_defer_begin(world, stage); error: return false; } bool ecs_defer_end( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); return flecs_defer_end(world, stage); error: return false; } void ecs_defer_suspend( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, NULL); stage->defer = -stage->defer; error: return; } void ecs_defer_resume( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, NULL); stage->defer = -stage->defer; error: return; } const char* ecs_id_flag_str( ecs_entity_t entity) { if (ECS_HAS_ID_FLAG(entity, PAIR)) { return "PAIR"; } else if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { return "TOGGLE"; } else if (ECS_HAS_ID_FLAG(entity, AND)) { return "AND"; } else if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { return "OVERRIDE"; } else { return "UNKNOWN"; } } void ecs_id_str_buf( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, AND)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t obj = ECS_PAIR_SECOND(id); ecs_entity_t e; if ((e = ecs_get_alive(world, rel))) { rel = e; } if ((e = ecs_get_alive(world, obj))) { obj = e; } ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_entity_t e = id & ECS_COMPONENT_MASK; ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); } error: return; } char* ecs_id_str( const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_id_str_buf(world, id, &buf); return ecs_strbuf_get(&buf); } static void ecs_type_str_buf( const ecs_world_t *world, const ecs_type_t *type, ecs_strbuf_t *buf) { ecs_entity_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; if (i) { ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, ' '); } if (id == 1) { ecs_strbuf_appendlit(buf, "Component"); } else { ecs_id_str_buf(world, id, buf); } } } char* ecs_type_str( const ecs_world_t *world, const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_type_str_buf(world, type, &buf); return ecs_strbuf_get(&buf); } char* ecs_table_str( const ecs_world_t *world, const ecs_table_t *table) { if (table) { return ecs_type_str(world, &table->type); } else { return NULL; } } char* ecs_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); ecs_strbuf_appendlit(&buf, " ["); const ecs_type_t *type = ecs_get_type(world, entity); if (type) { ecs_type_str_buf(world, type, &buf); } ecs_strbuf_appendch(&buf, ']'); return ecs_strbuf_get(&buf); error: return NULL; } static void flecs_flush_bulk_new( ecs_world_t *world, ecs_cmd_t *cmd) { ecs_entity_t *entities = cmd->is._n.entities; if (cmd->id) { int i, count = cmd->is._n.count; for (i = 0; i < count; i ++) { flecs_entities_ensure(world, entities[i]); flecs_add_id(world, entities[i], cmd->id); } } ecs_os_free(entities); } static void flecs_dtor_value( ecs_world_t *world, ecs_id_t id, void *value, int32_t count) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { ecs_size_t size = ti->size; void *ptr; int i; for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { dtor(ptr, 1, ti); } } } static void flecs_discard_cmd( ecs_world_t *world, ecs_cmd_t *cmd) { if (cmd->kind != EcsOpBulkNew) { void *value = cmd->is._1.value; if (value) { flecs_dtor_value(world, cmd->id, value, 1); flecs_stack_free(value, cmd->is._1.size); } } else { ecs_os_free(cmd->is._n.entities); } } static bool flecs_remove_invalid( ecs_world_t *world, ecs_id_t id, ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); if (!flecs_entities_is_valid(world, rel)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } else { ecs_entity_t obj = ECS_PAIR_SECOND(id); if (!flecs_entities_is_valid(world, obj)) { /* Check the relationship's policy for deleted objects */ ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(rel, EcsWildcard)); if (idr) { ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(idr->flags); if (action == EcsDelete) { /* Entity should be deleted, don't bother checking * other ids */ return false; } else if (action == EcsPanic) { /* If policy is throw this object should not have * been deleted */ flecs_throw_invalid_delete(world, id); } else { *id_out = 0; return true; } } else { *id_out = 0; return true; } } } } else { id &= ECS_COMPONENT_MASK; if (!flecs_entities_is_valid(world, id)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } } return true; } static void flecs_cmd_batch_for_entity( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_entity_t entity, ecs_cmd_t *cmds, int32_t start) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = NULL; if (r) { table = r->table; } world->info.cmd.batched_entity_count ++; ecs_table_t *start_table = table; ecs_cmd_t *cmd; int32_t next_for_entity; ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ int32_t cur = start; ecs_id_t id; bool has_set = false; do { cmd = &cmds[cur]; id = cmd->id; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { /* First command for an entity has a negative index, flip sign */ next_for_entity *= -1; } /* Check if added id is still valid (like is the parent of a ChildOf * pair still alive), if not run cleanup actions for entity */ if (id) { if (flecs_remove_invalid(world, id, &id)) { if (!id) { /* Entity should remain alive but id should not be added */ cmd->kind = EcsOpSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsOpSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsOpAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsOpModified; /* fall through */ case EcsOpAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpModified: if (start_table) { /* If a modified was inserted for an existing component, the value * of the component could have been changed. If this is the case, * call on_set hooks before the OnAdd/OnRemove observers are invoked * when moving the entity to a different table. * This ensures that if OnAdd/OnRemove observers access the modified * component value, the on_set hook has had the opportunity to * run first to set any computed values of the component. */ int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_component_ptr_t ptr = flecs_get_component_ptr( world, start_table, row, cmd->id); if (ptr.ptr) { const ecs_type_info_t *ti = ptr.ti; ecs_iter_action_t on_set; if ((on_set = ti->hooks.on_set)) { flecs_invoke_hook(world, start_table, 1, row, &entity, ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); } } } break; case EcsOpSet: case EcsOpMut: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; has_set = true; break; case EcsOpEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsOpRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpClear: if (table) { ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, &diff->removed, ecs_id_t, table->type.count); ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, table->type.count); } table = &world->store.root; world->info.cmd.batched_command_count ++; break; case EcsOpClone: case EcsOpBulkNew: case EcsOpPath: case EcsOpDelete: case EcsOpOnDeleteAction: case EcsOpEnable: case EcsOpDisable: case EcsOpSkip: break; } /* Add, remove and clear operations can be skipped since they have no * side effects besides adding/removing components */ if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) { cmd->kind = EcsOpSkip; } } while ((cur = next_for_entity)); /* Move entity to destination table in single operation */ flecs_table_diff_build_noalloc(diff, &table_diff); flecs_defer_begin(world, &world->stages[0]); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, &world->stages[0]); flecs_table_diff_builder_clear(diff); /* If ids were both removed and set, check if there are ids that were both * set and removed. If so, skip the set command so that the id won't get * re-added */ if (has_set && table_diff.removed.count) { cur = start; do { cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsOpSet: case EcsOpMut: { ecs_id_record_t *idr = cmd->idr; if (!idr) { idr = flecs_id_record_get(world, cmd->id); } if (!flecs_id_record_get_table(idr, table)) { /* Component was deleted */ cmd->kind = EcsOpSkip; world->info.cmd.batched_command_count --; world->info.cmd.discard_count ++; } break; } case EcsOpClone: case EcsOpBulkNew: case EcsOpAdd: case EcsOpRemove: case EcsOpEmplace: case EcsOpModified: case EcsOpAddModified: case EcsOpPath: case EcsOpDelete: case EcsOpClear: case EcsOpOnDeleteAction: case EcsOpEnable: case EcsOpDisable: case EcsOpSkip: break; } } while ((cur = next_for_entity)); } } /* Leave safe section. Run all deferred commands. */ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); if (stage->defer < 0) { /* Defer suspending makes it possible to do operations on the storage * without flushing the commands in the queue */ return false; } ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); if (!--stage->defer) { /* Test whether we're flushing to another queue or whether we're * flushing to the storage */ bool merge_to_world = false; if (ecs_poly_is(world, ecs_world_t)) { merge_to_world = world->stages[0].defer == 0; } ecs_stage_t *dst_stage = flecs_stage_from_world(&world); if (ecs_vec_count(&stage->commands)) { ecs_vec_t commands = stage->commands; stage->commands.array = NULL; stage->commands.count = 0; stage->commands.size = 0; ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); ecs_stack_t stack = stage->defer_stack; flecs_stack_init(&stage->defer_stack); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_sparse_clear(&stage->cmd_entries); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_alive(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind != EcsOpPath) && ((kind == EcsOpSkip) || (e && !is_alive))) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsOpAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsOpRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsOpClone: ecs_clone(world, e, id, cmd->is._1.clone_value); break; case EcsOpSet: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsOpEmplace: if (merge_to_world) { ecs_emplace_id(world, e, id); } flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpMut: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpModified: flecs_modified_id_if(world, e, id); world->info.cmd.modified_count ++; break; case EcsOpAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id); world->info.cmd.add_count ++; world->info.cmd.modified_count ++; break; case EcsOpDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsOpClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsOpOnDeleteAction: ecs_defer_begin(world); flecs_on_delete(world, id, e, false); ecs_defer_end(world); world->info.cmd.other_count ++; break; case EcsOpEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsOpDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsOpBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsOpPath: ecs_ensure(world, e); if (cmd->id) { ecs_add_pair(world, e, EcsChildOf, cmd->id); } ecs_set_name(world, e, cmd->is._1.value); ecs_os_free(cmd->is._1.value); cmd->is._1.value = NULL; break; case EcsOpSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); /* Restore defer queue */ ecs_vec_clear(&commands); stage->commands = commands; /* Restore stack */ flecs_stack_fini(&stage->defer_stack); stage->defer_stack = stack; flecs_stack_reset(&stage->defer_stack); flecs_table_diff_builder_fini(world, &diff); } return true; } return false; } /* Delete operations from queue without executing them. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); if (!--stage->defer) { ecs_vec_t commands = stage->commands; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->defer_stack); flecs_sparse_clear(&stage->cmd_entries); } return true; } error: return false; } /** * @file entity_filter.c * @brief Filters that are applied to entities in a table. * * After a table has been matched by a query, additional filters may have to * be applied before returning entities to the application. The two scenarios * under which this happens are queries for union relationship pairs (entities * for multiple targets are stored in the same table) and toggles (components * that are enabled/disabled with a bitset). */ static int flecs_entity_filter_find_smallest_term( ecs_table_t *table, ecs_entity_filter_iter_t *iter) { ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); flecs_switch_term_t *sw_terms = ecs_vec_first(&iter->entity_filter->sw_terms); int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); int32_t min = INT_MAX, index = 0; for (i = 0; i < count; i ++) { /* The array with sparse queries for the matched table */ flecs_switch_term_t *sparse_column = &sw_terms[i]; /* Pointer to the switch column struct of the table */ ecs_switch_t *sw = sparse_column->sw_column; /* If the sparse column pointer hadn't been retrieved yet, do it now */ if (!sw) { /* Get the table column index from the signature column index */ int32_t table_column_index = iter->columns[ sparse_column->signature_column_index]; /* Translate the table column index to switch column index */ table_column_index -= table->_->sw_offset; ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); /* Get the sparse column */ sw = sparse_column->sw_column = &table->_->sw_columns[table_column_index - 1]; } /* Find the smallest column */ int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); if (case_count < min) { min = case_count; index = i + 1; } } return index; } static int flecs_entity_filter_switch_next( ecs_table_t *table, ecs_entity_filter_iter_t *iter, bool filter) { bool first_iteration = false; int32_t switch_smallest; if (!(switch_smallest = iter->sw_smallest)) { switch_smallest = iter->sw_smallest = flecs_entity_filter_find_smallest_term(table, iter); first_iteration = true; } switch_smallest -= 1; flecs_switch_term_t *columns = ecs_vec_first(&iter->entity_filter->sw_terms); flecs_switch_term_t *column = &columns[switch_smallest]; ecs_switch_t *sw, *sw_smallest = column->sw_column; ecs_entity_t case_smallest = column->sw_case; /* Find next entity to iterate in sparse column */ int32_t first, sparse_first = iter->sw_offset; if (!filter) { if (first_iteration) { first = flecs_switch_first(sw_smallest, case_smallest); } else { first = flecs_switch_next(sw_smallest, sparse_first); } } else { int32_t cur_first = iter->range.offset, cur_count = iter->range.count; first = cur_first; while (flecs_switch_get(sw_smallest, first) != case_smallest) { first ++; if (first >= (cur_first + cur_count)) { first = -1; break; } } } if (first == -1) { goto done; } /* Check if entity matches with other sparse columns, if any */ int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); do { for (i = 0; i < count; i ++) { if (i == switch_smallest) { /* Already validated this one */ continue; } column = &columns[i]; sw = column->sw_column; if (flecs_switch_get(sw, first) != column->sw_case) { first = flecs_switch_next(sw_smallest, first); if (first == -1) { goto done; } } } } while (i != count); iter->range.offset = iter->sw_offset = first; iter->range.count = 1; return 0; done: /* Iterated all elements in the sparse list, we should move to the * next matched table. */ iter->sw_smallest = 0; iter->sw_offset = 0; return -1; } #define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) static int flecs_entity_filter_bitset_next( ecs_table_t *table, ecs_entity_filter_iter_t *iter) { /* Precomputed single-bit test */ static const uint64_t bitmask[64] = { (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 }; /* Precomputed test to verify if remainder of block is set (or not) */ static const uint64_t bitmask_remain[64] = { BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), BS_MAX - (BS_MAX >> 1) }; int32_t i, count = ecs_vec_count(&iter->entity_filter->bs_terms); flecs_bitset_term_t *terms = ecs_vec_first(&iter->entity_filter->bs_terms); int32_t bs_offset = table->_->bs_offset; int32_t first = iter->bs_offset; int32_t last = 0; for (i = 0; i < count; i ++) { flecs_bitset_term_t *column = &terms[i]; ecs_bitset_t *bs = terms[i].bs_column; if (!bs) { int32_t index = column->column_index; ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); bs = &table->_->bs_columns[index - bs_offset]; terms[i].bs_column = bs; } int32_t bs_elem_count = bs->count; int32_t bs_block = first >> 6; int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; if (bs_block >= bs_block_count) { goto done; } uint64_t *data = bs->data; int32_t bs_start = first & 0x3F; /* Step 1: find the first non-empty block */ uint64_t v = data[bs_block]; uint64_t remain = bitmask_remain[bs_start]; while (!(v & remain)) { /* If no elements are remaining, move to next block */ if ((++bs_block) >= bs_block_count) { /* No non-empty blocks left */ goto done; } bs_start = 0; remain = BS_MAX; /* Test the full block */ v = data[bs_block]; } /* Step 2: find the first non-empty element in the block */ while (!(v & bitmask[bs_start])) { bs_start ++; /* Block was not empty, so bs_start must be smaller than 64 */ ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); } /* Step 3: Find number of contiguous enabled elements after start */ int32_t bs_end = bs_start, bs_block_end = bs_block; remain = bitmask_remain[bs_end]; while ((v & remain) == remain) { bs_end = 0; bs_block_end ++; if (bs_block_end == bs_block_count) { break; } v = data[bs_block_end]; remain = BS_MAX; /* Test the full block */ } /* Step 4: find remainder of enabled elements in current block */ if (bs_block_end != bs_block_count) { while ((v & bitmask[bs_end])) { bs_end ++; } } /* Block was not 100% occupied, so bs_start must be smaller than 64 */ ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); /* Step 5: translate to element start/end and make sure that each column * range is a subset of the previous one. */ first = bs_block * 64 + bs_start; int32_t cur_last = bs_block_end * 64 + bs_end; /* No enabled elements found in table */ if (first == cur_last) { goto done; } /* If multiple bitsets are evaluated, make sure each subsequent range * is equal or a subset of the previous range */ if (i) { /* If the first element of a subsequent bitset is larger than the * previous last value, start over. */ if (first >= last) { i = -1; continue; } /* Make sure the last element of the range doesn't exceed the last * element of the previous range. */ if (cur_last > last) { cur_last = last; } } last = cur_last; int32_t elem_count = last - first; /* Make sure last element doesn't exceed total number of elements in * the table */ if (elem_count > (bs_elem_count - first)) { elem_count = (bs_elem_count - first); if (!elem_count) { iter->bs_offset = 0; goto done; } } iter->range.offset = first; iter->range.count = elem_count; iter->bs_offset = first; } /* Keep track of last processed element for iteration */ iter->bs_offset = last; return 0; done: iter->sw_smallest = 0; iter->sw_offset = 0; return -1; } #undef BS_MAX static int32_t flecs_get_flattened_target( ecs_world_t *world, EcsTarget *cur, ecs_entity_t rel, ecs_id_t id, ecs_entity_t *src_out, ecs_table_record_t **tr_out) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return -1; } ecs_record_t *r = cur->target; ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return -1; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr) { *src_out = ecs_record_get_entity(r); *tr_out = tr; return tr->index; } if (table->flags & EcsTableHasTarget) { int32_t col = table->column_map[table->_->ft_offset]; ecs_assert(col != -1, ECS_INTERNAL_ERROR, NULL); EcsTarget *next = table->data.columns[col].data.array; next = ECS_ELEM_T(next, EcsTarget, ECS_RECORD_TO_ROW(r->row)); return flecs_get_flattened_target( world, next, rel, id, src_out, tr_out); } return ecs_search_relation( world, table, 0, id, rel, EcsSelf|EcsUp, src_out, NULL, tr_out); } void flecs_entity_filter_init( ecs_world_t *world, ecs_entity_filter_t **entity_filter, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns) { ecs_poly_assert(world, ecs_world_t); ecs_assert(entity_filter != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &world->allocator; ecs_entity_filter_t ef; ecs_os_zeromem(&ef); ecs_vec_t *sw_terms = &ef.sw_terms; ecs_vec_t *bs_terms = &ef.bs_terms; ecs_vec_t *ft_terms = &ef.ft_terms; if (*entity_filter) { ef.sw_terms = (*entity_filter)->sw_terms; ef.bs_terms = (*entity_filter)->bs_terms; ef.ft_terms = (*entity_filter)->ft_terms; } ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t); ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t); ecs_vec_reset_t(a, ft_terms, flecs_flat_table_term_t); ecs_term_t *terms = filter->terms; int32_t i, term_count = filter->term_count; bool has_filter = false; ef.flat_tree_column = -1; /* Look for union fields */ if (table->flags & EcsTableHasUnion) { for (i = 0; i < term_count; i ++) { if (ecs_term_match_0(&terms[i])) { continue; } ecs_id_t id = terms[i].id; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { continue; } int32_t field = terms[i].field_index; int32_t column = columns[field]; if (column <= 0) { continue; } ecs_id_t table_id = table->type.array[column - 1]; if (ECS_PAIR_FIRST(table_id) != EcsUnion) { continue; } flecs_switch_term_t *el = ecs_vec_append_t(a, sw_terms, flecs_switch_term_t); el->signature_column_index = field; el->sw_case = ecs_pair_second(world, id); el->sw_column = NULL; ids[field] = id; has_filter = true; } } /* Look for disabled fields */ if (table->flags & EcsTableHasToggle) { for (i = 0; i < term_count; i ++) { if (ecs_term_match_0(&terms[i])) { continue; } int32_t field = terms[i].field_index; ecs_id_t id = ids[field]; ecs_id_t bs_id = ECS_TOGGLE | id; int32_t bs_index = ecs_table_get_type_index(world, table, bs_id); if (bs_index != -1) { flecs_bitset_term_t *bc = ecs_vec_append_t(a, bs_terms, flecs_bitset_term_t); bc->column_index = bs_index; bc->bs_column = NULL; has_filter = true; } } } /* Look for flattened fields */ if (table->flags & EcsTableHasTarget) { const ecs_table_record_t *tr = flecs_table_record_get(world, table, ecs_pair_t(EcsTarget, EcsWildcard)); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->index; ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); ecs_entity_t rel = ecs_pair_second(world, table->type.array[column]); for (i = 0; i < term_count; i ++) { if (ecs_term_match_0(&terms[i])) { continue; } if (terms[i].src.trav == rel) { ef.flat_tree_column = table->column_map[column]; ecs_assert(ef.flat_tree_column != -1, ECS_INTERNAL_ERROR, NULL); has_filter = true; flecs_flat_table_term_t *term = ecs_vec_append_t( a, ft_terms, flecs_flat_table_term_t); term->field_index = terms[i].field_index; term->term = &terms[i]; ecs_os_zeromem(&term->monitor); } } } if (has_filter) { if (!*entity_filter) { *entity_filter = ecs_os_malloc_t(ecs_entity_filter_t); } ecs_assert(*entity_filter != NULL, ECS_OUT_OF_MEMORY, NULL); **entity_filter = ef; } } void flecs_entity_filter_fini( ecs_world_t *world, ecs_entity_filter_t *ef) { if (!ef) { return; } ecs_allocator_t *a = &world->allocator; flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); int32_t i, term_count = ecs_vec_count(&ef->ft_terms); for (i = 0; i < term_count; i ++) { ecs_vec_fini_t(NULL, &fields[i].monitor, flecs_flat_monitor_t); } ecs_vec_fini_t(a, &ef->sw_terms, flecs_switch_term_t); ecs_vec_fini_t(a, &ef->bs_terms, flecs_bitset_term_t); ecs_vec_fini_t(a, &ef->ft_terms, flecs_flat_table_term_t); ecs_os_free(ef); } int flecs_entity_filter_next( ecs_entity_filter_iter_t *it) { ecs_table_t *table = it->range.table; flecs_switch_term_t *sw_terms = ecs_vec_first(&it->entity_filter->sw_terms); flecs_bitset_term_t *bs_terms = ecs_vec_first(&it->entity_filter->bs_terms); ecs_entity_filter_t *ef = it->entity_filter; int32_t flat_tree_column = ef->flat_tree_column; ecs_table_range_t *range = &it->range; int32_t range_end = range->offset + range->count; int result = EcsIterNext; bool found = false; do { found = false; if (bs_terms) { if (flecs_entity_filter_bitset_next(table, it) == -1) { /* No more enabled components for table */ it->bs_offset = 0; break; } else { result = EcsIterYield; found = true; } } if (sw_terms) { if (flecs_entity_filter_switch_next(table, it, found) == -1) { /* No more elements in sparse column */ if (found) { /* Try again */ result = EcsIterNext; found = false; } else { /* Nothing found */ it->bs_offset = 0; break; } } else { result = EcsIterYield; found = true; it->bs_offset = range->offset + range->count; } } if (flat_tree_column != -1) { bool first_for_table = it->prev != table; ecs_iter_t *iter = it->it; ecs_world_t *world = iter->real_world; EcsTarget *ft = table->data.columns[flat_tree_column].data.array; int32_t ft_offset; int32_t ft_count; if (first_for_table) { ft_offset = it->flat_tree_offset = range->offset; it->target_count = 1; } else { it->flat_tree_offset += ft[it->flat_tree_offset].count; ft_offset = it->flat_tree_offset; it->target_count ++; } ecs_assert(ft_offset < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); EcsTarget *cur = &ft[ft_offset]; ft_count = cur->count; bool is_last = (ft_offset + ft_count) >= range_end; int32_t i, field_count = ecs_vec_count(&ef->ft_terms); flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); for (i = 0; i < field_count; i ++) { flecs_flat_table_term_t *field = &fields[i]; ecs_vec_init_if_t(&field->monitor, flecs_flat_monitor_t); int32_t field_index = field->field_index; ecs_id_t id = it->it->ids[field_index]; ecs_id_t flat_pair = table->type.array[flat_tree_column]; ecs_entity_t rel = ECS_PAIR_FIRST(flat_pair); ecs_entity_t tgt; ecs_table_record_t *tr; int32_t tgt_col = flecs_get_flattened_target( world, cur, rel, id, &tgt, &tr); if (tgt_col != -1) { iter->sources[field_index] = tgt; iter->columns[field_index] = /* encode flattened field */ -(iter->field_count + tgt_col + 1); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of maximum value encountered in target table * dirty state so this doesn't have to be recomputed when * synchronizing the query monitor. */ ecs_vec_set_min_count_zeromem_t(NULL, &field->monitor, flecs_flat_monitor_t, it->target_count); ecs_table_t *tgt_table = tr->hdr.table; int32_t *ds = flecs_table_get_dirty_state(world, tgt_table); ecs_assert(ds != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_get_t(&field->monitor, flecs_flat_monitor_t, it->target_count - 1)->table_state = ds[tgt_col + 1]; } else { if (field->term->oper == EcsOptional) { iter->columns[field_index] = 0; iter->ptrs[field_index] = NULL; } else { it->prev = NULL; break; } } } if (i != field_count) { if (is_last) { break; } } else { found = true; if ((ft_offset + ft_count) == range_end) { result = EcsIterNextYield; } else { result = EcsIterYield; } } range->offset = ft_offset; range->count = ft_count; it->prev = table; } } while (!found); it->prev = table; if (!found) { return EcsIterNext; } else { return result; } } /** * @file entity_name.c * @brief Functions for working with named entities. */ #include #define ECS_NAME_BUFFER_LENGTH (64) static bool flecs_path_append( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix, ecs_strbuf_t *buf) { ecs_poly_assert(world, ecs_world_t); ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t cur = 0; const char *name = NULL; ecs_size_t name_len = 0; if (child && ecs_is_alive(world, child)) { cur = ecs_get_target(world, child, EcsChildOf, 0); if (cur) { ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { flecs_path_append(world, parent, cur, sep, prefix, buf); if (!sep[1]) { ecs_strbuf_appendch(buf, sep[0]); } else { ecs_strbuf_appendstr(buf, sep); } } } else if (prefix && prefix[0]) { if (!prefix[1]) { ecs_strbuf_appendch(buf, prefix[0]); } else { ecs_strbuf_appendstr(buf, prefix); } } const EcsIdentifier *id = ecs_get_pair( world, child, EcsIdentifier, EcsName); if (id) { name = id->value; name_len = id->length; } } if (name) { ecs_strbuf_appendstrn(buf, name, name_len); } else { ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); } return cur != 0; } bool flecs_name_is_id( const char *name) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (!isdigit(name[0])) { return false; } ecs_size_t i, length = ecs_os_strlen(name); for (i = 1; i < length; i ++) { char ch = name[i]; if (!isdigit(ch)) { break; } } return i >= length; } ecs_entity_t flecs_name_to_id( const ecs_world_t *world, const char *name) { int64_t result = atoll(name); ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); if (alive) { return alive; } else { if ((uint32_t)result == (uint64_t)result) { return (ecs_entity_t)result; } else { return 0; } } } static ecs_entity_t flecs_get_builtin( const char *name) { if (name[0] == '.' && name[1] == '\0') { return EcsThis; } else if (name[0] == '*' && name[1] == '\0') { return EcsWildcard; } else if (name[0] == '_' && name[1] == '\0') { return EcsAny; } else if (name[0] == '$' && name[1] == '\0') { return EcsVariable; } return 0; } static bool flecs_is_sep( const char **ptr, const char *sep) { ecs_size_t len = ecs_os_strlen(sep); if (!ecs_os_strncmp(*ptr, sep, len)) { *ptr += len; return true; } else { return false; } } static const char* flecs_path_elem( const char *path, const char *sep, int32_t *len) { const char *ptr; char ch; int32_t template_nesting = 0; int32_t count = 0; for (ptr = path; (ch = *ptr); ptr ++) { if (ch == '<') { template_nesting ++; } else if (ch == '>') { template_nesting --; } ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); if (!template_nesting && flecs_is_sep(&ptr, sep)) { break; } count ++; } if (len) { *len = count; } if (count) { return ptr; } else { return NULL; } error: return NULL; } static bool flecs_is_root_path( const char *path, const char *prefix) { if (prefix) { return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); } else { return false; } } static ecs_entity_t flecs_get_parent_from_path( const ecs_world_t *world, ecs_entity_t parent, const char **path_ptr, const char *prefix, bool new_entity) { bool start_from_root = false; const char *path = *path_ptr; if (flecs_is_root_path(path, prefix)) { path += ecs_os_strlen(prefix); parent = 0; start_from_root = true; } if (!start_from_root && !parent && new_entity) { parent = ecs_get_scope(world); } *path_ptr = path; return parent; } static void flecs_on_set_symbol(ecs_iter_t *it) { EcsIdentifier *n = ecs_field(it, EcsIdentifier, 1); ecs_world_t *world = it->world; int i; for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; flecs_name_index_ensure( &world->symbols, e, n[i].value, n[i].length, n[i].hash); } } void flecs_bootstrap_hierarchy(ecs_world_t *world) { ecs_observer(world, { .entity = ecs_entity(world, {.add = {ecs_childof(EcsFlecsInternals)}}), .filter.terms[0] = { .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), .src.flags = EcsSelf }, .callback = flecs_on_set_symbol, .events = {EcsOnSet}, .yield_existing = true }); } /* Public functions */ void ecs_get_path_w_sep_buf( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (child == EcsWildcard) { ecs_strbuf_appendch(buf, '*'); return; } if (child == EcsAny) { ecs_strbuf_appendch(buf, '_'); return; } if (!sep) { sep = "."; } if (!child || parent != child) { flecs_path_append(world, parent, child, sep, prefix, buf); } else { ecs_strbuf_appendstrn(buf, "", 0); } error: return; } char* ecs_get_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); return ecs_strbuf_get(&buf); } ecs_entity_t ecs_lookup_child( const ecs_world_t *world, ecs_entity_t parent, const char *name) { ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); if (flecs_name_is_id(name)) { ecs_entity_t result = flecs_name_to_id(world, name); if (result && ecs_is_alive(world, result)) { if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { return 0; } return result; } } ecs_id_t pair = ecs_childof(parent); ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); if (index) { return flecs_name_index_find(index, name, 0, 0); } else { return 0; } error: return 0; } ecs_entity_t ecs_lookup( const ecs_world_t *world, const char *name) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = flecs_get_builtin(name); if (e) { return e; } if (flecs_name_is_id(name)) { return flecs_name_to_id(world, name); } e = flecs_name_index_find(&world->aliases, name, 0, 0); if (e) { return e; } return ecs_lookup_child(world, 0, name); error: return 0; } ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, const char *name, bool lookup_as_path, bool recursive) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); if (e) { return e; } if (lookup_as_path) { return ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); } error: return 0; } ecs_entity_t ecs_lookup_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, const char *path, const char *sep, const char *prefix, bool recursive) { if (!path) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_world_t *stage = world; world = ecs_get_world(world); ecs_entity_t e = flecs_get_builtin(path); if (e) { return e; } e = flecs_name_index_find(&world->aliases, path, 0, 0); if (e) { return e; } char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr, *ptr_start; char *elem = buff; int32_t len, size = ECS_NAME_BUFFER_LENGTH; ecs_entity_t cur; bool lookup_path_search = false; const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); const ecs_entity_t *lookup_path_cur = lookup_path; while (lookup_path_cur && *lookup_path_cur) { lookup_path_cur ++; } if (!sep) { sep = "."; } parent = flecs_get_parent_from_path(stage, parent, &path, prefix, true); if (!sep[0]) { return ecs_lookup_child(world, parent, path); } retry: cur = parent; ptr_start = ptr = path; while ((ptr = flecs_path_elem(ptr, sep, &len))) { if (len < size) { ecs_os_memcpy(elem, ptr_start, len); } else { if (size == ECS_NAME_BUFFER_LENGTH) { elem = NULL; } elem = ecs_os_realloc(elem, len + 1); ecs_os_memcpy(elem, ptr_start, len); size = len + 1; } elem[len] = '\0'; ptr_start = ptr; cur = ecs_lookup_child(world, cur, elem); if (!cur) { goto tail; } } tail: if (!cur && recursive) { if (!lookup_path_search) { if (parent) { parent = ecs_get_target(world, parent, EcsChildOf, 0); goto retry; } else { lookup_path_search = true; } } if (lookup_path_search) { if (lookup_path_cur != lookup_path) { lookup_path_cur --; parent = lookup_path_cur[0]; goto retry; } } } if (elem != buff) { ecs_os_free(elem); } return cur; error: return 0; } ecs_entity_t ecs_set_scope( ecs_world_t *world, ecs_entity_t scope) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t cur = stage->scope; stage->scope = scope; return cur; error: return 0; } ecs_entity_t ecs_get_scope( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->scope; error: return 0; } ecs_entity_t* ecs_set_lookup_path( ecs_world_t *world, const ecs_entity_t *lookup_path) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Safe: application owns lookup path */ ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); stage->lookup_path = lookup_path; return cur; error: return NULL; } ecs_entity_t* ecs_get_lookup_path( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); /* Safe: application owns lookup path */ return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); error: return NULL; } const char* ecs_set_name_prefix( ecs_world_t *world, const char *prefix) { ecs_poly_assert(world, ecs_world_t); const char *old_prefix = world->info.name_prefix; world->info.name_prefix = prefix; return old_prefix; } static void flecs_add_path( ecs_world_t *world, bool defer_suspend, ecs_entity_t parent, ecs_entity_t entity, const char *name) { ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (defer_suspend) { real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, parent); } ecs_set_name(world, entity, name); if (defer_suspend) { flecs_resume_readonly(real_world, &srs); flecs_defer_path((ecs_stage_t*)world, parent, entity, name); } } ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!sep) { sep = "."; } if (!path) { if (!entity) { entity = ecs_new_id(world); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, entity); } return entity; } bool root_path = flecs_is_root_path(path, prefix); parent = flecs_get_parent_from_path(world, parent, &path, prefix, !entity); char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr = path; const char *ptr_start = path; char *elem = buff; int32_t len, size = ECS_NAME_BUFFER_LENGTH; /* If we're in deferred/readonly mode suspend it, so that the name index is * immediately updated. Without this, we could create multiple entities for * the same name in a single command queue. */ bool suspend_defer = ecs_poly_is(world, ecs_stage_t) && (ecs_get_stage_count(world) <= 1); ecs_entity_t cur = parent; char *name = NULL; if (sep[0]) { while ((ptr = flecs_path_elem(ptr, sep, &len))) { if (len < size) { ecs_os_memcpy(elem, ptr_start, len); } else { if (size == ECS_NAME_BUFFER_LENGTH) { elem = NULL; } elem = ecs_os_realloc(elem, len + 1); ecs_os_memcpy(elem, ptr_start, len); size = len + 1; } elem[len] = '\0'; ptr_start = ptr; ecs_entity_t e = ecs_lookup_child(world, cur, elem); if (!e) { if (name) { ecs_os_free(name); } name = ecs_os_strdup(elem); /* If this is the last entity in the path, use the provided id */ bool last_elem = false; if (!flecs_path_elem(ptr, sep, NULL)) { e = entity; last_elem = true; } if (!e) { if (last_elem) { ecs_entity_t prev = ecs_set_scope(world, 0); e = ecs_new(world, 0); ecs_set_scope(world, prev); } else { e = ecs_new_id(world); } } if (!cur && last_elem && root_path) { ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); } flecs_add_path(world, suspend_defer, cur, e, name); } cur = e; } if (entity && (cur != entity)) { ecs_throw(ECS_ALREADY_DEFINED, name); } if (name) { ecs_os_free(name); } if (elem != buff) { ecs_os_free(elem); } } else { flecs_add_path(world, suspend_defer, parent, entity, path); } return cur; error: return 0; } ecs_entity_t ecs_new_from_path_w_sep( ecs_world_t *world, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } /** * @file filter.c * @brief Uncached query implementation. * * Uncached queries (filters) are stateless objects that do not cache their * results. This file contains the creation and validation of uncached queries * and code for query iteration. * * There file contains the implementation for term queries and filters. Term * queries are uncached queries that only apply to a single term. Filters are * uncached queries that support multiple terms. Filters are built on top of * term queries: before iteration a filter will first find a "pivot" term (the * term with the smallest number of elements), and create a term iterator for * it. The output of that term iterator is then evaluated against the rest of * the terms of the filter. * * Cached queries and observers are built using filters. */ #include ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; /* Helper type for passing around context required for error messages */ typedef struct { const ecs_world_t *world; ecs_filter_t *filter; ecs_term_t *term; int32_t term_index; } ecs_filter_finalize_ctx_t; static char* flecs_filter_str( const ecs_world_t *world, const ecs_filter_t *filter, const ecs_filter_finalize_ctx_t *ctx, int32_t *term_start_out); static void flecs_filter_error( const ecs_filter_finalize_ctx_t *ctx, const char *fmt, ...) { va_list args; va_start(args, fmt); int32_t term_start = 0; char *expr = NULL; if (ctx->filter) { expr = flecs_filter_str(ctx->world, ctx->filter, ctx, &term_start); } else { expr = ecs_term_str(ctx->world, ctx->term); } const char *name = NULL; if (ctx->filter && ctx->filter->entity) { name = ecs_get_name(ctx->filter->world, ctx->filter->entity); } ecs_parser_errorv(name, expr, term_start, fmt, args); ecs_os_free(expr); va_end(args); } static int flecs_term_id_finalize_flags( ecs_term_id_t *term_id, ecs_filter_finalize_ctx_t *ctx) { if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); return -1; } if (!(term_id->flags & (EcsIsEntity|EcsIsVariable|EcsIsName))) { if (term_id->id || term_id->name) { if (term_id->id == EcsThis || term_id->id == EcsWildcard || term_id->id == EcsAny || term_id->id == EcsVariable) { /* Builtin variable ids default to variable */ term_id->flags |= EcsIsVariable; } else { term_id->flags |= EcsIsEntity; } } } if (term_id->flags & EcsParent) { term_id->flags |= EcsUp; term_id->trav = EcsChildOf; } if ((term_id->flags & EcsCascade) && !(term_id->flags & (EcsUp|EcsDown))) { term_id->flags |= EcsUp; } if ((term_id->flags & (EcsUp|EcsDown)) && !term_id->trav) { term_id->trav = EcsIsA; } if (term_id->trav && !(term_id->flags & EcsTraverseFlags)) { term_id->flags |= EcsUp; } return 0; } static int flecs_term_id_lookup( const ecs_world_t *world, ecs_entity_t scope, ecs_term_id_t *term_id, bool free_name, ecs_filter_finalize_ctx_t *ctx) { const char *name = term_id->name; if (!name) { return 0; } if (term_id->flags & EcsIsVariable) { if (!ecs_os_strcmp(name, "This") || !ecs_os_strcmp(name, "this")) { term_id->id = EcsThis; if (free_name) { /* Safe, if free_name is true the filter owns the name */ ecs_os_free(ECS_CONST_CAST(char*, term_id->name)); } term_id->name = NULL; } return 0; } else if (term_id->flags & EcsIsName) { return 0; } ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); if (ecs_identifier_is_0(name)) { if (term_id->id) { flecs_filter_error(ctx, "name '0' does not match entity id"); return -1; } return 0; } ecs_entity_t e = ecs_lookup_symbol(world, name, true, true); if (scope && !e) { e = ecs_lookup_child(world, scope, name); } if (!e) { if (ctx->filter && (ctx->filter->flags & EcsFilterUnresolvedByName)) { term_id->flags |= EcsIsName; term_id->flags &= ~EcsIsEntity; } else { flecs_filter_error(ctx, "unresolved identifier '%s'", name); return -1; } } if (term_id->id && term_id->id != e) { char *e_str = ecs_get_fullpath(world, term_id->id); flecs_filter_error(ctx, "name '%s' does not match term.id '%s'", name, e_str); ecs_os_free(e_str); return -1; } term_id->id = e; if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || !ecs_os_strcmp(name, "$")) { term_id->flags &= ~EcsIsEntity; term_id->flags |= EcsIsVariable; } /* Check if looked up id is alive (relevant for numerical ids) */ if (!(term_id->flags & EcsIsName)) { if (!ecs_is_alive(world, term_id->id)) { flecs_filter_error(ctx, "identifier '%s' is not alive", term_id->name); return -1; } if (free_name) { /* Safe, if free_name is true, the filter owns the name */ ecs_os_free(ECS_CONST_CAST(char*, name)); } term_id->name = NULL; } return 0; } static int flecs_term_ids_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_filter_finalize_ctx_t *ctx) { ecs_term_id_t *src = &term->src; ecs_term_id_t *first = &term->first; ecs_term_id_t *second = &term->second; /* Include inherited components (like from prefabs) by default for src */ if (!(src->flags & EcsTraverseFlags)) { src->flags |= EcsSelf | EcsUp; } /* Include subsets for component by default, to support inheritance */ if (!(first->flags & EcsTraverseFlags)) { first->flags |= EcsSelf; if (first->id && first->flags & EcsIsEntity) { if (flecs_id_record_get(world, ecs_pair(EcsIsA, first->id))) { first->flags |= EcsDown; } } } /* Traverse Self by default for pair target */ if (!(second->flags & EcsTraverseFlags)) { second->flags |= EcsSelf; } /* Source defaults to This */ if ((src->id == 0) && (src->name == NULL) && !(src->flags & EcsIsEntity)) { src->id = EcsThis; src->flags |= EcsIsVariable; } /* Initialize term identifier flags */ if (flecs_term_id_finalize_flags(src, ctx)) { return -1; } if (flecs_term_id_finalize_flags(first, ctx)) { return -1; } if (flecs_term_id_finalize_flags(second, ctx)) { return -1; } /* Lookup term identifiers by name */ if (flecs_term_id_lookup(world, 0, src, term->move, ctx)) { return -1; } if (flecs_term_id_lookup(world, 0, first, term->move, ctx)) { return -1; } ecs_entity_t first_id = 0; ecs_entity_t oneof = 0; if (first->flags & EcsIsEntity) { first_id = first->id; /* If first element of pair has OneOf property, lookup second element of * pair in the value of the OneOf property */ oneof = flecs_get_oneof(world, first_id); } if (flecs_term_id_lookup(world, oneof, &term->second, term->move, ctx)) { return -1; } /* If source is 0, reset traversal flags */ if (src->id == 0 && src->flags & EcsIsEntity) { src->flags &= ~EcsTraverseFlags; src->trav = 0; } /* If second is 0, reset traversal flags */ if (second->id == 0 && second->flags & EcsIsEntity) { second->flags &= ~EcsTraverseFlags; second->trav = 0; } /* If source is wildcard, term won't return any data */ if ((src->flags & EcsIsVariable) && ecs_id_is_wildcard(src->id)) { term->inout |= EcsInOutNone; } return 0; } static ecs_entity_t flecs_term_id_get_entity( const ecs_term_id_t *term_id) { if (term_id->flags & EcsIsEntity) { return term_id->id; /* Id is known */ } else if (term_id->flags & EcsIsVariable) { /* Return wildcard for variables, as they aren't known yet */ if (term_id->id != EcsAny) { /* Any variable should not use wildcard, as this would return all * ids matching a wildcard, whereas Any returns the first match */ return EcsWildcard; } else { return EcsAny; } } else { return 0; /* Term id is uninitialized */ } } static int flecs_term_populate_id( ecs_term_t *term) { ecs_entity_t first = flecs_term_id_get_entity(&term->first); ecs_entity_t second = flecs_term_id_get_entity(&term->second); ecs_id_t role = term->id_flags; if (first & ECS_ID_FLAGS_MASK) { return -1; } if (second & ECS_ID_FLAGS_MASK) { return -1; } if ((second || term->second.flags == EcsIsEntity)) { role = term->id_flags |= ECS_PAIR; } if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { term->id = first | role; } else { term->id = ecs_pair(first, second) | role; } return 0; } static int flecs_term_populate_from_id( const ecs_world_t *world, ecs_term_t *term, ecs_filter_finalize_ctx_t *ctx) { ecs_entity_t first = 0; ecs_entity_t second = 0; ecs_id_t role = term->id & ECS_ID_FLAGS_MASK; if (!role && term->id_flags) { role = term->id_flags; term->id |= role; } if (term->id_flags && term->id_flags != role) { flecs_filter_error(ctx, "mismatch between term.id & term.id_flags"); return -1; } term->id_flags = role; if (ECS_HAS_ID_FLAG(term->id, PAIR)) { first = ECS_PAIR_FIRST(term->id); second = ECS_PAIR_SECOND(term->id); if (!first) { flecs_filter_error(ctx, "missing first element in term.id"); return -1; } if (!second) { if (first != EcsChildOf) { flecs_filter_error(ctx, "missing second element in term.id"); return -1; } else { /* (ChildOf, 0) is allowed so filter can be used to efficiently * query for root entities */ } } } else { first = term->id & ECS_COMPONENT_MASK; if (!first) { flecs_filter_error(ctx, "missing first element in term.id"); return -1; } } ecs_entity_t term_first = flecs_term_id_get_entity(&term->first); if (term_first) { if ((uint32_t)term_first != (uint32_t)first) { flecs_filter_error(ctx, "mismatch between term.id and term.first"); return -1; } } else { if (!(term->first.id = ecs_get_alive(world, first))) { term->first.id = first; } } ecs_entity_t term_second = flecs_term_id_get_entity(&term->second); if (term_second) { if ((uint32_t)term_second != second) { flecs_filter_error(ctx, "mismatch between term.id and term.second"); return -1; } } else if (second) { if (!(term->second.id = ecs_get_alive(world, second))) { term->second.id = second; } } return 0; } static int flecs_term_verify_eq_pred( const ecs_term_t *term, ecs_filter_finalize_ctx_t *ctx) { ecs_entity_t first_id = term->first.id; const ecs_term_id_t *second = &term->second; const ecs_term_id_t *src = &term->src; if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { flecs_filter_error(ctx, "invalid operator combination"); goto error; } if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { flecs_filter_error(ctx, "both sides of operator cannot be a name"); goto error; } if ((src->flags & EcsIsEntity) && (second->flags & EcsIsEntity)) { flecs_filter_error(ctx, "both sides of operator cannot be an entity"); goto error; } if (!(src->flags & EcsIsVariable)) { flecs_filter_error(ctx, "left-hand of operator must be a variable"); goto error; } if (first_id == EcsPredMatch && !(second->flags & EcsIsName)) { flecs_filter_error(ctx, "right-hand of match operator must be a string"); goto error; } if ((src->flags & EcsIsVariable) && (second->flags & EcsIsVariable)) { if (src->id && src->id == second->id) { flecs_filter_error(ctx, "both sides of operator are equal"); goto error; } if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { flecs_filter_error(ctx, "both sides of operator are equal"); goto error; } } return 0; error: return -1; } static int flecs_term_verify( const ecs_world_t *world, const ecs_term_t *term, ecs_filter_finalize_ctx_t *ctx) { const ecs_term_id_t *first = &term->first; const ecs_term_id_t *second = &term->second; const ecs_term_id_t *src = &term->src; ecs_entity_t first_id = 0, second_id = 0; ecs_id_t role = term->id_flags; ecs_id_t id = term->id; if ((src->flags & EcsIsName) && (second->flags & EcsIsName)) { flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); return -1; } if (first->flags & EcsIsEntity) { first_id = first->id; } if (second->flags & EcsIsEntity) { second_id = second->id; } if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { return flecs_term_verify_eq_pred(term, ctx); } if (role != (id & ECS_ID_FLAGS_MASK)) { flecs_filter_error(ctx, "mismatch between term.id_flags & term.id"); return -1; } if (ecs_term_id_is_set(second) && !ECS_HAS_ID_FLAG(role, PAIR)) { flecs_filter_error(ctx, "expected PAIR flag for term with pair"); return -1; } else if (!ecs_term_id_is_set(second) && ECS_HAS_ID_FLAG(role, PAIR)) { if (first_id != EcsChildOf) { flecs_filter_error(ctx, "unexpected PAIR flag for term without pair"); return -1; } else { /* Exception is made for ChildOf so we can use (ChildOf, 0) to match * all entities in the root */ } } if (!ecs_term_id_is_set(src)) { flecs_filter_error(ctx, "term.src is not initialized"); return -1; } if (!ecs_term_id_is_set(first)) { flecs_filter_error(ctx, "term.first is not initialized"); return -1; } if (ECS_HAS_ID_FLAG(role, PAIR)) { if (!ECS_PAIR_FIRST(id)) { flecs_filter_error(ctx, "invalid 0 for first element in pair id"); return -1; } if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { flecs_filter_error(ctx, "invalid 0 for second element in pair id"); return -1; } if ((first->flags & EcsIsEntity) && (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) { flecs_filter_error(ctx, "mismatch between term.id and term.first"); return -1; } if ((first->flags & EcsIsVariable) && !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) { char *id_str = ecs_id_str(world, id); flecs_filter_error(ctx, "expected wildcard for variable term.first (got %s)", id_str); ecs_os_free(id_str); return -1; } if ((second->flags & EcsIsEntity) && (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) { flecs_filter_error(ctx, "mismatch between term.id and term.second"); return -1; } if ((second->flags & EcsIsVariable) && !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) { char *id_str = ecs_id_str(world, id); flecs_filter_error(ctx, "expected wildcard for variable term.second (got %s)", id_str); ecs_os_free(id_str); return -1; } } else { ecs_entity_t component = id & ECS_COMPONENT_MASK; if (!component) { flecs_filter_error(ctx, "missing component id"); return -1; } if ((first->flags & EcsIsEntity) && (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) { flecs_filter_error(ctx, "mismatch between term.id and term.first"); return -1; } if ((first->flags & EcsIsVariable) && !ecs_id_is_wildcard(component)) { char *id_str = ecs_id_str(world, id); flecs_filter_error(ctx, "expected wildcard for variable term.first (got %s)", id_str); ecs_os_free(id_str); return -1; } } if (first_id) { if (ecs_term_id_is_set(second)) { ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; if ((src->flags & mask) == (second->flags & mask)) { bool is_same = false; if (src->flags & EcsIsEntity) { is_same = src->id == second->id; } else if (src->name && second->name) { is_same = !ecs_os_strcmp(src->name, second->name); } if (is_same && ecs_has_id(world, first_id, EcsAcyclic) && !(term->flags & EcsTermReflexive)) { char *pred_str = ecs_get_fullpath(world, term->first.id); flecs_filter_error(ctx, "term with acyclic relationship" " '%s' cannot have same subject and object", pred_str); ecs_os_free(pred_str); return -1; } } } if (second_id && !ecs_id_is_wildcard(second_id)) { ecs_entity_t oneof = flecs_get_oneof(world, first_id); if (oneof) { if (!ecs_has_pair(world, second_id, EcsChildOf, oneof)) { char *second_str = ecs_get_fullpath(world, second_id); char *oneof_str = ecs_get_fullpath(world, oneof); char *id_str = ecs_id_str(world, term->id); flecs_filter_error(ctx, "invalid target '%s' for %s: must be child of '%s'", second_str, id_str, oneof_str); ecs_os_free(second_str); ecs_os_free(oneof_str); ecs_os_free(id_str); return -1; } } } } if (term->src.trav) { if (!ecs_has_id(world, term->src.trav, EcsTraversable)) { char *r_str = ecs_get_fullpath(world, term->src.trav); flecs_filter_error(ctx, "cannot traverse non-traversable relationship '%s'", r_str); ecs_os_free(r_str); return -1; } } return 0; } static int flecs_term_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_filter_finalize_ctx_t *ctx) { ctx->term = term; ecs_term_id_t *src = &term->src; ecs_term_id_t *first = &term->first; ecs_term_id_t *second = &term->second; ecs_flags32_t first_flags = first->flags; ecs_flags32_t src_flags = src->flags; ecs_flags32_t second_flags = second->flags; if (term->id) { if (flecs_term_populate_from_id(world, term, ctx)) { return -1; } } if (flecs_term_ids_finalize(world, term, ctx)) { return -1; } if ((first->flags & EcsIsVariable) && (term->first.id == EcsAny)) { term->flags |= EcsTermMatchAny; } if ((second->flags & EcsIsVariable) && (term->second.id == EcsAny)) { term->flags |= EcsTermMatchAny; } if ((src->flags & EcsIsVariable) && (term->src.id == EcsAny)) { term->flags |= EcsTermMatchAnySrc; } /* If EcsVariable is used by itself, assign to predicate (singleton) */ if ((src->id == EcsVariable) && (src->flags & EcsIsVariable)) { src->id = first->id; src->flags &= ~(EcsIsVariable | EcsIsEntity); src->flags |= first->flags & (EcsIsVariable | EcsIsEntity); } if ((second->id == EcsVariable) && (second->flags & EcsIsVariable)) { second->id = first->id; second->flags &= ~(EcsIsVariable | EcsIsEntity); second->flags |= first->flags & (EcsIsVariable | EcsIsEntity); } ecs_flags32_t mask = EcsIsEntity | EcsIsVariable; if ((src->flags & mask) == (second->flags & mask)) { bool is_same = false; if (src->flags & EcsIsEntity) { is_same = src->id == second->id; } else if (src->name && second->name) { is_same = !ecs_os_strcmp(src->name, second->name); } if (is_same) { term->flags |= EcsTermSrcSecondEq; } } if ((src->flags & mask) == (first->flags & mask)) { bool is_same = false; if (src->flags & EcsIsEntity) { is_same = src->id == first->id; } else if (src->name && first->name) { is_same = !ecs_os_strcmp(src->name, first->name); } if (is_same) { term->flags |= EcsTermSrcFirstEq; } } if (!term->id) { if (flecs_term_populate_id(term)) { return -1; } } /* If term queries for !(ChildOf, _), translate it to the builtin * (ChildOf, 0) index which is a cheaper way to find root entities */ if (term->oper == EcsNot && term->id == ecs_pair(EcsChildOf, EcsAny)) { term->oper = EcsAnd; term->id = ecs_pair(EcsChildOf, 0); term->second.id = 0; term->second.flags |= EcsIsEntity; term->second.flags &= ~EcsIsVariable; } ecs_entity_t first_id = 0; if (term->first.flags & EcsIsEntity) { first_id = term->first.id; } term->idr = flecs_query_id_record_get(world, term->id); ecs_flags32_t id_flags = term->idr ? term->idr->flags : 0; if (first_id) { ecs_entity_t first_trav = first->trav; /* If component is inherited from, set correct traversal flags */ ecs_flags32_t first_trav_flags = first_flags & EcsTraverseFlags; if (!first_trav && first_trav_flags != EcsSelf) { /* Inheritance uses IsA by default, but can use any relationship */ first_trav = EcsIsA; } ecs_record_t *trav_record = NULL; ecs_table_t *trav_table = NULL; if (first_trav) { trav_record = flecs_entities_get(world, first_trav); trav_table = trav_record ? trav_record->table : NULL; if (first_trav != EcsIsA) { if (!trav_table || !ecs_table_has_id(world, trav_table, EcsTraversable)) { flecs_filter_error(ctx, "first.trav is not traversable"); return -1; } } } /* Only enable inheritance for ids which are inherited from at the time * of filter creation. To force component inheritance to be evaluated, * an application can explicitly set traversal flags. */ if ((first_trav_flags & EcsDown) || flecs_id_record_get(world, ecs_pair(first_trav, first->id))) { if (first_trav_flags == EcsSelf) { flecs_filter_error(ctx, "first.trav specified with self"); return -1; } if (!first_trav_flags || (first_trav_flags & EcsDown)) { term->flags |= EcsTermIdInherited; first->trav = first_trav; if (!first_trav_flags) { first->flags &= ~EcsTraverseFlags; first->flags |= EcsDown; ecs_assert(trav_table != NULL, ECS_INTERNAL_ERROR, NULL); if ((first_trav == EcsIsA) || ecs_table_has_id( world, trav_table, EcsReflexive)) { first->flags |= EcsSelf; } } } } /* Don't traverse ids that cannot be inherited */ if ((id_flags & EcsIdDontInherit) && (src->trav == EcsIsA)) { if (src_flags & (EcsUp | EcsDown)) { flecs_filter_error(ctx, "traversing not allowed for id that can't be inherited"); return -1; } src->flags &= ~(EcsUp | EcsDown); src->trav = 0; } /* If component id is final, don't attempt component inheritance */ ecs_record_t *first_record = flecs_entities_get(world, first_id); ecs_table_t *first_table = first_record ? first_record->table : NULL; if (first_table) { if (ecs_table_has_id(world, first_table, EcsFinal)) { if (first_flags & EcsDown) { flecs_filter_error(ctx, "final id cannot be traversed down"); return -1; } } /* Add traversal flags for transitive relationships */ if (!(second_flags & EcsTraverseFlags) && ecs_term_id_is_set(second)) { if (!((src->flags & EcsIsVariable) && (src->id == EcsAny))) { if (!((second->flags & EcsIsVariable) && (second->id == EcsAny))) { if (ecs_table_has_id(world, first_table, EcsTransitive)) { second->flags |= EcsSelf|EcsUp|EcsTraverseAll; second->trav = first_id; term->flags |= EcsTermTransitive; } } } } if (ecs_table_has_id(world, first_table, EcsReflexive)) { term->flags |= EcsTermReflexive; } } } if (first->id == EcsVariable) { flecs_filter_error(ctx, "invalid $ for term.first"); return -1; } if (term->id_flags & ECS_AND) { term->oper = EcsAndFrom; term->id &= ECS_COMPONENT_MASK; term->id_flags = 0; } if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { flecs_filter_error(ctx, "invalid inout value for AndFrom/OrFrom/NotFrom term"); return -1; } } if (flecs_term_verify(world, term, ctx)) { return -1; } return 0; } ecs_id_t flecs_to_public_id( ecs_id_t id) { if (ECS_PAIR_FIRST(id) == EcsUnion) { return ecs_pair(ECS_PAIR_SECOND(id), EcsWildcard); } else { return id; } } ecs_id_t flecs_from_public_id( ecs_world_t *world, ecs_id_t id) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_id_record_t *idr = flecs_id_record_ensure(world, ecs_pair(first, EcsWildcard)); if (idr->flags & EcsIdUnion) { return ecs_pair(EcsUnion, first); } } return id; } bool ecs_identifier_is_0( const char *id) { return id[0] == '0' && !id[1]; } bool ecs_id_match( ecs_id_t id, ecs_id_t pattern) { if (id == pattern) { return true; } if (ECS_HAS_ID_FLAG(pattern, PAIR)) { if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } ecs_entity_t id_rel = ECS_PAIR_FIRST(id); ecs_entity_t id_obj = ECS_PAIR_SECOND(id); ecs_entity_t pattern_rel = ECS_PAIR_FIRST(pattern); ecs_entity_t pattern_obj = ECS_PAIR_SECOND(pattern); ecs_check(id_rel != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(id_obj != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(pattern_rel != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(pattern_obj != 0, ECS_INVALID_PARAMETER, NULL); if (pattern_rel == EcsWildcard) { if (pattern_obj == EcsWildcard || pattern_obj == id_obj) { return true; } } else if (pattern_rel == EcsFlag) { /* Used for internals, helps to keep track of which ids are used in * pairs that have additional flags (like OVERRIDE and TOGGLE) */ if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { if (ECS_PAIR_FIRST(id) == pattern_obj) { return true; } if (ECS_PAIR_SECOND(id) == pattern_obj) { return true; } } } else if (pattern_obj == EcsWildcard) { if (pattern_rel == id_rel) { return true; } } } else { if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { return false; } if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { return true; } } error: return false; } bool ecs_id_is_pair( ecs_id_t id) { return ECS_HAS_ID_FLAG(id, PAIR); } bool ecs_id_is_wildcard( ecs_id_t id) { if ((id == EcsWildcard) || (id == EcsAny)) { return true; } bool is_pair = ECS_IS_PAIR(id); if (!is_pair) { return false; } ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_entity_t second = ECS_PAIR_SECOND(id); return (first == EcsWildcard) || (second == EcsWildcard) || (first == EcsAny) || (second == EcsAny); } bool ecs_id_is_valid( const ecs_world_t *world, ecs_id_t id) { if (!id) { return false; } if (ecs_id_is_wildcard(id)) { return false; } if (ECS_HAS_ID_FLAG(id, PAIR)) { if (!ECS_PAIR_FIRST(id)) { return false; } if (!ECS_PAIR_SECOND(id)) { return false; } } else if (id & ECS_ID_FLAGS_MASK) { if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { return false; } } return true; } ecs_flags32_t ecs_id_get_flags( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { return idr->flags; } else { return 0; } } bool ecs_term_id_is_set( const ecs_term_id_t *id) { return id->id != 0 || id->name != NULL || id->flags & EcsIsEntity; } bool ecs_term_is_initialized( const ecs_term_t *term) { return term->id != 0 || ecs_term_id_is_set(&term->first); } bool ecs_term_match_this( const ecs_term_t *term) { return (term->src.flags & EcsIsVariable) && (term->src.id == EcsThis); } bool ecs_term_match_0( const ecs_term_t *term) { return (term->src.id == 0) && (term->src.flags & EcsIsEntity); } int ecs_term_finalize( const ecs_world_t *world, ecs_term_t *term) { ecs_filter_finalize_ctx_t ctx = {0}; ctx.world = world; ctx.term = term; return flecs_term_finalize(world, term, &ctx); } ecs_term_t ecs_term_copy( const ecs_term_t *src) { ecs_term_t dst = *src; dst.name = ecs_os_strdup(src->name); dst.first.name = ecs_os_strdup(src->first.name); dst.src.name = ecs_os_strdup(src->src.name); dst.second.name = ecs_os_strdup(src->second.name); return dst; } ecs_term_t ecs_term_move( ecs_term_t *src) { if (src->move) { ecs_term_t dst = *src; src->name = NULL; src->first.name = NULL; src->src.name = NULL; src->second.name = NULL; dst.move = false; return dst; } else { ecs_term_t dst = ecs_term_copy(src); dst.move = false; return dst; } } void ecs_term_fini( ecs_term_t *term) { /* Safe, values are owned by term */ ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); ecs_os_free(ECS_CONST_CAST(char*, term->src.name)); ecs_os_free(ECS_CONST_CAST(char*, term->second.name)); ecs_os_free(term->name); term->first.name = NULL; term->src.name = NULL; term->second.name = NULL; term->name = NULL; } static ecs_term_t* flecs_filter_or_other_type( ecs_filter_t *f, int32_t t) { ecs_term_t *term = &f->terms[t]; ecs_term_t *first = NULL; while (t--) { if (f->terms[t].oper != EcsOr) { break; } first = &f->terms[t]; } if (first) { ecs_world_t *world = f->world; const ecs_type_info_t *first_type; if (first->idr) { first_type = first->idr->type_info; } else { first_type = ecs_get_type_info(world, first->id); } const ecs_type_info_t *term_type; if (term->idr) { term_type = term->idr->type_info; } else { term_type = ecs_get_type_info(world, term->id); } if (first_type == term_type) { return NULL; } return first; } else { return NULL; } } int ecs_filter_finalize( const ecs_world_t *world, ecs_filter_t *f) { int32_t i, term_count = f->term_count, field_count = 0; ecs_term_t *terms = f->terms; int32_t filter_terms = 0, scope_nesting = 0; bool cond_set = false; ecs_filter_finalize_ctx_t ctx = {0}; ctx.world = world; ctx.filter = f; f->flags |= EcsFilterMatchOnlyThis; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ctx.term_index = i; if (flecs_term_finalize(world, term, &ctx)) { return -1; } if (i && term[-1].oper == EcsOr) { if (term[-1].src.id != term->src.id) { flecs_filter_error(&ctx, "mismatching src.id for OR terms"); return -1; } if (term->oper != EcsOr && term->oper != EcsAnd) { flecs_filter_error(&ctx, "term after OR operator must use AND operator"); return -1; } } else { field_count ++; } if (term->oper == EcsOr || (i && term[-1].oper == EcsOr)) { ecs_term_t *first = flecs_filter_or_other_type(f, i); if (first) { filter_terms ++; if (first == &term[-1]) { filter_terms ++; } } } term->field_index = field_count - 1; if (ecs_term_match_this(term)) { ECS_BIT_SET(f->flags, EcsFilterMatchThis); } else { ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlyThis); } if (term->id == EcsPrefab) { ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); } if (term->id == EcsDisabled && (term->src.flags & EcsSelf)) { ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); } if (ECS_BIT_IS_SET(f->flags, EcsFilterNoData)) { term->inout = EcsInOutNone; } if (term->oper == EcsNot && term->inout == EcsInOutDefault) { term->inout = EcsInOutNone; } if (term->inout == EcsInOutNone) { filter_terms ++; } else if (term->idr) { if (!term->idr->type_info && !(term->idr->flags & EcsIdUnion)) { filter_terms ++; } } else if (ecs_id_is_tag(world, term->id)) { if (!ecs_id_is_union(world, term->id)) { /* Union ids aren't filters because they return their target * as component value with type ecs_entity_t */ filter_terms ++; } } if ((term->id == EcsWildcard) || (term->id == ecs_pair(EcsWildcard, EcsWildcard))) { /* If term type is unknown beforehand, default the inout type to * none. This prevents accidentally requesting lots of components, * which can put stress on serializer code. */ if (term->inout == EcsInOutDefault) { term->inout = EcsInOutNone; } } if (term->oper != EcsNot || !ecs_term_match_this(term)) { ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); } if (term->idr) { if (ecs_os_has_threading()) { ecs_os_ainc(&term->idr->keep_alive); } else { term->idr->keep_alive ++; } } if (term->oper == EcsOptional || term->oper == EcsNot) { cond_set = true; } if (term->first.id == EcsPredEq || term->first.id == EcsPredMatch || term->first.id == EcsPredLookup) { f->flags |= EcsFilterHasPred; } if (term->first.id == EcsScopeOpen) { f->flags |= EcsFilterHasScopes; scope_nesting ++; } if (term->first.id == EcsScopeClose) { if (i && terms[i - 1].first.id == EcsScopeOpen) { flecs_filter_error(&ctx, "invalid empty scope"); return -1; } f->flags |= EcsFilterHasScopes; scope_nesting --; } if (scope_nesting < 0) { flecs_filter_error(&ctx, "'}' without matching '{'"); } } if (scope_nesting != 0) { flecs_filter_error(&ctx, "missing '}'"); return -1; } if (term_count && (terms[term_count - 1].oper == EcsOr)) { flecs_filter_error(&ctx, "last term of filter can't have OR operator"); return -1; } f->field_count = field_count; if (field_count) { for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ecs_id_record_t *idr = term->idr; int32_t field = term->field_index; if (term->oper == EcsOr || (i && (term[-1].oper == EcsOr))) { if (flecs_filter_or_other_type(f, i)) { f->sizes[field] = 0; continue; } } if (idr) { if (!ECS_IS_PAIR(idr->id) || ECS_PAIR_FIRST(idr->id) != EcsWildcard) { if (idr->flags & EcsIdUnion) { f->sizes[field] = ECS_SIZEOF(ecs_entity_t); } else if (idr->type_info) { f->sizes[field] = idr->type_info->size; } } } else { bool is_union = false; if (ECS_IS_PAIR(term->id)) { ecs_entity_t first = ecs_pair_first(world, term->id); if (ecs_has_id(world, first, EcsUnion)) { is_union = true; } } if (is_union) { f->sizes[field] = ECS_SIZEOF(ecs_entity_t); } else { const ecs_type_info_t *ti = ecs_get_type_info( world, term->id); if (ti) { f->sizes[field] = ti->size; } } } } } else { f->sizes = NULL; } if (filter_terms >= term_count) { ECS_BIT_SET(f->flags, EcsFilterNoData); } ECS_BIT_COND(f->flags, EcsFilterHasCondSet, cond_set); return 0; } /* Implementation for iterable mixin */ static void flecs_filter_iter_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, ecs_term_t *filter) { ecs_poly_assert(poly, ecs_filter_t); if (filter) { iter[1] = ecs_filter_iter(world, ECS_CONST_CAST(ecs_filter_t*, poly)); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_filter_iter(world, ECS_CONST_CAST(ecs_filter_t*, poly)); } } /* Implementation for dtor mixin */ static void flecs_filter_fini( ecs_filter_t *filter) { if (filter->terms) { int i, count = filter->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &filter->terms[i]; if (term->idr) { if (!(filter->world->flags & EcsWorldQuit)) { if (ecs_os_has_threading()) { ecs_os_adec(&term->idr->keep_alive); } else { term->idr->keep_alive --; } } } ecs_term_fini(&filter->terms[i]); } if (filter->terms_owned) { /* Memory allocated for both terms & sizes */ ecs_os_free(filter->terms); } else { ecs_os_free(filter->sizes); } } filter->terms = NULL; if (filter->owned) { ecs_os_free(filter); } } void ecs_filter_fini( ecs_filter_t *filter) { if (filter->owned && filter->entity) { /* If filter is associated with entity, use poly dtor path */ ecs_delete(filter->world, filter->entity); } else { flecs_filter_fini(filter); } } ecs_filter_t* ecs_filter_init( ecs_world_t *world, const ecs_filter_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_filter_t *f = desc->storage; int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; const ecs_term_t *terms = desc->terms_buffer; ecs_term_t *storage_terms = NULL, *expr_terms = NULL; if (f) { ecs_check(f->hdr.magic == ecs_filter_t_magic, ECS_INVALID_PARAMETER, NULL); storage_count = f->term_count; storage_terms = f->terms; ecs_poly_init(f, ecs_filter_t); } else { f = ecs_poly_new(ecs_filter_t); f->owned = true; } if (!storage_terms) { f->terms_owned = true; } ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); ECS_BIT_SET(f->flags, EcsFilterMatchAnything); f->flags |= desc->flags; f->world = world; /* If terms_buffer was not set, count number of initialized terms in * static desc::terms array */ if (!terms) { ecs_check(term_count == 0, ECS_INVALID_PARAMETER, NULL); terms = desc->terms; for (i = 0; i < FLECS_TERM_DESC_MAX; i ++) { if (!ecs_term_is_initialized(&terms[i])) { break; } term_count ++; } } else { ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); } /* If expr is set, parse query expression */ const char *expr = desc->expr; ecs_entity_t entity = desc->entity; if (expr) { #ifdef FLECS_PARSER const char *name = NULL; const char *ptr = desc->expr; ecs_term_t term = {0}; int32_t expr_size = 0; if (entity) { name = ecs_get_name(world, entity); } while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (expr_count == expr_size) { expr_size = expr_size ? expr_size * 2 : 8; expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); } expr_terms[expr_count ++] = term; if (ptr[0] == '\n') { break; } } if (!ptr) { /* Set terms in filter object to make sur they get cleaned up */ f->terms = expr_terms; f->term_count = expr_count; f->terms_owned = true; goto error; } #else (void)expr; ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } /* If storage is provided, make sure it's large enough */ ecs_check(!storage_terms || storage_count >= (term_count + expr_count), ECS_INVALID_PARAMETER, NULL); if (term_count || expr_count) { /* Allocate storage for terms and sizes array */ if (!storage_terms) { ecs_assert(f->terms_owned == true, ECS_INTERNAL_ERROR, NULL); f->term_count = term_count + expr_count; ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * f->term_count; ecs_size_t sizes_size = ECS_SIZEOF(int32_t) * f->term_count; f->terms = ecs_os_calloc(terms_size + sizes_size); f->sizes = ECS_OFFSET(f->terms, terms_size); } else { f->terms = storage_terms; f->term_count = storage_count; f->sizes = ecs_os_calloc_n(ecs_size_t, term_count); } /* Copy terms to filter storage */ for (i = 0; i < term_count; i ++) { f->terms[i] = ecs_term_copy(&terms[i]); /* Allow freeing resources from expr parser during finalization */ f->terms[i].move = true; } /* Move expr terms to filter storage */ for (i = 0; i < expr_count; i ++) { f->terms[i + term_count] = ecs_term_move(&expr_terms[i]); /* Allow freeing resources from expr parser during finalization */ f->terms[i + term_count].move = true; } ecs_os_free(expr_terms); } /* Ensure all fields are consistent and properly filled out */ if (ecs_filter_finalize(world, f)) { goto error; } /* Any allocated resources remaining in terms are now owned by filter */ for (i = 0; i < f->term_count; i ++) { f->terms[i].move = false; } f->variable_names[0] = NULL; f->iterable.init = flecs_filter_iter_init; f->dtor = (ecs_poly_dtor_t)flecs_filter_fini; f->entity = entity; if (entity && f->owned) { EcsPoly *poly = ecs_poly_bind(world, entity, ecs_filter_t); poly->poly = f; ecs_poly_modified(world, entity, ecs_filter_t); } return f; error: ecs_filter_fini(f); return NULL; } void ecs_filter_copy( ecs_filter_t *dst, const ecs_filter_t *src) { if (src == dst) { return; } if (src) { *dst = *src; int32_t i, term_count = src->term_count; ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * term_count; ecs_size_t sizes_size = ECS_SIZEOF(int32_t) * term_count; dst->terms = ecs_os_malloc(terms_size + sizes_size); dst->sizes = ECS_OFFSET(dst->terms, terms_size); dst->terms_owned = true; ecs_os_memcpy_n(dst->sizes, src->sizes, int32_t, term_count); for (i = 0; i < term_count; i ++) { dst->terms[i] = ecs_term_copy(&src->terms[i]); } } else { ecs_os_memset_t(dst, 0, ecs_filter_t); } } void ecs_filter_move( ecs_filter_t *dst, ecs_filter_t *src) { if (src == dst) { return; } if (src) { *dst = *src; if (src->terms_owned) { dst->terms = src->terms; dst->sizes = src->sizes; dst->terms_owned = true; } else { ecs_filter_copy(dst, src); } src->terms = NULL; src->sizes = NULL; src->term_count = 0; } else { ecs_os_memset_t(dst, 0, ecs_filter_t); } } static void flecs_filter_str_add_id( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_term_id_t *id, bool is_subject, ecs_flags32_t default_traverse_flags) { bool is_added = false; if (!is_subject || id->id != EcsThis) { if (id->flags & EcsIsVariable && !ecs_id_is_wildcard(id->id)) { ecs_strbuf_appendlit(buf, "$"); } if (id->id) { char *path = ecs_get_fullpath(world, id->id); ecs_strbuf_appendstr(buf, path); ecs_os_free(path); } else if (id->name) { ecs_strbuf_appendstr(buf, id->name); } else { ecs_strbuf_appendlit(buf, "0"); } is_added = true; } ecs_flags32_t flags = id->flags; if (!(flags & EcsTraverseFlags)) { /* If flags haven't been set yet, initialize with defaults. This can * happen if an error is thrown while the term is being finalized */ flags |= default_traverse_flags; } if ((flags & EcsTraverseFlags) != default_traverse_flags) { if (is_added) { ecs_strbuf_list_push(buf, ":", "|"); } else { ecs_strbuf_list_push(buf, "", "|"); } if (id->flags & EcsSelf) { ecs_strbuf_list_appendstr(buf, "self"); } if (id->flags & EcsUp) { ecs_strbuf_list_appendstr(buf, "up"); } if (id->flags & EcsDown) { ecs_strbuf_list_appendstr(buf, "down"); } if (id->trav && (id->trav != EcsIsA)) { ecs_strbuf_list_push(buf, "(", ""); char *rel_path = ecs_get_fullpath(world, id->trav); ecs_strbuf_appendstr(buf, rel_path); ecs_os_free(rel_path); ecs_strbuf_list_pop(buf, ")"); } ecs_strbuf_list_pop(buf, ""); } } static void flecs_term_str_w_strbuf( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf, int32_t t) { const ecs_term_id_t *src = &term->src; const ecs_term_id_t *second = &term->second; uint8_t def_src_mask = EcsSelf|EcsUp; uint8_t def_first_mask = EcsSelf; uint8_t def_second_mask = EcsSelf; bool pred_set = ecs_term_id_is_set(&term->first); bool subj_set = !ecs_term_match_0(term); bool obj_set = ecs_term_id_is_set(second); if (term->first.id == EcsScopeOpen) { ecs_strbuf_appendlit(buf, "{"); return; } else if (term->first.id == EcsScopeClose) { ecs_strbuf_appendlit(buf, "}"); return; } if (!t || !(term[-1].oper == EcsOr)) { if (term->inout == EcsIn) { ecs_strbuf_appendlit(buf, "[in] "); } else if (term->inout == EcsInOut) { ecs_strbuf_appendlit(buf, "[inout] "); } else if (term->inout == EcsOut) { ecs_strbuf_appendlit(buf, "[out] "); } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { ecs_strbuf_appendlit(buf, "[none] "); } } if (term->first.flags & EcsIsEntity && term->first.id != 0) { if (ecs_has_id(world, term->first.id, EcsDontInherit)) { def_src_mask = EcsSelf; } } if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, "!"); } else if (term->oper == EcsOptional) { ecs_strbuf_appendlit(buf, "?"); } if (!subj_set) { flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); if (!obj_set) { ecs_strbuf_appendlit(buf, "()"); } else { ecs_strbuf_appendlit(buf, "(0,"); flecs_filter_str_add_id(world, buf, &term->second, false, def_second_mask); ecs_strbuf_appendlit(buf, ")"); } } else if (ecs_term_match_this(term) && (src->flags & EcsTraverseFlags) == def_src_mask) { if (pred_set) { if (obj_set) { ecs_strbuf_appendlit(buf, "("); } flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); if (obj_set) { ecs_strbuf_appendlit(buf, ","); flecs_filter_str_add_id( world, buf, &term->second, false, def_second_mask); ecs_strbuf_appendlit(buf, ")"); } } else if (term->id) { char *str = ecs_id_str(world, term->id); ecs_strbuf_appendstr(buf, str); ecs_os_free(str); } } else { if (term->id_flags && !ECS_HAS_ID_FLAG(term->id_flags, PAIR)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(term->id_flags)); ecs_strbuf_appendch(buf, '|'); } flecs_filter_str_add_id(world, buf, &term->first, false, def_first_mask); ecs_strbuf_appendlit(buf, "("); if (term->src.flags & EcsIsEntity && term->src.id == term->first.id) { ecs_strbuf_appendlit(buf, "$"); } else { flecs_filter_str_add_id(world, buf, &term->src, true, def_src_mask); } if (obj_set) { ecs_strbuf_appendlit(buf, ","); flecs_filter_str_add_id(world, buf, &term->second, false, def_second_mask); } ecs_strbuf_appendlit(buf, ")"); } } char* ecs_term_str( const ecs_world_t *world, const ecs_term_t *term) { ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_term_str_w_strbuf(world, term, &buf, 0); return ecs_strbuf_get(&buf); } static char* flecs_filter_str( const ecs_world_t *world, const ecs_filter_t *filter, const ecs_filter_finalize_ctx_t *ctx, int32_t *term_start_out) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (term_start_out && ctx) { if (ctx->term_index == i) { term_start_out[0] = ecs_strbuf_written(&buf); if (i) { term_start_out[0] += 2; /* whitespace + , */ } } } flecs_term_str_w_strbuf(world, term, &buf, i); if (i != (count - 1)) { if (term->oper == EcsOr) { ecs_strbuf_appendlit(&buf, " || "); } else { if (term->first.id != EcsScopeOpen) { if (term[1].first.id != EcsScopeClose) { ecs_strbuf_appendlit(&buf, ", "); } } } } } return ecs_strbuf_get(&buf); error: return NULL; } char* ecs_filter_str( const ecs_world_t *world, const ecs_filter_t *filter) { return flecs_filter_str(world, filter, NULL, NULL); } int32_t ecs_filter_find_this_var( const ecs_filter_t *filter) { ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { /* Filters currently only support the This variable at index 0. Only * return 0 if filter actually has terms for the This variable. */ return 0; } error: return -1; } /* Check if the id is a pair that has Any as first or second element. Any * pairs behave just like Wildcard pairs and reuses the same data structures, * with as only difference that the number of results returned for an Any pair * is never more than one. This function is used to tell the difference. */ static bool is_any_pair( ecs_id_t id) { if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } if (ECS_PAIR_FIRST(id) == EcsAny) { return true; } if (ECS_PAIR_SECOND(id) == EcsAny) { return true; } return false; } static bool flecs_n_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, ecs_entity_t type_id, ecs_oper_kind_t oper, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, int32_t *match_index_out, bool first, ecs_flags32_t iter_flags) { (void)column_out; const ecs_type_t *type = ecs_get_type(world, type_id); ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = type->array; int32_t i, count = type->count; ecs_term_t temp = *term; temp.oper = EcsAnd; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { continue; } bool result; if (ECS_HAS_ID_FLAG(id, AND)) { ecs_oper_kind_t id_oper = EcsAndFrom; result = flecs_n_term_match_table(world, term, table, id & ECS_COMPONENT_MASK, id_oper, id_out, column_out, subject_out, match_index_out, first, iter_flags); } else { temp.id = id; result = flecs_term_match_table(world, &temp, table, id_out, 0, subject_out, match_index_out, first, iter_flags); } if (!result && oper == EcsAndFrom) { return false; } else if (result && oper == EcsOrFrom) { return true; } } if (oper == EcsAndFrom) { if (id_out) { id_out[0] = type_id; } return true; } else if (oper == EcsOrFrom) { return false; } return false; } bool flecs_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, int32_t *match_index_out, bool first, ecs_flags32_t iter_flags) { const ecs_term_id_t *src = &term->src; ecs_oper_kind_t oper = term->oper; const ecs_table_t *match_table = table; ecs_id_t id = term->id; ecs_entity_t src_id = src->id; if (ecs_term_match_0(term)) { if (id_out) { id_out[0] = id; /* If no entity is matched, just set id */ } return true; } if (oper == EcsAndFrom || oper == EcsOrFrom) { return flecs_n_term_match_table(world, term, table, term->id, term->oper, id_out, column_out, subject_out, match_index_out, first, iter_flags); } /* If source is not This, search in table of source */ if (!ecs_term_match_this(term)) { if (iter_flags & EcsIterEntityOptional) { /* Treat entity terms as optional */ oper = EcsOptional; } match_table = ecs_get_table(world, src_id); if (match_table) { } else if (oper != EcsOptional) { return false; } } else { /* If filter contains This terms, a table must be provided */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } if (!match_table) { return false; } ecs_entity_t source = 0; /* If first = false, we're searching from an offset. This supports returning * multiple results when using wildcard filters. */ int32_t column = 0; if (!first && column_out && column_out[0] != 0) { column = column_out[0]; if (column < 0) { /* In case column is not from This, flip sign */ column = -column; } /* Remove base 1 offset */ column --; } /* Find location, source and id of match in table type */ ecs_table_record_t *tr = 0; bool is_any = is_any_pair(id); column = flecs_search_relation_w_idr(world, match_table, column, id, src->trav, src->flags, &source, id_out, &tr, term->idr); if (tr && match_index_out) { if (!is_any) { match_index_out[0] = tr->count; } else { match_index_out[0] = 1; } } bool result = column != -1; if (oper == EcsNot) { if (match_index_out) { match_index_out[0] = 1; } result = !result; } if (oper == EcsOptional) { result = true; } if ((column == -1) && (src->flags & EcsUp) && (table->flags & EcsTableHasTarget)) { ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t rel = ECS_PAIR_SECOND(table->type.array[table->_->ft_offset]); if (rel == (uint32_t)src->trav) { result = true; } } if (!result) { if (iter_flags & EcsFilterPopulate) { column = 0; } else { return false; } } if (!ecs_term_match_this(term)) { if (!source) { source = src_id; } } if (id_out && column < 0) { id_out[0] = id; } if (column_out) { if (column >= 0) { column ++; if (source != 0) { column *= -1; } column_out[0] = column; } else { column_out[0] = 0; } } if (subject_out) { subject_out[0] = source; } return result; } bool flecs_filter_match_table( ecs_world_t *world, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns, ecs_entity_t *sources, int32_t *match_indices, int32_t *matches_left, bool first, int32_t skip_term, ecs_flags32_t iter_flags) { ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; int32_t match_count = 1; bool result = true; if (matches_left) { match_count = *matches_left; } for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_oper_kind_t oper = term->oper; if (i == skip_term) { if (oper != EcsAndFrom && oper != EcsOrFrom && oper != EcsNotFrom) { continue; } } ecs_term_id_t *src = &term->src; const ecs_table_t *match_table = table; int32_t t_i = term->field_index; ecs_entity_t src_id = src->id; if (!src_id) { if (ids) { ids[t_i] = term->id; } continue; } if (!ecs_term_match_this(term)) { match_table = ecs_get_table(world, src_id); } else { if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { continue; } /* If filter contains This terms, table must be provided */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } int32_t match_index = 0; if (!i || term[-1].oper != EcsOr) { result = false; } else { if (result) { continue; /* Already found matching OR term */ } } bool term_result = flecs_term_match_table(world, term, match_table, ids ? &ids[t_i] : NULL, columns ? &columns[t_i] : NULL, sources ? &sources[t_i] : NULL, &match_index, first, iter_flags); if (i && term[-1].oper == EcsOr) { result |= term_result; } else { result = term_result; } if (oper != EcsOr && !result) { return false; } if (first && match_index) { match_count *= match_index; } if (match_indices) { match_indices[t_i] = match_index; } } if (matches_left) { *matches_left = match_count; } return true; } static void term_iter_init_no_data( ecs_term_iter_t *iter) { iter->term = (ecs_term_t){ .field_index = -1 }; iter->self_index = NULL; iter->index = 0; } static void term_iter_init_w_idr( const ecs_term_t *term, ecs_term_iter_t *iter, ecs_id_record_t *idr, bool empty_tables) { if (idr) { if (empty_tables) { flecs_table_cache_all_iter(&idr->cache, &iter->it); } else { flecs_table_cache_iter(&idr->cache, &iter->it); } } else { term_iter_init_no_data(iter); } iter->index = 0; iter->empty_tables = empty_tables; iter->size = 0; if (term && term->idr && term->idr->type_info) { iter->size = term->idr->type_info->size; } } static void term_iter_init_wildcard( const ecs_world_t *world, ecs_term_iter_t *iter, bool empty_tables) { iter->term = (ecs_term_t){ .field_index = -1 }; iter->self_index = flecs_id_record_get(world, EcsAny); ecs_id_record_t *idr = iter->cur = iter->self_index; term_iter_init_w_idr(NULL, iter, idr, empty_tables); } static void term_iter_init( const ecs_world_t *world, ecs_term_t *term, ecs_term_iter_t *iter, bool empty_tables) { const ecs_term_id_t *src = &term->src; iter->term = *term; if (src->flags & EcsSelf) { iter->self_index = term->idr; if (!iter->self_index) { iter->self_index = flecs_query_id_record_get(world, term->id); } } if (src->flags & EcsUp) { iter->set_index = flecs_id_record_get(world, ecs_pair(src->trav, EcsWildcard)); } ecs_id_record_t *idr; if (iter->self_index) { idr = iter->cur = iter->self_index; } else { idr = iter->cur = iter->set_index; } term_iter_init_w_idr(term, iter, idr, empty_tables); } ecs_iter_t ecs_term_iter( const ecs_world_t *stage, ecs_term_t *term) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); flecs_process_pending_tables(world); if (ecs_term_finalize(world, term)) { ecs_throw(ECS_INVALID_PARAMETER, NULL); } ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), .field_count = 1, .next = ecs_term_next }; /* Term iter populates the iterator with arrays from its own cache, ensure * they don't get overwritten by flecs_iter_validate. * * Note: the reason the term iterator doesn't use the iterator cache itself * (which could easily accomodate a single term) is that the filter iterator * is built on top of the term iterator. The private cache of the term * iterator keeps the filter iterator code simple, as it doesn't need to * worry about the term iter overwriting the iterator fields. */ flecs_iter_init(stage, &it, 0); term_iter_init(world, term, &it.priv.iter.term, false); ECS_BIT_COND(it.flags, EcsIterNoData, it.priv.iter.term.size == 0); return it; error: return (ecs_iter_t){ 0 }; } ecs_iter_t ecs_term_chain_iter( const ecs_iter_t *chain_it, ecs_term_t *term) { ecs_check(chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); ecs_world_t *world = chain_it->real_world; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (ecs_term_finalize(world, term)) { ecs_throw(ECS_INVALID_PARAMETER, NULL); } ecs_iter_t it = { .real_world = world, .world = chain_it->world, .terms = term, .field_count = 1, .chain_it = ECS_CONST_CAST(ecs_iter_t*, chain_it), .next = ecs_term_next }; flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); term_iter_init(world, term, &it.priv.iter.term, false); return it; error: return (ecs_iter_t){ 0 }; } ecs_iter_t ecs_children( const ecs_world_t *world, ecs_entity_t parent) { return ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(parent) }); } bool ecs_children_next( ecs_iter_t *it) { return ecs_term_next(it); } static const ecs_table_record_t *flecs_term_iter_next_table( ecs_term_iter_t *iter) { ecs_id_record_t *idr = iter->cur; if (!idr) { return NULL; } return flecs_table_cache_next(&iter->it, ecs_table_record_t); } static bool flecs_term_iter_find_superset( ecs_world_t *world, ecs_table_t *table, ecs_term_t *term, ecs_entity_t *source, ecs_id_t *id, int32_t *column) { ecs_term_id_t *src = &term->src; /* Test if following the relationship finds the id */ int32_t index = flecs_search_relation_w_idr(world, table, 0, term->id, src->trav, src->flags, source, id, 0, term->idr); if (index == -1) { *source = 0; return false; } ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); *column = (index + 1) * -1; return true; } static bool flecs_term_iter_next( ecs_world_t *world, ecs_term_iter_t *iter, bool match_prefab, bool match_disabled) { ecs_table_t *table = iter->table; ecs_entity_t source = 0; const ecs_table_record_t *tr; ecs_term_t *term = &iter->term; do { if (table) { iter->cur_match ++; if (iter->cur_match >= iter->match_count) { table = NULL; } else { iter->last_column = ecs_search_offset( world, table, iter->last_column + 1, term->id, 0); iter->column = iter->last_column + 1; if (iter->last_column >= 0) { iter->id = table->type.array[iter->last_column]; } } } if (!table) { if (!(tr = flecs_term_iter_next_table(iter))) { if (iter->cur != iter->set_index && iter->set_index != NULL) { if (iter->observed_table_count != 0) { iter->cur = iter->set_index; if (iter->empty_tables) { flecs_table_cache_all_iter( &iter->set_index->cache, &iter->it); } else { flecs_table_cache_iter( &iter->set_index->cache, &iter->it); } iter->index = 0; tr = flecs_term_iter_next_table(iter); } } if (!tr) { return false; } } table = tr->hdr.table; if (table->_->traversable_count) { iter->observed_table_count ++; } if (!match_prefab && (table->flags & EcsTableIsPrefab)) { continue; } if (!match_disabled && (table->flags & EcsTableIsDisabled)) { continue; } iter->table = table; iter->match_count = tr->count; if (is_any_pair(term->id)) { iter->match_count = 1; } iter->cur_match = 0; iter->last_column = tr->index; iter->column = tr->index + 1; iter->id = flecs_to_public_id(table->type.array[tr->index]); } if (iter->cur == iter->set_index) { if (iter->self_index) { if (flecs_id_record_get_table(iter->self_index, table) != NULL) { /* If the table has the id itself and this term matched Self * we already matched it */ continue; } } if (!flecs_term_iter_find_superset( world, table, term, &source, &iter->id, &iter->column)) { continue; } /* The tr->count field refers to the number of relationship instances, * not to the number of matches. Superset terms can only yield a * single match. */ iter->match_count = 1; } break; } while (true); iter->subject = source; return true; } static bool flecs_term_iter_set_table( ecs_world_t *world, ecs_term_iter_t *iter, ecs_table_t *table) { const ecs_table_record_t *tr = NULL; const ecs_id_record_t *idr = iter->self_index; if (idr) { tr = ecs_table_cache_get(&idr->cache, table); if (tr) { iter->match_count = tr->count; iter->last_column = tr->index; iter->column = tr->index + 1; iter->id = flecs_to_public_id(table->type.array[tr->index]); } } if (!tr) { idr = iter->set_index; if (idr) { tr = ecs_table_cache_get(&idr->cache, table); if (!flecs_term_iter_find_superset(world, table, &iter->term, &iter->subject, &iter->id, &iter->column)) { return false; } iter->match_count = 1; } } if (!tr) { return false; } /* Populate fields as usual */ iter->table = table; iter->cur_match = 0; return true; } bool ecs_term_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_term_next, ECS_INVALID_PARAMETER, NULL); flecs_iter_validate(it); ecs_term_iter_t *iter = &it->priv.iter.term; ecs_term_t *term = &iter->term; ecs_world_t *world = it->real_world; ecs_table_t *table; it->ids = &iter->id; it->sources = &iter->subject; it->columns = &iter->column; it->terms = &iter->term; it->sizes = &iter->size; it->ptrs = &iter->ptr; ecs_iter_t *chain_it = it->chain_it; if (chain_it) { ecs_iter_next_action_t next = chain_it->next; bool match; do { if (!next(chain_it)) { goto done; } table = chain_it->table; match = flecs_term_match_table(world, term, table, it->ids, it->columns, it->sources, it->match_indices, true, it->flags); } while (!match); goto yield; } else { if (!flecs_term_iter_next(world, iter, (term->flags & EcsTermMatchPrefab) != 0, (term->flags & EcsTermMatchDisabled) != 0)) { goto done; } table = iter->table; /* Source must either be 0 (EcsThis) or nonzero in case of substitution */ ecs_assert(iter->subject || iter->cur != iter->set_index, ECS_INTERNAL_ERROR, NULL); ecs_assert(iter->table != NULL, ECS_INTERNAL_ERROR, NULL); } yield: flecs_iter_populate_data(world, it, table, 0, ecs_table_count(table), it->ptrs); ECS_BIT_SET(it->flags, EcsIterIsValid); return true; done: ecs_iter_fini(it); error: return false; } static void flecs_init_filter_iter( ecs_iter_t *it, const ecs_filter_t *filter) { ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); it->priv.iter.filter.filter = filter; it->field_count = filter->field_count; } int32_t ecs_filter_pivot_term( const ecs_world_t *world, const ecs_filter_t *filter) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); ecs_term_t *terms = filter->terms; int32_t i, term_count = filter->term_count; int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ecs_id_t id = term->id; if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { continue; } if (!ecs_term_match_this(term)) { continue; } ecs_id_record_t *idr = flecs_query_id_record_get(world, id); if (!idr) { /* If one of the terms does not match with any data, iterator * should not return anything */ return -2; /* -2 indicates filter doesn't match anything */ } int32_t table_count = flecs_table_cache_count(&idr->cache); if (min_count == -1 || table_count < min_count) { min_count = table_count; pivot_term = i; if ((term->src.flags & EcsTraverseFlags) == EcsSelf) { self_pivot_term = i; } } } if (self_pivot_term != -1) { pivot_term = self_pivot_term; } return pivot_term; error: return -2; } void flecs_filter_apply_iter_flags( ecs_iter_t *it, const ecs_filter_t *filter) { ECS_BIT_COND(it->flags, EcsIterIsInstanced, ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); ECS_BIT_COND(it->flags, EcsIterNoData, ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); ECS_BIT_COND(it->flags, EcsIterHasCondSet, ECS_BIT_IS_SET(filter->flags, EcsFilterHasCondSet)); } ecs_iter_t flecs_filter_iter_w_flags( const ecs_world_t *stage, const ecs_filter_t *filter, ecs_flags32_t flags) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(filter->flags & (EcsFilterHasPred|EcsFilterHasScopes)), ECS_UNSUPPORTED, NULL); const ecs_world_t *world = ecs_get_world(stage); if (!(flags & EcsIterMatchVar)) { flecs_process_pending_tables(world); } ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), .terms = filter ? filter->terms : NULL, .next = ecs_filter_next, .flags = flags, .sizes = filter->sizes }; ecs_filter_iter_t *iter = &it.priv.iter.filter; iter->pivot_term = -1; flecs_init_filter_iter(&it, filter); flecs_filter_apply_iter_flags(&it, filter); /* Find term that represents smallest superset */ if (ECS_BIT_IS_SET(flags, EcsIterIgnoreThis)) { term_iter_init_no_data(&iter->term_iter); } else if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { ecs_term_t *terms = filter->terms; int32_t pivot_term = -1; ecs_check(terms != NULL, ECS_INVALID_PARAMETER, NULL); pivot_term = ecs_filter_pivot_term(world, filter); iter->kind = EcsIterEvalTables; iter->pivot_term = pivot_term; if (pivot_term == -2) { /* One or more terms have no matching results */ term_iter_init_no_data(&iter->term_iter); } else if (pivot_term == -1) { /* No terms meet the criteria to be a pivot term, evaluate filter * against all tables */ term_iter_init_wildcard(world, &iter->term_iter, ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); } else { ecs_assert(pivot_term >= 0, ECS_INTERNAL_ERROR, NULL); term_iter_init(world, &terms[pivot_term], &iter->term_iter, ECS_BIT_IS_SET(filter->flags, EcsFilterMatchEmptyTables)); } } else { if (!ECS_BIT_IS_SET(filter->flags, EcsFilterMatchAnything)) { term_iter_init_no_data(&iter->term_iter); } else { iter->kind = EcsIterEvalNone; } } ECS_BIT_COND(it.flags, EcsIterNoData, ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { /* Make space for one variable if the filter has terms for This var */ it.variable_count = 1; /* Set variable name array */ it.variable_names = ECS_CONST_CAST(char**, filter->variable_names); } flecs_iter_init(stage, &it, flecs_iter_cache_all); return it; error: return (ecs_iter_t){ 0 }; } ecs_iter_t ecs_filter_iter( const ecs_world_t *stage, const ecs_filter_t *filter) { return flecs_filter_iter_w_flags(stage, filter, 0); } ecs_iter_t ecs_filter_chain_iter( const ecs_iter_t *chain_it, const ecs_filter_t *filter) { ecs_iter_t it = { .terms = filter->terms, .field_count = filter->field_count, .world = chain_it->world, .real_world = chain_it->real_world, .chain_it = ECS_CONST_CAST(ecs_iter_t*, chain_it), .next = ecs_filter_next, .sizes = filter->sizes }; flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); ecs_filter_iter_t *iter = &it.priv.iter.filter; flecs_init_filter_iter(&it, filter); iter->kind = EcsIterEvalChain; return it; } bool ecs_filter_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_filter_next_instanced(it)); error: return false; } bool ecs_filter_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_filter_next, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != it, ECS_INVALID_PARAMETER, NULL); ecs_filter_iter_t *iter = &it->priv.iter.filter; const ecs_filter_t *filter = iter->filter; ecs_world_t *world = it->real_world; ecs_table_t *table = NULL; bool match; flecs_iter_validate(it); ecs_iter_t *chain_it = it->chain_it; ecs_iter_kind_t kind = iter->kind; if (chain_it) { ecs_assert(kind == EcsIterEvalChain, ECS_INVALID_PARAMETER, NULL); ecs_iter_next_action_t next = chain_it->next; do { if (!next(chain_it)) { goto done; } table = chain_it->table; match = flecs_filter_match_table(world, filter, table, it->ids, it->columns, it->sources, it->match_indices, NULL, true, -1, it->flags); } while (!match); goto yield; } else if (kind == EcsIterEvalTables || kind == EcsIterEvalCondition) { ecs_term_iter_t *term_iter = &iter->term_iter; ecs_term_t *term = &term_iter->term; int32_t pivot_term = iter->pivot_term; bool first; /* Check if the This variable has been set on the iterator. If set, * the filter should only be applied to the variable value */ ecs_var_t *this_var = NULL; ecs_table_t *this_table = NULL; if (it->variable_count) { if (ecs_iter_var_is_constrained(it, 0)) { this_var = it->variables; this_table = this_var->range.table; /* If variable is constrained, make sure it's a value that's * pointing to a table, as a filter can't iterate single * entities (yet) */ ecs_assert(this_table != NULL, ECS_INVALID_OPERATION, NULL); /* Can't set variable for filter that does not iterate tables */ ecs_assert(kind == EcsIterEvalTables, ECS_INVALID_OPERATION, NULL); } } do { /* If there are no matches left for the previous table, this is the * first match of the next table. */ first = iter->matches_left == 0; if (first) { if (kind != EcsIterEvalCondition) { /* Check if this variable was constrained */ if (this_table != NULL) { /* If this is the first match of a new result and the * previous result was equal to the value of a * constrained var, there's nothing left to iterate */ if (it->table == this_table) { goto done; } /* If table doesn't match term iterator, it doesn't * match filter. */ if (!flecs_term_iter_set_table( world, term_iter, this_table)) { goto done; } it->offset = this_var->range.offset; it->count = this_var->range.count; /* But if it does, forward it to filter matching */ ecs_assert(term_iter->table == this_table, ECS_INTERNAL_ERROR, NULL); /* If This variable is not constrained, iterate as usual */ } else { it->offset = 0; it->count = 0; /* Find new match, starting with the leading term */ if (!flecs_term_iter_next(world, term_iter, ECS_BIT_IS_SET(filter->flags, EcsFilterMatchPrefab), ECS_BIT_IS_SET(filter->flags, EcsFilterMatchDisabled))) { goto done; } } ecs_assert(term_iter->match_count != 0, ECS_INTERNAL_ERROR, NULL); if (pivot_term == -1) { /* Without a pivot term, we're iterating all tables with * a wildcard, so the match count is meaningless. */ term_iter->match_count = 1; } else { it->match_indices[pivot_term] = term_iter->match_count; } iter->matches_left = term_iter->match_count; /* Filter iterator takes control over iterating all the * permutations that match the wildcard. */ term_iter->match_count = 1; table = term_iter->table; if (pivot_term != -1) { int32_t index = term->field_index; it->ids[index] = term_iter->id; it->sources[index] = term_iter->subject; it->columns[index] = term_iter->column; } } else { /* Progress iterator to next match for table, if any */ table = it->table; if (term_iter->index == 0) { iter->matches_left = 1; term_iter->index = 1; /* prevents looping again */ } else { goto done; } } /* Match the remainder of the terms */ match = flecs_filter_match_table(world, filter, table, it->ids, it->columns, it->sources, it->match_indices, &iter->matches_left, first, pivot_term, it->flags); if (!match) { it->table = table; iter->matches_left = 0; continue; } /* Table got matched, set This variable */ if (table) { ecs_assert(it->variable_count == 1, ECS_INTERNAL_ERROR, NULL); ecs_assert(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); it->variables[0].range.table = table; } ecs_assert(iter->matches_left != 0, ECS_INTERNAL_ERROR, NULL); } /* If this is not the first result for the table, and the table * is matched more than once, iterate remaining matches */ if (!first && (iter->matches_left > 0)) { table = it->table; /* Find first term that still has matches left */ int32_t i, j, count = it->field_count; for (i = count - 1; i >= 0; i --) { int32_t mi = -- it->match_indices[i]; if (mi) { if (mi < 0) { continue; } break; } } /* If matches_left > 0 we should've found at least one match */ ecs_assert(i >= 0, ECS_INTERNAL_ERROR, NULL); /* Progress first term to next match (must be at least one) */ int32_t column = it->columns[i]; if (column < 0) { /* If this term was matched on a non-This entity, reconvert * the column back to a positive value */ column = -column; } it->columns[i] = column + 1; flecs_term_match_table(world, &filter->terms[i], table, &it->ids[i], &it->columns[i], &it->sources[i], &it->match_indices[i], false, it->flags); /* Reset remaining terms (if any) to first match */ for (j = i + 1; j < count; j ++) { flecs_term_match_table(world, &filter->terms[j], table, &it->ids[j], &it->columns[j], &it->sources[j], &it->match_indices[j], true, it->flags); } } match = iter->matches_left != 0; iter->matches_left --; ecs_assert(iter->matches_left >= 0, ECS_INTERNAL_ERROR, NULL); } while (!match); goto yield; } done: error: ecs_iter_fini(it); return false; yield: if (!it->count && table) { it->count = ecs_table_count(table); } flecs_iter_populate_data(world, it, table, it->offset, it->count, it->ptrs); ECS_BIT_SET(it->flags, EcsIterIsValid); return true; } /** * @file iter.c * @brief Iterator API. * * The iterator API contains functions that apply to all iterators, such as * resource management, or fetching resources for a matched table. The API also * contains functions for generic iterators, which make it possible to iterate * an iterator without needing to know what created the iterator. */ #include /* Utility macros to enforce consistency when initializing iterator fields */ /* If term count is smaller than cache size, initialize with inline array, * otherwise allocate. */ #define INIT_CACHE(it, stack, fields, f, T, count)\ if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ it->f = flecs_stack_calloc_n(stack, T, count);\ it->priv.cache.used |= flecs_iter_cache_##f;\ } /* If array is allocated, free it when finalizing the iterator */ #define FINI_CACHE(it, f, T, count)\ if (it->priv.cache.used & flecs_iter_cache_##f) {\ flecs_stack_free_n((void*)it->f, T, count);\ } void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align) { ecs_world_t *world = it->world; ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); ecs_stack_t *stack = &stage->allocators.iter_stack; return flecs_stack_calloc(stack, size, align); } void flecs_iter_free( void *ptr, ecs_size_t size) { flecs_stack_free(ptr, size); } void flecs_iter_init( const ecs_world_t *world, ecs_iter_t *it, ecs_flags8_t fields) { ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INTERNAL_ERROR, NULL); ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); ecs_stack_t *stack = &stage->allocators.iter_stack; it->priv.cache.used = 0; it->priv.cache.allocated = 0; it->priv.cache.stack_cursor = flecs_stack_get_cursor(stack); it->priv.entity_iter = flecs_stack_calloc_t( stack, ecs_entity_filter_iter_t); INIT_CACHE(it, stack, fields, ids, ecs_id_t, it->field_count); INIT_CACHE(it, stack, fields, sources, ecs_entity_t, it->field_count); INIT_CACHE(it, stack, fields, match_indices, int32_t, it->field_count); INIT_CACHE(it, stack, fields, columns, int32_t, it->field_count); INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count); } void flecs_iter_validate( ecs_iter_t *it) { ECS_BIT_SET(it->flags, EcsIterIsValid); /* Make sure multithreaded iterator isn't created for real world */ ecs_world_t *world = it->real_world; ecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldMultiThreaded) || it->world != it->real_world, ECS_INVALID_PARAMETER, "create iterator for stage when world is in multithreaded mode"); (void)world; error: return; } void ecs_iter_fini( ecs_iter_t *it) { ECS_BIT_CLEAR(it->flags, EcsIterIsValid); if (it->fini) { it->fini(it); } ecs_world_t *world = it->world; if (!world) { return; } FINI_CACHE(it, ids, ecs_id_t, it->field_count); FINI_CACHE(it, sources, ecs_entity_t, it->field_count); FINI_CACHE(it, match_indices, int32_t, it->field_count); FINI_CACHE(it, columns, int32_t, it->field_count); FINI_CACHE(it, variables, ecs_var_t, it->variable_count); FINI_CACHE(it, ptrs, void*, it->field_count); flecs_stack_free_t(it->priv.entity_iter, ecs_entity_filter_iter_t); ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_stack_restore_cursor(&stage->allocators.iter_stack, it->priv.cache.stack_cursor); } static bool flecs_iter_populate_term_data( ecs_world_t *world, ecs_iter_t *it, int32_t t, int32_t column, void **ptr_out) { bool is_shared = false; ecs_table_t *table; void *data; int32_t row, u_index; if (!column) { /* Term has no data. This includes terms that have Not operators. */ goto no_data; } /* Filter terms may match with data but don't return it */ if (it->terms[t].inout == EcsInOutNone) { goto no_data; } ecs_assert(it->sizes != NULL, ECS_INTERNAL_ERROR, NULL); int32_t size = it->sizes[t]; if (!size) { goto no_data; } if (column < 0) { table = it->table; is_shared = true; /* Data is not from This */ if (it->references && (!table || !(table->flags & EcsTableHasTarget))) { /* The reference array is used only for components matched on a * table (vs. individual entities). Remaining components should be * assigned outside of this function */ if (ecs_term_match_this(&it->terms[t])) { /* Iterator provides cached references for non-This terms */ ecs_ref_t *ref = &it->references[-column - 1]; if (ptr_out) { if (ref->id) { ptr_out[0] = (void*)ecs_ref_get_id(world, ref, ref->id); } else { ptr_out[0] = NULL; } } if (!ref->id) { is_shared = false; } return is_shared; } return true; } else { ecs_entity_t subj = it->sources[t]; ecs_assert(subj != 0, ECS_INTERNAL_ERROR, NULL); /* Don't use ecs_get_id directly. Instead, go directly to the * storage so that we can get both the pointer and size */ ecs_record_t *r = flecs_entities_get(world, subj); ecs_assert(r != NULL && r->table != NULL, ECS_INTERNAL_ERROR, NULL); row = ECS_RECORD_TO_ROW(r->row); table = r->table; ecs_id_t id = it->ids[t]; ecs_table_record_t *tr; if (!(tr = flecs_table_record_get(world, table, id)) || (tr->column == -1)) { u_index = flecs_table_column_to_union_index(table, -column - 1); if (u_index != -1) { goto has_union; } goto no_data; } /* We now have row and column, so we can get the storage for the id * which gives us the pointer and size */ column = tr->column; ecs_vec_t *s = &table->data.columns[column].data; data = ecs_vec_first(s); /* Fallthrough to has_data */ } } else { /* Data is from This, use table from iterator */ table = it->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); row = it->offset; int32_t storage_column = ecs_table_type_to_column_index( table, column - 1); if (storage_column == -1) { u_index = flecs_table_column_to_union_index(table, column - 1); if (u_index != -1) { goto has_union; } goto no_data; } if (!it->count) { goto no_data; } ecs_vec_t *s = &table->data.columns[storage_column].data; data = ecs_vec_first(s); /* Fallthrough to has_data */ } has_data: if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); return is_shared; has_union: { /* Edge case: if column is a switch we should return the vector with case * identifiers. Will be replaced in the future with pluggable storage */ ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &table->_->sw_columns[u_index]; data = ecs_vec_first(flecs_switch_values(sw)); goto has_data; } no_data: if (ptr_out) ptr_out[0] = NULL; return false; } void flecs_iter_populate_data( ecs_world_t *world, ecs_iter_t *it, ecs_table_t *table, int32_t offset, int32_t count, void **ptrs) { ecs_table_t *prev_table = it->table; if (prev_table) { it->frame_offset += ecs_table_count(prev_table); } it->table = table; it->offset = offset; it->count = count; if (table) { ecs_assert(count != 0 || !ecs_table_count(table) || (it->flags & EcsIterTableOnly), ECS_INTERNAL_ERROR, NULL); if (count) { it->entities = ecs_vec_get_t( &table->data.entities, ecs_entity_t, offset); } else { it->entities = NULL; } } int t, field_count = it->field_count; if (ECS_BIT_IS_SET(it->flags, EcsIterNoData)) { ECS_BIT_CLEAR(it->flags, EcsIterHasShared); return; } bool has_shared = false; if (ptrs) { for (t = 0; t < field_count; t ++) { int32_t column = it->columns[t]; has_shared |= flecs_iter_populate_term_data(world, it, t, column, &ptrs[t]); } } ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); } bool flecs_iter_next_row( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); if (!is_instanced) { int32_t instance_count = it->instance_count; int32_t count = it->count; int32_t offset = it->offset; if (instance_count > count && offset < (instance_count - 1)) { ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); int t, field_count = it->field_count; for (t = 0; t < field_count; t ++) { int32_t column = it->columns[t]; if (column >= 0) { void *ptr = it->ptrs[t]; if (ptr) { it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); } } } if (it->entities) { it->entities ++; } it->offset ++; return true; } } return false; } bool flecs_iter_next_instanced( ecs_iter_t *it, bool result) { it->instance_count = it->count; bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); if (result && !is_instanced && it->count && has_shared) { it->count = 1; } return result; } /* --- Public API --- */ void* ecs_field_w_size( const ecs_iter_t *it, size_t size, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); ecs_check(!size || ecs_field_size(it, index) == size || (!ecs_field_size(it, index) && (!it->ptrs[index - 1])), ECS_INVALID_PARAMETER, NULL); (void)size; return it->ptrs[index - 1]; error: return NULL; } bool ecs_field_is_readonly( const ecs_iter_t *it, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); ecs_term_t *term = &it->terms[index - 1]; if (term->inout == EcsIn) { return true; } else if (term->inout == EcsInOutDefault) { if (!ecs_term_match_this(term)) { return true; } ecs_term_id_t *src = &term->src; if (!(src->flags & EcsSelf)) { return true; } } error: return false; } bool ecs_field_is_writeonly( const ecs_iter_t *it, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); ecs_term_t *term = &it->terms[index - 1]; return term->inout == EcsOut; error: return false; } bool ecs_field_is_set( const ecs_iter_t *it, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); int32_t column = it->columns[index - 1]; if (!column) { return false; } else if (column < 0) { if (it->references) { column = -column - 1; ecs_ref_t *ref = &it->references[column]; return ref->entity != 0; } else { return true; } } return true; error: return false; } bool ecs_field_is_self( const ecs_iter_t *it, int32_t index) { ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); return it->sources == NULL || it->sources[index - 1] == 0; } ecs_id_t ecs_field_id( const ecs_iter_t *it, int32_t index) { ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); return it->ids[index - 1]; } int32_t ecs_field_column_index( const ecs_iter_t *it, int32_t index) { ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); int32_t result = it->columns[index - 1]; if (result <= 0) { return -1; } else { return result - 1; } } ecs_entity_t ecs_field_src( const ecs_iter_t *it, int32_t index) { ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); if (it->sources) { return it->sources[index - 1]; } else { return 0; } } size_t ecs_field_size( const ecs_iter_t *it, int32_t index) { ecs_assert(index >= 1, ECS_INVALID_PARAMETER, NULL); return (size_t)it->sizes[index - 1]; } char* ecs_iter_str( const ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_strbuf_t buf = ECS_STRBUF_INIT; int i; if (it->field_count) { ecs_strbuf_list_push(&buf, "id: ", ","); for (i = 0; i < it->field_count; i ++) { ecs_id_t id = ecs_field_id(it, i + 1); char *str = ecs_id_str(world, id); ecs_strbuf_list_appendstr(&buf, str); ecs_os_free(str); } ecs_strbuf_list_pop(&buf, "\n"); ecs_strbuf_list_push(&buf, "src: ", ","); for (i = 0; i < it->field_count; i ++) { ecs_entity_t subj = ecs_field_src(it, i + 1); char *str = ecs_get_fullpath(world, subj); ecs_strbuf_list_appendstr(&buf, str); ecs_os_free(str); } ecs_strbuf_list_pop(&buf, "\n"); ecs_strbuf_list_push(&buf, "set: ", ","); for (i = 0; i < it->field_count; i ++) { if (ecs_field_is_set(it, i + 1)) { ecs_strbuf_list_appendlit(&buf, "true"); } else { ecs_strbuf_list_appendlit(&buf, "false"); } } ecs_strbuf_list_pop(&buf, "\n"); } if (it->variable_count) { int32_t actual_count = 0; for (i = 0; i < it->variable_count; i ++) { const char *var_name = it->variable_names[i]; if (!var_name || var_name[0] == '_' || !strcmp(var_name, "This")) { /* Skip anonymous variables */ continue; } ecs_var_t var = it->variables[i]; if (!var.entity) { /* Skip table variables */ continue; } if (!actual_count) { ecs_strbuf_list_push(&buf, "var: ", ","); } char *str = ecs_get_fullpath(world, var.entity); ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); ecs_os_free(str); actual_count ++; } if (actual_count) { ecs_strbuf_list_pop(&buf, "\n"); } } if (it->count) { ecs_strbuf_appendlit(&buf, "this:\n"); for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; char *str = ecs_get_fullpath(world, e); ecs_strbuf_appendlit(&buf, " - "); ecs_strbuf_appendstr(&buf, str); ecs_strbuf_appendch(&buf, '\n'); ecs_os_free(str); } } return ecs_strbuf_get(&buf); } void ecs_iter_poly( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter_out, ecs_term_t *filter) { ecs_iterable_t *iterable = ecs_get_iterable(poly); iterable->init(world, poly, iter_out, filter); } bool ecs_iter_next( ecs_iter_t *iter) { ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); return iter->next(iter); error: return false; } int32_t ecs_iter_count( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ECS_BIT_SET(it->flags, EcsIterIsInstanced); int32_t count = 0; while (ecs_iter_next(it)) { count += it->count; } return count; error: return 0; } ecs_entity_t ecs_iter_first( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ECS_BIT_SET(it->flags, EcsIterIsInstanced); ecs_entity_t result = 0; if (ecs_iter_next(it)) { result = it->entities[0]; ecs_iter_fini(it); } return result; error: return 0; } bool ecs_iter_is_true( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ECS_BIT_SET(it->flags, EcsIterIsInstanced); bool result = ecs_iter_next(it); if (result) { ecs_iter_fini(it); } return result; error: return false; } ecs_entity_t ecs_iter_get_var( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &it->variables[var_id]; ecs_entity_t e = var->entity; if (!e) { ecs_table_t *table = var->range.table; if (table) { if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { ecs_assert(ecs_table_count(table) > var->range.offset, ECS_INTERNAL_ERROR, NULL); e = ecs_vec_get_t(&table->data.entities, ecs_entity_t, var->range.offset)[0]; } } } else { ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); } return e; error: return 0; } ecs_table_t* ecs_iter_get_var_as_table( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &it->variables[var_id]; ecs_table_t *table = var->range.table; if (!table) { /* If table is not set, try to get table from entity */ ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { table = r->table; if (ecs_table_count(table) != 1) { /* If table contains more than the entity, make sure not to * return a partial table. */ return NULL; } } } } if (table) { if (var->range.offset) { /* Don't return whole table if only partial table is matched */ return NULL; } if (!var->range.count || ecs_table_count(table) == var->range.count) { /* Return table if count matches */ return table; } } error: return NULL; } ecs_table_range_t ecs_iter_get_var_as_range( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_range_t result = { 0 }; ecs_var_t *var = &it->variables[var_id]; ecs_table_t *table = var->range.table; if (!table) { ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { result.table = r->table; result.offset = ECS_RECORD_TO_ROW(r->row); result.count = 1; } } } else { result.table = table; result.offset = var->range.offset; result.count = var->range.count; if (!result.count) { result.count = ecs_table_count(table); } } return result; error: return (ecs_table_range_t){0}; } void ecs_iter_set_var( ecs_iter_t *it, int32_t var_id, ecs_entity_t entity) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < FLECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Can't set variable while iterating */ ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &it->variables[var_id]; var->entity = entity; ecs_record_t *r = flecs_entities_get(it->real_world, entity); if (r) { var->range.table = r->table; var->range.offset = ECS_RECORD_TO_ROW(r->row); var->range.count = 1; } else { var->range.table = NULL; var->range.offset = 0; var->range.count = 0; } it->constrained_vars |= flecs_ito(uint64_t, 1 << var_id); if (it->set_var) { it->set_var(it); } error: return; } void ecs_iter_set_var_as_table( ecs_iter_t *it, int32_t var_id, const ecs_table_t *table) { ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; ecs_iter_set_var_as_range(it, var_id, &range); } void ecs_iter_set_var_as_range( ecs_iter_t *it, int32_t var_id, const ecs_table_range_t *range) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < FLECS_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!range->offset || range->offset < ecs_table_count(range->table), ECS_INVALID_PARAMETER, NULL); ecs_check((range->offset + range->count) <= ecs_table_count(range->table), ECS_INVALID_PARAMETER, NULL); /* Can't set variable while iterating */ ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, NULL); ecs_var_t *var = &it->variables[var_id]; var->range = *range; if (range->count == 1) { ecs_table_t *table = range->table; var->entity = ecs_vec_get_t( &table->data.entities, ecs_entity_t, range->offset)[0]; } else { var->entity = 0; } it->constrained_vars |= flecs_uto(uint64_t, 1 << var_id); error: return; } bool ecs_iter_var_is_constrained( ecs_iter_t *it, int32_t var_id) { return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; } static void ecs_chained_iter_fini( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_fini(it->chain_it); it->chain_it = NULL; } ecs_iter_t ecs_page_iter( const ecs_iter_t *it, int32_t offset, int32_t limit) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = *it; result.priv.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ result.priv.iter.page = (ecs_page_iter_t){ .offset = offset, .limit = limit, .remaining = limit }; result.next = ecs_page_next; result.fini = ecs_chained_iter_fini; result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); return result; error: return (ecs_iter_t){ 0 }; } static void flecs_offset_iter( ecs_iter_t *it, int32_t offset) { it->entities = &it->entities[offset]; int32_t t, field_count = it->field_count; void **it_ptrs = it->ptrs; if (it_ptrs) { for (t = 0; t < field_count; t ++) { void *ptrs = it_ptrs[t]; if (!ptrs) { continue; } if (it->sources[t]) { continue; } it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); } } } static bool ecs_page_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); ecs_iter_t *chain_it = it->chain_it; bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); do { if (!ecs_iter_next(chain_it)) { goto depleted; } ecs_page_iter_t *iter = &it->priv.iter.page; /* Copy everything up to the private iterator data */ ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); /* Keep instancing setting from original iterator */ ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); if (!chain_it->table) { goto yield; /* Task query */ } int32_t offset = iter->offset; int32_t limit = iter->limit; if (!(offset || limit)) { if (it->count) { goto yield; } else { goto depleted; } } int32_t count = it->count; int32_t remaining = iter->remaining; if (offset) { if (offset > count) { /* No entities to iterate in current table */ iter->offset -= count; it->count = 0; continue; } else { it->offset += offset; count = it->count -= offset; iter->offset = 0; flecs_offset_iter(it, offset); } } if (remaining) { if (remaining > count) { iter->remaining -= count; } else { it->count = remaining; iter->remaining = 0; } } else if (limit) { /* Limit hit: no more entities left to iterate */ goto done; } } while (it->count == 0); yield: if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { it->offset = 0; } return true; done: /* Cleanup iterator resources if it wasn't yet depleted */ ecs_iter_fini(chain_it); depleted: error: return false; } bool ecs_page_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); error: return false; } ecs_iter_t ecs_worker_iter( const ecs_iter_t *it, int32_t index, int32_t count) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = *it; result.priv.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ result.priv.iter.worker = (ecs_worker_iter_t){ .index = index, .count = count }; result.next = ecs_worker_next; result.fini = ecs_chained_iter_fini; result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); return result; error: return (ecs_iter_t){ 0 }; } static bool ecs_worker_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); ecs_iter_t *chain_it = it->chain_it; ecs_worker_iter_t *iter = &it->priv.iter.worker; int32_t res_count = iter->count, res_index = iter->index; int32_t per_worker, instances_per_worker, first; do { if (!ecs_iter_next(chain_it)) { return false; } /* Copy everything up to the private iterator data */ ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv)); /* Keep instancing setting from original iterator */ ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); int32_t count = it->count; int32_t instance_count = it->instance_count; per_worker = count / res_count; instances_per_worker = instance_count / res_count; first = per_worker * res_index; count -= per_worker * res_count; if (count) { if (res_index < count) { per_worker ++; first += res_index; } else { first += count; } } if (!per_worker && it->table == NULL) { if (res_index == 0) { return true; } else { // chained iterator was not yet cleaned up // since it returned true from ecs_iter_next, so clean it up here. ecs_iter_fini(chain_it); return false; } } } while (!per_worker); it->instance_count = instances_per_worker; it->frame_offset += first; flecs_offset_iter(it, it->offset + first); it->count = per_worker; if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { it->offset += first; } else { it->offset = 0; } return true; error: return false; } bool ecs_worker_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); error: return false; } /** * @file misc.c * @brief Miscallaneous functions. */ #include #include #ifndef FLECS_NDEBUG static int64_t flecs_s_min[] = { [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; static int64_t flecs_s_max[] = { [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; static uint64_t flecs_u_max[] = { [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; uint64_t flecs_ito_( size_t size, bool is_signed, bool lt_zero, uint64_t u, const char *err) { union { uint64_t u; int64_t s; } v; v.u = u; if (is_signed) { ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); } else { ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); } return u; } #endif int32_t flecs_next_pow_of_2( int32_t n) { n --; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n ++; return n; } /** Convert time to double */ double ecs_time_to_double( ecs_time_t t) { double result; result = t.sec; return result + (double)t.nanosec / (double)1000000000; } ecs_time_t ecs_time_sub( ecs_time_t t1, ecs_time_t t2) { ecs_time_t result; if (t1.nanosec >= t2.nanosec) { result.nanosec = t1.nanosec - t2.nanosec; result.sec = t1.sec - t2.sec; } else { result.nanosec = t1.nanosec - t2.nanosec + 1000000000; result.sec = t1.sec - t2.sec - 1; } return result; } void ecs_sleepf( double t) { if (t > 0) { int sec = (int)t; int nsec = (int)((t - sec) * 1000000000); ecs_os_sleep(sec, nsec); } } double ecs_time_measure( ecs_time_t *start) { ecs_time_t stop, temp; ecs_os_get_time(&stop); temp = stop; stop = ecs_time_sub(stop, *start); *start = temp; return ecs_time_to_double(stop); } void* ecs_os_memdup( const void *src, ecs_size_t size) { if (!src) { return NULL; } void *dst = ecs_os_malloc(size); ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_memcpy(dst, src, size); return dst; } int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2) { (void)ptr1; (void)ptr2; return (e1 > e2) - (e1 < e2); } uint64_t flecs_string_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } char* ecs_vasprintf( const char *fmt, va_list args) { ecs_size_t size = 0; char *result = NULL; va_list tmpa; va_copy(tmpa, args); size = vsnprintf(result, 0, fmt, tmpa); va_end(tmpa); if ((int32_t)size < 0) { return NULL; } result = (char *) ecs_os_malloc(size + 1); if (!result) { return NULL; } ecs_os_vsprintf(result, fmt, args); return result; } char* ecs_asprintf( const char *fmt, ...) { va_list args; va_start(args, fmt); char *result = ecs_vasprintf(fmt, args); va_end(args); return result; } char* flecs_to_snake_case(const char *str) { int32_t upper_count = 0, len = 1; const char *ptr = str; char ch, *out, *out_ptr; for (ptr = &str[1]; (ch = *ptr); ptr ++) { if (isupper(ch)) { upper_count ++; } len ++; } out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1); for (ptr = str; (ch = *ptr); ptr ++) { if (isupper(ch)) { if ((ptr != str) && (out_ptr[-1] != '_')) { out_ptr[0] = '_'; out_ptr ++; } out_ptr[0] = (char)tolower(ch); out_ptr ++; } else { out_ptr[0] = ch; out_ptr ++; } } out_ptr[0] = '\0'; return out; } /** * @file observable.c * @brief Observable implementation. * * The observable implementation contains functions that find the set of * observers to invoke for an event. The code also contains the implementation * of a reachable id cache, which is used to speedup event propagation when * relationships are added/removed to/from entities. */ void flecs_observable_init( ecs_observable_t *observable) { flecs_sparse_init_t(&observable->events, NULL, NULL, ecs_event_record_t); observable->on_add.event = EcsOnAdd; observable->on_remove.event = EcsOnRemove; observable->on_set.event = EcsOnSet; observable->un_set.event = EcsUnSet; } void flecs_observable_fini( ecs_observable_t *observable) { ecs_assert(!ecs_map_is_init(&observable->on_add.event_ids), ECS_INTERNAL_ERROR, NULL); ecs_assert(!ecs_map_is_init(&observable->on_remove.event_ids), ECS_INTERNAL_ERROR, NULL); ecs_assert(!ecs_map_is_init(&observable->on_set.event_ids), ECS_INTERNAL_ERROR, NULL); ecs_assert(!ecs_map_is_init(&observable->un_set.event_ids), ECS_INTERNAL_ERROR, NULL); ecs_sparse_t *events = &observable->events; int32_t i, count = flecs_sparse_count(events); for (i = 0; i < count; i ++) { ecs_event_record_t *er = flecs_sparse_get_dense_t(events, ecs_event_record_t, i); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); (void)er; /* All triggers should've unregistered by now */ ecs_assert(!ecs_map_is_init(&er->event_ids), ECS_INTERNAL_ERROR, NULL); } flecs_sparse_fini(&observable->events); } ecs_event_record_t* flecs_event_record_get( const ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); /* Builtin events*/ if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add); else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove); else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set); else if (event == EcsUnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->un_set); else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); /* User events */ return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); } ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { return er; } er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); er->event = event; return er; } static const ecs_event_record_t* flecs_event_record_get_if( const ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { if (ecs_map_is_init(&er->event_ids)) { return er; } if (er->any) { return er; } if (er->wildcard) { return er; } if (er->wildcard_pair) { return er; } } return NULL; } ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id) { if (!er) { return NULL; } if (id == EcsAny) return er->any; else if (id == EcsWildcard) return er->wildcard; else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; else { if (ecs_map_is_init(&er->event_ids)) { return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); } return NULL; } } static ecs_event_id_record_t* flecs_event_id_record_get_if( const ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (!ider) { return NULL; } if (ider->observer_count) { return ider; } return NULL; } ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (ider) { return ider; } ider = ecs_os_calloc_t(ecs_event_id_record_t); if (id == EcsAny) { return er->any = ider; } else if (id == EcsWildcard) { return er->wildcard = ider; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { return er->wildcard_pair = ider; } ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); ecs_map_insert_ptr(&er->event_ids, id, ider); return ider; } void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id) { if (id == EcsAny) { er->any = NULL; } else if (id == EcsWildcard) { er->wildcard = NULL; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { er->wildcard_pair = NULL; } else { ecs_map_remove(&er->event_ids, id); if (!ecs_map_count(&er->event_ids)) { ecs_map_fini(&er->event_ids); } } } static int32_t flecs_event_observers_get( const ecs_event_record_t *er, ecs_id_t id, ecs_event_id_record_t **iders) { if (!er) { return 0; } /* Populate array with observer sets matching the id */ int32_t count = 0; 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 (ECS_IS_PAIR(id)) { ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); iders[count] = flecs_event_id_record_get_if(er, id_fwc); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id_swc); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id_pwc); count += iders[count] != 0; } else { iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); count += iders[count] != 0; } return count; } bool flecs_observers_exist( ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event) { const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); if (!er) { return false; } return flecs_event_id_record_get_if(er, id) != NULL; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *tgt_idr, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("propagate events/invalidate cache for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Propagate to records of traversable relationships */ ecs_id_record_t *cur = tgt_idr; while ((cur = cur->trav.next)) { cur->reachable.generation ++; /* Invalidate cache */ ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { continue; } /* Get traversed relationship */ ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table)) { continue; } bool owned = flecs_id_record_get_table(idr, table) != NULL; int32_t e, entity_count = ecs_table_count(table); it->table = table; it->other_table = NULL; it->offset = 0; it->count = entity_count; if (entity_count) { it->entities = ecs_vec_first(&table->data.entities); } /* Treat as new event as this could invoke observers again for * different tables. */ int32_t evtx = ++ world->event_id; int32_t ider_i; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav, evtx); if (!owned) { /* Owned takes precedence */ flecs_observers_invoke( world, &ider->self_up, it, table, trav, evtx); } } if (!table->_->traversable_count) { continue; } ecs_record_t **records = ecs_vec_first(&table->data.records); for (e = 0; e < entity_count; e ++) { ecs_record_t *r = records[e]; ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *idr_t = r->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate(world, it, idr, idr_t, iders, ider_count); } } } } ecs_log_pop_3(); } static void flecs_emit_propagate_invalidate_tables( ecs_world_t *world, ecs_id_record_t *tgt_idr) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("invalidate reachable cache for %s", idstr); ecs_os_free(idstr); } /* Invalidate records of traversable relationships */ ecs_id_record_t *cur = tgt_idr; while ((cur = cur->trav.next)) { ecs_reachable_cache_t *rc = &cur->reachable; if (rc->current != rc->generation) { /* Subtree is already marked invalid */ continue; } rc->generation ++; ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { continue; } const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } int32_t e, entity_count = ecs_table_count(table); ecs_record_t **records = ecs_vec_first(&table->data.records); for (e = 0; e < entity_count; e ++) { ecs_id_record_t *idr_t = records[e]->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } } } void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count) { ecs_record_t **recs = ecs_vec_get_t(&table->data.records, ecs_record_t*, offset); int32_t i; for (i = 0; i < count; i ++) { ecs_record_t *record = recs[i]; if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Event is used as target in traversable relationship, propagate */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } static void flecs_override_copy( ecs_world_t *world, ecs_table_t *table, const ecs_type_info_t *ti, void *dst, const void *src, int32_t offset, int32_t count) { void *ptr = dst; ecs_copy_t copy = ti->hooks.copy; ecs_size_t size = ti->size; int32_t i; if (copy) { for (i = 0; i < count; i ++) { copy(ptr, src, count, ti); ptr = ECS_OFFSET(ptr, size); } } else { for (i = 0; i < count; i ++) { ecs_os_memcpy(ptr, src, size); ptr = ECS_OFFSET(ptr, size); } } ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_entity_t *entities = ecs_vec_get_t( &table->data.entities, ecs_entity_t, offset); flecs_invoke_hook(world, table, count, offset, entities, dst, ti->component, ti, EcsOnSet, on_set); } } static void* flecs_override( ecs_iter_t *it, const ecs_type_t *emit_ids, ecs_id_t id, ecs_table_t *table, ecs_id_record_t *idr) { if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { return NULL; } int32_t i = 0, count = emit_ids->count; ecs_id_t *ids = emit_ids->array; for (i = 0; i < count; i ++) { if (ids[i] == id) { /* If an id was both inherited and overridden in the same event * (like what happens during an auto override), we need to copy the * value of the inherited component to the new component. * Also flag to the callee that this component was overridden, so * that an OnSet event can be emmitted for it. * Note that this is different from a component that was overridden * after it was inherited, as this does not change the actual value * of the component for the entity (it is copied from the existing * overridden component), and does not require an OnSet event. */ ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { continue; } int32_t index = tr->column; ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); ecs_column_t *column = &table->data.columns[index]; ecs_size_t size = column->ti->size; return ecs_vec_get(&column->data, size, it->offset); } } return NULL; } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids, int32_t evtx); static void flecs_emit_forward_id( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_entity_t tgt, ecs_table_t *tgt_table, int32_t column, int32_t offset, ecs_entity_t trav, int32_t evtx) { ecs_id_t id = idr->id; ecs_entity_t event = er ? er->event : 0; bool inherit = trav == EcsIsA; bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); ecs_event_id_record_t *iders[5]; ecs_event_id_record_t *iders_onset[5]; /* Skip id if there are no observers for it */ int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); int32_t ider_onset_i, ider_onset_count = 0; if (er_onset) { ider_onset_count = flecs_event_observers_get( er_onset, id, iders_onset); } if (!may_override && (!ider_count && !ider_onset_count)) { return; } it->ids[0] = id; it->sources[0] = tgt; it->event_id = id; it->ptrs[0] = NULL; it->sizes[0] = 0; int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); if (storage_i != -1) { ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *c = &tgt_table->data.columns[storage_i]; it->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset); it->sizes[0] = c->ti->size; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); bool owned = tr != NULL; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav, evtx); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke(world, &ider->self_up, it, table, trav, evtx); } } /* Emit OnSet events for newly inherited components */ if (storage_i != -1) { bool override = false; /* If component was added together with IsA relationship, still emit * OnSet event, as it's a new value for the entity. */ void *base_ptr = it->ptrs[0]; void *ptr = flecs_override(it, emit_ids, id, table, idr); if (ptr) { override = true; it->ptrs[0] = ptr; } if (ider_onset_count) { it->event = er_onset->event; for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; flecs_observers_invoke(world, &ider->up, it, table, trav, evtx); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke( world, &ider->self_up, it, table, trav, evtx); } else if (override) { ecs_entity_t src = it->sources[0]; it->sources[0] = 0; flecs_observers_invoke(world, &ider->self, it, table, 0, evtx); flecs_observers_invoke(world, &ider->self_up, it, table, 0, evtx); it->sources[0] = src; } } it->event = event; it->ptrs[0] = base_ptr; } } } static void flecs_emit_forward_and_cache_id( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_entity_t tgt, ecs_record_t *tgt_record, ecs_table_t *tgt_table, const ecs_table_record_t *tgt_tr, int32_t column, int32_t offset, ecs_vec_t *reachable_ids, ecs_entity_t trav, int32_t evtx) { /* Cache forwarded id for (rel, tgt) pair */ ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator, reachable_ids, ecs_reachable_elem_t); elem->tr = tgt_tr; elem->record = tgt_record; elem->src = tgt; elem->id = idr->id; #ifndef NDEBUG elem->table = tgt_table; #endif ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, tgt, tgt_table, column, offset, trav, evtx); } static int32_t flecs_emit_stack_at( ecs_vec_t *stack, ecs_id_record_t *idr) { int32_t sp = 0, stack_count = ecs_vec_count(stack); ecs_table_t **stack_elems = ecs_vec_first(stack); for (sp = 0; sp < stack_count; sp ++) { ecs_table_t *elem = stack_elems[sp]; if (flecs_id_record_get_table(idr, elem)) { break; } } return sp; } static bool flecs_emit_stack_has( ecs_vec_t *stack, ecs_id_record_t *idr) { return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); } static void flecs_emit_forward_cached_ids( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_reachable_cache_t *rc, ecs_vec_t *reachable_ids, ecs_vec_t *stack, ecs_entity_t trav, int32_t evtx) { ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t i, count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *rc_elem = &elems[i]; const ecs_table_record_t *rc_tr = rc_elem->tr; ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache; ecs_record_t *rc_record = rc_elem->record; ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, rc_elem->src) == rc_record, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(rc_record->table == rc_elem->table, ECS_INTERNAL_ERROR, NULL); if (flecs_emit_stack_has(stack, rc_idr)) { continue; } int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row); flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, rc_idr, rc_elem->src, rc_record, rc_record->table, rc_tr, rc_tr->index, rc_offset, reachable_ids, trav, evtx); } } static void flecs_emit_dump_cache( ecs_world_t *world, const ecs_vec_t *vec) { ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); for (int i = 0; i < ecs_vec_count(vec); i ++) { ecs_reachable_elem_t *elem = &elems[i]; char *idstr = ecs_id_str(world, elem->id); char *estr = ecs_id_str(world, elem->src); ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", idstr, (uint32_t)elem->id, estr, (uint32_t)elem->src, elem->table); ecs_os_free(idstr); ecs_os_free(estr); } if (!ecs_vec_count(vec)) { ecs_dbg_3("- no entries"); } } static void flecs_emit_forward_table_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t tgt, ecs_table_t *tgt_table, ecs_record_t *tgt_record, ecs_id_record_t *tgt_idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids, int32_t evtx) { ecs_allocator_t *a = &world->allocator; int32_t i, id_count = tgt_table->type.count; ecs_id_t *ids = tgt_table->type.array; int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row); int32_t rc_child_offset = ecs_vec_count(reachable_ids); int32_t stack_count = ecs_vec_count(stack); /* If tgt_idr is out of sync but is not the current id record being updated, * keep track so that we can update two records for the cost of one. */ ecs_reachable_cache_t *rc = &tgt_idr->reachable; bool parent_revalidate = (reachable_ids != &rc->ids) && (rc->current != rc->generation); if (parent_revalidate) { ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); } if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("forward events from %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Function may have to copy values from overridden components if an IsA * relationship was added together with other components. */ ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id); bool inherit = trav == EcsIsA; for (i = 0; i < id_count; i ++) { ecs_id_t id = ids[i]; ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; if (inherit && (idr->flags & EcsIdDontInherit)) { continue; } /* Id has the same relationship, traverse to find ids for forwarding */ if (ECS_PAIR_FIRST(id) == trav) { ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, ecs_table_t*); t[0] = tgt_table; ecs_reachable_cache_t *idr_rc = &idr->reachable; if (idr_rc->current == idr_rc->generation) { /* Cache hit, use cached ids to prevent traversing the same * hierarchy multiple times. This especially speeds up code * where (deep) hierarchies are created. */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, id); ecs_dbg_3("forward cached for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, table, idr_rc, reachable_ids, stack, trav, evtx); ecs_log_pop_3(); } else { /* Cache is dirty, traverse upwards */ do { flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, idr, stack, reachable_ids, evtx); if (++i >= id_count) { break; } id = ids[i]; if (ECS_PAIR_FIRST(id) != trav) { break; } } while (true); } ecs_vec_remove_last(stack); continue; } int32_t stack_at = flecs_emit_stack_at(stack, idr); if (parent_revalidate && (stack_at == (stack_count - 1))) { /* If parent id record needs to be revalidated, add id */ ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, ecs_reachable_elem_t); elem->tr = tgt_tr; elem->record = tgt_record; elem->src = tgt; elem->id = idr->id; #ifndef NDEBUG elem->table = tgt_table; #endif } /* Skip id if it's masked by a lower table in the tree */ if (stack_at != stack_count) { continue; } flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, offset, reachable_ids, trav, evtx); } if (parent_revalidate) { /* If this is not the current cache being updated, but it's marked * as out of date, use intermediate results to populate cache. */ int32_t rc_parent_offset = ecs_vec_count(&rc->ids); /* Only add ids that were added for this table */ int32_t count = ecs_vec_count(reachable_ids); count -= rc_child_offset; /* Append ids to any ids that already were added /*/ if (count) { ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, ecs_reachable_elem_t, rc_parent_offset); ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, ecs_reachable_elem_t, rc_child_offset); ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); } rc->current = rc->generation; if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("cache revalidated for %s:", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } } ecs_log_pop_3(); } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids, int32_t evtx) { ecs_id_t id = idr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_generation(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *tgt_record = flecs_entities_try(world, tgt); ecs_table_t *tgt_table; if (!tgt_record || !(tgt_table = tgt_record->table)) { return; } flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, tgt, tgt_table, tgt_record, idr, stack, reachable_ids, evtx); } static void flecs_emit_forward( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, int32_t evtx) { ecs_reachable_cache_t *rc = &idr->reachable; if (rc->current != rc->generation) { /* Cache miss, iterate the tree to find ids to forward */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, idr->id); ecs_dbg_3("reachable cache miss for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); ecs_vec_t stack; ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, idr, &stack, &rc->ids, evtx); it->sources[0] = 0; ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); if (it->event == EcsOnAdd || it->event == EcsOnRemove) { /* Only OnAdd/OnRemove events can validate top-level cache, which * is for the id for which the event is emitted. * The reason for this is that we don't want to validate the cache * while the administration for the mutated entity isn't up to * date yet. */ rc->current = rc->generation; } if (ecs_should_log_3()) { ecs_dbg_3("cache after rebuild:"); flecs_emit_dump_cache(world, &rc->ids); } ecs_log_pop_3(); } else { /* Cache hit, use cached values instead of walking the tree */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, idr->id); ecs_dbg_3("reachable cache hit for %s", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } ecs_entity_t trav = ECS_PAIR_FIRST(idr->id); ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t i, count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *elem = &elems[i]; const ecs_table_record_t *tr = elem->tr; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; ecs_record_t *r = elem->record; ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, elem->src) == r, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); int32_t offset = ECS_RECORD_TO_ROW(r->row); flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, rc_idr, elem->src, r->table, tr->index, offset, trav, evtx); } } } /* The emit function is responsible for finding and invoking the observers * matching the emitted event. The function is also capable of forwarding events * for newly reachable ids (after adding a relationship) and propagating events * downwards. Both capabilities are not just useful in application logic, but * are also an important building block for keeping query caches in sync. */ void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_event_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); ecs_time_t t = {0}; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&t); } const ecs_type_t *ids = desc->ids; ecs_entity_t event = desc->event; ecs_table_t *table = desc->table, *other_table = desc->other_table; int32_t offset = desc->offset; int32_t i, r, count = desc->count; ecs_flags32_t table_flags = table->flags; /* Table events are emitted for internal table operations only, and do not * provide component data and/or entity ids. */ bool table_event = desc->flags & EcsEventTableOnly; if (!count && !table_event) { /* If no count is provided, forward event for all entities in table */ count = ecs_table_count(table) - offset; } /* When the NoOnSet flag is provided, no OnSet/UnSet events should be * generated when new components are inherited. */ bool no_on_set = desc->flags & EcsEventNoOnSet; ecs_id_t ids_cache = 0; void *ptrs_cache = NULL; ecs_size_t sizes_cache = 0; int32_t columns_cache = 0; ecs_entity_t sources_cache = 0; ecs_iter_t it = { .world = stage, .real_world = world, .event = event, .table = table, .field_count = 1, .ids = &ids_cache, .ptrs = &ptrs_cache, .sizes = &sizes_cache, .columns = &columns_cache, .sources = &sources_cache, .other_table = other_table, .offset = offset, .count = count, .param = ECS_CONST_CAST(void*, desc->param), .flags = desc->flags | EcsIterIsValid }; /* 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_observable_t *observable = ecs_get_observable(desc->observable); ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); /* Event records contain all observers for a specific event. In addition to * the emitted event, also request data for the Wildcard event (for * observers subscribing to the wildcard event), OnSet and UnSet events. The * latter to are used for automatically emitting OnSet/UnSet events for * inherited components, for example when an IsA relationship is added to an * entity. This doesn't add much overhead, as fetching records is cheap for * builtin event types. */ const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); const ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet); ecs_data_t *storage = NULL; ecs_column_t *columns = NULL; if (count) { storage = &table->data; columns = storage->columns; it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset); } int32_t id_count = ids->count; ecs_id_t *id_array = ids->array; /* If a table has IsA relationships, OnAdd/OnRemove events can trigger * (un)overriding a component. When a component is overridden its value is * initialized with the value of the overridden component. */ bool can_override = count && (table_flags & EcsTableHasIsA) && ( (event == EcsOnAdd) || (event == EcsOnRemove)); /* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove * event) this will cause the components of the target entity to be * propagated to the source entity. This makes it possible for observers to * get notified of any new reachable components though the relationship. */ bool can_forward = event != EcsOnSet; /* Set if event has been propagated */ bool propagated = false; /* Does table has observed entities */ bool has_observed = table_flags & EcsTableHasTraversable; /* When a relationship is removed, the events reachable through that * relationship should emit UnSet events. This is part of the behavior that * allows observers to be agnostic of whether a component is inherited. */ bool can_unset = count && (event == EcsOnRemove) && !no_on_set; ecs_event_id_record_t *iders[5] = {0}; int32_t unset_count = 0; repeat_event: /* This is the core event logic, which is executed for each event. By * default this is just the event kind from the ecs_event_desc_t struct, but * can also include the Wildcard and UnSet events. The latter is emitted as * counterpart to OnSet, for any removed ids associated with data. */ for (i = 0; i < id_count; i ++) { /* Emit event for each id passed to the function. In most cases this * will just be one id, like a component that was added, removed or set. * In some cases events are emitted for multiple ids. * * One example is when an id was added with a "With" property, or * inheriting from a prefab with overrides. In these cases an entity is * moved directly to the archetype with the additional components. */ ecs_id_record_t *idr = NULL; const ecs_type_info_t *ti = NULL; ecs_id_t id = id_array[i]; int32_t ider_i, ider_count = 0; bool is_pair = ECS_IS_PAIR(id); void *override_ptr = NULL; ecs_entity_t base = 0; /* Check if this id is a pair of an traversable relationship. If so, we * may have to forward ids from the pair's target. */ if ((can_forward && is_pair) || can_override) { idr = flecs_query_id_record_get(world, id); ecs_flags32_t idr_flags = idr->flags; if (is_pair && (idr_flags & EcsIdTraversable)) { const ecs_event_record_t *er_fwd = NULL; if (ECS_PAIR_FIRST(id) == EcsIsA) { if (event == EcsOnAdd) { if (!world->stages[0].base) { /* Adding an IsA relationship can trigger prefab * instantiation, which can instantiate prefab * hierarchies for the entity to which the * relationship was added. */ ecs_entity_t tgt = ECS_PAIR_SECOND(id); /* Setting this value prevents flecs_instantiate * from being called recursively, in case prefab * children also have IsA relationships. */ world->stages[0].base = tgt; flecs_instantiate(world, tgt, table, offset, count); world->stages[0].base = 0; } /* Adding an IsA relationship will emit OnSet events for * any new reachable components. */ er_fwd = er_onset; } else if (event == EcsOnRemove) { /* Vice versa for removing an IsA relationship. */ er_fwd = er_unset; } } /* Forward events for components from pair target */ flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr, evtx); } if (can_override && (!(idr_flags & EcsIdDontInherit))) { /* Initialize overridden components with value from base */ ti = idr->type_info; if (ti) { ecs_table_record_t *base_tr = NULL; int32_t base_column = ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, &base, NULL, &base_tr); if (base_column != -1) { /* Base found with component */ ecs_table_t *base_table = base_tr->hdr.table; base_column = base_tr->column; ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL); ecs_record_t *base_r = flecs_entities_get(world, base); ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL); int32_t base_row = ECS_RECORD_TO_ROW(base_r->row); ecs_vec_t *base_v = &base_table->data.columns[base_column].data; override_ptr = ecs_vec_get(base_v, ti->size, base_row); } } } } if (er) { /* Get observer sets for id. There can be multiple sets of matching * observers, in case an observer matches for wildcard ids. For * example, both observers for (ChildOf, p) and (ChildOf, *) would * match an event for (ChildOf, p). */ ider_count = flecs_event_observers_get(er, id, iders); idr = idr ? idr : flecs_query_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); } if (can_unset) { /* Increase UnSet count in case this is a component (has data). This * will cause the event loop to be ran again as UnSet event. */ idr = idr ? idr : flecs_query_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); unset_count += (idr->type_info != NULL); } if (!ider_count && !override_ptr) { /* If nothing more to do for this id, early out */ continue; } ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr == NULL) { /* When a single batch contains multiple add's for an exclusive * relationship, it's possible that an id was in the added list * that is no longer available for the entity. */ continue; } int32_t column = tr->index, storage_i; it.columns[0] = column + 1; it.ptrs[0] = NULL; it.sizes[0] = 0; it.event_id = id; it.ids[0] = id; if (count) { storage_i = tr->column; if (storage_i != -1) { /* If this is a component, fetch pointer & size */ ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *c = &columns[storage_i]; ecs_size_t size = c->ti->size; void *ptr = ecs_vec_get(&c->data, size, offset); it.sizes[0] = size; if (override_ptr) { if (event == EcsOnAdd) { /* If this is a new override, initialize the component * with the value of the overridden component. */ flecs_override_copy( world, table, ti, ptr, override_ptr, offset, count); } else if (er_onset) { /* If an override was removed, this re-exposes the * overridden component. Because this causes the actual * (now inherited) value of the component to change, an * OnSet event must be emitted for the base component.*/ ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); ecs_event_id_record_t *iders_set[5] = {0}; int32_t ider_set_i, ider_set_count = flecs_event_observers_get(er_onset, id, iders_set); if (ider_set_count) { /* Set the source temporarily to the base and base * component pointer. */ it.sources[0] = base; it.ptrs[0] = ptr; for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { ecs_event_id_record_t *ider = iders_set[ider_set_i]; flecs_observers_invoke(world, &ider->self_up, &it, table, EcsIsA, evtx); flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA, evtx); } it.sources[0] = 0; } } } it.ptrs[0] = ptr; } else { if (it.event == EcsUnSet) { /* Only valid for components, not tags */ continue; } } } /* Actually invoke observers for this event/id */ for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->self, &it, table, 0, evtx); flecs_observers_invoke(world, &ider->self_up, &it, table, 0, evtx); } if (!ider_count || !count || !has_observed) { continue; } /* If event is propagated, we don't have to manually invalidate entities * lower in the tree(s). */ propagated = true; /* The table->traversable_count value indicates if the table contains any * entities that are used as targets of traversable relationships. If the * entity/entities for which the event was generated is used as such a * target, events must be propagated downwards. */ ecs_entity_t *entities = it.entities; it.entities = NULL; ecs_record_t **recs = ecs_vec_get_t(&storage->records, ecs_record_t*, offset); for (r = 0; r < count; r ++) { ecs_record_t *record = recs[r]; if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Entity is used as target in traversable pairs, propagate */ ecs_entity_t e = entities[r]; it.sources[0] = e; flecs_emit_propagate(world, &it, idr, idr_t, iders, ider_count); } } it.table = table; it.other_table = other_table; it.entities = entities; it.count = count; it.offset = offset; it.sources[0] = 0; } if (count && can_forward && has_observed && !propagated) { flecs_emit_propagate_invalidate(world, table, offset, count); } can_override = false; /* Don't override twice */ can_unset = false; /* Don't unset twice */ can_forward = false; /* Don't forward twice */ if (unset_count && er_unset && (er != er_unset)) { /* Repeat event loop for UnSet event */ unset_count = 0; er = er_unset; it.event = EcsUnSet; goto repeat_event; } if (wcer && er != wcer) { /* Repeat event loop for Wildcard event */ er = wcer; it.event = event; goto repeat_event; } error: 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)); if (desc->entity) { ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, desc->entity); ecs_table_t *table; if (!r || !(table = r->table)) { /* Empty entities can't trigger observers */ return; } desc->table = table; desc->offset = ECS_RECORD_TO_ROW(r->row); desc->count = 1; } if (!desc->observable) { desc->observable = world; } flecs_emit(world, stage, desc); } /** * @file observer.c * @brief Observer implementation. * * The observer implementation contains functions for creating, deleting and * invoking observers. The code is split up into single-term observers and * multi-term observers. Multi-term observers are created from multiple single- * term observers. */ #include static ecs_entity_t flecs_get_observer_event( ecs_term_t *term, ecs_entity_t event) { /* If operator is Not, reverse the event */ if (term->oper == EcsNot) { if (event == EcsOnAdd) { event = EcsOnRemove; } else if (event == EcsOnRemove) { event = EcsOnAdd; } } return event; } static ecs_flags32_t flecs_id_flag_for_event( ecs_entity_t e) { if (e == EcsOnAdd) { return EcsIdHasOnAdd; } if (e == EcsOnRemove) { return EcsIdHasOnRemove; } if (e == EcsOnSet) { return EcsIdHasOnSet; } if (e == EcsUnSet) { return EcsIdHasUnSet; } if (e == EcsOnTableFill) { return EcsIdHasOnTableFill; } if (e == EcsOnTableEmpty) { return EcsIdHasOnTableEmpty; } if (e == EcsOnTableCreate) { return EcsIdHasOnTableCreate; } if (e == EcsOnTableDelete) { return EcsIdHasOnTableDelete; } return 0; } static void flecs_inc_observer_count( ecs_world_t *world, ecs_entity_t event, ecs_event_record_t *evt, ecs_id_t id, int32_t value) { ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); int32_t result = idt->observer_count += value; if (result == 1) { /* Notify framework that there are observers for the event/id. This * allows parts of the code to skip event evaluation early */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags |= flags; } } } else if (result == 0) { /* Ditto, but the reverse */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableNoTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags &= ~flags; } } flecs_event_id_record_remove(evt, id); ecs_os_free(idt); } } static void flecs_register_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer, size_t offset) { ecs_id_t term_id = observer->register_id; ecs_term_t *term = &observer->filter.terms[0]; ecs_entity_t trav = term->src.trav; int i; for (i = 0; i < observer->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, observer->events[i]); /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_ensure(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id for event */ ecs_event_id_record_t *idt = flecs_event_id_record_ensure( world, er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *observers = ECS_OFFSET(idt, offset); ecs_map_init_w_params_if(observers, &world->allocators.ptr); ecs_map_insert_ptr(observers, observer->filter.entity, observer); flecs_inc_observer_count(world, event, er, term_id, 1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), 1); } } } static void flecs_uni_observer_register( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer) { ecs_term_t *term = &observer->filter.terms[0]; ecs_flags32_t flags = term->src.flags; if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_register_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_register_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL); flecs_register_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, up)); } } static void flecs_unregister_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer, size_t offset) { ecs_id_t term_id = observer->register_id; ecs_term_t *term = &observer->filter.terms[0]; ecs_entity_t trav = term->src.trav; int i; for (i = 0; i < observer->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, observer->events[i]); /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_get(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id */ ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *id_observers = ECS_OFFSET(idt, offset); ecs_map_remove(id_observers, observer->filter.entity); if (!ecs_map_count(id_observers)) { ecs_map_fini(id_observers); } flecs_inc_observer_count(world, event, er, term_id, -1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), -1); } } } static void flecs_unregister_observer( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer) { ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); if (!observer->filter.terms) { ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); return; } ecs_term_t *term = &observer->filter.terms[0]; ecs_flags32_t flags = term->src.flags; if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_unregister_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_unregister_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { flecs_unregister_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, up)); } } static bool flecs_ignore_observer( ecs_observer_t *observer, ecs_table_t *table, int32_t evtx) { ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *last_event_id = observer->last_event_id; if (last_event_id && last_event_id[0] == evtx) { return true; } ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags; bool result = (table_flags & EcsTableIsPrefab) && !(filter_flags & EcsFilterMatchPrefab); result = result || ((table_flags & EcsTableIsDisabled) && !(filter_flags & EcsFilterMatchDisabled)); return result; } static bool flecs_is_simple_result( ecs_iter_t *it) { return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); } static void flecs_observer_invoke( ecs_world_t *world, ecs_iter_t *it, ecs_observer_t *observer, ecs_iter_action_t callback, int32_t term_index, bool simple_result) { ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); if (ecs_should_log_3()) { char *path = ecs_get_fullpath(world, it->system); ecs_dbg_3("observer: invoke %s", path); ecs_os_free(path); } ecs_log_push_3(); world->info.observers_ran_frame ++; ecs_filter_t *filter = &observer->filter; ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL); ecs_term_t *term = &filter->terms[term_index]; if (term->oper != EcsNot) { ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), ECS_INTERNAL_ERROR, NULL); } bool instanced = filter->flags & EcsFilterIsInstanced; bool match_this = filter->flags & EcsFilterMatchThis; bool table_only = it->flags & EcsIterTableOnly; if (match_this && (simple_result || instanced || table_only)) { callback(it); } else { ecs_entity_t observer_src = term->src.id; if (observer_src && !(term->src.flags & EcsIsEntity)) { observer_src = 0; } ecs_entity_t *entities = it->entities; int32_t i, count = it->count; ecs_entity_t src = it->sources[0]; it->count = 1; for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; it->entities = &e; if (!observer_src) { callback(it); } else if (observer_src == e) { ecs_entity_t dummy = 0; it->entities = &dummy; if (!src) { it->sources[0] = e; } callback(it); it->sources[0] = src; break; } } it->entities = entities; it->count = count; } ecs_log_pop_3(); } static void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; it->ctx = o->ctx; it->callback = o->callback; if (ecs_should_log_3()) { char *path = ecs_get_fullpath(it->world, it->system); ecs_dbg_3("observer %s", path); ecs_os_free(path); } ecs_log_push_3(); flecs_observer_invoke(it->real_world, it, o, o->callback, 0, flecs_is_simple_result(it)); ecs_log_pop_3(); } static void flecs_uni_observer_invoke( ecs_world_t *world, ecs_observer_t *observer, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, int32_t evtx, bool simple_result) { ecs_filter_t *filter = &observer->filter; ecs_term_t *term = &filter->terms[0]; if (flecs_ignore_observer(observer, table, evtx)) { return; } ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); if (trav && term->src.trav != trav) { return; } bool is_filter = term->inout == EcsInOutNone; ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); it->system = observer->filter.entity; it->ctx = observer->ctx; it->binding_ctx = observer->binding_ctx; it->term_index = observer->term_index; it->terms = term; ecs_entity_t event = it->event; it->event = flecs_get_observer_event(term, event); if (observer->run) { it->next = flecs_default_observer_next_callback; it->callback = flecs_default_uni_observer_run_callback; it->ctx = observer; observer->run(it); } else { ecs_iter_action_t callback = observer->callback; it->callback = callback; flecs_observer_invoke(world, it, observer, callback, 0, simple_result); } it->event = event; } void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, int32_t evtx) { if (ecs_map_is_init(observers)) { ecs_table_lock(it->world, table); bool simple_result = flecs_is_simple_result(it); ecs_map_iter_t oit = ecs_map_iter(observers); while (ecs_map_next(&oit)) { ecs_observer_t *o = ecs_map_ptr(&oit); ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); flecs_uni_observer_invoke(world, o, it, table, trav, evtx, simple_result); } ecs_table_unlock(it->world, table); } } static bool flecs_multi_observer_invoke(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; ecs_world_t *world = it->real_world; if (o->last_event_id[0] == world->event_id) { /* Already handled this event */ return false; } o->last_event_id[0] = world->event_id; ecs_iter_t user_it = *it; user_it.field_count = o->filter.field_count; user_it.terms = o->filter.terms; user_it.flags = 0; ECS_BIT_COND(user_it.flags, EcsIterNoData, ECS_BIT_IS_SET(o->filter.flags, EcsFilterNoData)); user_it.ids = NULL; user_it.columns = NULL; user_it.sources = NULL; user_it.sizes = NULL; user_it.ptrs = NULL; flecs_iter_init(it->world, &user_it, flecs_iter_cache_all); user_it.flags |= (it->flags & EcsIterTableOnly); ecs_table_t *table = it->table; ecs_table_t *prev_table = it->other_table; int32_t pivot_term = it->term_index; ecs_term_t *term = &o->filter.terms[pivot_term]; int32_t column = it->columns[0]; if (term->oper == EcsNot) { table = it->other_table; prev_table = it->table; } if (!table) { table = &world->store.root; } if (!prev_table) { prev_table = &world->store.root; } if (column < 0) { column = -column; } user_it.columns[0] = 0; user_it.columns[pivot_term] = column; user_it.sources[pivot_term] = it->sources[0]; user_it.sizes = o->filter.sizes; if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, user_it.columns, user_it.sources, NULL, NULL, false, pivot_term, user_it.flags)) { /* Monitor observers only invoke when the filter matches for the first * time with an entity */ if (o->is_monitor) { if (flecs_filter_match_table(world, &o->filter, prev_table, NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) { goto done; } } /* While filter matching needs to be reversed for a Not term, the * component data must be fetched from the table we got notified for. * Repeat the matching process for the non-matching table so we get the * correct column ids and sources, which we need for populate_data */ if (term->oper == EcsNot) { flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, user_it.columns, user_it.sources, NULL, NULL, false, -1, user_it.flags | EcsFilterPopulate); } flecs_iter_populate_data(world, &user_it, it->table, it->offset, it->count, user_it.ptrs); user_it.ptrs[pivot_term] = it->ptrs[0]; user_it.ids[pivot_term] = it->event_id; user_it.system = o->filter.entity; user_it.term_index = pivot_term; user_it.ctx = o->ctx; user_it.binding_ctx = o->binding_ctx; user_it.field_count = o->filter.field_count; user_it.callback = o->callback; flecs_iter_validate(&user_it); ecs_table_lock(it->world, table); flecs_observer_invoke(world, &user_it, o, o->callback, pivot_term, flecs_is_simple_result(&user_it)); ecs_table_unlock(it->world, table); ecs_iter_fini(&user_it); return true; } done: ecs_iter_fini(&user_it); return false; } bool ecs_observer_default_run_action(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; if (o->is_multi) { return flecs_multi_observer_invoke(it); } else { it->ctx = o->ctx; ecs_table_lock(it->world, it->table); flecs_observer_invoke(it->real_world, it, o, o->callback, 0, flecs_is_simple_result(it)); ecs_table_unlock(it->world, it->table); return true; } } static void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { flecs_multi_observer_invoke(it); } /* For convenience, so applications can (in theory) use a single run callback * that uses ecs_iter_next to iterate results */ bool flecs_default_observer_next_callback(ecs_iter_t *it) { if (it->interrupted_by) { return false; } else { /* Use interrupted_by to signal the next iteration must return false */ it->interrupted_by = it->system; return true; } } /* Run action for children of multi observer */ static void flecs_multi_observer_builtin_run(ecs_iter_t *it) { ecs_observer_t *observer = it->ctx; ecs_run_action_t run = observer->run; if (run) { it->next = flecs_default_observer_next_callback; it->callback = flecs_default_multi_observer_run_callback; it->interrupted_by = 0; run(it); } else { flecs_multi_observer_invoke(it); } } static void flecs_uni_observer_yield_existing( ecs_world_t *world, ecs_observer_t *observer) { ecs_iter_action_t callback = observer->callback; ecs_defer_begin(world); /* If yield existing is enabled, observer for each thing that matches * the event, if the event is iterable. */ int i, count = observer->event_count; for (i = 0; i < count; i ++) { ecs_entity_t evt = observer->events[i]; const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); if (!iterable) { continue; } ecs_iter_t it; iterable->init(world, world, &it, &observer->filter.terms[0]); it.system = observer->filter.entity; it.ctx = observer->ctx; it.binding_ctx = observer->binding_ctx; it.event = evt; ecs_iter_next_action_t next = it.next; ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); while (next(&it)) { it.event_id = it.ids[0]; callback(&it); } } ecs_defer_end(world); } static void flecs_multi_observer_yield_existing( ecs_world_t *world, ecs_observer_t *observer) { ecs_run_action_t run = observer->run; if (!run) { run = flecs_default_multi_observer_run_callback; } ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_defer_begin(world); int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); if (pivot_term < 0) { return; } /* If yield existing is enabled, invoke for each thing that matches * the event, if the event is iterable. */ int i, count = observer->event_count; for (i = 0; i < count; i ++) { ecs_entity_t evt = observer->events[i]; const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); if (!iterable) { continue; } ecs_iter_t it; iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); it.terms = observer->filter.terms; it.field_count = 1; it.term_index = pivot_term; it.system = observer->filter.entity; it.ctx = observer; it.binding_ctx = observer->binding_ctx; it.event = evt; ecs_iter_next_action_t next = it.next; ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); while (next(&it)) { it.event_id = it.ids[0]; run(&it); world->event_id ++; } } ecs_defer_end(world); } static int flecs_uni_observer_init( ecs_world_t *world, ecs_observer_t *observer, const ecs_observer_desc_t *desc) { ecs_term_t *term = &observer->filter.terms[0]; observer->last_event_id = desc->last_event_id; if (!observer->last_event_id) { observer->last_event_id = &observer->last_event_id_storage; } observer->register_id = flecs_from_public_id(world, term->id); term->field_index = desc->term_index; if (ecs_id_is_tag(world, term->id)) { /* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */ int32_t e, count = observer->event_count; for (e = 0; e < count; e ++) { if (observer->events[e] == EcsOnSet) { observer->events[e] = EcsOnAdd; } else if (observer->events[e] == EcsUnSet) { observer->events[e] = EcsOnRemove; } } } flecs_uni_observer_register(world, observer->observable, observer); if (desc->yield_existing) { flecs_uni_observer_yield_existing(world, observer); } return 0; } static int flecs_multi_observer_init( ecs_world_t *world, ecs_observer_t *observer, const ecs_observer_desc_t *desc) { /* Create last event id for filtering out the same event that arrives from * more than one term */ observer->last_event_id = ecs_os_calloc_t(int32_t); /* Mark observer as multi observer */ observer->is_multi = true; /* Create a child observer for each term in the filter */ ecs_filter_t *filter = &observer->filter; ecs_observer_desc_t child_desc = *desc; child_desc.last_event_id = observer->last_event_id; child_desc.run = NULL; child_desc.callback = flecs_multi_observer_builtin_run; child_desc.ctx = observer; child_desc.ctx_free = NULL; child_desc.filter.expr = NULL; child_desc.filter.terms_buffer = NULL; child_desc.filter.terms_buffer_count = 0; child_desc.binding_ctx = NULL; child_desc.binding_ctx_free = NULL; child_desc.yield_existing = false; ecs_os_zeromem(&child_desc.entity); ecs_os_zeromem(&child_desc.filter.terms); ecs_os_memcpy_n(child_desc.events, observer->events, ecs_entity_t, observer->event_count); int i, term_count = filter->term_count; bool optional_only = filter->flags & EcsFilterMatchThis; for (i = 0; i < term_count; i ++) { if (filter->terms[i].oper != EcsOptional) { if (ecs_term_match_this(&filter->terms[i])) { optional_only = false; } } } if (filter->flags & EcsFilterMatchPrefab) { child_desc.filter.flags |= EcsFilterMatchPrefab; } if (filter->flags & EcsFilterMatchDisabled) { child_desc.filter.flags |= EcsFilterMatchDisabled; } /* Create observers as children of observer */ ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); for (i = 0; i < term_count; i ++) { if (filter->terms[i].src.flags & EcsFilter) { continue; } ecs_term_t *term = &child_desc.filter.terms[0]; child_desc.term_index = filter->terms[i].field_index; *term = filter->terms[i]; ecs_oper_kind_t oper = term->oper; ecs_id_t id = term->id; /* AndFrom & OrFrom terms insert multiple observers */ if (oper == EcsAndFrom || oper == EcsOrFrom) { const ecs_type_t *type = ecs_get_type(world, id); int32_t ti, ti_count = type->count; ecs_id_t *ti_ids = type->array; /* Correct operator will be applied when an event occurs, and * the observer is evaluated on the observer source */ term->oper = EcsAnd; for (ti = 0; ti < ti_count; ti ++) { ecs_id_t ti_id = ti_ids[ti]; ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); if (idr->flags & EcsIdDontInherit) { continue; } term->first.name = NULL; term->first.id = ti_ids[ti]; term->id = ti_ids[ti]; if (ecs_observer_init(world, &child_desc) == 0) { goto error; } } continue; } /* Single component observers never use OR */ if (oper == EcsOr) { term->oper = EcsAnd; } /* If observer only contains optional terms, match everything */ if (optional_only) { term->id = EcsAny; term->first.id = EcsAny; term->src.id = EcsThis; term->src.flags = EcsIsVariable; term->second.id = 0; } else if (term->oper == EcsOptional) { continue; } if (ecs_observer_init(world, &child_desc) == 0) { goto error; } if (optional_only) { break; } } ecs_set_scope(world, old_scope); if (desc->yield_existing) { flecs_multi_observer_yield_existing(world, observer); } return 0; error: return -1; } ecs_entity_t ecs_observer_init( ecs_world_t *world, const ecs_observer_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); ecs_entity_t entity = desc->entity; if (!entity) { entity = ecs_new(world, 0); } EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t); if (!poly->poly) { ecs_check(desc->callback != NULL || desc->run != NULL, ECS_INVALID_OPERATION, NULL); ecs_observer_t *observer = ecs_poly_new(ecs_observer_t); ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini; /* Make writeable copy of filter desc so that we can set name. This will * make debugging easier, as any error messages related to creating the * filter will have the name of the observer. */ ecs_filter_desc_t filter_desc = desc->filter; filter_desc.entity = entity; ecs_filter_t *filter = filter_desc.storage = &observer->filter; *filter = ECS_FILTER_INIT; /* Parse filter */ if (ecs_filter_init(world, &filter_desc) == NULL) { flecs_observer_fini(observer); return 0; } /* Observer must have at least one term */ ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL); poly->poly = observer; ecs_observable_t *observable = desc->observable; if (!observable) { observable = ecs_get_observable(world); } observer->run = desc->run; observer->callback = desc->callback; observer->ctx = desc->ctx; observer->binding_ctx = desc->binding_ctx; observer->ctx_free = desc->ctx_free; observer->binding_ctx_free = desc->binding_ctx_free; observer->term_index = desc->term_index; observer->observable = observable; /* Check if observer is monitor. Monitors are created as multi observers * since they require pre/post checking of the filter to test if the * entity is entering/leaving the monitor. */ int i; for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) { ecs_entity_t event = desc->events[i]; if (!event) { break; } if (event == EcsMonitor) { /* Monitor event must be first and last event */ ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); observer->events[0] = EcsOnAdd; observer->events[1] = EcsOnRemove; observer->event_count ++; observer->is_monitor = true; } else { observer->events[i] = event; } observer->event_count ++; } /* Observer must have at least one event */ ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); bool multi = false; if (filter->term_count == 1 && !desc->last_event_id) { ecs_term_t *term = &filter->terms[0]; /* If the filter has a single term but it is a *From operator, we * need to create a multi observer */ multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); /* An observer with only optional terms is a special case that is * only handled by multi observers */ multi |= term->oper == EcsOptional; } if (filter->term_count == 1 && !observer->is_monitor && !multi) { if (flecs_uni_observer_init(world, observer, desc)) { goto error; } } else { if (flecs_multi_observer_init(world, observer, desc)) { goto error; } } if (ecs_get_name(world, entity)) { ecs_trace("#[green]observer#[reset] %s created", ecs_get_name(world, entity)); } } else { ecs_observer_t *observer = ecs_poly(poly->poly, ecs_observer_t); if (desc->run) { observer->run = desc->run; } if (desc->callback) { observer->callback = desc->callback; } if (observer->ctx_free) { if (observer->ctx && observer->ctx != desc->ctx) { observer->ctx_free(observer->ctx); } } if (observer->binding_ctx_free) { if (observer->binding_ctx && observer->binding_ctx != desc->binding_ctx) { observer->binding_ctx_free(observer->binding_ctx); } } if (desc->ctx) { observer->ctx = desc->ctx; } if (desc->binding_ctx) { observer->binding_ctx = desc->binding_ctx; } if (desc->ctx_free) { observer->ctx_free = desc->ctx_free; } if (desc->binding_ctx_free) { observer->binding_ctx_free = desc->binding_ctx_free; } } ecs_poly_modified(world, entity, ecs_observer_t); return entity; error: ecs_delete(world, entity); return 0; } void* ecs_observer_get_ctx( const ecs_world_t *world, ecs_entity_t observer) { const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); if (o) { ecs_poly_assert(o->poly, ecs_observer_t); return ((ecs_observer_t*)o->poly)->ctx; } else { return NULL; } } void* ecs_observer_get_binding_ctx( const ecs_world_t *world, ecs_entity_t observer) { const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); if (o) { ecs_poly_assert(o->poly, ecs_observer_t); return ((ecs_observer_t*)o->poly)->binding_ctx; } else { return NULL; } } void flecs_observer_fini( ecs_observer_t *observer) { if (observer->is_multi) { ecs_os_free(observer->last_event_id); } else { if (observer->filter.term_count) { flecs_unregister_observer( observer->filter.world, observer->observable, observer); } else { /* Observer creation failed while creating filter */ } } /* Cleanup filters */ ecs_filter_fini(&observer->filter); /* Cleanup context */ if (observer->ctx_free) { observer->ctx_free(observer->ctx); } if (observer->binding_ctx_free) { observer->binding_ctx_free(observer->binding_ctx); } ecs_poly_free(observer, ecs_observer_t); } /** * @file os_api.c * @brief Operating system abstraction API. * * The OS API implements an overridable interface for implementing functions * that are operating system specific, in addition to a number of hooks which * allow for customization by the user, like logging. */ #include #include void ecs_os_api_impl(ecs_os_api_t *api); static bool ecs_os_api_initialized = false; static bool ecs_os_api_initializing = false; static int ecs_os_api_init_count = 0; #ifndef __EMSCRIPTEN__ ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; #else /* Disable colors by default for emscripten */ ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; #endif int64_t ecs_os_api_malloc_count = 0; int64_t ecs_os_api_realloc_count = 0; int64_t ecs_os_api_calloc_count = 0; int64_t ecs_os_api_free_count = 0; void ecs_os_set_api( ecs_os_api_t *os_api) { if (!ecs_os_api_initialized) { ecs_os_api = *os_api; ecs_os_api_initialized = true; } } ecs_os_api_t ecs_os_get_api(void) { return ecs_os_api; } void ecs_os_init(void) { if (!ecs_os_api_initialized) { ecs_os_set_api_defaults(); } if (!(ecs_os_api_init_count ++)) { if (ecs_os_api.init_) { ecs_os_api.init_(); } } } void ecs_os_fini(void) { if (!--ecs_os_api_init_count) { if (ecs_os_api.fini_) { ecs_os_api.fini_(); } } } /* Assume every non-glibc Linux target has no execinfo. This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ #if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__) #define HAVE_EXECINFO 0 #elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) #define HAVE_EXECINFO 1 #else #define HAVE_EXECINFO 0 #endif #if HAVE_EXECINFO #include #define ECS_BT_BUF_SIZE 100 void flecs_dump_backtrace( FILE *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( FILE *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; if (level >= 0) { stream = stdout; } else { stream = stderr; } bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; time_t now = 0; if (deltatime) { now = time(NULL); int64_t delta = 0; if (ecs_os_api.log_last_timestamp_) { delta = now - ecs_os_api.log_last_timestamp_; } ecs_os_api.log_last_timestamp_ = (int64_t)now; if (delta) { if (delta < 10) { fputs(" ", stream); } if (delta < 100) { fputs(" ", stream); } char time_buf[20]; ecs_os_sprintf(time_buf, "%u", (uint32_t)delta); fputs("+", stream); fputs(time_buf, stream); fputs(" ", stream); } else { fputs(" ", stream); } } if (timestamp) { if (!now) { now = time(NULL); } char time_buf[20]; ecs_os_sprintf(time_buf, "%u", (uint32_t)now); fputs(time_buf, stream); fputs(" ", stream); } if (level >= 4) { if (use_colors) fputs(ECS_NORMAL, stream); fputs("jrnl", stream); } else if (level >= 0) { if (level == 0) { if (use_colors) fputs(ECS_MAGENTA, stream); } else { if (use_colors) fputs(ECS_GREY, stream); } fputs("info", stream); } else if (level == -2) { if (use_colors) fputs(ECS_YELLOW, stream); fputs("warning", stream); } else if (level == -3) { if (use_colors) fputs(ECS_RED, stream); fputs("error", stream); } else if (level == -4) { if (use_colors) fputs(ECS_RED, stream); fputs("fatal", stream); } if (use_colors) fputs(ECS_NORMAL, stream); fputs(": ", stream); if (level >= 0) { if (ecs_os_api.log_indent_) { char indent[32]; int i, indent_count = ecs_os_api.log_indent_; if (indent_count > 15) indent_count = 15; for (i = 0; i < indent_count; i ++) { indent[i * 2] = '|'; indent[i * 2 + 1] = ' '; } if (ecs_os_api.log_indent_ != indent_count) { indent[i * 2 - 2] = '+'; } indent[i * 2] = '\0'; fputs(indent, stream); } } if (level < 0) { if (file) { const char *file_ptr = strrchr(file, '/'); if (!file_ptr) { file_ptr = strrchr(file, '\\'); } if (file_ptr) { file = file_ptr + 1; } fputs(file, stream); fputs(": ", stream); } if (line) { fprintf(stream, "%d: ", line); } } fputs(msg, stream); fputs("\n", stream); if (level == -4) { flecs_dump_backtrace(stream); } } void ecs_os_dbg( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(1, file, line, msg); } } void ecs_os_trace( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(0, file, line, msg); } } void ecs_os_warn( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-2, file, line, msg); } } void ecs_os_err( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-3, file, line, msg); } } void ecs_os_fatal( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-4, file, line, msg); } } static void ecs_os_gettime(ecs_time_t *time) { ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); uint64_t now = ecs_os_now(); uint64_t sec = now / 1000000000; assert(sec < UINT32_MAX); assert((now - sec * 1000000000) < UINT32_MAX); time->sec = (uint32_t)sec; time->nanosec = (uint32_t)(now - sec * 1000000000); } static void* ecs_os_api_malloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_malloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); return malloc((size_t)size); } static void* ecs_os_api_calloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_calloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); return calloc(1, (size_t)size); } static void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_os_linc(&ecs_os_api_realloc_count); } else { /* If not actually reallocing, treat as malloc */ ecs_os_linc(&ecs_os_api_malloc_count); } return realloc(ptr, (size_t)size); } static void ecs_os_api_free(void *ptr) { if (ptr) { ecs_os_linc(&ecs_os_api_free_count); } free(ptr); } static char* ecs_os_api_strdup(const char *str) { if (str) { int len = ecs_os_strlen(str); char *result = ecs_os_malloc(len + 1); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcpy(result, str); return result; } else { return NULL; } } void ecs_os_strset(char **str, const char *value) { char *old = str[0]; str[0] = ecs_os_strdup(value); ecs_os_free(old); } /* Replace dots with underscores */ static char *module_file_base(const char *module, char sep) { char *base = ecs_os_strdup(module); ecs_size_t i, len = ecs_os_strlen(base); for (i = 0; i < len; i ++) { if (base[i] == '.') { base[i] = sep; } } return base; } static char* ecs_os_api_module_to_dl(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with underscores + OS library extension */ char *file_base = module_file_base(module, '_'); # if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".so"); # elif defined(ECS_TARGET_DARWIN) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dylib"); # elif defined(ECS_TARGET_WINDOWS) ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dll"); # endif ecs_os_free(file_base); return ecs_strbuf_get(&lib); } static char* ecs_os_api_module_to_etc(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with dashes + /etc */ char *file_base = module_file_base(module, '-'); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, "/etc"); ecs_os_free(file_base); return ecs_strbuf_get(&lib); } void ecs_os_set_api_defaults(void) { /* Don't overwrite if already initialized */ if (ecs_os_api_initialized != 0) { return; } if (ecs_os_api_initializing != 0) { return; } ecs_os_api_initializing = true; /* Memory management */ ecs_os_api.malloc_ = ecs_os_api_malloc; ecs_os_api.free_ = ecs_os_api_free; ecs_os_api.realloc_ = ecs_os_api_realloc; ecs_os_api.calloc_ = ecs_os_api_calloc; /* Strings */ ecs_os_api.strdup_ = ecs_os_api_strdup; /* Time */ ecs_os_api.get_time_ = ecs_os_gettime; /* Logging */ ecs_os_api.log_ = flecs_log_msg; /* Modules */ if (!ecs_os_api.module_to_dl_) { ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; } if (!ecs_os_api.module_to_etc_) { ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } ecs_os_api.abort_ = abort; # ifdef FLECS_OS_API_IMPL /* Initialize defaults to OS API IMPL addon, but still allow for overriding * by the application */ ecs_set_os_api_impl(); ecs_os_api_initialized = false; # endif ecs_os_api_initializing = false; } bool ecs_os_has_heap(void) { return (ecs_os_api.malloc_ != NULL) && (ecs_os_api.calloc_ != NULL) && (ecs_os_api.realloc_ != NULL) && (ecs_os_api.free_ != NULL); } bool ecs_os_has_threading(void) { return (ecs_os_api.mutex_new_ != NULL) && (ecs_os_api.mutex_free_ != NULL) && (ecs_os_api.mutex_lock_ != NULL) && (ecs_os_api.mutex_unlock_ != NULL) && (ecs_os_api.cond_new_ != NULL) && (ecs_os_api.cond_free_ != NULL) && (ecs_os_api.cond_wait_ != NULL) && (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.thread_new_ != NULL) && (ecs_os_api.thread_join_ != NULL) && (ecs_os_api.thread_self_ != NULL); } bool ecs_os_has_task_support(void) { return (ecs_os_api.mutex_new_ != NULL) && (ecs_os_api.mutex_free_ != NULL) && (ecs_os_api.mutex_lock_ != NULL) && (ecs_os_api.mutex_unlock_ != NULL) && (ecs_os_api.cond_new_ != NULL) && (ecs_os_api.cond_free_ != NULL) && (ecs_os_api.cond_wait_ != NULL) && (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.task_new_ != NULL) && (ecs_os_api.task_join_ != NULL); } bool ecs_os_has_time(void) { return (ecs_os_api.get_time_ != NULL) && (ecs_os_api.sleep_ != NULL) && (ecs_os_api.now_ != NULL); } bool ecs_os_has_logging(void) { return (ecs_os_api.log_ != NULL); } bool ecs_os_has_dl(void) { return (ecs_os_api.dlopen_ != NULL) && (ecs_os_api.dlproc_ != NULL) && (ecs_os_api.dlclose_ != NULL); } bool ecs_os_has_modules(void) { return (ecs_os_api.module_to_dl_ != NULL) && (ecs_os_api.module_to_etc_ != NULL); } #if defined(ECS_TARGET_WINDOWS) static char error_str[255]; #endif const char* ecs_os_strerror(int err) { # if defined(ECS_TARGET_WINDOWS) strerror_s(error_str, 255, err); return error_str; # else return strerror(err); # endif } /** * @file poly.c * @brief Functions for managing poly objects. * * The poly framework makes it possible to generalize common functionality for * different kinds of API objects, as well as improved type safety checks. Poly * objects have a header that identifiers what kind of object it is. This can * then be used to discover a set of "mixins" implemented by the type. * * Mixins are like a vtable, but for members. Each type populates the table with * offsets to the members that correspond with the mixin. If an entry in the * mixin table is not set, the type does not support the mixin. * * An example is the Iterable mixin, which makes it possible to create an * iterator for any poly object (like filters, queries, the world) that * implements the Iterable mixin. */ static const char* mixin_kind_str[] = { [EcsMixinWorld] = "world", [EcsMixinEntity] = "entity", [EcsMixinObservable] = "observable", [EcsMixinIterable] = "iterable", [EcsMixinDtor] = "dtor", [EcsMixinMax] = "max (should never be requested by application)" }; ecs_mixins_t ecs_world_t_mixins = { .type_name = "ecs_world_t", .elems = { [EcsMixinWorld] = offsetof(ecs_world_t, self), [EcsMixinObservable] = offsetof(ecs_world_t, observable), [EcsMixinIterable] = offsetof(ecs_world_t, iterable) } }; ecs_mixins_t ecs_stage_t_mixins = { .type_name = "ecs_stage_t", .elems = { [EcsMixinWorld] = offsetof(ecs_stage_t, world) } }; ecs_mixins_t ecs_query_t_mixins = { .type_name = "ecs_query_t", .elems = { [EcsMixinWorld] = offsetof(ecs_query_t, filter.world), [EcsMixinEntity] = offsetof(ecs_query_t, filter.entity), [EcsMixinIterable] = offsetof(ecs_query_t, iterable), [EcsMixinDtor] = offsetof(ecs_query_t, dtor) } }; ecs_mixins_t ecs_observer_t_mixins = { .type_name = "ecs_observer_t", .elems = { [EcsMixinWorld] = offsetof(ecs_observer_t, filter.world), [EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity), [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) } }; ecs_mixins_t ecs_filter_t_mixins = { .type_name = "ecs_filter_t", .elems = { [EcsMixinWorld] = offsetof(ecs_filter_t, world), [EcsMixinEntity] = offsetof(ecs_filter_t, entity), [EcsMixinIterable] = offsetof(ecs_filter_t, iterable), [EcsMixinDtor] = offsetof(ecs_filter_t, dtor) } }; static void* assert_mixin( const ecs_poly_t *poly, ecs_mixin_kind_t kind) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); const ecs_mixins_t *mixins = hdr->mixins; ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); ecs_size_t offset = mixins->elems[kind]; ecs_assert(offset != 0, ECS_INVALID_PARAMETER, "mixin %s not available for type %s", mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); (void)mixin_kind_str; /* Object has mixin, return its address */ return ECS_OFFSET(hdr, offset); } void* ecs_poly_init_( ecs_poly_t *poly, int32_t type, ecs_size_t size, ecs_mixins_t *mixins) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_os_memset(poly, 0, size); hdr->magic = ECS_OBJECT_MAGIC; hdr->type = type; hdr->mixins = mixins; return poly; } void ecs_poly_fini_( ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); (void)type; ecs_header_t *hdr = poly; /* Don't deinit poly that wasn't initialized */ ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); hdr->magic = 0; } EcsPoly* ecs_poly_bind_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { /* Add tag to the entity for easy querying. This will make it possible to * query for `Query` instead of `(Poly, Query) */ if (!ecs_has_id(world, entity, tag)) { ecs_add_id(world, entity, tag); } /* Never defer creation of a poly object */ bool deferred = false; if (ecs_is_deferred(world)) { deferred = true; ecs_defer_suspend(world); } /* If this is a new poly, leave the actual creation up to the caller so they * call tell the difference between a create or an update */ EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); if (deferred) { ecs_defer_resume(world); } return result; } void ecs_poly_modified_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); } const EcsPoly* ecs_poly_bind_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { return ecs_get_pair(world, entity, EcsPoly, tag); } ecs_poly_t* ecs_poly_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { const EcsPoly *p = ecs_poly_bind_get_(world, entity, tag); if (p) { return p->poly; } return NULL; } #ifndef FLECS_NDEBUG #define assert_object(cond, file, line, type_name)\ ecs_assert_((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ assert(cond) void* ecs_poly_assert_( const ecs_poly_t *poly, int32_t type, const char *file, int32_t line) { assert_object(poly != NULL, file, line, 0); const ecs_header_t *hdr = poly; const char *type_name = hdr->mixins->type_name; assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); assert_object(hdr->type == type, file, line, type_name); return ECS_CONST_CAST(ecs_poly_t*, poly); } #endif bool ecs_poly_is_( const ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); return hdr->type == type; } ecs_iterable_t* ecs_get_iterable( const ecs_poly_t *poly) { return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); } ecs_observable_t* ecs_get_observable( const ecs_poly_t *poly) { return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); } const ecs_world_t* ecs_get_world( const ecs_poly_t *poly) { if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { return poly; } return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); } ecs_entity_t ecs_get_entity( const ecs_poly_t *poly) { return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); } ecs_poly_dtor_t* ecs_get_dtor( const ecs_poly_t *poly) { return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); } /** * @file query.c * @brief Cached query implementation. * * Cached queries store a list of matched tables. The inputs for a cached query * are a filter and an observer. The filter is used to initially populate the * cache, and an observer is used to keep the cacne up to date. * * Cached queries additionally support features like sorting and grouping. * With sorting, an application can iterate over entities that can be sorted by * a component. Grouping allows an application to group matched tables, which is * used internally to implement the cascade feature, and can additionally be * used to implement things like world cells. */ static uint64_t flecs_query_get_group_id( ecs_query_t *query, ecs_table_t *table) { if (query->group_by) { return query->group_by(query->filter.world, table, query->group_by_id, query->group_by_ctx); } else { return 0; } } static void flecs_query_compute_group_id( ecs_query_t *query, ecs_query_table_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (query->group_by) { ecs_table_t *table = match->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); match->group_id = flecs_query_get_group_id(query, table); } else { match->group_id = 0; } } static ecs_query_table_list_t* flecs_query_get_group( const ecs_query_t *query, uint64_t group_id) { return ecs_map_get_deref(&query->groups, ecs_query_table_list_t, group_id); } static ecs_query_table_list_t* flecs_query_ensure_group( ecs_query_t *query, uint64_t id) { ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, ecs_query_table_list_t, id); if (!group) { group = ecs_map_insert_alloc_t(&query->groups, ecs_query_table_list_t, id); ecs_os_zeromem(group); if (query->on_group_create) { group->info.ctx = query->on_group_create( query->filter.world, id, query->group_by_ctx); } } return group; } static void flecs_query_remove_group( ecs_query_t *query, uint64_t id) { if (query->on_group_delete) { ecs_query_table_list_t *group = ecs_map_get_deref(&query->groups, ecs_query_table_list_t, id); if (group) { query->on_group_delete(query->filter.world, id, group->info.ctx, query->group_by_ctx); } } ecs_map_remove_free(&query->groups, id); } static uint64_t flecs_query_default_group_by( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, void *ctx) { (void)ctx; ecs_id_t match; if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { return ecs_pair_second(world, match); } return 0; } /* Find the last node of the group after which this group should be inserted */ static ecs_query_table_match_t* flecs_query_find_group_insertion_node( ecs_query_t *query, uint64_t group_id) { /* Grouping must be enabled */ ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_iter_t it = ecs_map_iter(&query->groups); ecs_query_table_list_t *list, *closest_list = NULL; uint64_t id, closest_id = 0; /* Find closest smaller group id */ while (ecs_map_next(&it)) { id = ecs_map_key(&it); if (id >= group_id) { continue; } list = ecs_map_ptr(&it); if (!list->last) { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); continue; } if (!closest_list || ((group_id - id) < (group_id - closest_id))) { closest_id = id; closest_list = list; } } if (closest_list) { return closest_list->last; } else { return NULL; /* Group should be first in query */ } } /* Initialize group with first node */ static void flecs_query_create_group( ecs_query_t *query, ecs_query_table_match_t *match) { uint64_t group_id = match->group_id; /* If query has grouping enabled & this is a new/empty group, find * the insertion point for the group */ ecs_query_table_match_t *insert_after = flecs_query_find_group_insertion_node( query, group_id); if (!insert_after) { /* This group should appear first in the query list */ ecs_query_table_match_t *query_first = query->list.first; if (query_first) { /* If this is not the first match for the query, insert before it */ match->next = query_first; query_first->prev = match; query->list.first = match; } else { /* If this is the first match of the query, initialize its list */ ecs_assert(query->list.last == NULL, ECS_INTERNAL_ERROR, NULL); query->list.first = match; query->list.last = match; } } else { ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); /* This group should appear after another group */ ecs_query_table_match_t *insert_before = insert_after->next; match->prev = insert_after; insert_after->next = match; match->next = insert_before; if (insert_before) { insert_before->prev = match; } else { ecs_assert(query->list.last == insert_after, ECS_INTERNAL_ERROR, NULL); /* This group should appear last in the query list */ query->list.last = match; } } } /* Find the list the node should be part of */ static ecs_query_table_list_t* flecs_query_get_node_list( ecs_query_t *query, ecs_query_table_match_t *match) { if (query->group_by) { return flecs_query_get_group(query, match->group_id); } else { return &query->list; } } /* Find or create the list the node should be part of */ static ecs_query_table_list_t* flecs_query_ensure_node_list( ecs_query_t *query, ecs_query_table_match_t *match) { if (query->group_by) { return flecs_query_ensure_group(query, match->group_id); } else { return &query->list; } } /* Remove node from list */ static void flecs_query_remove_table_node( ecs_query_t *query, ecs_query_table_match_t *match) { ecs_query_table_match_t *prev = match->prev; ecs_query_table_match_t *next = match->next; ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); ecs_query_table_list_t *list = flecs_query_get_node_list(query, match); if (!list || !list->first) { /* If list contains no matches, the match must be empty */ ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); return; } ecs_assert(prev != NULL || query->list.first == match, ECS_INTERNAL_ERROR, NULL); ecs_assert(next != NULL || query->list.last == match, ECS_INTERNAL_ERROR, NULL); if (prev) { prev->next = next; } if (next) { next->prev = prev; } ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); list->info.table_count --; if (query->group_by) { uint64_t group_id = match->group_id; /* Make sure query.list is updated if this is the first or last group */ if (query->list.first == match) { ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); query->list.first = next; prev = next; } if (query->list.last == match) { ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); query->list.last = prev; next = prev; } ecs_assert(query->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); query->list.info.table_count --; list->info.match_count ++; /* Make sure group list only contains nodes that belong to the group */ if (prev && prev->group_id != group_id) { /* The previous node belonged to another group */ prev = next; } if (next && next->group_id != group_id) { /* The next node belonged to another group */ next = prev; } /* Do check again, in case both prev & next belonged to another group */ if ((!prev && !next) || (prev && prev->group_id != group_id)) { /* There are no more matches left in this group */ flecs_query_remove_group(query, group_id); list = NULL; } } if (list) { if (list->first == match) { list->first = next; } if (list->last == match) { list->last = prev; } } match->prev = NULL; match->next = NULL; query->match_count ++; } /* Add node to list */ static void flecs_query_insert_table_node( ecs_query_t *query, ecs_query_table_match_t *match) { /* Node should not be part of an existing list */ ecs_assert(match->prev == NULL && match->next == NULL, ECS_INTERNAL_ERROR, NULL); /* If this is the first match, activate system */ if (!query->list.first && query->filter.entity) { ecs_remove_id(query->filter.world, query->filter.entity, EcsEmpty); } flecs_query_compute_group_id(query, match); ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, match); if (list->last) { ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_table_match_t *last = list->last; ecs_query_table_match_t *last_next = last->next; match->prev = last; match->next = last_next; last->next = match; if (last_next) { last_next->prev = match; } list->last = match; if (query->group_by) { /* Make sure to update query list if this is the last group */ if (query->list.last == last) { query->list.last = match; } } } else { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); list->first = match; list->last = match; if (query->group_by) { /* Initialize group with its first node */ flecs_query_create_group(query, match); } } if (query->group_by) { list->info.table_count ++; list->info.match_count ++; } query->list.info.table_count ++; query->match_count ++; ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(query->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); } static ecs_query_table_match_t* flecs_query_cache_add( ecs_world_t *world, ecs_query_table_t *elem) { ecs_query_table_match_t *result = flecs_bcalloc(&world->allocators.query_table_match); if (!elem->first) { elem->first = result; elem->last = result; } else { ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); elem->last->next_match = result; elem->last = result; } return result; } typedef struct { ecs_table_t *table; int32_t column; } flecs_table_column_t; static void flecs_query_get_column_for_term( ecs_query_t *query, ecs_query_table_match_t *match, int32_t t, flecs_table_column_t *out) { const ecs_filter_t *filter = &query->filter; ecs_world_t *world = filter->world; ecs_term_t *term = &filter->terms[t]; int32_t field = term->field_index; ecs_entity_t src = match->sources[field]; ecs_table_t *table = NULL; int32_t column = -1; if (term->oper != EcsNot) { if (!src) { if (term->src.flags != EcsIsEntity) { table = match->table; column = match->storage_columns[field]; if (column == -2) { /* Shared field */ column = -1; } } } else { table = ecs_get_table(world, src); if (ecs_term_match_this(term)) { int32_t ref_index = -match->columns[field] - 1; ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); if (ref->id != 0) { ecs_ref_update(world, ref); column = ref->tr->column; } } else { column = -(match->columns[field] + 1); } } } out->table = table; out->column = column; } /* Get match monitor. Monitors are used to keep track of whether components * matched by the query in a table have changed. */ static bool flecs_query_get_match_monitor( ecs_query_t *query, ecs_query_table_match_t *match) { if (match->monitor) { return false; } int32_t *monitor = flecs_balloc(&query->allocators.monitors); monitor[0] = 0; /* Mark terms that don't need to be monitored. This saves time when reading * and/or updating the monitor. */ const ecs_filter_t *f = &query->filter; int32_t i, field = -1, term_count = f->term_count; flecs_table_column_t tc; for (i = 0; i < term_count; i ++) { if (field == f->terms[i].field_index) { if (monitor[field + 1] != -1) { continue; } } field = f->terms[i].field_index; monitor[field + 1] = -1; if (f->terms[i].inout != EcsIn && f->terms[i].inout != EcsInOut && f->terms[i].inout != EcsInOutDefault) { continue; /* If term isn't read, don't monitor */ } int32_t column = match->columns[field]; if (column == 0) { continue; /* Don't track terms that aren't matched */ } flecs_query_get_column_for_term(query, match, i, &tc); if (tc.column == -1) { continue; /* Don't track terms that aren't stored */ } monitor[field + 1] = 0; } /* If matched table needs entity filter, make sure to test fields that could * be matched by flattened parents. */ ecs_entity_filter_t *ef = match->entity_filter; if (ef && ef->flat_tree_column != -1) { int32_t *fields = ecs_vec_first(&ef->ft_terms); int32_t field_count = ecs_vec_count(&ef->ft_terms); for (i = 0; i < field_count; i ++) { monitor[fields[i] + 1] = 0; } } match->monitor = monitor; query->flags |= EcsQueryHasMonitor; return true; } /* Synchronize match monitor with table dirty state */ static void flecs_query_sync_match_monitor( ecs_query_t *query, ecs_query_table_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (!match->monitor) { if (query->flags & EcsQueryHasMonitor) { flecs_query_get_match_monitor(query, match); } else { return; } } int32_t *monitor = match->monitor; ecs_table_t *table = match->table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ } ecs_filter_t *filter = &query->filter; { flecs_table_column_t tc; int32_t t, term_count = filter->term_count; for (t = 0; t < term_count; t ++) { int32_t field = filter->terms[t].field_index; if (monitor[field + 1] == -1) { continue; } flecs_query_get_column_for_term(query, match, t, &tc); monitor[field + 1] = flecs_table_get_dirty_state( filter->world, tc.table)[tc.column + 1]; } } ecs_entity_filter_t *ef = match->entity_filter; if (ef && ef->flat_tree_column != -1) { flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); int32_t f, field_count = ecs_vec_count(&ef->ft_terms); for (f = 0; f < field_count; f ++) { flecs_flat_table_term_t *field = &fields[f]; flecs_flat_monitor_t *tgt_mon = ecs_vec_first(&field->monitor); int32_t tgt, tgt_count = ecs_vec_count(&field->monitor); for (tgt = 0; tgt < tgt_count; tgt ++) { tgt_mon[tgt].monitor = tgt_mon[tgt].table_state; } } } query->prev_match_count = query->match_count; } /* Check if single match term has changed */ static bool flecs_query_check_match_monitor_term( ecs_query_t *query, ecs_query_table_match_t *match, int32_t term) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_get_match_monitor(query, match)) { return true; } int32_t *monitor = match->monitor; int32_t state = monitor[term]; if (state == -1) { return false; } ecs_table_t *table = match->table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (!term) { return monitor[0] != dirty_state[0]; } } else if (!term) { return false; } flecs_table_column_t cur; flecs_query_get_column_for_term(query, match, term - 1, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); return monitor[term] != flecs_table_get_dirty_state( query->filter.world, cur.table)[cur.column + 1]; } /* Check if any term for match has changed */ static bool flecs_query_check_match_monitor( ecs_query_t *query, ecs_query_table_match_t *match, const ecs_iter_t *it) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_get_match_monitor(query, match)) { return true; } int32_t *monitor = match->monitor; ecs_table_t *table = match->table; int32_t *dirty_state = NULL; if (table) { dirty_state = flecs_table_get_dirty_state( query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (monitor[0] != dirty_state[0]) { return true; } } bool has_flat = false, is_this = false; const ecs_filter_t *filter = &query->filter; ecs_world_t *world = filter->world; int32_t i, j, field_count = filter->field_count; int32_t *storage_columns = match->storage_columns; int32_t *columns = it ? it->columns : NULL; if (!columns) { columns = match->columns; } ecs_vec_t *refs = &match->refs; for (i = 0; i < field_count; i ++) { int32_t mon = monitor[i + 1]; if (mon == -1) { continue; } int32_t column = storage_columns[i]; if (columns[i] >= 0) { /* owned component */ ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (mon != dirty_state[column + 1]) { return true; } continue; } else if (column == -1) { continue; /* owned but not a component */ } column = columns[i]; if (!column) { /* Not matched */ continue; } ecs_assert(column < 0, ECS_INTERNAL_ERROR, NULL); column = -column; /* Find term index from field index, which differ when using || */ int32_t term_index = i; if (filter->terms[i].field_index != i) { for (j = i; j < filter->term_count; j ++) { if (filter->terms[j].field_index == i) { term_index = j; break; } } } is_this = ecs_term_match_this(&filter->terms[term_index]); /* Flattened fields are encoded by adding field_count to the column * index of the parent component. */ if (is_this && it && (column > field_count)) { has_flat = true; } else { if (is_this) { /* Component reached through traversal from this */ int32_t ref_index = column - 1; ecs_ref_t *ref = ecs_vec_get_t(refs, ecs_ref_t, ref_index); if (ref->id != 0) { ecs_ref_update(world, ref); ecs_table_record_t *tr = ref->tr; ecs_table_t *src_table = tr->hdr.table; column = tr->index; column = ecs_table_type_to_column_index(src_table, column); int32_t *src_dirty_state = flecs_table_get_dirty_state( world, src_table); if (mon != src_dirty_state[column + 1]) { return true; } } } else { /* Component from static source */ ecs_entity_t src = match->sources[i]; ecs_table_t *src_table = ecs_get_table(world, src); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); column = ecs_table_type_to_column_index(src_table, column - 1); int32_t *src_dirty_state = flecs_table_get_dirty_state( world, src_table); if (mon != src_dirty_state[column + 1]) { return true; } } } } if (has_flat) { ecs_entity_filter_t *ef = match->entity_filter; flecs_flat_table_term_t *fields = ecs_vec_first(&ef->ft_terms); ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; int32_t cur_tgt = ent_it->target_count - 1; field_count = ecs_vec_count(&ef->ft_terms); for (i = 0; i < field_count; i ++) { flecs_flat_table_term_t *field = &fields[i]; flecs_flat_monitor_t *fmon = ecs_vec_get_t(&field->monitor, flecs_flat_monitor_t, cur_tgt); if (fmon->monitor != fmon->table_state) { return true; } } } return false; } /* Check if any term for matched table has changed */ static bool flecs_query_check_table_monitor( ecs_query_t *query, ecs_query_table_t *table, int32_t term) { ecs_query_table_match_t *cur, *end = table->last->next; for (cur = table->first; cur != end; cur = cur->next) { ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; if (term == -1) { if (flecs_query_check_match_monitor(query, match, NULL)) { return true; } } else { if (flecs_query_check_match_monitor_term(query, match, term)) { return true; } } } return false; } static bool flecs_query_check_query_monitor( ecs_query_t *query) { ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&query->cache, &it)) { ecs_query_table_t *qt; while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { if (flecs_query_check_table_monitor(query, qt, -1)) { return true; } } } return false; } static void flecs_query_init_query_monitors( ecs_query_t *query) { ecs_query_table_match_t *cur = query->list.first; /* Ensure each match has a monitor */ for (; cur != NULL; cur = cur->next) { ecs_query_table_match_t *match = (ecs_query_table_match_t*)cur; flecs_query_get_match_monitor(query, match); } } /* The group by function for cascade computes the tree depth for the table type. * This causes tables in the query cache to be ordered by depth, which ensures * breadth-first iteration order. */ static uint64_t flecs_query_group_by_cascade( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, void *ctx) { (void)id; ecs_term_t *term = ctx; ecs_entity_t rel = term->src.trav; int32_t depth = flecs_relation_depth(world, rel, table); return flecs_ito(uint64_t, depth); } static void flecs_query_add_ref( ecs_world_t *world, ecs_query_t *query, ecs_query_table_match_t *qm, ecs_entity_t component, ecs_entity_t entity, ecs_size_t size) { ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_ref_t *ref = ecs_vec_append_t(&world->allocator, &qm->refs, ecs_ref_t); ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); if (size) { *ref = ecs_ref_init_id(world, entity, component); } else { *ref = (ecs_ref_t){ .entity = entity, .id = 0 }; } query->flags |= EcsQueryHasRefs; } static ecs_query_table_match_t* flecs_query_add_table_match( ecs_query_t *query, ecs_query_table_t *qt, ecs_table_t *table) { /* Add match for table. One table can have more than one match, if * the query contains wildcards. */ ecs_query_table_match_t *qm = flecs_query_cache_add(query->filter.world, qt); qm->table = table; qm->columns = flecs_balloc(&query->allocators.columns); qm->storage_columns = flecs_balloc(&query->allocators.columns); qm->ids = flecs_balloc(&query->allocators.ids); qm->sources = flecs_balloc(&query->allocators.sources); /* Insert match to iteration list if table is not empty */ if (!table || ecs_table_count(table) != 0) { flecs_query_insert_table_node(query, qm); } return qm; } static void flecs_query_set_table_match( ecs_world_t *world, ecs_query_t *query, ecs_query_table_match_t *qm, ecs_table_t *table, ecs_iter_t *it) { ecs_allocator_t *a = &world->allocator; ecs_filter_t *filter = &query->filter; int32_t i, term_count = filter->term_count; int32_t field_count = filter->field_count; ecs_term_t *terms = filter->terms; /* Reset resources in case this is an existing record */ ecs_vec_reset_t(a, &qm->refs, ecs_ref_t); ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); if (table) { /* Initialize storage columns for faster access to component storage */ for (i = 0; i < field_count; i ++) { if (terms[i].inout == EcsInOutNone) { qm->storage_columns[i] = -1; continue; } int32_t column = qm->columns[i]; if (column > 0) { qm->storage_columns[i] = ecs_table_type_to_column_index(table, qm->columns[i] - 1); } else { /* Shared field (not from table) */ qm->storage_columns[i] = -2; } } flecs_entity_filter_init(world, &qm->entity_filter, filter, table, qm->ids, qm->columns); if (qm->entity_filter) { query->flags &= ~EcsQueryTrivialIter; } if (table->flags & EcsTableHasUnion) { query->flags &= ~EcsQueryTrivialIter; } } /* Add references for substituted terms */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (!ecs_term_match_this(term)) { /* non-This terms are set during iteration */ continue; } int32_t field = terms[i].field_index; ecs_entity_t src = it->sources[field]; ecs_size_t size = 0; if (it->sizes) { size = it->sizes[field]; } if (src) { ecs_id_t id = it->ids[field]; ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); if (id) { flecs_query_add_ref(world, query, qm, id, src, size); /* Use column index to bind term and ref */ if (qm->columns[field] != 0) { qm->columns[field] = -ecs_vec_count(&qm->refs); } } } } } static ecs_query_table_t* flecs_query_table_insert( ecs_world_t *world, ecs_query_t *query, ecs_table_t *table) { ecs_query_table_t *qt = flecs_bcalloc(&world->allocators.query_table); if (table) { qt->table_id = table->id; } else { qt->table_id = 0; } ecs_table_cache_insert(&query->cache, table, &qt->hdr); return qt; } /** Populate query cache with tables */ static void flecs_query_match_tables( ecs_world_t *world, ecs_query_t *query) { ecs_table_t *table = NULL; ecs_query_table_t *qt = NULL; ecs_iter_t it = ecs_filter_iter(world, &query->filter); ECS_BIT_SET(it.flags, EcsIterIsInstanced); ECS_BIT_SET(it.flags, EcsIterNoData); ECS_BIT_SET(it.flags, EcsIterTableOnly); ECS_BIT_SET(it.flags, EcsIterEntityOptional); while (ecs_filter_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { /* New table matched, add record to cache */ table = it.table; qt = flecs_query_table_insert(world, query, table); } ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); flecs_query_set_table_match(world, query, qm, table, &it); } } static bool flecs_query_match_table( ecs_world_t *world, ecs_query_t *query, ecs_table_t *table) { if (!ecs_map_is_init(&query->cache.index)) { return false; } ecs_query_table_t *qt = NULL; ecs_filter_t *filter = &query->filter; int var_id = ecs_filter_find_this_var(filter); if (var_id == -1) { /* If query doesn't match with This term, it can't match with tables */ return false; } ecs_iter_t it = flecs_filter_iter_w_flags(world, filter, EcsIterMatchVar| EcsIterIsInstanced|EcsIterNoData|EcsIterEntityOptional); ecs_iter_set_var_as_table(&it, var_id, table); while (ecs_filter_next(&it)) { ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); if (qt == NULL) { table = it.table; qt = flecs_query_table_insert(world, query, table); } ecs_query_table_match_t *qm = flecs_query_add_table_match(query, qt, table); flecs_query_set_table_match(world, query, qm, table, &it); } return qt != NULL; } ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_sort_table_generic, order_by, static) static void flecs_query_sort_table( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_order_by_action_t compare, ecs_sort_table_action_t sort) { ecs_data_t *data = &table->data; if (!ecs_vec_count(&data->entities)) { /* Nothing to sort */ return; } int32_t count = flecs_table_data_count(data); if (count < 2) { return; } ecs_entity_t *entities = ecs_vec_first(&data->entities); void *ptr = NULL; int32_t size = 0; if (column_index != -1) { ecs_column_t *column = &data->columns[column_index]; ecs_type_info_t *ti = column->ti; size = ti->size; ptr = ecs_vec_first(&column->data); } if (sort) { sort(world, table, entities, ptr, size, 0, count - 1, compare); } else { flecs_query_sort_table_generic(world, table, entities, ptr, size, 0, count - 1, compare); } } /* Helper struct for building sorted table ranges */ typedef struct sort_helper_t { ecs_query_table_match_t *match; ecs_entity_t *entities; const void *ptr; int32_t row; int32_t elem_size; int32_t count; bool shared; } sort_helper_t; static const void* ptr_from_helper( sort_helper_t *helper) { ecs_assert(helper->row < helper->count, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper->elem_size >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper->row >= 0, ECS_INTERNAL_ERROR, NULL); if (helper->shared) { return helper->ptr; } else { return ECS_ELEM(helper->ptr, helper->elem_size, helper->row); } } static ecs_entity_t e_from_helper( sort_helper_t *helper) { if (helper->row < helper->count) { return helper->entities[helper->row]; } else { return 0; } } static void flecs_query_build_sorted_table_range( ecs_query_t *query, ecs_query_table_list_t *list) { ecs_world_t *world = query->filter.world; ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, "cannot sort query in multithreaded mode"); ecs_entity_t id = query->order_by_component; ecs_order_by_action_t compare = query->order_by; int32_t table_count = list->info.table_count; if (!table_count) { return; } ecs_vec_init_if_t(&query->table_slices, ecs_query_table_match_t); int32_t to_sort = 0; int32_t order_by_term = query->order_by_term; sort_helper_t *helper = flecs_alloc_n( &world->allocator, sort_helper_t, table_count); ecs_query_table_match_t *cur, *end = list->last->next; for (cur = list->first; cur != end; cur = cur->next) { ecs_table_t *table = cur->table; ecs_data_t *data = &table->data; ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); if (id) { const ecs_term_t *term = &query->filter.terms[order_by_term]; int32_t field = term->field_index; int32_t column = cur->columns[field]; ecs_size_t size = query->filter.sizes[field]; ecs_assert(column != 0, ECS_INTERNAL_ERROR, NULL); if (column >= 0) { column = table->column_map[column - 1]; ecs_vec_t *vec = &data->columns[column].data; helper[to_sort].ptr = ecs_vec_first(vec); helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else { ecs_entity_t src = cur->sources[field]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); if (term->src.flags & EcsUp) { ecs_entity_t base = 0; ecs_search_relation(world, r->table, 0, id, EcsIsA, term->src.flags & EcsTraverseFlags, &base, 0, 0); if (base && base != src) { /* Component could be inherited */ r = flecs_entities_get(world, base); } } helper[to_sort].ptr = ecs_table_get_id( world, r->table, id, ECS_RECORD_TO_ROW(r->row)); helper[to_sort].elem_size = size; helper[to_sort].shared = true; } ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); } else { helper[to_sort].ptr = NULL; helper[to_sort].elem_size = 0; helper[to_sort].shared = false; } helper[to_sort].match = cur; helper[to_sort].entities = ecs_vec_first(&data->entities); helper[to_sort].row = 0; helper[to_sort].count = ecs_table_count(table); to_sort ++; } ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); bool proceed; do { int32_t j, min = 0; proceed = true; ecs_entity_t e1; while (!(e1 = e_from_helper(&helper[min]))) { min ++; if (min == to_sort) { proceed = false; break; } } if (!proceed) { break; } for (j = min + 1; j < to_sort; j++) { ecs_entity_t e2 = e_from_helper(&helper[j]); if (!e2) { continue; } const void *ptr1 = ptr_from_helper(&helper[min]); const void *ptr2 = ptr_from_helper(&helper[j]); if (compare(e1, ptr1, e2, ptr2) > 0) { min = j; e1 = e_from_helper(&helper[min]); } } sort_helper_t *cur_helper = &helper[min]; if (!cur || cur->columns != cur_helper->match->columns) { cur = ecs_vec_append_t(NULL, &query->table_slices, ecs_query_table_match_t); *cur = *(cur_helper->match); cur->offset = cur_helper->row; cur->count = 1; } else { cur->count ++; } cur_helper->row ++; } while (proceed); /* Iterate through the vector of slices to set the prev/next ptrs. This * can't be done while building the vector, as reallocs may occur */ int32_t i, count = ecs_vec_count(&query->table_slices); ecs_query_table_match_t *nodes = ecs_vec_first(&query->table_slices); for (i = 0; i < count; i ++) { nodes[i].prev = &nodes[i - 1]; nodes[i].next = &nodes[i + 1]; } nodes[0].prev = NULL; nodes[i - 1].next = NULL; flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } static void flecs_query_build_sorted_tables( ecs_query_t *query) { ecs_vec_clear(&query->table_slices); if (query->group_by) { /* Populate sorted node list in grouping order */ ecs_query_table_match_t *cur = query->list.first; if (cur) { do { /* Find list for current group */ uint64_t group_id = cur->group_id; ecs_query_table_list_t *list = ecs_map_get_deref( &query->groups, ecs_query_table_list_t, group_id); ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); /* Sort tables in current group */ flecs_query_build_sorted_table_range(query, list); /* Find next group to sort */ cur = list->last->next; } while (cur); } } else { flecs_query_build_sorted_table_range(query, &query->list); } } static void flecs_query_sort_tables( ecs_world_t *world, ecs_query_t *query) { ecs_order_by_action_t compare = query->order_by; if (!compare) { return; } ecs_sort_table_action_t sort = query->sort_table; ecs_entity_t order_by_component = query->order_by_component; int32_t order_by_term = query->order_by_term; /* Iterate over non-empty tables. Don't bother with empty tables as they * have nothing to sort */ bool tables_sorted = false; ecs_id_record_t *idr = flecs_id_record_get(world, order_by_component); ecs_table_cache_iter_t it; ecs_query_table_t *qt; flecs_table_cache_iter(&query->cache, &it); while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { ecs_table_t *table = qt->hdr.table; bool dirty = false; if (flecs_query_check_table_monitor(query, qt, 0)) { dirty = true; } int32_t column = -1; if (order_by_component) { if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { dirty = true; } if (dirty) { column = -1; const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); if (tr) { column = tr->column; } if (column == -1) { /* Component is shared, no sorting is needed */ dirty = false; } } } if (!dirty) { continue; } /* Something has changed, sort the table. Prefers using * flecs_query_sort_table when available */ flecs_query_sort_table(world, table, column, compare, sort); tables_sorted = true; } if (tables_sorted || query->match_count != query->prev_match_count) { flecs_query_build_sorted_tables(query); query->match_count ++; /* Increase version if tables changed */ } } static bool flecs_query_has_refs( ecs_query_t *query) { ecs_term_t *terms = query->filter.terms; int32_t i, count = query->filter.term_count; for (i = 0; i < count; i ++) { if (terms[i].src.flags & (EcsUp | EcsIsEntity)) { return true; } } return false; } static void flecs_query_for_each_component_monitor( ecs_world_t *world, ecs_query_t *query, void(*callback)( ecs_world_t* world, ecs_id_t id, ecs_query_t *query)) { ecs_term_t *terms = query->filter.terms; int32_t i, count = query->filter.term_count; for (i = 0; i < count; i++) { ecs_term_t *term = &terms[i]; ecs_term_id_t *src = &term->src; if (src->flags & EcsUp) { callback(world, ecs_pair(src->trav, EcsWildcard), query); if (src->trav != EcsIsA) { callback(world, ecs_pair(EcsIsA, EcsWildcard), query); } callback(world, term->id, query); } else if (src->flags & EcsSelf && !ecs_term_match_this(term)) { callback(world, term->id, query); } } } static bool flecs_query_is_term_id_supported( ecs_term_id_t *term_id) { if (!(term_id->flags & EcsIsVariable)) { return true; } if (ecs_id_is_wildcard(term_id->id)) { return true; } return false; } static int flecs_query_process_signature( ecs_world_t *world, ecs_query_t *query) { ecs_term_t *terms = query->filter.terms; int32_t i, count = query->filter.term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_id_t *first = &term->first; ecs_term_id_t *src = &term->src; ecs_term_id_t *second = &term->second; ecs_inout_kind_t inout = term->inout; bool is_src_ok = flecs_query_is_term_id_supported(src); bool is_first_ok = flecs_query_is_term_id_supported(first); bool is_second_ok = flecs_query_is_term_id_supported(second); (void)first; (void)second; (void)is_src_ok; (void)is_first_ok; (void)is_second_ok; /* Queries do not support named variables */ ecs_check(is_src_ok || ecs_term_match_this(term), ECS_UNSUPPORTED, NULL); ecs_check(is_first_ok, ECS_UNSUPPORTED, NULL); ecs_check(is_second_ok, ECS_UNSUPPORTED, NULL); ecs_check(!(src->flags & EcsFilter), ECS_INVALID_PARAMETER, "invalid usage of Filter for query"); if (inout != EcsIn && inout != EcsInOutNone) { /* Non-this terms default to EcsIn */ if (ecs_term_match_this(term) || inout != EcsInOutDefault) { query->flags |= EcsQueryHasOutTerms; } bool match_non_this = !ecs_term_match_this(term) || (term->src.flags & EcsUp); if (match_non_this && inout != EcsInOutDefault) { query->flags |= EcsQueryHasNonThisOutTerms; } } if (src->flags & EcsCascade) { /* Query can only have one cascade column */ ecs_assert(query->cascade_by == 0, ECS_INVALID_PARAMETER, NULL); query->cascade_by = i + 1; } } query->flags |= (ecs_flags32_t)(flecs_query_has_refs(query) * EcsQueryHasRefs); if (!(query->flags & EcsQueryIsSubquery)) { flecs_query_for_each_component_monitor(world, query, flecs_monitor_register); } return 0; error: return -1; } /** When a table becomes empty remove it from the query list, or vice versa. */ static void flecs_query_update_table( ecs_query_t *query, ecs_table_t *table, bool empty) { int32_t prev_count = ecs_query_table_count(query); ecs_table_cache_set_empty(&query->cache, table, empty); int32_t cur_count = ecs_query_table_count(query); if (prev_count != cur_count) { ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_table_match_t *cur, *next; for (cur = qt->first; cur != NULL; cur = next) { next = cur->next_match; if (empty) { ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); flecs_query_remove_table_node(query, cur); } else { ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); flecs_query_insert_table_node(query, cur); } } } ecs_assert(cur_count || query->list.first == NULL, ECS_INTERNAL_ERROR, NULL); } static void flecs_query_add_subquery( ecs_world_t *world, ecs_query_t *parent, ecs_query_t *subquery) { ecs_vec_init_if_t(&parent->subqueries, ecs_query_t*); ecs_query_t **elem = ecs_vec_append_t( NULL, &parent->subqueries, ecs_query_t*); *elem = subquery; ecs_table_cache_t *cache = &parent->cache; ecs_table_cache_iter_t it; ecs_query_table_t *qt; flecs_table_cache_all_iter(cache, &it); while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { flecs_query_match_table(world, subquery, qt->hdr.table); } } static void flecs_query_notify_subqueries( ecs_world_t *world, ecs_query_t *query, ecs_query_event_t *event) { if (query->subqueries.array) { ecs_query_t **queries = ecs_vec_first(&query->subqueries); int32_t i, count = ecs_vec_count(&query->subqueries); ecs_query_event_t sub_event = *event; sub_event.parent_query = query; for (i = 0; i < count; i ++) { ecs_query_t *sub = queries[i]; flecs_query_notify(world, sub, &sub_event); } } } /* Remove table */ static void flecs_query_table_match_free( ecs_query_t *query, ecs_query_table_t *elem, ecs_query_table_match_t *first) { ecs_query_table_match_t *cur, *next; ecs_world_t *world = query->filter.world; for (cur = first; cur != NULL; cur = next) { flecs_bfree(&query->allocators.columns, cur->columns); flecs_bfree(&query->allocators.columns, cur->storage_columns); flecs_bfree(&query->allocators.ids, cur->ids); flecs_bfree(&query->allocators.sources, cur->sources); if (cur->monitor) { flecs_bfree(&query->allocators.monitors, cur->monitor); } if (!elem->hdr.empty) { flecs_query_remove_table_node(query, cur); } ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); flecs_entity_filter_fini(world, cur->entity_filter); next = cur->next_match; flecs_bfree(&world->allocators.query_table_match, cur); } } static void flecs_query_table_free( ecs_query_t *query, ecs_query_table_t *elem) { flecs_query_table_match_free(query, elem, elem->first); flecs_bfree(&query->filter.world->allocators.query_table, elem); } static void flecs_query_unmatch_table( ecs_query_t *query, ecs_table_t *table, ecs_query_table_t *elem) { if (!elem) { elem = ecs_table_cache_get(&query->cache, table); } if (elem) { ecs_table_cache_remove(&query->cache, elem->table_id, &elem->hdr); flecs_query_table_free(query, elem); } } /* Rematch system with tables after a change happened to a watched entity */ static void flecs_query_rematch_tables( ecs_world_t *world, ecs_query_t *query, ecs_query_t *parent_query) { ecs_iter_t it, parent_it; ecs_table_t *table = NULL; ecs_query_table_t *qt = NULL; ecs_query_table_match_t *qm = NULL; if (query->monitor_generation == world->monitor_generation) { return; } query->monitor_generation = world->monitor_generation; if (parent_query) { parent_it = ecs_query_iter(world, parent_query); it = ecs_filter_chain_iter(&parent_it, &query->filter); } else { it = ecs_filter_iter(world, &query->filter); } ECS_BIT_SET(it.flags, EcsIterIsInstanced); ECS_BIT_SET(it.flags, EcsIterNoData); ECS_BIT_SET(it.flags, EcsIterEntityOptional); world->info.rematch_count_total ++; int32_t rematch_count = ++ query->rematch_count; ecs_time_t t = {0}; if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_measure(&t); } while (ecs_filter_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { if (qm && qm->next_match) { flecs_query_table_match_free(query, qt, qm->next_match); qm->next_match = NULL; } table = it.table; qt = ecs_table_cache_get(&query->cache, table); if (!qt) { qt = flecs_query_table_insert(world, query, table); } ecs_assert(qt->hdr.table == table, ECS_INTERNAL_ERROR, NULL); qt->rematch_count = rematch_count; qm = NULL; } if (!qm) { qm = qt->first; } else { qm = qm->next_match; } if (!qm) { qm = flecs_query_add_table_match(query, qt, table); } flecs_query_set_table_match(world, query, qm, table, &it); if (table && ecs_table_count(table) && query->group_by) { if (flecs_query_get_group_id(query, table) != qm->group_id) { /* Update table group */ flecs_query_remove_table_node(query, qm); flecs_query_insert_table_node(query, qm); } } } if (qm && qm->next_match) { flecs_query_table_match_free(query, qt, qm->next_match); qm->next_match = NULL; } /* Iterate all tables in cache, remove ones that weren't just matched */ ecs_table_cache_iter_t cache_it; if (flecs_table_cache_all_iter(&query->cache, &cache_it)) { while ((qt = flecs_table_cache_next(&cache_it, ecs_query_table_t))) { if (qt->rematch_count != rematch_count) { flecs_query_unmatch_table(query, qt->hdr.table, qt); } } } if (world->flags & EcsWorldMeasureFrameTime) { world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } static void flecs_query_remove_subquery( ecs_query_t *parent, ecs_query_t *sub) { ecs_assert(parent != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(sub != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(parent->subqueries.array, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&parent->subqueries); ecs_query_t **sq = ecs_vec_first(&parent->subqueries); for (i = 0; i < count; i ++) { if (sq[i] == sub) { break; } } ecs_vec_remove_t(&parent->subqueries, ecs_query_t*, i); } /* -- Private API -- */ void flecs_query_notify( ecs_world_t *world, ecs_query_t *query, ecs_query_event_t *event) { bool notify = true; switch(event->kind) { case EcsQueryTableMatch: /* Creation of new table */ if (flecs_query_match_table(world, query, event->table)) { if (query->subqueries.array) { flecs_query_notify_subqueries(world, query, event); } } notify = false; break; case EcsQueryTableUnmatch: /* Deletion of table */ flecs_query_unmatch_table(query, event->table, NULL); break; case EcsQueryTableRematch: /* Rematch tables of query */ flecs_query_rematch_tables(world, query, event->parent_query); break; case EcsQueryOrphan: ecs_assert(query->flags & EcsQueryIsSubquery, ECS_INTERNAL_ERROR, NULL); query->flags |= EcsQueryIsOrphaned; query->parent = NULL; break; } if (notify) { flecs_query_notify_subqueries(world, query, event); } } static void flecs_query_order_by( ecs_world_t *world, ecs_query_t *query, ecs_entity_t order_by_component, ecs_order_by_action_t order_by, ecs_sort_table_action_t action) { ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_id_is_wildcard(order_by_component), ECS_INVALID_PARAMETER, NULL); /* Find order_by_component term & make sure it is queried for */ const ecs_filter_t *filter = &query->filter; int32_t i, count = filter->term_count; int32_t order_by_term = -1; if (order_by_component) { for (i = 0; i < count; i ++) { ecs_term_t *term = &filter->terms[i]; /* Only And terms are supported */ if (term->id == order_by_component && term->oper == EcsAnd) { order_by_term = i; break; } } ecs_check(order_by_term != -1, ECS_INVALID_PARAMETER, "sorted component not is queried for"); } query->order_by_component = order_by_component; query->order_by = order_by; query->order_by_term = order_by_term; query->sort_table = action; ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); flecs_query_sort_tables(world, query); if (!query->table_slices.array) { flecs_query_build_sorted_tables(query); } query->flags &= ~EcsQueryTrivialIter; error: return; } static void flecs_query_group_by( ecs_query_t *query, ecs_entity_t sort_component, ecs_group_by_action_t group_by) { /* Cannot change grouping once a query has been created */ ecs_check(query->group_by_id == 0, ECS_INVALID_OPERATION, NULL); ecs_check(query->group_by == 0, ECS_INVALID_OPERATION, NULL); if (!group_by) { /* Builtin function that groups by relationship */ group_by = flecs_query_default_group_by; } query->group_by_id = sort_component; query->group_by = group_by; ecs_map_init_w_params(&query->groups, &query->filter.world->allocators.query_table_list); error: return; } /* Implementation for iterable mixin */ static void flecs_query_iter_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, ecs_term_t *filter) { ecs_poly_assert(poly, ecs_query_t); if (filter) { iter[1] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly)); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_query_iter(world, ECS_CONST_CAST(ecs_query_t*, poly)); } } static void flecs_query_on_event( ecs_iter_t *it) { /* Because this is the observer::run callback, checking if this is event is * already handled is not done for us. */ ecs_world_t *world = it->world; ecs_observer_t *o = it->ctx; if (o->last_event_id) { if (o->last_event_id[0] == world->event_id) { return; } o->last_event_id[0] = world->event_id; } ecs_query_t *query = o->ctx; ecs_table_t *table = it->table; ecs_entity_t event = it->event; if (event == EcsOnTableCreate) { /* Creation of new table */ if (flecs_query_match_table(world, query, table)) { if (query->subqueries.array) { ecs_query_event_t evt = { .kind = EcsQueryTableMatch, .table = table, .parent_query = query }; flecs_query_notify_subqueries(world, query, &evt); } } return; } ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); /* The observer isn't doing the matching because the query can do it more * efficiently by checking the table with the query cache. */ if (ecs_table_cache_get(&query->cache, table) == NULL) { return; } if (event == EcsOnTableEmpty) { flecs_query_update_table(query, table, true); } else if (event == EcsOnTableFill) { flecs_query_update_table(query, table, false); } else if (event == EcsOnTableDelete) { /* Deletion of table */ flecs_query_unmatch_table(query, table, NULL); if (query->subqueries.array) { ecs_query_event_t evt = { .kind = EcsQueryTableUnmatch, .table = table, .parent_query = query }; flecs_query_notify_subqueries(world, query, &evt); } return; } } static void flecs_query_table_cache_free( ecs_query_t *query) { ecs_table_cache_iter_t it; ecs_query_table_t *qt; if (flecs_table_cache_all_iter(&query->cache, &it)) { while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { flecs_query_table_free(query, qt); } } ecs_table_cache_fini(&query->cache); } static void flecs_query_allocators_init( ecs_query_t *query) { int32_t field_count = query->filter.field_count; if (field_count) { flecs_ballocator_init(&query->allocators.columns, field_count * ECS_SIZEOF(int32_t)); flecs_ballocator_init(&query->allocators.ids, field_count * ECS_SIZEOF(ecs_id_t)); flecs_ballocator_init(&query->allocators.sources, field_count * ECS_SIZEOF(ecs_entity_t)); flecs_ballocator_init(&query->allocators.monitors, (1 + field_count) * ECS_SIZEOF(int32_t)); } } static void flecs_query_allocators_fini( ecs_query_t *query) { int32_t field_count = query->filter.field_count; if (field_count) { flecs_ballocator_fini(&query->allocators.columns); flecs_ballocator_fini(&query->allocators.ids); flecs_ballocator_fini(&query->allocators.sources); flecs_ballocator_fini(&query->allocators.monitors); } } static void flecs_query_fini( ecs_query_t *query) { ecs_world_t *world = query->filter.world; ecs_group_delete_action_t on_delete = query->on_group_delete; if (on_delete) { ecs_map_iter_t it = ecs_map_iter(&query->groups); while (ecs_map_next(&it)) { ecs_query_table_list_t *group = ecs_map_ptr(&it); uint64_t group_id = ecs_map_key(&it); on_delete(world, group_id, group->info.ctx, query->group_by_ctx); } query->on_group_delete = NULL; } if (query->group_by_ctx_free) { if (query->group_by_ctx) { query->group_by_ctx_free(query->group_by_ctx); } } if ((query->flags & EcsQueryIsSubquery) && !(query->flags & EcsQueryIsOrphaned)) { flecs_query_remove_subquery(query->parent, query); } flecs_query_notify_subqueries(world, query, &(ecs_query_event_t){ .kind = EcsQueryOrphan }); flecs_query_for_each_component_monitor(world, query, flecs_monitor_unregister); flecs_query_table_cache_free(query); ecs_map_fini(&query->groups); ecs_vec_fini_t(NULL, &query->subqueries, ecs_query_t*); ecs_vec_fini_t(NULL, &query->table_slices, ecs_query_table_match_t); ecs_filter_fini(&query->filter); flecs_query_allocators_fini(query); if (query->ctx_free) { query->ctx_free(query->ctx); } if (query->binding_ctx_free) { query->binding_ctx_free(query->binding_ctx); } ecs_poly_free(query, ecs_query_t); } /* -- Public API -- */ ecs_query_t* ecs_query_init( ecs_world_t *world, const ecs_query_desc_t *desc) { ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); ecs_query_t *result = ecs_poly_new(ecs_query_t); ecs_observer_desc_t observer_desc = { .filter = desc->filter }; ecs_entity_t entity = desc->filter.entity; observer_desc.filter.flags = EcsFilterMatchEmptyTables; observer_desc.filter.storage = &result->filter; result->filter = ECS_FILTER_INIT; if (ecs_filter_init(world, &observer_desc.filter) == NULL) { goto error; } ECS_BIT_COND(result->flags, EcsQueryTrivialIter, !!(result->filter.flags & EcsFilterMatchOnlyThis)); flecs_query_allocators_init(result); if (result->filter.term_count) { observer_desc.entity = entity; observer_desc.run = flecs_query_on_event; observer_desc.ctx = result; observer_desc.events[0] = EcsOnTableEmpty; observer_desc.events[1] = EcsOnTableFill; if (!desc->parent) { observer_desc.events[2] = EcsOnTableCreate; observer_desc.events[3] = EcsOnTableDelete; } observer_desc.filter.flags |= EcsFilterNoData; observer_desc.filter.instanced = true; /* ecs_filter_init could have moved away resources from the terms array * in the descriptor, so use the terms array from the filter. */ observer_desc.filter.terms_buffer = result->filter.terms; observer_desc.filter.terms_buffer_count = result->filter.term_count; observer_desc.filter.expr = NULL; /* Already parsed */ entity = ecs_observer_init(world, &observer_desc); if (!entity) { goto error; } } result->iterable.init = flecs_query_iter_init; result->dtor = (ecs_poly_dtor_t)flecs_query_fini; result->prev_match_count = -1; result->ctx = desc->ctx; result->binding_ctx = desc->binding_ctx; result->ctx_free = desc->ctx_free; result->binding_ctx_free = desc->binding_ctx_free; if (ecs_should_log_1()) { char *filter_expr = ecs_filter_str(world, &result->filter); ecs_dbg_1("#[green]query#[normal] [%s] created", filter_expr ? filter_expr : ""); ecs_os_free(filter_expr); } ecs_log_push_1(); if (flecs_query_process_signature(world, result)) { goto error; } /* Group before matching so we won't have to move tables around later */ int32_t cascade_by = result->cascade_by; if (cascade_by) { flecs_query_group_by(result, result->filter.terms[cascade_by - 1].id, flecs_query_group_by_cascade); result->group_by_ctx = &result->filter.terms[cascade_by - 1]; } if (desc->group_by || desc->group_by_id) { /* Can't have a cascade term and group by at the same time, as cascade * uses the group_by mechanism */ ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, NULL); flecs_query_group_by(result, desc->group_by_id, desc->group_by); result->group_by_ctx = desc->group_by_ctx; result->on_group_create = desc->on_group_create; result->on_group_delete = desc->on_group_delete; result->group_by_ctx_free = desc->group_by_ctx_free; } if (desc->parent != NULL) { result->flags |= EcsQueryIsSubquery; } /* If the query refers to itself, add the components that were queried for * to the query itself. */ if (entity) { int32_t t, term_count = result->filter.term_count; ecs_term_t *terms = result->filter.terms; for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (term->src.id == entity) { ecs_add_id(world, entity, term->id); } } } if (!entity) { entity = ecs_new_id(world); } EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t); if (poly->poly) { /* If entity already had poly query, delete previous */ flecs_query_fini(poly->poly); } poly->poly = result; result->filter.entity = entity; /* Ensure that while initially populating the query with tables, they are * in the right empty/non-empty list. This ensures the query won't miss * empty/non-empty events for tables that are currently out of sync, but * change back to being in sync before processing pending events. */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_table_cache_init(world, &result->cache); if (!desc->parent) { flecs_query_match_tables(world, result); } else { flecs_query_add_subquery(world, desc->parent, result); result->parent = desc->parent; } if (desc->order_by) { flecs_query_order_by( world, result, desc->order_by_component, desc->order_by, desc->sort_table); } if (!ecs_query_table_count(result) && result->filter.term_count) { ecs_add_id(world, entity, EcsEmpty); } ecs_poly_modified(world, entity, ecs_query_t); ecs_log_pop_1(); return result; error: if (result) { ecs_filter_fini(&result->filter); ecs_os_free(result); } return NULL; } void ecs_query_fini( ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); ecs_delete(query->filter.world, query->filter.entity); } const ecs_filter_t* ecs_query_get_filter( const ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); return &query->filter; } static void flecs_query_set_var( ecs_iter_t *it) { ecs_check(it->constrained_vars == 1, ECS_INVALID_OPERATION, "can only set $this variable for queries"); ecs_var_t *var = &it->variables[0]; ecs_table_t *table = var->range.table; if (!table) { goto nodata; } ecs_query_iter_t *qit = &it->priv.iter.query; ecs_query_t *query = qit->query; ecs_query_table_t *qt = ecs_table_cache_get(&query->cache, table); if (!qt) { goto nodata; } qit->node = qt->first; qit->last = qt->last->next_match; it->offset = var->range.offset; it->count = var->range.count; return; error: nodata: it->priv.iter.query.node = NULL; it->priv.iter.query.last = NULL; return; } ecs_iter_t ecs_query_iter( const ecs_world_t *stage, ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); ecs_world_t *world = query->filter.world; ecs_poly_assert(world, ecs_world_t); /* Process table events to ensure that the list of iterated tables doesn't * contain empty tables. */ flecs_process_pending_tables(world); /* If query has order_by, apply sort */ flecs_query_sort_tables(world, query); /* If monitors changed, do query rematching */ if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { flecs_eval_component_monitors(world); } /* Prepare iterator */ int32_t table_count; if (ecs_vec_count(&query->table_slices)) { table_count = ecs_vec_count(&query->table_slices); } else { table_count = ecs_query_table_count(query); } ecs_query_iter_t it = { .query = query, .node = query->list.first, .last = NULL }; if (query->order_by && query->list.info.table_count) { it.node = ecs_vec_first(&query->table_slices); } ecs_iter_t result = { .real_world = world, .world = ECS_CONST_CAST(ecs_world_t*, stage), .terms = query->filter.terms, .field_count = query->filter.field_count, .table_count = table_count, .variable_count = 1, .priv.iter.query = it, .next = ecs_query_next, .set_var = flecs_query_set_var }; flecs_filter_apply_iter_flags(&result, &query->filter); ecs_filter_t *filter = &query->filter; ecs_iter_t fit; if (!(query->flags & EcsQueryTrivialIter)) { /* Check if non-This terms (like singleton terms) still match */ if (!(filter->flags & EcsFilterMatchOnlyThis)) { fit = flecs_filter_iter_w_flags(ECS_CONST_CAST(ecs_world_t*, stage), &query->filter, EcsIterIgnoreThis); if (!ecs_filter_next(&fit)) { /* No match, so return nothing */ goto noresults; } } flecs_iter_init(stage, &result, flecs_iter_cache_all); /* Copy the data */ if (!(filter->flags & EcsFilterMatchOnlyThis)) { int32_t field_count = filter->field_count; if (field_count) { if (result.ptrs) { ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, field_count); } ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, field_count); ecs_os_memcpy_n(result.columns, fit.columns, int32_t, field_count); ecs_os_memcpy_n(result.sources, fit.sources, int32_t, field_count); } ecs_iter_fini(&fit); } } else { /* Trivial iteration, use arrays from query cache */ flecs_iter_init(stage, &result, flecs_iter_cache_ptrs|flecs_iter_cache_variables); } result.sizes = query->filter.sizes; return result; error: noresults: result.priv.iter.query.node = NULL; return result; } void ecs_query_set_group( ecs_iter_t *it, uint64_t group_id) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv.iter.query; ecs_query_t *q = qit->query; ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_table_list_t *node = flecs_query_get_group(q, group_id); if (!node) { qit->node = NULL; return; } ecs_query_table_match_t *first = node->first; if (first) { qit->node = node->first; qit->last = node->last->next; } else { qit->node = NULL; qit->last = NULL; } error: return; } const ecs_query_group_info_t* ecs_query_get_group_info( const ecs_query_t *query, uint64_t group_id) { ecs_query_table_list_t *node = flecs_query_get_group(query, group_id); if (!node) { return NULL; } return &node->info; } void* ecs_query_get_group_ctx( const ecs_query_t *query, uint64_t group_id) { const ecs_query_group_info_t *info = ecs_query_get_group_info(query, group_id); if (!info) { return NULL; } else { return info->ctx; } } static void flecs_query_mark_columns_dirty( ecs_query_t *query, ecs_query_table_match_t *qm) { ecs_table_t *table = qm->table; ecs_filter_t *filter = &query->filter; if ((table && table->dirty_state) || (query->flags & EcsQueryHasNonThisOutTerms)) { ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_inout_kind_t inout = term->inout; if (inout == EcsIn || inout == EcsInOutNone) { /* Don't mark readonly terms dirty */ continue; } flecs_table_column_t tc; flecs_query_get_column_for_term(query, qm, i, &tc); if (tc.column == -1) { continue; } ecs_assert(tc.table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *dirty_state = tc.table->dirty_state; if (!dirty_state) { continue; } if (table != tc.table) { if (inout == EcsInOutDefault) { continue; } } ecs_assert(tc.column >= 0, ECS_INTERNAL_ERROR, NULL); dirty_state[tc.column + 1] ++; } } } bool ecs_query_next_table( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); flecs_iter_validate(it); ecs_query_iter_t *iter = &it->priv.iter.query; ecs_query_table_match_t *node = iter->node; ecs_query_t *query = iter->query; ecs_query_table_match_t *prev = iter->prev; if (prev) { if (query->flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev); } if (query->flags & EcsQueryHasOutTerms) { if (it->count) { flecs_query_mark_columns_dirty(query, prev); } } } if (node != iter->last) { it->table = node->table; it->group_id = node->group_id; it->count = 0; iter->node = node->next; iter->prev = node; return true; } error: query->match_count = query->prev_match_count; ecs_iter_fini(it); return false; } static void flecs_query_populate_trivial( ecs_iter_t *it, ecs_query_table_match_t *match) {; ecs_table_t *table = match->table; int32_t offset, count; if (!it->constrained_vars) { it->offset = offset = 0; it->count = count = ecs_table_count(table); } else { offset = it->offset; count = it->count; } it->ids = match->ids; it->sources = match->sources; it->columns = match->columns; it->group_id = match->group_id; it->instance_count = 0; it->references = ecs_vec_first(&match->refs); if (!it->references) { ecs_data_t *data = &table->data; if (!(it->flags & EcsIterNoData)) { int32_t i; for (i = 0; i < it->field_count; i ++) { int32_t column = match->storage_columns[i]; if (column < 0) { it->ptrs[i] = NULL; continue; } ecs_size_t size = it->sizes[i]; if (!size) { it->ptrs[i] = NULL; continue; } it->ptrs[i] = ecs_vec_get(&data->columns[column].data, it->sizes[i], offset); } } it->frame_offset += it->table ? ecs_table_count(it->table) : 0; it->table = table; it->entities = ecs_vec_get_t(&data->entities, ecs_entity_t, offset); } else { flecs_iter_populate_data( it->real_world, it, table, offset, count, it->ptrs); } } int ecs_query_populate( ecs_iter_t *it, bool when_changed) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *iter = &it->priv.iter.query; ecs_query_t *query = iter->query; ecs_query_table_match_t *match = iter->prev; ecs_assert(match != NULL, ECS_INVALID_OPERATION, NULL); if (query->flags & EcsQueryTrivialIter) { flecs_query_populate_trivial(it, match); return EcsIterNextYield; } ecs_table_t *table = match->table; ecs_world_t *world = query->filter.world; const ecs_filter_t *filter = &query->filter; ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_range_t *range = &ent_it->range; int32_t t, term_count = filter->term_count; int result; repeat: result = EcsIterNextYield; ecs_os_memcpy_n(it->sources, match->sources, ecs_entity_t, filter->field_count); for (t = 0; t < term_count; t ++) { ecs_term_t *term = &filter->terms[t]; int32_t field = term->field_index; if (!ecs_term_match_this(term)) { continue; } it->ids[field] = match->ids[field]; it->columns[field] = match->columns[field]; } if (table) { range->offset = match->offset; range->count = match->count; if (!range->count) { range->count = ecs_table_count(table); ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL); } if (match->entity_filter) { ent_it->entity_filter = match->entity_filter; ent_it->columns = match->columns; ent_it->range.table = table; ent_it->it = it; result = flecs_entity_filter_next(ent_it); if (result == EcsIterNext) { goto done; } } it->group_id = match->group_id; } else { range->offset = 0; range->count = 0; } if (when_changed) { if (!ecs_query_changed(NULL, it)) { if (result == EcsIterYield) { goto repeat; } else { result = EcsIterNext; goto done; } } } it->references = ecs_vec_first(&match->refs); it->instance_count = 0; flecs_iter_populate_data(world, it, table, range->offset, range->count, it->ptrs); error: done: return result; } bool ecs_query_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_query_next_instanced(it)); error: return false; } bool ecs_query_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *iter = &it->priv.iter.query; ecs_query_t *query = iter->query; ecs_flags32_t flags = query->flags; ecs_query_table_match_t *prev, *next, *cur = iter->node, *last = iter->last; if ((prev = iter->prev)) { /* Match has been iterated, update monitor for change tracking */ if (flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev); } if (flags & EcsQueryHasOutTerms) { flecs_query_mark_columns_dirty(query, prev); } } flecs_iter_validate(it); iter->skip_count = 0; /* Trivial iteration: each entry in the cache is a full match and ids are * only matched on $this or through traversal starting from $this. */ if (flags & EcsQueryTrivialIter) { if (cur == last) { goto done; } iter->node = cur->next; iter->prev = cur; flecs_query_populate_trivial(it, cur); return true; } /* Non-trivial iteration: query matches with static sources, or matches with * tables that require per-entity filtering. */ for (; cur != last; cur = next) { next = cur->next; iter->prev = cur; switch(ecs_query_populate(it, false)) { case EcsIterNext: iter->node = next; continue; case EcsIterYield: next = cur; /* fall through */ case EcsIterNextYield: goto yield; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } done: error: query->match_count = query->prev_match_count; ecs_iter_fini(it); return false; yield: iter->node = next; iter->prev = cur; return true; } bool ecs_query_changed( ecs_query_t *query, const ecs_iter_t *it) { if (it) { ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_query_table_match_t *qm = (ecs_query_table_match_t*)it->priv.iter.query.prev; ecs_assert(qm != NULL, ECS_INVALID_PARAMETER, NULL); if (!query) { query = it->priv.iter.query.query; } else { ecs_check(query == it->priv.iter.query.query, ECS_INVALID_PARAMETER, NULL); } ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_poly_assert(query, ecs_query_t); return flecs_query_check_match_monitor(query, qm, it); } ecs_poly_assert(query, ecs_query_t); ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); flecs_process_pending_tables(query->filter.world); if (!(query->flags & EcsQueryHasMonitor)) { query->flags |= EcsQueryHasMonitor; flecs_query_init_query_monitors(query); return true; /* Monitors didn't exist yet */ } if (query->match_count != query->prev_match_count) { return true; } return flecs_query_check_query_monitor(query); error: return false; } void ecs_query_skip( ecs_iter_t *it) { ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); if (it->instance_count > it->count) { it->priv.iter.query.skip_count ++; if (it->priv.iter.query.skip_count == it->instance_count) { /* For non-instanced queries, make sure all entities are skipped */ it->priv.iter.query.prev = NULL; } } else { it->priv.iter.query.prev = NULL; } } bool ecs_query_orphaned( const ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); return query->flags & EcsQueryIsOrphaned; } char* ecs_query_str( const ecs_query_t *query) { return ecs_filter_str(query->filter.world, &query->filter); } int32_t ecs_query_table_count( const ecs_query_t *query) { ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); return query->cache.tables.count; } int32_t ecs_query_empty_table_count( const ecs_query_t *query) { ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); return query->cache.empty_tables.count; } int32_t ecs_query_entity_count( const ecs_query_t *query) { ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); int32_t result = 0; ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last; if (!last) { return 0; } for (cur = query->cache.tables.first; cur != NULL; cur = cur->next) { result += ecs_table_count(cur->table); } return result; } void* ecs_query_get_ctx( const ecs_query_t *query) { return query->ctx; } void* ecs_query_get_binding_ctx( const ecs_query_t *query) { return query->binding_ctx; } /** * @file search.c * @brief Search functions to find (component) ids in table types. * * Search functions are used to find the column index of a (component) id in a * table. Additionally, search functions implement the logic for finding a * component id by following a relationship upwards. */ static int32_t flecs_type_search( const ecs_table_t *table, ecs_id_t search_id, ecs_id_record_t *idr, ecs_id_t *ids, ecs_id_t *id_out, ecs_table_record_t **tr_out) { ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); if (tr) { int32_t r = tr->index; if (tr_out) tr_out[0] = tr; if (id_out) { if (ECS_PAIR_FIRST(search_id) == EcsUnion) { id_out[0] = ids[r]; } else { id_out[0] = flecs_to_public_id(ids[r]); } } return r; } return -1; } static int32_t flecs_type_offset_search( int32_t offset, ecs_id_t id, ecs_id_t *ids, int32_t count, ecs_id_t *id_out) { ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); while (offset < count) { ecs_id_t type_id = ids[offset ++]; if (ecs_id_match(type_id, id)) { if (id_out) { id_out[0] = flecs_to_public_id(type_id); } return offset - 1; } } return -1; } static bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_id_record_t *idr, ecs_id_t id) { ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (idr->flags & EcsIdDontInherit) { return false; } if (idr->flags & EcsIdExclusive) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t er = ECS_PAIR_FIRST(id); if (flecs_table_record_get( world, table, ecs_pair(er, EcsWildcard))) { return false; } } } return true; } static int32_t flecs_type_search_relation( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_record_t *idr, ecs_id_t rel, ecs_id_record_t *idr_r, bool self, ecs_entity_t *subject_out, ecs_id_t *id_out, ecs_table_record_t **tr_out) { ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t count = type.count; if (self) { if (offset) { int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); if (r != -1) { return r; } } else { int32_t r = flecs_type_search(table, id, idr, ids, id_out, tr_out); if (r != -1) { return r; } } } ecs_flags32_t flags = table->flags; if ((flags & EcsTableHasPairs) && rel) { bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); if (is_a) { if (!(flags & EcsTableHasIsA)) { return -1; } idr_r = world->idr_isa_wildcard; if (!flecs_type_can_inherit_id(world, table, idr, id)) { return -1; } } if (!idr_r) { idr_r = flecs_id_record_get(world, rel); if (!idr_r) { return -1; } } ecs_id_t id_r; int32_t r, r_column; if (offset) { r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r); } else { r_column = flecs_type_search(table, id, idr_r, ids, &id_r, 0); } while (r_column != -1) { ecs_entity_t obj = ECS_PAIR_SECOND(id_r); ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *rec = flecs_entities_get_any(world, obj); ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *obj_table = rec->table; if (obj_table) { ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); r = flecs_type_search_relation(world, obj_table, 0, id, idr, rel, idr_r, true, subject_out, id_out, tr_out); if (r != -1) { if (subject_out && !subject_out[0]) { subject_out[0] = ecs_get_alive(world, obj); } return r_column; } if (!is_a) { r = flecs_type_search_relation(world, obj_table, 0, id, idr, ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, true, subject_out, id_out, tr_out); if (r != -1) { if (subject_out && !subject_out[0]) { subject_out[0] = ecs_get_alive(world, obj); } return r_column; } } } r_column = flecs_type_offset_search( r_column + 1, rel, ids, count, &id_r); } } return -1; } int32_t flecs_search_relation_w_idr( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, ecs_id_record_t *idr) { if (!table) return -1; ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); flags = flags ? flags : (EcsSelf|EcsUp); if (!idr) { idr = flecs_query_id_record_get(world, id); if (!idr) { return -1; } } if (subject_out) subject_out[0] = 0; if (!(flags & EcsUp)) { if (offset) { return ecs_search_offset(world, table, offset, id, id_out); } else { return flecs_type_search( table, id, idr, table->type.array, id_out, tr_out); } } int32_t result = flecs_type_search_relation(world, table, offset, id, idr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, id_out, tr_out); return result; } int32_t ecs_search_relation( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out) { if (!table) return -1; ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); flags = flags ? flags : (EcsSelf|EcsUp); if (subject_out) subject_out[0] = 0; if (!(flags & EcsUp)) { return ecs_search_offset(world, table, offset, id, id_out); } ecs_id_record_t *idr = flecs_query_id_record_get(world, id); if (!idr) { return -1; } int32_t result = flecs_type_search_relation(world, table, offset, id, idr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, id_out, tr_out); return result; } int32_t flecs_search_w_idr( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, ecs_id_t *id_out, ecs_id_record_t *idr) { if (!table) return -1; ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_type_t type = table->type; ecs_id_t *ids = type.array; return flecs_type_search(table, id, idr, ids, id_out, 0); } int32_t ecs_search( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, ecs_id_t *id_out) { if (!table) return -1; ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_query_id_record_get(world, id); if (!idr) { return -1; } ecs_type_t type = table->type; ecs_id_t *ids = type.array; return flecs_type_search(table, id, idr, ids, id_out, 0); } int32_t ecs_search_offset( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_t *id_out) { if (!offset) { ecs_poly_assert(world, ecs_world_t); return ecs_search(world, table, id, id_out); } if (!table) return -1; ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t count = type.count; return flecs_type_offset_search(offset, id, ids, count, id_out); } static int32_t flecs_relation_depth_walk( const ecs_world_t *world, const ecs_id_record_t *idr, const ecs_table_t *first, const ecs_table_t *table) { int32_t result = 0; ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return 0; } int32_t i = tr->index, end = i + tr->count; for (; i != end; i ++) { ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_t *ot = ecs_get_table(world, o); if (!ot) { continue; } ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); int32_t cur = flecs_relation_depth_walk(world, idr, first, ot); if (cur > result) { result = cur; } } return result + 1; } int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); if (!idr) { return 0; } int32_t depth_offset = 0; if (table->flags & EcsTableHasTarget) { if (ecs_table_get_type_index(world, table, ecs_pair_t(EcsTarget, r)) != -1) { ecs_id_t id; int32_t col = ecs_search(world, table, ecs_pair(EcsFlatten, EcsWildcard), &id); if (col == -1) { return 0; } ecs_entity_t did = ecs_pair_second(world, id); ecs_assert(did != 0, ECS_INTERNAL_ERROR, NULL); uint64_t *val = ecs_map_get(&world->store.entity_to_depth, did); ecs_assert(val != NULL, ECS_INTERNAL_ERROR, NULL); depth_offset = flecs_uto(int32_t, val[0]); } } return flecs_relation_depth_walk(world, idr, table, table) + depth_offset; } /** * @file stage.c * @brief Staging implementation. * * A stage is an object that can be used to temporarily store mutations to a * world while a world is in readonly mode. ECS operations that are invoked on * a stage are stored in a command buffer, which is flushed during sync points, * or manually by the user. * * Stages contain additional state to enable other API functionality without * having to mutate the world, such as setting the current scope, and allocators * that are local to a stage. * * In a multi threaded application, each thread has its own stage which allows * threads to insert mutations without having to lock administration. */ static ecs_cmd_t* flecs_cmd_alloc( ecs_stage_t *stage) { ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->commands, ecs_cmd_t); ecs_os_zeromem(cmd); return cmd; } static ecs_cmd_t* flecs_cmd_new( ecs_stage_t *stage, ecs_entity_t e, bool is_delete, bool can_batch) { if (e) { ecs_vec_t *cmds = &stage->commands; ecs_cmd_entry_t *entry = flecs_sparse_try_t( &stage->cmd_entries, ecs_cmd_entry_t, e); int32_t cur = ecs_vec_count(cmds); if (entry) { int32_t last = entry->last; if (entry->last == -1) { /* Entity was deleted, don't insert command */ return NULL; } if (can_batch) { ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *last_op = &arr[last]; last_op->next_for_entity = cur; if (last == entry->first) { /* Flip sign bit so flush logic can tell which command * is the first for an entity */ last_op->next_for_entity *= -1; } } } else if (can_batch || is_delete) { entry = flecs_sparse_ensure_t(&stage->cmd_entries, ecs_cmd_entry_t, e); entry->first = cur; } if (can_batch) { entry->last = cur; } if (is_delete) { /* Prevent insertion of more commands for entity */ entry->last = -1; } } return flecs_cmd_alloc(stage); } static void flecs_stages_merge( ecs_world_t *world, bool force_merge) { bool is_stage = ecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); bool measure_frame_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; if (measure_frame_time) { ecs_os_get_time(&t_start); } ecs_dbg_3("#[magenta]merge"); ecs_log_push_3(); if (is_stage) { /* Check for consistency if force_merge is enabled. In practice this * function will never get called with force_merge disabled for just * a single stage. */ if (force_merge || stage->auto_merge) { ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, "mismatching defer_begin/defer_end detected"); flecs_defer_end(world, stage); } } else { /* Merge stages. Only merge if the stage has auto_merging turned on, or * if this is a forced merge (like when ecs_merge is called) */ int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); ecs_poly_assert(s, ecs_stage_t); if (force_merge || s->auto_merge) { flecs_defer_end(world, s); } } } flecs_eval_component_monitors(world); if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; /* If stage is asynchronous, deferring is always enabled */ if (stage->async) { flecs_defer_begin(world, stage); } ecs_log_pop_3(); } static void flecs_stage_auto_merge( ecs_world_t *world) { flecs_stages_merge(world, false); } static void flecs_stage_manual_merge( ecs_world_t *world) { flecs_stages_merge(world, true); } bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); (void)world; if (stage->defer < 0) return false; return (++ stage->defer) == 1; } bool flecs_defer_cmd( ecs_stage_t *stage) { if (stage->defer) { return (stage->defer > 0); } stage->defer ++; return false; } bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = EcsOpClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; } return true; } return false; } bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name) { if (stage->defer > 0) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = EcsOpPath; cmd->entity = entity; cmd->id = parent; cmd->is._1.value = ecs_os_strdup(name); } return true; } return false; } bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, true, false); if (cmd) { cmd->kind = EcsOpDelete; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpClear; cmd->entity = entity; } return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_alloc(stage); cmd->kind = EcsOpOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = enable ? EcsOpEnable : EcsOpDisable; cmd->entity = entity; cmd->id = id; } return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new_id as this is thread safe */ int i; for (i = 0; i < count; i ++) { ids[i] = ecs_new_id(world); } *ids_out = ids; /* Store data in op */ ecs_cmd_t *cmd = flecs_cmd_alloc(stage); if (cmd) { cmd->kind = EcsOpBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; } return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpAdd; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpRemove; cmd->id = id; cmd->entity = entity; } return true; } return false; } void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t cmd_kind, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value, bool need_value) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (!cmd) { if (need_value) { /* Entity is deleted by a previous command, but we still need to * return a temporary storage to the application. */ cmd_kind = EcsOpSkip; } else { /* No value needs to be returned, we can drop the command */ return NULL; } } /* Find type info for id */ const ecs_type_info_t *ti = NULL; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If idr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (stage->async || (world->flags & EcsWorldMultiThreaded)) { ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); } else { /* When not in multi threaded mode, it's safe to find or * create the id record. */ idr = flecs_id_record_ensure(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); /* Get type_info from id record. We could have called * ecs_get_type_info directly, but since this function can be * expensive for pairs, creating the id record ensures we can * find the type_info quickly for subsequent operations. */ ti = idr->type_info; } } else { ti = idr->type_info; } /* If the id isn't associated with a type, we can't set anything */ ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); /* Make sure the size of the value equals the type size */ ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); size = ti->size; /* Find existing component. Make sure it's owned, so that we won't use the * component of a prefab. */ void *existing = NULL; ecs_table_t *table = NULL; if (idr) { /* Entity can only have existing component if id record exists */ ecs_record_t *r = flecs_entities_get(world, entity); table = r->table; if (r && table) { const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); if (tr) { ecs_assert(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); /* Entity has the component */ ecs_vec_t *column = &table->data.columns[tr->column].data; existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); } } } /* Get existing value from storage */ void *cmd_value = existing; bool emplace = cmd_kind == EcsOpEmplace; /* If the component does not yet exist, create a temporary value. This is * necessary so we can store a component value in the deferred command, * without adding the component to the entity which is not allowed in * deferred mode. */ if (!existing) { ecs_stack_t *stack = &stage->defer_stack; cmd_value = flecs_stack_alloc(stack, size, ti->alignment); /* If the component doesn't yet exist, construct it and move the * provided value into the component, if provided. Don't construct if * this is an emplace operation, in which case the application is * responsible for constructing. */ if (value) { if (emplace) { ecs_move_t move = ti->hooks.move_ctor; if (move) { move(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } } else if (!emplace) { /* If the command is not an emplace, construct the temp storage */ /* Check if entity inherits component */ void *base = NULL; if (table && (table->flags & EcsTableHasIsA)) { base = flecs_get_base_component(world, table, id, idr, 0); } if (!base) { /* Normal ctor */ ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(cmd_value, 1, ti); } } else { /* Override */ ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, base, 1, ti); } else { ecs_os_memcpy(cmd_value, base, size); } } } } else if (value) { /* If component exists and value is provided, copy */ ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(existing, value, 1, ti); } else { ecs_os_memcpy(existing, value, size); } } if (!cmd) { /* If cmd is NULL, entity was already deleted. Check if we need to * insert a command into the queue. */ if (!ti->hooks.dtor) { /* If temporary memory does not need to be destructed, it'll get * freed when the stack allocator is reset. This prevents us * from having to insert a command when the entity was * already deleted. */ return cmd_value; } cmd = flecs_cmd_alloc(stage); } if (!existing) { /* If component didn't exist yet, insert command that will create it */ cmd->kind = cmd_kind; cmd->id = id; cmd->idr = idr; cmd->entity = entity; cmd->is._1.size = size; cmd->is._1.value = cmd_value; } else { /* If component already exists, still insert an Add command to ensure * that any preceding remove commands won't remove the component. If the * operation is a set, also insert a Modified command. */ if (cmd_kind == EcsOpSet) { cmd->kind = EcsOpAddModified; } else { cmd->kind = EcsOpAdd; } cmd->id = id; cmd->entity = entity; } return cmd_value; error: return NULL; } void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage) { /* Execute post frame actions */ int32_t i, count = ecs_vec_count(&stage->post_frame_actions); ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_clear(&stage->post_frame_actions); } void flecs_stage_init( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_init(stage, ecs_stage_t); stage->world = world; stage->thread_ctx = world; stage->auto_merge = true; stage->async = false; flecs_stack_init(&stage->defer_stack); flecs_stack_init(&stage->allocators.iter_stack); flecs_stack_init(&stage->allocators.deser_stack); flecs_allocator_init(&stage->allocator); flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, FLECS_SPARSE_PAGE_SIZE); ecs_allocator_t *a = &stage->allocator; ecs_vec_init_t(a, &stage->commands, ecs_cmd_t, 0); ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); flecs_sparse_init_t(&stage->cmd_entries, &stage->allocator, &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } void flecs_stage_fini( ecs_world_t *world, ecs_stage_t *stage) { (void)world; ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); /* Make sure stage has no unmerged data */ ecs_assert(ecs_vec_count(&stage->commands) == 0, ECS_INTERNAL_ERROR, NULL); ecs_poly_fini(stage, ecs_stage_t); flecs_sparse_fini(&stage->cmd_entries); ecs_allocator_t *a = &stage->allocator; ecs_vec_fini_t(a, &stage->commands, ecs_cmd_t); ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); ecs_vec_fini(NULL, &stage->variables, 0); ecs_vec_fini(NULL, &stage->operations, 0); flecs_stack_fini(&stage->defer_stack); flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); flecs_allocator_fini(&stage->allocator); } void ecs_set_stage_count( ecs_world_t *world, int32_t stage_count) { ecs_poly_assert(world, ecs_world_t); /* World must have at least one default stage */ ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), ECS_INTERNAL_ERROR, NULL); bool auto_merge = true; const ecs_entity_t *lookup_path = NULL; ecs_entity_t scope = 0; ecs_entity_t with = 0; if (world->stage_count >= 1) { auto_merge = world->stages[0].auto_merge; lookup_path = world->stages[0].lookup_path; scope = world->stages[0].scope; with = world->stages[0].with; } int32_t i, count = world->stage_count; if (count && count != stage_count) { ecs_stage_t *stages = world->stages; for (i = 0; i < count; i ++) { /* If stage contains a thread handle, ecs_set_threads was used to * create the stages. ecs_set_threads and ecs_set_stage_count should not * be mixed. */ ecs_poly_assert(&stages[i], ecs_stage_t); ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); flecs_stage_fini(world, &stages[i]); } ecs_os_free(world->stages); } if (stage_count) { world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); for (i = 0; i < stage_count; i ++) { ecs_stage_t *stage = &world->stages[i]; flecs_stage_init(world, stage); stage->id = i; /* Set thread_ctx to stage, as this stage might be used in a * multithreaded context */ stage->thread_ctx = (ecs_world_t*)stage; stage->thread = 0; } } else { /* Set to NULL to prevent double frees */ world->stages = NULL; } /* Regardless of whether the stage was just initialized or not, when the * ecs_set_stage_count function is called, all stages inherit the auto_merge * property from the world */ for (i = 0; i < stage_count; i ++) { world->stages[i].auto_merge = auto_merge; world->stages[i].lookup_path = lookup_path; world->stages[0].scope = scope; world->stages[0].with = with; } world->stage_count = stage_count; error: return; } int32_t ecs_get_stage_count( const ecs_world_t *world) { world = ecs_get_world(world); return world->stage_count; } int32_t ecs_get_stage_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (ecs_poly_is(world, ecs_stage_t)) { ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); /* Index 0 is reserved for main stage */ return stage->id; } else if (ecs_poly_is(world, ecs_world_t)) { return 0; } else { ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } ecs_world_t* ecs_get_stage( const ecs_world_t *world, int32_t stage_id) { ecs_poly_assert(world, ecs_world_t); ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); return (ecs_world_t*)&world->stages[stage_id]; error: return NULL; } bool ecs_readonly_begin( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *stage = &world->stages[i]; stage->lookup_path = world->stages[0].lookup_path; ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, "deferred mode cannot be enabled when entering readonly mode"); flecs_defer_begin(world, stage); } bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); /* From this point on, the world is "locked" for mutations, and it is only * allowed to enqueue commands from stages */ ECS_BIT_SET(world->flags, EcsWorldReadonly); /* If world has more than one stage, signal we might be running on multiple * threads. This is a stricter version of readonly mode: while some * mutations like implicit component registration are still allowed in plain * readonly mode, no mutations are allowed when multithreaded. */ if (world->worker_cond) { ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); } return is_readonly; } void ecs_readonly_end( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); /* After this it is safe again to mutate the world directly */ ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); ecs_log_pop_3(); flecs_stage_auto_merge(world); error: return; } void ecs_merge( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); flecs_stage_manual_merge(world); error: return; } void ecs_set_automerge( ecs_world_t *world, bool auto_merge) { /* If a world is provided, set auto_merge globally for the world. This * doesn't actually do anything (the main stage never merges) but it serves * as the default for when stages are created. */ if (ecs_poly_is(world, ecs_world_t)) { world->stages[0].auto_merge = auto_merge; /* Propagate change to all stages */ int i, stage_count = ecs_get_stage_count(world); for (i = 0; i < stage_count; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); stage->auto_merge = auto_merge; } /* If a stage is provided, override the auto_merge value for the individual * stage. This allows an application to control per-stage which stage should * be automatically merged and which one shouldn't */ } else { ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; stage->auto_merge = auto_merge; } } bool ecs_stage_is_readonly( const ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); if (ecs_poly_is(stage, ecs_stage_t)) { if (((const ecs_stage_t*)stage)->async) { return false; } } if (world->flags & EcsWorldReadonly) { if (ecs_poly_is(stage, ecs_world_t)) { return true; } } else { if (ecs_poly_is(stage, ecs_stage_t)) { return true; } } return false; } ecs_world_t* ecs_async_stage_new( ecs_world_t *world) { ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); flecs_stage_init(world, stage); stage->id = -1; stage->auto_merge = false; stage->async = true; flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } void ecs_async_stage_free( ecs_world_t *world) { ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL); flecs_stage_fini(stage->world, stage); ecs_os_free(stage); error: return; } bool ecs_stage_is_async( ecs_world_t *stage) { if (!stage) { return false; } if (!ecs_poly_is(stage, ecs_stage_t)) { return false; } return ((ecs_stage_t*)stage)->async; } bool ecs_is_deferred( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->defer > 0; error: return false; } /** * @file value.c * @brief Utility functions to work with non-trivial pointers of user types. */ int ecs_value_init_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void *ptr) { ecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_xtor_t ctor; if ((ctor = ti->hooks.ctor)) { ctor(ptr, 1, ti); } else { ecs_os_memset(ptr, 0, ti->size); } return 0; error: return -1; } int ecs_value_init( const ecs_world_t *world, ecs_entity_t type, void *ptr) { ecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_init_w_type_info(world, ti, ptr); error: return -1; } void* ecs_value_new_w_type_info( ecs_world_t *world, const ecs_type_info_t *ti) { ecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; void *result = flecs_alloc(&world->allocator, ti->size); if (ecs_value_init_w_type_info(world, ti, result) != 0) { flecs_free(&world->allocator, ti->size, result); goto error; } return result; error: return NULL; } void* ecs_value_new( ecs_world_t *world, ecs_entity_t type) { ecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_new_w_type_info(world, ti); error: return NULL; } int ecs_value_fini_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void *ptr) { ecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_xtor_t dtor; if ((dtor = ti->hooks.dtor)) { dtor(ptr, 1, ti); } return 0; error: return -1; } int ecs_value_fini( const ecs_world_t *world, ecs_entity_t type, void* ptr) { ecs_poly_assert(world, ecs_world_t); (void)world; const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_fini_w_type_info(world, ti, ptr); error: return -1; } int ecs_value_free( ecs_world_t *world, ecs_entity_t type, void* ptr) { ecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { goto error; } flecs_free(&world->allocator, ti->size, ptr); return 0; error: return -1; } int ecs_value_copy_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, const void *src) { ecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_copy_t copy; if ((copy = ti->hooks.copy)) { copy(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, ti->size); } return 0; error: return -1; } int ecs_value_copy( const ecs_world_t *world, ecs_entity_t type, void* dst, const void *src) { ecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_copy_w_type_info(world, ti, dst, src); error: return -1; } int ecs_value_move_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, void *src) { ecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_move_t move; if ((move = ti->hooks.move)) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, ti->size); } return 0; error: return -1; } int ecs_value_move( const ecs_world_t *world, ecs_entity_t type, void* dst, void *src) { ecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_move_w_type_info(world, ti, dst, src); error: return -1; } int ecs_value_move_ctor_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, void *src) { ecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_move_t move; if ((move = ti->hooks.move_ctor)) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, ti->size); } return 0; error: return -1; } int ecs_value_move_ctor( const ecs_world_t *world, ecs_entity_t type, void* dst, void *src) { ecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_move_w_type_info(world, ti, dst, src); error: return -1; } /** * @file world.c * @brief World-level API. */ /* Id flags */ const ecs_id_t ECS_PAIR = (1ull << 63); const ecs_id_t ECS_OVERRIDE = (1ull << 62); const ecs_id_t ECS_TOGGLE = (1ull << 61); const ecs_id_t ECS_AND = (1ull << 60); /** Builtin component ids */ const ecs_entity_t ecs_id(EcsComponent) = 1; const ecs_entity_t ecs_id(EcsIdentifier) = 2; const ecs_entity_t ecs_id(EcsIterable) = 3; const ecs_entity_t ecs_id(EcsPoly) = 4; /* Poly target components */ const ecs_entity_t EcsQuery = 5; const ecs_entity_t EcsObserver = 6; const ecs_entity_t EcsSystem = 7; /* Core scopes & entities */ const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 8; const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 9; /* Relationship properties */ const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 10; const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 11; const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 12; const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 13; const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 14; const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 15; const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 17; const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsAlwaysOverride = FLECS_HI_COMPONENT_ID + 19; const ecs_entity_t EcsTag = FLECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 21; const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 23; const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 24; const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 26; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 29; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 32; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 35; const ecs_entity_t EcsUnSet = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 38; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 46; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 47; const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 48; const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 49; /* Actions */ const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 50; const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 51; const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 52; /* Misc */ const ecs_entity_t ecs_id(EcsTarget) = FLECS_HI_COMPONENT_ID + 53; const ecs_entity_t EcsFlatten = FLECS_HI_COMPONENT_ID + 54; const ecs_entity_t EcsDefaultChildComponent = FLECS_HI_COMPONENT_ID + 55; /* Builtin predicate ids (used by rule engine) */ const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 56; const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 57; const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 58; const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 59; const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 60; /* Systems */ const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 61; const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 62; const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 63; const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 64; const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 65; const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 67; const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 68; const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 69; const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 70; const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 71; const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 72; const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 73; const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 74; const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 75; /* Meta primitive components (don't use low ids to save id space) */ const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 80; const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 97; const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 98; const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 110; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 111; /* Doc module components */ const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 112; const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 113; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 116; /* REST module components */ const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 117; /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; /* Declarations for addons. Located in world.c to avoid issues during linking of * static library */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(EcsAlert); ECS_COMPONENT_DECLARE(EcsAlertInstance); ECS_COMPONENT_DECLARE(EcsAlertsActive); ECS_TAG_DECLARE(EcsAlertInfo); ECS_TAG_DECLARE(EcsAlertWarning); ECS_TAG_DECLARE(EcsAlertError); ECS_TAG_DECLARE(EcsAlertCritical); #endif #ifdef FLECS_UNITS ECS_DECLARE(EcsUnitPrefixes); ECS_DECLARE(EcsYocto); ECS_DECLARE(EcsZepto); ECS_DECLARE(EcsAtto); ECS_DECLARE(EcsFemto); ECS_DECLARE(EcsPico); ECS_DECLARE(EcsNano); ECS_DECLARE(EcsMicro); ECS_DECLARE(EcsMilli); ECS_DECLARE(EcsCenti); ECS_DECLARE(EcsDeci); ECS_DECLARE(EcsDeca); ECS_DECLARE(EcsHecto); ECS_DECLARE(EcsKilo); ECS_DECLARE(EcsMega); ECS_DECLARE(EcsGiga); ECS_DECLARE(EcsTera); ECS_DECLARE(EcsPeta); ECS_DECLARE(EcsExa); ECS_DECLARE(EcsZetta); ECS_DECLARE(EcsYotta); ECS_DECLARE(EcsKibi); ECS_DECLARE(EcsMebi); ECS_DECLARE(EcsGibi); ECS_DECLARE(EcsTebi); ECS_DECLARE(EcsPebi); ECS_DECLARE(EcsExbi); ECS_DECLARE(EcsZebi); ECS_DECLARE(EcsYobi); ECS_DECLARE(EcsDuration); ECS_DECLARE(EcsPicoSeconds); ECS_DECLARE(EcsNanoSeconds); ECS_DECLARE(EcsMicroSeconds); ECS_DECLARE(EcsMilliSeconds); ECS_DECLARE(EcsSeconds); ECS_DECLARE(EcsMinutes); ECS_DECLARE(EcsHours); ECS_DECLARE(EcsDays); ECS_DECLARE(EcsTime); ECS_DECLARE(EcsDate); ECS_DECLARE(EcsMass); ECS_DECLARE(EcsGrams); ECS_DECLARE(EcsKiloGrams); ECS_DECLARE(EcsElectricCurrent); ECS_DECLARE(EcsAmpere); ECS_DECLARE(EcsAmount); ECS_DECLARE(EcsMole); ECS_DECLARE(EcsLuminousIntensity); ECS_DECLARE(EcsCandela); ECS_DECLARE(EcsForce); ECS_DECLARE(EcsNewton); ECS_DECLARE(EcsLength); ECS_DECLARE(EcsMeters); ECS_DECLARE(EcsPicoMeters); ECS_DECLARE(EcsNanoMeters); ECS_DECLARE(EcsMicroMeters); ECS_DECLARE(EcsMilliMeters); ECS_DECLARE(EcsCentiMeters); ECS_DECLARE(EcsKiloMeters); ECS_DECLARE(EcsMiles); ECS_DECLARE(EcsPixels); ECS_DECLARE(EcsPressure); ECS_DECLARE(EcsPascal); ECS_DECLARE(EcsBar); ECS_DECLARE(EcsSpeed); ECS_DECLARE(EcsMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerHour); ECS_DECLARE(EcsMilesPerHour); ECS_DECLARE(EcsAcceleration); ECS_DECLARE(EcsTemperature); ECS_DECLARE(EcsKelvin); ECS_DECLARE(EcsCelsius); ECS_DECLARE(EcsFahrenheit); ECS_DECLARE(EcsData); ECS_DECLARE(EcsBits); ECS_DECLARE(EcsKiloBits); ECS_DECLARE(EcsMegaBits); ECS_DECLARE(EcsGigaBits); ECS_DECLARE(EcsBytes); ECS_DECLARE(EcsKiloBytes); ECS_DECLARE(EcsMegaBytes); ECS_DECLARE(EcsGigaBytes); ECS_DECLARE(EcsKibiBytes); ECS_DECLARE(EcsGibiBytes); ECS_DECLARE(EcsMebiBytes); ECS_DECLARE(EcsDataRate); ECS_DECLARE(EcsBitsPerSecond); ECS_DECLARE(EcsKiloBitsPerSecond); ECS_DECLARE(EcsMegaBitsPerSecond); ECS_DECLARE(EcsGigaBitsPerSecond); ECS_DECLARE(EcsBytesPerSecond); ECS_DECLARE(EcsKiloBytesPerSecond); ECS_DECLARE(EcsMegaBytesPerSecond); ECS_DECLARE(EcsGigaBytesPerSecond); ECS_DECLARE(EcsPercentage); ECS_DECLARE(EcsAngle); ECS_DECLARE(EcsRadians); ECS_DECLARE(EcsDegrees); ECS_DECLARE(EcsBel); ECS_DECLARE(EcsDeciBel); ECS_DECLARE(EcsFrequency); ECS_DECLARE(EcsHertz); ECS_DECLARE(EcsKiloHertz); ECS_DECLARE(EcsMegaHertz); ECS_DECLARE(EcsGigaHertz); ECS_DECLARE(EcsUri); ECS_DECLARE(EcsUriHyperlink); ECS_DECLARE(EcsUriImage); ECS_DECLARE(EcsUriFile); #endif /* -- Private functions -- */ const ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world) { ecs_assert(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (ecs_poly_is(world, ecs_world_t)) { return &world->stages[0]; } else if (ecs_poly_is(world, ecs_stage_t)) { return ECS_CONST_CAST(ecs_stage_t*, world); } return NULL; } ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr) { ecs_world_t *world = *world_ptr; ecs_assert(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (ecs_poly_is(world, ecs_world_t)) { return &world->stages[0]; } *world_ptr = ((ecs_stage_t*)world)->world; return ECS_CONST_CAST(ecs_stage_t*, world); } ecs_world_t* flecs_suspend_readonly( const ecs_world_t *stage_world, ecs_suspend_readonly_state_t *state) { ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); ecs_poly_assert(world, ecs_world_t); bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); bool is_deferred = ecs_is_deferred(world); if (!is_readonly && !is_deferred) { state->is_readonly = false; state->is_deferred = false; return world; } ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = is_deferred; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; /* Hack around safety checks (this ought to look ugly) */ ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); state->defer_count = stage->defer; state->commands = stage->commands; state->defer_stack = stage->defer_stack; flecs_stack_init(&stage->defer_stack); state->scope = stage->scope; state->with = stage->with; stage->defer = 0; ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); return world; } void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state) { ecs_poly_assert(world, ecs_world_t); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (state->is_readonly || state->is_deferred) { ecs_dbg_3("resuming readonly mode"); ecs_run_aperiodic(world, 0); /* Restore readonly state / defer count */ ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); stage->defer = state->defer_count; ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); stage->commands = state->commands; flecs_stack_fini(&stage->defer_stack); stage->defer_stack = state->defer_stack; stage->scope = state->scope; stage->with = state->with; } } /* Evaluate component monitor. If a monitored entity changed it will have set a * flag in one of the world's component monitors. Queries can register * themselves with component monitors to determine whether they need to rematch * with tables. */ static void flecs_eval_component_monitor( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); if (!world->monitors.is_dirty) { return; } world->monitors.is_dirty = false; ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); while (ecs_map_next(&it)) { ecs_monitor_t *m = ecs_map_ptr(&it); if (!m->is_dirty) { continue; } m->is_dirty = false; int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **elems = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { ecs_query_t *q = elems[i]; flecs_query_notify(world, q, &(ecs_query_event_t) { .kind = EcsQueryTableRematch }); } } } void flecs_monitor_mark_dirty( ecs_world_t *world, ecs_entity_t id) { ecs_map_t *monitors = &world->monitors.monitors; /* Only flag if there are actually monitors registered, so that we * don't waste cycles evaluating monitors if there's no interest */ if (ecs_map_is_init(monitors)) { ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (m) { if (!world->monitors.is_dirty) { world->monitor_generation ++; } m->is_dirty = true; world->monitors.is_dirty = true; } } } void flecs_monitor_register( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *monitors = &world->monitors.monitors; ecs_map_init_if(monitors, &world->allocator); ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); ecs_vec_init_if_t(&m->queries, ecs_query_t*); ecs_query_t **q = ecs_vec_append_t( &world->allocator, &m->queries, ecs_query_t*); *q = query; } void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *monitors = &world->monitors.monitors; if (!ecs_map_is_init(monitors)) { return; } ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (!m) { return; } int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **queries = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { if (queries[i] == query) { ecs_vec_remove_t(&m->queries, ecs_query_t*, i); count --; break; } } if (!count) { ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); ecs_map_remove_free(monitors, id); } if (!ecs_map_count(monitors)) { ecs_map_fini(monitors); } } static void flecs_init_store( ecs_world_t *world) { ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); ecs_vec_init_t(a, &world->store.depth_ids, ecs_entity_t, 0); ecs_map_init(&world->store.entity_to_depth, &world->allocator); /* Initialize entity index */ flecs_entities_init(world); /* Initialize root table */ flecs_sparse_init_t(&world->store.tables, a, &world->allocators.sparse_chunk, ecs_table_t); /* Initialize table map */ flecs_table_hashmap_init(world, &world->store.table_map); /* Initialize one root table per stage */ flecs_init_root_table(world); } static void flecs_clean_tables( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->store.tables); /* Ensure that first table in sparse set has id 0. This is a dummy table * that only exists so that there is no table with id 0 */ ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, 0); (void)first; for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free(world, t); } /* Free table types separately so that if application destructors rely on * a type it's still valid. */ for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free_type(world, t); } /* Clear the root table */ if (count) { flecs_table_reset(world, &world->store.root); } } static void flecs_fini_roots(ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_table_cache_iter_t it; bool has_roots = flecs_table_cache_iter(&idr->cache, &it); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, &world->stages[0]); const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableHasBuiltins) { continue; /* Filter out modules */ } int32_t i, count = table->data.entities.count; ecs_entity_t *entities = table->data.entities.array; /* Count backwards so that we're always deleting the last entity in the * table which reduces moving components around */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(r->row); if (!(flags & EcsEntityIsTarget)) { continue; /* Filter out entities that aren't objects */ } ecs_delete(world, entities[i]); } } flecs_defer_end(world, &world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_free(world, &world->store.root); flecs_entities_clear(world); flecs_hashmap_fini(&world->store.table_map); ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); ecs_vec_fini_t(a, &world->store.depth_ids, ecs_entity_t); ecs_map_fini(&world->store.entity_to_depth); } /* Implementation for iterable mixin */ static bool flecs_world_iter_next( ecs_iter_t *it) { if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { ECS_BIT_CLEAR(it->flags, EcsIterIsValid); ecs_iter_fini(it); return false; } ecs_world_t *world = it->real_world; it->entities = ECS_CONST_CAST(ecs_entity_t*, flecs_entities_ids(world)); it->count = flecs_entities_count(world); flecs_iter_validate(it); return true; } static void flecs_world_iter_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, ecs_term_t *filter) { ecs_poly_assert(poly, ecs_world_t); (void)poly; if (filter) { iter[0] = ecs_term_iter(world, filter); } else { iter[0] = (ecs_iter_t){ .world = ECS_CONST_CAST(ecs_world_t*, world), .real_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)), .next = flecs_world_iter_next }; } } static void flecs_world_allocators_init( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; flecs_allocator_init(&world->allocator); ecs_map_params_init(&a->ptr, &world->allocator); ecs_map_params_init(&a->query_table_list, &world->allocator); flecs_ballocator_init_t(&a->query_table, ecs_query_table_t); flecs_ballocator_init_t(&a->query_table_match, ecs_query_table_match_t); flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, FLECS_HI_COMPONENT_ID); flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); flecs_ballocator_init_t(&a->id_record, ecs_id_record_t); flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); flecs_table_diff_builder_init(world, &world->allocators.diff_builder); } static void flecs_world_allocators_fini( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; ecs_map_params_fini(&a->ptr); ecs_map_params_fini(&a->query_table_list); flecs_ballocator_fini(&a->query_table); flecs_ballocator_fini(&a->query_table_match); flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->id_record); flecs_ballocator_fini(&a->id_record_chunk); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); flecs_ballocator_fini(&a->hashmap); flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); flecs_allocator_fini(&world->allocator); } static void flecs_log_addons(void) { ecs_trace("addons included in build:"); ecs_log_push(); #ifdef FLECS_CPP ecs_trace("FLECS_CPP"); #endif #ifdef FLECS_MODULE ecs_trace("FLECS_MODULE"); #endif #ifdef FLECS_PARSER ecs_trace("FLECS_PARSER"); #endif #ifdef FLECS_PLECS ecs_trace("FLECS_PLECS"); #endif #ifdef FLECS_RULES ecs_trace("FLECS_RULES"); #endif #ifdef FLECS_SNAPSHOT ecs_trace("FLECS_SNAPSHOT"); #endif #ifdef FLECS_STATS ecs_trace("FLECS_STATS"); #endif #ifdef FLECS_MONITOR ecs_trace("FLECS_MONITOR"); #endif #ifdef FLECS_METRICS ecs_trace("FLECS_METRICS"); #endif #ifdef FLECS_SYSTEM ecs_trace("FLECS_SYSTEM"); #endif #ifdef FLECS_PIPELINE ecs_trace("FLECS_PIPELINE"); #endif #ifdef FLECS_TIMER ecs_trace("FLECS_TIMER"); #endif #ifdef FLECS_META ecs_trace("FLECS_META"); #endif #ifdef FLECS_META_C ecs_trace("FLECS_META_C"); #endif #ifdef FLECS_UNITS ecs_trace("FLECS_UNITS"); #endif #ifdef FLECS_EXPR ecs_trace("FLECS_EXPR"); #endif #ifdef FLECS_JSON ecs_trace("FLECS_JSON"); #endif #ifdef FLECS_DOC ecs_trace("FLECS_DOC"); #endif #ifdef FLECS_COREDOC ecs_trace("FLECS_COREDOC"); #endif #ifdef FLECS_LOG ecs_trace("FLECS_LOG"); #endif #ifdef FLECS_JOURNAL ecs_trace("FLECS_JOURNAL"); #endif #ifdef FLECS_APP ecs_trace("FLECS_APP"); #endif #ifdef FLECS_OS_API_IMPL ecs_trace("FLECS_OS_API_IMPL"); #endif #ifdef FLECS_SCRIPT ecs_trace("FLECS_SCRIPT"); #endif #ifdef FLECS_HTTP ecs_trace("FLECS_HTTP"); #endif #ifdef FLECS_REST ecs_trace("FLECS_REST"); #endif ecs_log_pop(); } /* -- Public functions -- */ ecs_world_t *ecs_mini(void) { #ifdef FLECS_OS_API_IMPL ecs_set_os_api_impl(); #endif ecs_os_init(); ecs_trace("#[bold]bootstrapping world"); ecs_log_push(); ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); if (!ecs_os_has_heap()) { ecs_abort(ECS_MISSING_OS_API, NULL); } if (!ecs_os_has_threading()) { ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } if (!ecs_os_has_time()) { ecs_trace("time management not available"); } flecs_log_addons(); #ifdef FLECS_SANITIZE ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); #elif defined(FLECS_DEBUG) ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " "performance"); #else ecs_trace("#[green]release#[reset] build"); #endif #ifdef __clang__ ecs_trace("compiled with clang %s", __clang_version__); #elif defined(__GNUC__) ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); #elif defined (_MSC_VER) ecs_trace("compiled with msvc %d", _MSC_VER); #elif defined (__TINYC__) ecs_trace("compiled with tcc %d", __TINYC__); #endif ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_poly_init(world, ecs_world_t); world->flags |= EcsWorldInit; flecs_world_allocators_init(world); ecs_allocator_t *a = &world->allocator; world->self = world; flecs_sparse_init_t(&world->type_info, a, &world->allocators.sparse_chunk, ecs_type_info_t); ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); world->id_index_lo = ecs_os_calloc_n(ecs_id_record_t, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); world->iterable.init = flecs_world_iter_init; world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_tables, a, &world->allocators.sparse_chunk, ecs_table_t*); world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_buffer, a, &world->allocators.sparse_chunk, ecs_table_t*); flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); world->info.time_scale = 1.0; if (ecs_os_has_time()) { ecs_os_get_time(&world->world_start_time); } ecs_set_stage_count(world, 1); ecs_default_lookup_path[0] = EcsFlecsCore; ecs_set_lookup_path(world, ecs_default_lookup_path); flecs_init_store(world); flecs_bootstrap(world); world->flags &= ~EcsWorldInit; ecs_trace("world ready!"); ecs_log_pop(); return world; } ecs_world_t *ecs_init(void) { ecs_world_t *world = ecs_mini(); #ifdef FLECS_MODULE_H ecs_trace("#[bold]import addons"); ecs_log_push(); ecs_trace("use ecs_mini to create world without importing addons"); #ifdef FLECS_SYSTEM ECS_IMPORT(world, FlecsSystem); #endif #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); #endif #ifdef FLECS_TIMER ECS_IMPORT(world, FlecsTimer); #endif #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif #ifdef FLECS_COREDOC ECS_IMPORT(world, FlecsCoreDoc); #endif #ifdef FLECS_SCRIPT ECS_IMPORT(world, FlecsScript); #endif #ifdef FLECS_REST ECS_IMPORT(world, FlecsRest); #endif #ifdef FLECS_UNITS ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); #endif ecs_trace("addons imported!"); ecs_log_pop(); #endif return world; } ecs_world_t* ecs_init_w_args( int argc, char *argv[]) { ecs_world_t *world = ecs_init(); (void)argc; (void)argv; #ifdef FLECS_DOC if (argc) { char *app = argv[0]; char *last_elem = strrchr(app, '/'); if (!last_elem) { last_elem = strrchr(app, '\\'); } if (last_elem) { app = last_elem + 1; } ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } #endif return world; } void ecs_quit( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); world->flags |= EcsWorldQuit; error: return; } bool ecs_should_quit( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return true; } void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event) { ecs_poly_assert(world, ecs_world_t); /* If no id is specified, broadcast to all tables */ if (!id) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_notify(world, table, event); } /* If id is specified, only broadcast to tables with id */ } else { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return; } ecs_table_cache_iter_t it; const ecs_table_record_t *tr; flecs_table_cache_all_iter(&idr->cache, &it); while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_table_notify(world, tr->hdr.table, event); } } } void ecs_default_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_os_memset(ptr, 0, ti->size * count); } static void flecs_default_copy_ctor(void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->copy(dst_ptr, src_ptr, count, ti); } static void flecs_default_move_ctor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->move(dst_ptr, src_ptr, count, ti); } static void flecs_default_ctor_w_move_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->ctor(dst_ptr, count, ti); cl->move(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } static void flecs_default_move_ctor_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->move_ctor(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } static void flecs_default_move(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { const ecs_type_hooks_t *cl = &ti->hooks; cl->move(dst_ptr, src_ptr, count, ti); } static void flecs_default_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { /* When there is no move, destruct the destination component & memcpy the * component to dst. The src component does not have to be destructed when * a component has a trivial move. */ const ecs_type_hooks_t *cl = &ti->hooks; cl->dtor(dst_ptr, count, ti); ecs_os_memcpy(dst_ptr, src_ptr, flecs_uto(ecs_size_t, ti->size) * count); } static void flecs_default_move_w_dtor(void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *ti) { /* If a component has a move, the move will take care of memcpying the data * and destroying any data in dst. Because this is not a trivial move, the * src component must also be destructed. */ const ecs_type_hooks_t *cl = &ti->hooks; cl->move(dst_ptr, src_ptr, count, ti); cl->dtor(src_ptr, count, ti); } void ecs_set_hooks_id( ecs_world_t *world, ecs_entity_t component, const ecs_type_hooks_t *h) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); /* Ensure that no tables have yet been created for the component */ ecs_assert( ecs_id_in_use(world, component) == false, ECS_ALREADY_IN_USE, ecs_get_name(world, component)); ecs_assert( ecs_id_in_use(world, ecs_pair(component, EcsWildcard)) == false, ECS_ALREADY_IN_USE, ecs_get_name(world, component)); ecs_type_info_t *ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!ti->component || ti->component == component, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); if (!ti->size) { const EcsComponent *component_ptr = ecs_get( world, component, EcsComponent); /* Cannot register lifecycle actions for things that aren't a component */ ecs_check(component_ptr != NULL, ECS_INVALID_PARAMETER, NULL); /* Cannot register lifecycle actions for components with size 0 */ ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, NULL); ti->size = component_ptr->size; ti->alignment = component_ptr->alignment; } if (h->ctor) ti->hooks.ctor = h->ctor; if (h->dtor) ti->hooks.dtor = h->dtor; if (h->copy) ti->hooks.copy = h->copy; if (h->move) ti->hooks.move = h->move; if (h->copy_ctor) ti->hooks.copy_ctor = h->copy_ctor; if (h->move_ctor) ti->hooks.move_ctor = h->move_ctor; if (h->ctor_move_dtor) ti->hooks.ctor_move_dtor = h->ctor_move_dtor; if (h->move_dtor) ti->hooks.move_dtor = h->move_dtor; if (h->on_add) ti->hooks.on_add = h->on_add; if (h->on_remove) ti->hooks.on_remove = h->on_remove; if (h->on_set) ti->hooks.on_set = h->on_set; if (h->ctx) ti->hooks.ctx = h->ctx; if (h->binding_ctx) ti->hooks.binding_ctx = h->binding_ctx; if (h->ctx_free) ti->hooks.ctx_free = h->ctx_free; if (h->binding_ctx_free) ti->hooks.binding_ctx_free = h->binding_ctx_free; /* If no constructor is set, invoking any of the other lifecycle actions * is not safe as they will potentially access uninitialized memory. For * ease of use, if no constructor is specified, set a default one that * initializes the component to 0. */ if (!h->ctor && (h->dtor || h->copy || h->move)) { ti->hooks.ctor = ecs_default_ctor; } /* Set default copy ctor, move ctor and merge */ if (h->copy && !h->copy_ctor) { ti->hooks.copy_ctor = flecs_default_copy_ctor; } if (h->move && !h->move_ctor) { ti->hooks.move_ctor = flecs_default_move_ctor; } if (!h->ctor_move_dtor) { if (h->move) { if (h->dtor) { if (h->move_ctor) { /* If an explicit move ctor has been set, use callback * that uses the move ctor vs. using a ctor+move */ ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { /* If no explicit move_ctor has been set, use * combination of ctor + move + dtor */ ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; } } else { /* If no dtor has been set, this is just a move ctor */ ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } else { /* If move is not set but move_ctor and dtor is, we can still set * ctor_move_dtor. */ if (h->move_ctor) { if (h->dtor) { ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } } } if (!h->move_dtor) { if (h->move) { if (h->dtor) { ti->hooks.move_dtor = flecs_default_move_w_dtor; } else { ti->hooks.move_dtor = flecs_default_move; } } else { if (h->dtor) { ti->hooks.move_dtor = flecs_default_dtor; } } } error: return; } const ecs_type_hooks_t* ecs_get_hooks_id( ecs_world_t *world, ecs_entity_t id) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return &ti->hooks; } return NULL; } void ecs_atfini( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { ecs_poly_assert(world, ecs_world_t); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } void ecs_run_post_frame( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, &stage->post_frame_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } /* Unset data in tables */ static void flecs_fini_unset_tables( ecs_world_t *world) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_remove_actions(world, table); } } /* Invoke fini actions */ static void flecs_fini_actions( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->fini_actions); ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); } /* Cleanup remaining type info elements */ static void flecs_fini_type_info( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->type_info); ecs_sparse_t *type_info = &world->type_info; for (i = 0; i < count; i ++) { ecs_type_info_t *ti = flecs_sparse_get_dense_t(type_info, ecs_type_info_t, i); flecs_type_info_fini(ti); } flecs_sparse_fini(&world->type_info); } ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e) { if (ecs_is_alive(world, e)) { if (ecs_has_id(world, e, EcsOneOf)) { return e; } else { return ecs_get_target(world, e, EcsOneOf, 0); } } else { return 0; } } /* The destroyer of worlds */ int ecs_fini( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL); ecs_assert(world->stages[0].defer == 0, ECS_INVALID_OPERATION, "call defer_end before destroying world"); ecs_trace("#[bold]shutting down world"); ecs_log_push(); world->flags |= EcsWorldQuit; /* Delete root entities first using regular APIs. This ensures that cleanup * policies get a chance to execute. */ ecs_dbg_1("#[bold]cleanup root entities"); ecs_log_push_1(); flecs_fini_roots(world); ecs_log_pop_1(); world->flags |= EcsWorldFini; /* Run fini actions (simple callbacks ran when world is deleted) before * destroying the storage */ ecs_dbg_1("#[bold]run fini actions"); ecs_log_push_1(); flecs_fini_actions(world); ecs_log_pop_1(); ecs_dbg_1("#[bold]cleanup remaining entities"); ecs_log_push_1(); /* Operations invoked during UnSet/OnRemove/destructors are deferred and * will be discarded after world cleanup */ flecs_defer_begin(world, &world->stages[0]); /* Run UnSet/OnRemove actions for components while the store is still * unmodified by cleanup. */ flecs_fini_unset_tables(world); /* This will destroy all entities and components. */ flecs_fini_store(world); /* Purge deferred operations from the queue. This discards operations but * makes sure that any resources in the queue are freed */ flecs_defer_purge(world, &world->stages[0]); ecs_log_pop_1(); /* All queries are cleaned up, so monitors should've been cleaned up too */ ecs_assert(!ecs_map_is_init(&world->monitors.monitors), ECS_INTERNAL_ERROR, NULL); /* Cleanup world ctx and binding_ctx */ if (world->ctx_free) { world->ctx_free(world->ctx); } if (world->binding_ctx_free) { world->binding_ctx_free(world->binding_ctx); } /* After this point no more user code is invoked */ ecs_dbg_1("#[bold]cleanup world datastructures"); ecs_log_push_1(); flecs_entities_fini(world); flecs_sparse_fini(world->pending_tables); flecs_sparse_fini(world->pending_buffer); ecs_os_free(world->pending_tables); ecs_os_free(world->pending_buffer); flecs_fini_id_records(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); flecs_name_index_fini(&world->aliases); flecs_name_index_fini(&world->symbols); ecs_set_stage_count(world, 0); ecs_log_pop_1(); flecs_world_allocators_fini(world); /* End of the world */ ecs_poly_free(world, ecs_world_t); ecs_os_fini(); ecs_trace("world destroyed, bye!"); ecs_log_pop(); return 0; } bool ecs_is_fini( const ecs_world_t *world) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldFini); } void ecs_dim( ecs_world_t *world, int32_t entity_count) { ecs_poly_assert(world, ecs_world_t); flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); } void flecs_eval_component_monitors( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); flecs_eval_component_monitor(world); } void ecs_measure_frame_time( ecs_world_t *world, bool enable) { ecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); if (ECS_EQZERO(world->info.target_fps) || enable) { ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } error: return; } void ecs_measure_system_time( ecs_world_t *world, bool enable) { ecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); error: return; } void ecs_set_target_fps( ecs_world_t *world, ecs_ftime_t fps) { ecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ecs_measure_frame_time(world, true); world->info.target_fps = fps; error: return; } void* ecs_get_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->ctx; error: return NULL; } void* ecs_get_binding_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->binding_ctx; error: return NULL; } void ecs_set_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { ecs_poly_assert(world, ecs_world_t); world->ctx = ctx; world->ctx_free = ctx_free; } void ecs_set_binding_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { ecs_poly_assert(world, ecs_world_t); world->binding_ctx = ctx; world->binding_ctx_free = ctx_free; } void ecs_set_entity_range( ecs_world_t *world, ecs_entity_t id_start, ecs_entity_t id_end) { ecs_poly_assert(world, ecs_world_t); ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); ecs_check(!id_end || id_end > flecs_entities_max_id(world), ECS_INVALID_PARAMETER, NULL); uint32_t start = (uint32_t)id_start; uint32_t end = (uint32_t)id_end; if (flecs_entities_max_id(world) < start) { flecs_entities_max_id(world) = start - 1; } world->info.min_id = start; world->info.max_id = end; error: return; } bool ecs_enable_range_check( ecs_world_t *world, bool enable) { ecs_poly_assert(world, ecs_world_t); bool old_value = world->range_check_enabled; world->range_check_enabled = enable; return old_value; } ecs_entity_t ecs_get_max_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_max_id(world); error: return 0; } void ecs_set_entity_generation( ecs_world_t *world, ecs_entity_t entity_with_generation) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, NULL); flecs_entities_set_generation(world, entity_with_generation); ecs_record_t *r = flecs_entities_get(world, entity_with_generation); if (r && r->table) { int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_entity_t *entities = r->table->data.entities.array; entities[row] = entity_with_generation; } } const ecs_type_info_t* flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component) { ecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); return flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); } ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component) { ecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = flecs_type_info_get(world, component); ecs_type_info_t *ti_mut = NULL; if (!ti) { ti_mut = flecs_sparse_ensure_t( &world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); ti_mut->component = component; } else { ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); } if (!ti_mut->name) { const char *sym = ecs_get_symbol(world, component); if (sym) { ti_mut->name = ecs_os_strdup(sym); } else { const char *name = ecs_get_name(world, component); if (name) { ti_mut->name = ecs_os_strdup(name); } } } return ti_mut; } bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li) { bool changed = false; flecs_entities_ensure(world, component); ecs_type_info_t *ti = NULL; if (!size || !alignment) { ecs_assert(size == 0 && alignment == 0, ECS_INVALID_COMPONENT_SIZE, NULL); ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); } else { ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); changed |= ti->size != size; changed |= ti->alignment != alignment; ti->size = size; ti->alignment = alignment; if (li) { ecs_set_hooks_id(world, component, li); } } /* Set type info for id record of component */ ecs_id_record_t *idr = flecs_id_record_ensure(world, component); changed |= flecs_id_record_set_type_info(world, idr, ti); bool is_tag = idr->flags & EcsIdTag; /* All id records with component as relationship inherit type info */ idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); do { if (is_tag) { changed |= flecs_id_record_set_type_info(world, idr, NULL); } else if (ti) { changed |= flecs_id_record_set_type_info(world, idr, ti); } else if ((idr->type_info != NULL) && (idr->type_info->component == component)) { changed |= flecs_id_record_set_type_info(world, idr, NULL); } } while ((idr = idr->first.next)); /* All non-tag id records with component as object inherit type info, * if relationship doesn't have type info */ idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); do { if (!(idr->flags & EcsIdTag) && !idr->type_info) { changed |= flecs_id_record_set_type_info(world, idr, ti); } } while ((idr = idr->first.next)); /* Type info of (*, component) should always point to component */ ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> type_info == ti, ECS_INTERNAL_ERROR, NULL); return changed; } void flecs_type_info_fini( ecs_type_info_t *ti) { if (ti->hooks.ctx_free) { ti->hooks.ctx_free(ti->hooks.ctx); } if (ti->hooks.binding_ctx_free) { ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); } if (ti->name) { /* Safe to cast away const, world has ownership over string */ ecs_os_free(ECS_CONST_CAST(char*, ti->name)); ti->name = NULL; } } void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not * deleted before entities that use it. The remaining type info elements * will be deleted after the store is finalized. */ return; } ecs_type_info_t *ti = flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); if (ti) { flecs_type_info_fini(ti); flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); } } static ecs_ftime_t flecs_insert_sleep( ecs_world_t *world, ecs_time_t *stop) { ecs_poly_assert(world, ecs_world_t); ecs_time_t start = *stop, now = start; ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); if (ECS_EQZERO(world->info.target_fps)) { return delta_time; } ecs_ftime_t target_delta_time = ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); /* Calculate the time we need to sleep by taking the measured delta from the * previous frame, and subtracting it from target_delta_time. */ ecs_ftime_t sleep = target_delta_time - delta_time; /* Pick a sleep interval that is 4 times smaller than the time one frame * should take. */ ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; do { /* Only call sleep when sleep_time is not 0. On some platforms, even * a sleep with a timeout of 0 can cause stutter. */ if (ECS_NEQZERO(sleep_time)) { ecs_sleepf((double)sleep_time); } now = start; delta_time = (ecs_ftime_t)ecs_time_measure(&now); } while ((target_delta_time - delta_time) > (sleep_time / (ecs_ftime_t)2.0)); *stop = now; return delta_time; } static ecs_ftime_t flecs_start_measure_frame( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_poly_assert(world, ecs_world_t); ecs_ftime_t delta_time = 0; if ((world->flags & EcsWorldMeasureFrameTime) || (ECS_EQZERO(user_delta_time))) { ecs_time_t t = world->frame_start_time; do { if (world->frame_start_time.nanosec || world->frame_start_time.sec){ delta_time = flecs_insert_sleep(world, &t); } else { ecs_time_measure(&t); if (ECS_NEQZERO(world->info.target_fps)) { delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; } else { /* Best guess */ delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; } } /* Keep trying while delta_time is zero */ } while (ECS_EQZERO(delta_time)); world->frame_start_time = t; /* Keep track of total time passed in world */ world->info.world_time_total_raw += (ecs_ftime_t)delta_time; } return (ecs_ftime_t)delta_time; } static void flecs_stop_measure_frame( ecs_world_t* world) { ecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_t t = world->frame_start_time; world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } ecs_ftime_t ecs_frame_begin( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); /* Start measuring total frame time */ ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); if (ECS_EQZERO(user_delta_time)) { user_delta_time = delta_time; } world->info.delta_time_raw = user_delta_time; world->info.delta_time = user_delta_time * world->info.time_scale; /* Keep track of total scaled time passed in world */ world->info.world_time_total += world->info.delta_time; ecs_run_aperiodic(world, 0); return world->info.delta_time; error: return (ecs_ftime_t)0; } void ecs_frame_end( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); world->info.frame_count_total ++; ecs_stage_t *stages = world->stages; int32_t i, count = world->stage_count; for (i = 0; i < count; i ++) { flecs_stage_merge_post_frame(world, &stages[i]); } flecs_stop_measure_frame(world); error: return; } const ecs_world_info_t* ecs_get_world_info( const ecs_world_t *world) { world = ecs_get_world(world); return &world->info; } void flecs_delete_table( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); flecs_table_free(world, table); } static void flecs_process_empty_queries( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(ecs_id(EcsPoly), EcsQuery)); if (!idr) { return; } ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Make sure that we defer adding the inactive tags until after iterating * the query */ flecs_defer_begin(world, &world->stages[0]); ecs_table_cache_iter_t it; const ecs_table_record_t *tr; if (flecs_table_cache_iter(&idr->cache, &it)) { while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_query_t *query = queries[i].poly; ecs_entity_t *entities = table->data.entities.array; if (!ecs_query_table_count(query)) { ecs_add_id(world, entities[i], EcsEmpty); } } } } flecs_defer_end(world, &world->stages[0]); } /** Walk over tables that had a state change which requires bookkeeping */ void flecs_process_pending_tables( const ecs_world_t *world_r) { ecs_poly_assert(world_r, ecs_world_t); /* We can't update the administration while in readonly mode, but we can * ensure that when this function is called there are no pending events. */ if (world_r->flags & EcsWorldReadonly) { ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, ECS_INTERNAL_ERROR, NULL); return; } /* Safe to cast, world is not readonly */ ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); /* If pending buffer is NULL there already is a stackframe that's iterating * the table list. This can happen when an observer for a table event results * in a mutation that causes another table to change state. A typical * example of this is a system that becomes active/inactive as the result of * a query (and as a result, its matched tables) becoming empty/non empty */ if (!world->pending_buffer) { return; } /* Swap buffer. The logic could in theory have been implemented with a * single sparse set, but that would've complicated (and slowed down) the * iteration. Additionally, by using a double buffer approach we can still * keep most of the original ordering of events intact, which is desirable * as it means that the ordering of tables in the internal datastructures is * more predictable. */ int32_t i, count = flecs_sparse_count(world->pending_tables); if (!count) { return; } flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); do { ecs_sparse_t *pending_tables = world->pending_tables; world->pending_tables = world->pending_buffer; world->pending_buffer = NULL; /* Make sure that any ECS operations that occur while delivering the * events does not cause inconsistencies, like sending an Empty * notification for a table that just became non-empty. */ flecs_defer_begin(world, &world->stages[0]); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( pending_tables, ecs_table_t*, i)[0]; if (!table->id) { /* Table is being deleted, ignore empty events */ continue; } /* For each id in the table, add it to the empty/non empty list * based on its current state */ if (flecs_table_records_update_empty(table)) { int32_t table_count = ecs_table_count(table); if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { /* Only emit an event when there was a change in the * administration. It is possible that a table ended up in the * pending_tables list by going from empty->non-empty, but then * became empty again. By the time we run this code, no changes * in the administration would actually be made. */ ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; if (ecs_should_log_3()) { ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id, table_count ? "non-empty" : "empty"); } ecs_log_push_3(); flecs_emit(world, world, &(ecs_event_desc_t){ .event = evt, .table = table, .ids = &table->type, .observable = world, .flags = EcsEventTableOnly }); ecs_log_pop_3(); } world->info.empty_table_count += (table_count == 0) * 2 - 1; } } flecs_sparse_clear(pending_tables); ecs_defer_end(world); world->pending_buffer = pending_tables; } while ((count = flecs_sparse_count(world->pending_tables))); flecs_journal_end(); } void flecs_table_set_empty( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_count(table)) { table->_->generation = 0; } flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, (uint32_t)table->id)[0] = table; } bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return false; } return (flecs_table_cache_count(&idr->cache) != 0) || (flecs_table_cache_empty_count(&idr->cache) != 0); } void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags) { ecs_poly_assert(world, ecs_world_t); if (!flags || (flags & EcsAperiodicEmptyTables)) { flecs_process_pending_tables(world); } if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); } if (!flags || (flags & EcsAperiodicComponentMonitors)) { flecs_eval_component_monitors(world); } } int32_t ecs_delete_empty_tables( ecs_world_t *world, ecs_id_t id, uint16_t clear_generation, uint16_t delete_generation, int32_t min_id_count, double time_budget_seconds) { ecs_poly_assert(world, ecs_world_t); /* Make sure empty tables are in the empty table lists */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0, clear_count = 0; bool time_budget = false; if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } if (ECS_NEQZERO(time_budget_seconds)) { time_budget = true; } if (!id) { id = EcsAny; /* Iterate all empty tables */ } ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { if (time_budget) { cur = start; if (ecs_time_measure(&cur) > time_budget_seconds) { goto done; } } ecs_table_t *table = tr->hdr.table; ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); if (table->type.count < min_id_count) { continue; } uint16_t gen = ++ table->_->generation; if (delete_generation && (gen > delete_generation)) { flecs_table_free(world, table); delete_count ++; } else if (clear_generation && (gen > clear_generation)) { if (flecs_table_shrink(world, table)) { clear_count ++; } } } } done: if (ecs_should_log_1() && ecs_os_has_time()) { if (delete_count) { ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", delete_count, ecs_time_measure(&start)); } if (clear_count) { ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", clear_count, ecs_time_measure(&start)); } } return delete_count; } /** * @file addons/alerts.c * @brief Alerts addon. */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(FlecsAlerts); typedef struct EcsAlert { char *message; ecs_map_t instances; /* Active instances for metric */ ecs_ftime_t retain_period; /* How long to retain the alert */ ecs_vec_t severity_filters; /* Severity filters */ /* Member range monitoring */ ecs_id_t id; /* (Component) id that contains to monitor member */ ecs_entity_t member; /* Member to monitor */ int32_t offset; /* Offset of member in component */ int32_t size; /* Size of component */ ecs_primitive_kind_t kind; /* Primitive type kind */ ecs_ref_t ranges; /* Reference to ranges component */ int32_t var_id; /* Variable from which to obtain data (0 = $this) */ } EcsAlert; typedef struct EcsAlertTimeout { ecs_ftime_t inactive_time; /* Time the alert has been inactive */ ecs_ftime_t expire_time; /* Expiration duration */ } EcsAlertTimeout; ECS_COMPONENT_DECLARE(EcsAlertTimeout); static ECS_CTOR(EcsAlert, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->instances, NULL); ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); }) static ECS_DTOR(EcsAlert, ptr, { ecs_os_free(ptr->message); ecs_map_fini(&ptr->instances); ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); }) static ECS_MOVE(EcsAlert, dst, src, { ecs_os_free(dst->message); dst->message = src->message; src->message = NULL; ecs_map_fini(&dst->instances); dst->instances = src->instances; src->instances = (ecs_map_t){0}; ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); dst->severity_filters = src->severity_filters; src->severity_filters = (ecs_vec_t){0}; dst->retain_period = src->retain_period; dst->id = src->id; dst->member = src->member; dst->offset = src->offset; dst->size = src->size; dst->kind = src->kind; dst->ranges = src->ranges; dst->var_id = src->var_id; }) static ECS_CTOR(EcsAlertsActive, ptr, { ecs_map_init(&ptr->alerts, NULL); ptr->info_count = 0; ptr->warning_count = 0; ptr->error_count = 0; }) static ECS_DTOR(EcsAlertsActive, ptr, { ecs_map_fini(&ptr->alerts); }) static ECS_MOVE(EcsAlertsActive, dst, src, { ecs_map_fini(&dst->alerts); dst->alerts = src->alerts; dst->info_count = src->info_count; dst->warning_count = src->warning_count; dst->error_count = src->error_count; src->alerts = (ecs_map_t){0}; }) static ECS_DTOR(EcsAlertInstance, ptr, { ecs_os_free(ptr->message); }) static ECS_MOVE(EcsAlertInstance, dst, src, { ecs_os_free(dst->message); dst->message = src->message; src->message = NULL; }) static ECS_COPY(EcsAlertInstance, dst, src, { ecs_os_free(dst->message); dst->message = ecs_os_strdup(src->message); }) static void flecs_alerts_add_alert_to_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert, ecs_entity_t alert_instance) { EcsAlertsActive *active = ecs_get_mut( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count ++; } else if (severity == EcsAlertWarning) { active->warning_count ++; } else if (severity == EcsAlertError) { active->error_count ++; } ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr[0] = alert_instance; ecs_modified(world, source, EcsAlertsActive); } static void flecs_alerts_remove_alert_from_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert) { EcsAlertsActive *active = ecs_get_mut( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_remove(&active->alerts, alert); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count --; } else if (severity == EcsAlertWarning) { active->warning_count --; } else if (severity == EcsAlertError) { active->error_count --; } if (!ecs_map_count(&active->alerts)) { ecs_remove(world, source, EcsAlertsActive); } else { ecs_modified(world, source, EcsAlertsActive); } } static ecs_entity_t flecs_alert_get_severity( ecs_world_t *world, ecs_iter_t *it, EcsAlert *alert) { int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); ecs_alert_severity_filter_t *filters = ecs_vec_first(&alert->severity_filters); for (i = 0; i < filter_count; i ++) { ecs_alert_severity_filter_t *filter = &filters[i]; if (!filter->var) { if (ecs_table_has_id(world, it->table, filters[i].with)) { return filters[i].severity; } } else { ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); if (src && src != EcsWildcard) { if (ecs_has_id(world, src, filters[i].with)) { return filters[i].severity; } } } } return 0; } static ecs_entity_t flecs_alert_out_of_range_kind( EcsAlert *alert, const EcsMemberRanges *ranges, const void *value_ptr) { double value = 0; switch(alert->kind) { case EcsU8: value = *(const uint8_t*)value_ptr; break; case EcsU16: value = *(const uint16_t*)value_ptr; break; case EcsU32: value = *(const uint32_t*)value_ptr; break; case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; case EcsI8: value = *(const int8_t*)value_ptr; break; case EcsI16: value = *(const int16_t*)value_ptr; break; case EcsI32: value = *(const int32_t*)value_ptr; break; case EcsI64: value = (double)*(const int64_t*)value_ptr; break; case EcsF32: value = (double)*(const float*)value_ptr; break; case EcsF64: value = *(const double*)value_ptr; break; case EcsBool: case EcsChar: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: return 0; } bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); if (has_error && (value < ranges->error.min || value > ranges->error.max)) { return EcsAlertError; } else if (has_warning && (value < ranges->warning.min || value > ranges->warning.max)) { return EcsAlertWarning; } else { return 0; } } static void MonitorAlerts(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsAlert *alert = ecs_field(it, EcsAlert, 1); EcsPoly *poly = ecs_field(it, EcsPoly, 2); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t a = it->entities[i]; /* Alert entity */ ecs_entity_t default_severity = ecs_get_target( world, a, ecs_id(EcsAlert), 0); ecs_rule_t *rule = poly[i].poly; ecs_poly_assert(rule, ecs_rule_t); ecs_id_t member_id = alert[i].id; const EcsMemberRanges *ranges = NULL; if (member_id) { ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); } ecs_iter_t rit = ecs_rule_iter(world, rule); rit.flags |= EcsIterNoData; rit.flags |= EcsIterIsInstanced; while (ecs_rule_next(&rit)) { ecs_entity_t severity = flecs_alert_get_severity( world, &rit, &alert[i]); if (!severity) { severity = default_severity; } const void *member_data = NULL; ecs_entity_t member_src = 0; if (ranges) { if (alert[i].var_id) { member_src = ecs_iter_get_var(&rit, alert[i].var_id); if (!member_src || member_src == EcsWildcard) { continue; } } if (!member_src) { member_data = ecs_table_get_id( world, rit.table, member_id, rit.offset); } else { member_data = ecs_get_id(world, member_src, member_id); } if (!member_data) { continue; } member_data = ECS_OFFSET(member_data, alert[i].offset); } int32_t j, alert_src_count = rit.count; for (j = 0; j < alert_src_count; j ++) { ecs_entity_t src_severity = severity; ecs_entity_t e = rit.entities[j]; if (member_data) { ecs_entity_t range_severity = flecs_alert_out_of_range_kind( &alert[i], ranges, member_data); if (!member_src) { member_data = ECS_OFFSET(member_data, alert[i].size); } if (!range_severity) { continue; } if (range_severity < src_severity) { /* Range severity should not exceed alert severity */ src_severity = range_severity; } } ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); if (!aptr[0]) { /* Alert does not yet exist for entity */ ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); ecs_set(world, ai, EcsMetricSource, { .entity = e }); ecs_set(world, ai, EcsMetricValue, { .value = 0 }); ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); if (ECS_NEQZERO(alert[i].retain_period)) { ecs_set(world, ai, EcsAlertTimeout, { .inactive_time = 0, .expire_time = alert[i].retain_period }); } ecs_defer_suspend(it->world); flecs_alerts_add_alert_to_src(world, e, a, ai); ecs_defer_resume(it->world); aptr[0] = ai; } else { /* Make sure alert severity is up to date */ if (ecs_vec_count(&alert[i].severity_filters) || member_data) { ecs_entity_t cur_severity = ecs_get_target( world, aptr[0], ecs_id(EcsAlert), 0); if (cur_severity != src_severity) { ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), src_severity); } } } } } } } static void MonitorAlertInstances(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 1); EcsMetricSource *source = ecs_field(it, EcsMetricSource, 2); EcsMetricValue *value = ecs_field(it, EcsMetricValue, 3); EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 4); /* Get alert component from alert instance parent (the alert) */ ecs_id_t childof_pair; if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { ecs_err("alert instances must be a child of an alert"); return; } ecs_entity_t parent = ecs_pair_second(world, childof_pair); ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, "alert entity does not have Alert component"); EcsAlert *alert = ecs_get_mut(world, parent, EcsAlert); const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); ecs_assert(poly != NULL, ECS_INVALID_OPERATION, "alert entity does not have (Poly, Query) component"); ecs_rule_t *rule = poly->poly; ecs_poly_assert(rule, ecs_rule_t); ecs_id_t member_id = alert->id; const EcsMemberRanges *ranges = NULL; if (member_id) { ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); } ecs_vars_t vars = {0}; ecs_vars_init(world, &vars); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t ai = it->entities[i]; ecs_entity_t e = source[i].entity; /* If source of alert is no longer alive, delete alert instance even if * the alert has a retain period. */ if (!ecs_is_alive(world, e)) { ecs_delete(world, ai); continue; } /* Check if alert instance still matches rule */ ecs_iter_t rit = ecs_rule_iter(world, rule); rit.flags |= EcsIterNoData; rit.flags |= EcsIterIsInstanced; ecs_iter_set_var(&rit, 0, e); if (ecs_rule_next(&rit)) { bool match = true; /* If alert is monitoring member range, test value against range */ if (ranges) { ecs_entity_t member_src = e; if (alert->var_id) { member_src = ecs_iter_get_var(&rit, alert->var_id); } const void *member_data = ecs_get_id( world, member_src, member_id); if (!member_data) { match = false; } else { member_data = ECS_OFFSET(member_data, alert->offset); if (flecs_alert_out_of_range_kind( alert, ranges, member_data) == 0) { match = false; } } } if (match) { /* Only increase alert duration if the alert was active */ value[i].value += (double)it->delta_system_time; bool generate_message = alert->message; if (generate_message) { if (alert_instance[i].message) { /* If a message was already generated, only regenerate if * rule has multiple variables. Variable values could have * changed, this ensures the message remains up to date. */ generate_message = rit.variable_count > 1; } } if (generate_message) { if (alert_instance[i].message) { ecs_os_free(alert_instance[i].message); } ecs_iter_to_vars(&rit, &vars, 0); alert_instance[i].message = ecs_interpolate_string( world, alert->message, &vars); } if (timeout) { if (ECS_NEQZERO(timeout[i].inactive_time)) { /* The alert just became active. Remove Disabled tag */ flecs_alerts_add_alert_to_src(world, e, parent, ai); ecs_remove_id(world, ai, EcsDisabled); } timeout[i].inactive_time = 0; } /* Alert instance still matches rule, keep it alive */ ecs_iter_fini(&rit); continue; } ecs_iter_fini(&rit); } /* Alert instance is no longer active */ if (timeout) { if (ECS_EQZERO(timeout[i].inactive_time)) { /* The alert just became inactive. Add Disabled tag */ flecs_alerts_remove_alert_from_src(world, e, parent); ecs_add_id(world, ai, EcsDisabled); } ecs_ftime_t t = timeout[i].inactive_time; timeout[i].inactive_time += it->delta_system_time; if (t < timeout[i].expire_time) { /* Alert instance no longer matches rule, but is still * within the timeout period. Keep it alive. */ continue; } } /* Alert instance no longer matches rule, remove it */ flecs_alerts_remove_alert_from_src(world, e, parent); ecs_map_remove(&alert->instances, e); ecs_delete(world, ai); } ecs_vars_fini(&vars); } ecs_entity_t ecs_alert_init( ecs_world_t *world, const ecs_alert_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!desc->filter.entity || desc->entity == desc->filter.entity, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world, 0); } ecs_filter_desc_t private_desc = desc->filter; private_desc.entity = result; ecs_rule_t *rule = ecs_rule_init(world, &private_desc); if (!rule) { ecs_err("failed to create alert filter"); return 0; } const ecs_filter_t *filter = ecs_rule_get_filter(rule); if (!(filter->flags & EcsFilterMatchThis)) { ecs_err("alert filter must have at least one '$this' term"); ecs_rule_fini(rule); return 0; } /* Initialize Alert component which identifiers entity as alert */ EcsAlert *alert = ecs_get_mut(world, result, EcsAlert); ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); alert->message = ecs_os_strdup(desc->message); alert->retain_period = desc->retain_period; /* Initialize severity filters */ int32_t i; for (i = 0; i < 4; i ++) { if (desc->severity_filters[i].with) { if (!desc->severity_filters[i].severity) { ecs_err("severity filter must have severity"); goto error; } ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, &alert->severity_filters, ecs_alert_severity_filter_t); *sf = desc->severity_filters[i]; if (sf->var) { sf->_var_index = ecs_rule_find_var(rule, sf->var); if (sf->_var_index == -1) { ecs_err("unresolved variable '%s' in alert severity filter", sf->var); goto error; } } } } /* Fetch data for member monitoring */ if (desc->member) { alert->member = desc->member; if (!desc->id) { alert->id = ecs_get_parent(world, desc->member); if (!alert->id) { ecs_err("ecs_alert_desc_t::member is not a member"); goto error; } ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); } else { alert->id = desc->id; } ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (!idr->type_info) { ecs_err("ecs_alert_desc_t::id must be a component"); goto error; } ecs_entity_t type = idr->type_info->component; if (type != ecs_get_parent(world, desc->member)) { char *type_name = ecs_get_fullpath(world, type); ecs_err("member '%s' is not a member of '%s'", ecs_get_name(world, desc->member), type_name); ecs_os_free(type_name); goto error; } const EcsMember *member = ecs_get(world, alert->member, EcsMember); if (!member) { ecs_err("ecs_alert_desc_t::member is not a member"); goto error; } if (!member->type) { ecs_err("ecs_alert_desc_t::member must have a type"); goto error; } const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); if (!pr) { ecs_err("ecs_alert_desc_t::member must be of a primitive type"); goto error; } if (!ecs_has(world, desc->member, EcsMemberRanges)) { ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); goto error; } int32_t var_id = 0; if (desc->var) { var_id = ecs_rule_find_var(rule, desc->var); if (var_id == -1) { ecs_err("unresolved variable '%s' in alert member", desc->var); goto error; } } alert->offset = member->offset; alert->size = idr->type_info->size; alert->kind = pr->kind; alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); alert->var_id = var_id; } ecs_modified(world, result, EcsAlert); /* Register alert as metric */ ecs_add(world, result, EcsMetric); ecs_add_pair(world, result, EcsMetric, EcsCounter); /* Add severity to alert */ ecs_entity_t severity = desc->severity; if (!severity) { severity = EcsAlertError; } ecs_add_pair(world, result, ecs_id(EcsAlert), severity); if (desc->doc_name) { #ifdef FLECS_DOC ecs_doc_set_name(world, result, desc->doc_name); #else ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); goto error; #endif } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); goto error; #endif } return result; error: if (result) { ecs_delete(world, result); } return 0; } int32_t ecs_get_alert_count( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t alert) { ecs_poly_assert(world, ecs_world_t); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!alert || ecs_has(world, alert, EcsAlert), ECS_INVALID_PARAMETER, NULL); const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); if (!active) { return 0; } if (alert) { return ecs_map_get(&active->alerts, alert) != NULL; } return ecs_map_count(&active->alerts); error: return 0; } ecs_entity_t ecs_get_alert( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t alert) { ecs_poly_assert(world, ecs_world_t); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); if (!active) { return 0; } ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); if (ptr) { return ptr[0]; } error: return 0; } void FlecsAlertsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsAlerts); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); ECS_IMPORT(world, FlecsMetrics); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsAlert); ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); ECS_COMPONENT_DEFINE(world, EcsAlertsActive); ecs_set_name_prefix(world, "EcsAlert"); ECS_COMPONENT_DEFINE(world, EcsAlertInstance); ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); ECS_TAG_DEFINE(world, EcsAlertInfo); ECS_TAG_DEFINE(world, EcsAlertWarning); ECS_TAG_DEFINE(world, EcsAlertError); ECS_TAG_DEFINE(world, EcsAlertCritical); ecs_add_id(world, ecs_id(EcsAlert), EcsTag); ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate); ecs_struct(world, { .entity = ecs_id(EcsAlertInstance), .members = { { .name = "message", .type = ecs_id(ecs_string_t) } } }); ecs_set_hooks(world, EcsAlert, { .ctor = ecs_ctor(EcsAlert), .dtor = ecs_dtor(EcsAlert), .move = ecs_move(EcsAlert) }); ecs_set_hooks(world, EcsAlertsActive, { .ctor = ecs_ctor(EcsAlertsActive), .dtor = ecs_dtor(EcsAlertsActive), .move = ecs_move(EcsAlertsActive) }); ecs_set_hooks(world, EcsAlertInstance, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsAlertInstance), .move = ecs_move(EcsAlertInstance), .copy = ecs_copy(EcsAlertInstance) }); ecs_struct(world, { .entity = ecs_id(EcsAlertsActive), .members = { { .name = "info_count", .type = ecs_id(ecs_i32_t) }, { .name = "warning_count", .type = ecs_id(ecs_i32_t) }, { .name = "error_count", .type = ecs_id(ecs_i32_t) } } }); ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, flecs.metrics.Source, flecs.metrics.Value, ?EcsAlertTimeout, ?Disabled); ecs_system(world, { .entity = ecs_id(MonitorAlerts), .no_readonly = true, .interval = 0.5 }); ecs_system(world, { .entity = ecs_id(MonitorAlertInstances), .interval = 0.5 }); } #endif /** * @file addons/app.c * @brief App addon. */ #ifdef FLECS_APP static int flecs_default_run_action( ecs_world_t *world, ecs_app_desc_t *desc) { if (desc->init) { desc->init(world); } int result = 0; if (desc->frames) { int32_t i; for (i = 0; i < desc->frames; i ++) { if ((result = ecs_app_run_frame(world, desc)) != 0) { break; } } } else { while ((result = ecs_app_run_frame(world, desc)) == 0) { } } /* Ensure quit flag is set on world, which can be used to determine if * world needs to be cleaned up. */ ecs_quit(world); if (result == 1) { return 0; /* Normal exit */ } else { return result; /* Error code */ } } static int flecs_default_frame_action( ecs_world_t *world, const ecs_app_desc_t *desc) { return !ecs_progress(world, desc->delta_time); } static ecs_app_run_action_t run_action = flecs_default_run_action; static ecs_app_frame_action_t frame_action = flecs_default_frame_action; static ecs_app_desc_t ecs_app_desc; /* Serve REST API from wasm image when running in emscripten */ #ifdef ECS_TARGET_EM #include ecs_http_server_t *flecs_wasm_rest_server; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); if (reply.code == 200) { return ecs_strbuf_get(&reply.body); } else { char *body = ecs_strbuf_get(&reply.body); if (body) { return body; } else { return ecs_asprintf( "{\"error\": \"bad request (code %d)\"}", reply.code); } } } #endif int ecs_app_run( ecs_world_t *world, ecs_app_desc_t *desc) { ecs_app_desc = *desc; /* Don't set FPS & threads if custom run action is set, as the platform on * which the app is running may not support it. */ if (run_action == flecs_default_run_action) { if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } } /* REST server enables connecting to app with explorer */ if (desc->enable_rest) { #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); #else ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); #endif #else ecs_warn("cannot enable remote API, REST addon not available"); #endif } /* Monitoring periodically collects statistics */ if (desc->enable_monitor) { #ifdef FLECS_MONITOR ECS_IMPORT(world, FlecsMonitor); #else ecs_warn("cannot enable monitoring, MONITOR addon not available"); #endif } return run_action(world, &ecs_app_desc); } int ecs_app_run_frame( ecs_world_t *world, const ecs_app_desc_t *desc) { return frame_action(world, desc); } int ecs_app_set_run_action( ecs_app_run_action_t callback) { if (run_action != flecs_default_run_action && run_action != callback) { ecs_err("run action already set"); return -1; } run_action = callback; return 0; } int ecs_app_set_frame_action( ecs_app_frame_action_t callback) { if (frame_action != flecs_default_frame_action && frame_action != callback) { ecs_err("frame action already set"); return -1; } frame_action = callback; return 0; } #endif /** * @file addons/coredoc.c * @brief Core doc addon. */ #ifdef FLECS_COREDOC #define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.html/" void FlecsCoreDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsCoreDoc); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsDoc); ecs_set_name_prefix(world, "Ecs"); /* Initialize reflection data for core components */ ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsComponent), .members = { {.name = "size", .type = ecs_id(ecs_i32_t)}, {.name = "alignment", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsDocDescription), .members = { {.name = "value", .type = ecs_id(ecs_string_t)} } }); /* Initialize documentation data for core components */ ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components"); ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); ecs_doc_set_brief(world, EcsTraversable, "Traversable relationship property"); ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); ecs_doc_set_brief(world, EcsWith, "With relationship property"); ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); ecs_doc_set_link(world, EcsTraversable, URL_ROOT "#traversable-property"); ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); /* Initialize documentation for meta components */ ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); /* Initialize documentation for doc components */ ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); } #endif /** * @file addons/doc.c * @brief Doc addon. */ #ifdef FLECS_DOC static ECS_COPY(EcsDocDescription, dst, src, { ecs_os_strset((char**)&dst->value, src->value); }) static ECS_MOVE(EcsDocDescription, dst, src, { ecs_os_free((char*)dst->value); dst->value = src->value; src->value = NULL; }) static ECS_DTOR(EcsDocDescription, ptr, { ecs_os_free((char*)ptr->value); }) void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { ecs_set_pair(world, entity, EcsDocDescription, EcsName, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, name) }); } void ecs_doc_set_brief( ecs_world_t *world, ecs_entity_t entity, const char *description) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, description) }); } void ecs_doc_set_detail( ecs_world_t *world, ecs_entity_t entity, const char *description) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, description) }); } void ecs_doc_set_link( ecs_world_t *world, ecs_entity_t entity, const char *link) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, link) }); } void ecs_doc_set_color( ecs_world_t *world, ecs_entity_t entity, const char *color) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocColor, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, color) }); } const char* ecs_doc_get_name( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsName); if (ptr) { return ptr->value; } else { return ecs_get_name(world, entity); } } const char* ecs_doc_get_brief( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocBrief); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_detail( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocDetail); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_link( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocLink); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_color( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocColor); if (ptr) { return ptr->value; } else { return NULL; } } void FlecsDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsDoc); ecs_set_name_prefix(world, "EcsDoc"); flecs_bootstrap_component(world, EcsDocDescription); flecs_bootstrap_tag(world, EcsDocBrief); flecs_bootstrap_tag(world, EcsDocDetail); flecs_bootstrap_tag(world, EcsDocLink); flecs_bootstrap_tag(world, EcsDocColor); ecs_set_hooks(world, EcsDocDescription, { .ctor = ecs_default_ctor, .move = ecs_move(EcsDocDescription), .copy = ecs_copy(EcsDocDescription), .dtor = ecs_dtor(EcsDocDescription) }); ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate); } #endif /** * @file addons/flecs_cpp.c * @brief Utilities for C++ addon. */ #include /* Utilities for C++ API */ #ifdef FLECS_CPP /* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to * a uniform identifier */ #define ECS_CONST_PREFIX "const " #define ECS_STRUCT_PREFIX "struct " #define ECS_CLASS_PREFIX "class " #define ECS_ENUM_PREFIX "enum " #define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) #define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) #define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) #define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) static ecs_size_t ecs_cpp_strip_prefix( char *typeName, ecs_size_t len, const char *prefix, ecs_size_t prefix_len) { if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); typeName[len - prefix_len] = '\0'; len -= prefix_len; } return len; } static void ecs_cpp_trim_type_name( char *typeName) { ecs_size_t len = ecs_os_strlen(typeName); len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); while (typeName[len - 1] == ' ' || typeName[len - 1] == '&' || typeName[len - 1] == '*') { len --; typeName[len] = '\0'; } /* Remove const at end of string */ if (len > ECS_CONST_LEN) { if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { typeName[len - ECS_CONST_LEN] = '\0'; } len -= ECS_CONST_LEN; } /* Check if there are any remaining "struct " strings, which can happen * if this is a template type on msvc. */ if (len > ECS_STRUCT_LEN) { char *ptr = typeName; while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { /* Make sure we're not matched with part of a longer identifier * that contains 'struct' */ if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); len -= ECS_STRUCT_LEN; } } } } char* ecs_cpp_get_type_name( char *type_name, const char *func_name, size_t len, size_t front_len) { memcpy(type_name, func_name + front_len, len); type_name[len] = '\0'; ecs_cpp_trim_type_name(type_name); return type_name; } char* ecs_cpp_get_symbol_name( char *symbol_name, const char *type_name, size_t len) { // Symbol is same as name, but with '::' replaced with '.' ecs_os_strcpy(symbol_name, type_name); char *ptr; size_t i; for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { if (*ptr == ':') { symbol_name[i] = '.'; ptr ++; } else { symbol_name[i] = *ptr; } } symbol_name[i] = '\0'; return symbol_name; } static const char* flecs_cpp_func_rchr( const char *func_name, ecs_size_t func_name_len, ecs_size_t func_back_len, char ch) { const char *r = strrchr(func_name, ch); if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { return NULL; } return r; } static const char* flecs_cpp_func_max( const char *a, const char *b) { if (a > b) return a; return b; } char* ecs_cpp_get_constant_name( char *constant_name, const char *func_name, size_t func_name_len, size_t func_back_len) { ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len); const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' '); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ')')); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ':')); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ',')); ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); start ++; ecs_size_t len = flecs_uto(ecs_size_t, (f_len - (start - func_name) - fb_len)); ecs_os_memcpy_n(constant_name, start, char, len); constant_name[len] = '\0'; return constant_name; } // Names returned from the name_helper class do not start with :: // but are relative to the root. If the namespace of the type // overlaps with the namespace of the current module, strip it from // the implicit identifier. // This allows for registration of component types that are not in the // module namespace to still be registered under the module scope. const char* ecs_cpp_trim_module( ecs_world_t *world, const char *type_name) { ecs_entity_t scope = ecs_get_scope(world); if (!scope) { return type_name; } char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); if (path) { const char *ptr = strrchr(type_name, ':'); ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); if (ptr) { ptr --; ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); if (name_path_len <= ecs_os_strlen(path)) { if (!ecs_os_strncmp(type_name, path, name_path_len)) { type_name = &type_name[name_path_len + 2]; } } } } ecs_os_free(path); return type_name; } // Validate registered component void ecs_cpp_component_validate( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, size_t size, size_t alignment, bool implicit_name) { /* If entity has a name check if it matches */ if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { if (!implicit_name && id >= EcsFirstUserComponentId) { #ifndef FLECS_NDEBUG char *path = ecs_get_path_w_sep( world, 0, id, "::", NULL); if (ecs_os_strcmp(path, name)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' already registered with name '%s'", name, path); } ecs_os_free(path); #endif } if (symbol) { const char *existing_symbol = ecs_get_symbol(world, id); if (existing_symbol) { if (ecs_os_strcmp(symbol, existing_symbol)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' with symbol '%s' already registered with symbol '%s'", name, symbol, existing_symbol); } } } } else { /* Ensure that the entity id valid */ if (!ecs_is_alive(world, id)) { ecs_ensure(world, id); } /* Register name with entity, so that when the entity is created the * correct id will be resolved from the name. Only do this when the * entity is empty. */ ecs_add_path_w_sep(world, id, 0, name, "::", "::"); } /* If a component was already registered with this id but with a * different size, the ecs_component_init function will fail. */ /* We need to explicitly call ecs_component_init here again. Even though * the component was already registered, it may have been registered * with a different world. This ensures that the component is registered * with the same id for the current world. * If the component was registered already, nothing will change. */ ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ .entity = id, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); (void)ent; ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); } ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, ecs_size_t size, ecs_size_t alignment, bool implicit_name, bool *existing_out) { (void)size; (void)alignment; /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ bool existing = false; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t ent; if (id) { ent = id; } else { ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); existing = ent != 0 && ecs_has(world, ent, EcsComponent); } ecs_set_scope(world, prev_scope); /* If entity exists, compare symbol name to ensure that the component * we are trying to register under this name is the same */ if (ent) { const EcsComponent *component = ecs_get(world, ent, EcsComponent); if (component != NULL) { const char *sym = ecs_get_symbol(world, ent); if (sym && ecs_os_strcmp(sym, symbol)) { /* Application is trying to register a type with an entity that * was already associated with another type. In most cases this * is an error, with the exception of a scenario where the * application is wrapping a C type with a C++ type. * * In this case the C++ type typically inherits from the C type, * and adds convenience methods to the derived class without * changing anything that would change the size or layout. * * To meet this condition, the new type must have the same size * and alignment as the existing type, and the name of the type * type must be equal to the registered name (not symbol). * * The latter ensures that it was the intent of the application * to alias the type, vs. accidentally registering an unrelated * type with the same size/alignment. */ char *type_path = ecs_get_fullpath(world, ent); if (ecs_os_strcmp(type_path, symbol) || component->size != size || component->alignment != alignment) { ecs_err( "component with name '%s' is already registered for"\ " type '%s' (trying to register for type '%s')", name, sym, symbol); ecs_abort(ECS_NAME_IN_USE, NULL); } ecs_os_free(type_path); } else if (!sym) { ecs_set_symbol(world, ent, symbol); } } /* If no entity is found, lookup symbol to check if the component was * registered under a different name. */ } else if (!implicit_name) { ent = ecs_lookup_symbol(world, symbol, false, false); ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); } if (existing_out) { *existing_out = existing; } return ent; } ecs_entity_t ecs_cpp_component_register_explicit( ecs_world_t *world, ecs_entity_t s_id, ecs_entity_t id, const char *name, const char *type_name, const char *symbol, size_t size, size_t alignment, bool is_component, bool *existing_out) { char *existing_name = NULL; if (existing_out) *existing_out = false; // If an explicit id is provided, it is possible that the symbol and // name differ from the actual type, as the application may alias // one type to another. if (!id) { if (!name) { // If no name was provided first check if a type with the provided // symbol was already registered. id = ecs_lookup_symbol(world, symbol, false, false); if (id) { existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); name = existing_name; if (existing_out) *existing_out = true; } else { // If type is not yet known, derive from type name name = ecs_cpp_trim_module(world, type_name); } } } else { // If an explicit id is provided but it has no name, inherit // the name from the type. if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { name = ecs_cpp_trim_module(world, type_name); } } ecs_entity_t entity; if (is_component || size != 0) { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); entity = ecs_component_init(world, &(ecs_component_desc_t){ .entity = entity, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); } else { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); } ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); return entity; } void ecs_cpp_enum_init( ecs_world_t *world, ecs_entity_t id) { (void)world; (void)id; #ifdef FLECS_META ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_set(world, id, EcsEnum, {0}); flecs_resume_readonly(world, &readonly_state); #endif } ecs_entity_t ecs_cpp_enum_constant_register( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t id, const char *name, int value) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); const char *parent_name = ecs_get_name(world, parent); ecs_size_t parent_name_len = ecs_os_strlen(parent_name); if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { name += parent_name_len; if (name[0] == '_') { name ++; } } ecs_entity_t prev = ecs_set_scope(world, parent); id = ecs_entity(world, { .id = id, .name = name }); ecs_assert(id != 0, ECS_INVALID_OPERATION, name); ecs_set_scope(world, prev); #ifdef FLECS_DEBUG const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, "enum component must have 32bit size"); #endif #ifdef FLECS_META ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), sizeof(ecs_i32_t), &value); #endif flecs_resume_readonly(world, &readonly_state); ecs_trace("#[green]constant#[reset] %s.%s created with value %d", ecs_get_name(world, parent), name, value); return id; } static int32_t flecs_reset_count = 0; int32_t ecs_cpp_reset_count_get(void) { return flecs_reset_count; } int32_t ecs_cpp_reset_count_inc(void) { return ++flecs_reset_count; } #ifdef FLECS_META const ecs_member_t* ecs_cpp_last_member( const ecs_world_t *world, ecs_entity_t type) { const EcsStruct *st = ecs_get(world, type, EcsStruct); if (!st) { char *type_str = ecs_get_fullpath(world, type); ecs_err("entity '%s' is not a struct", type_str); ecs_os_free(type_str); return 0; } ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, ecs_vec_count(&st->members) - 1); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); return m; } #endif #endif /** * @file addons/http.c * @brief HTTP addon. * * This is a heavily modified version of the EmbeddableWebServer (see copyright * below). This version has been stripped from everything not strictly necessary * for receiving/replying to simple HTTP requests, and has been modified to use * the Flecs OS API. * * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and * CONTRIBUTORS (see below) - All rights reserved. * * CONTRIBUTORS: * Martin Pulec - bug fixes, warning fixes, IPv6 support * Daniel Barry - bug fix (ifa_addr != NULL) * * Released under the BSD 2-clause license: * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. THIS SOFTWARE IS * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef FLECS_HTTP #ifdef ECS_TARGET_MSVC #pragma comment(lib, "Ws2_32.lib") #endif #if defined(ECS_TARGET_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include typedef SOCKET ecs_http_socket_t; #else #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif typedef int ecs_http_socket_t; #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL (0) #endif #endif /* Max length of request method */ #define ECS_HTTP_METHOD_LEN_MAX (8) /* Timeout (s) before connection purge */ #define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) /* Number of dequeues before purging */ #define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) /* Number of retries receiving request */ #define ECS_HTTP_REQUEST_RECV_RETRY (10) /* Minimum interval between dequeueing requests (ms) */ #define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) /* Minimum interval between printing statistics (ms) */ #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Receive buffer size */ #define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) /* Cache invalidation timeout (s) */ #define ECS_HTTP_CACHE_TIMEOUT ((ecs_ftime_t)1.0) /* Cache entry purge timeout (s) */ #define ECS_HTTP_CACHE_PURGE_TIMEOUT ((ecs_ftime_t)10.0) /* Global statistics */ int64_t ecs_http_request_received_count = 0; int64_t ecs_http_request_invalid_count = 0; int64_t ecs_http_request_handled_ok_count = 0; int64_t ecs_http_request_handled_error_count = 0; int64_t ecs_http_request_not_handled_count = 0; int64_t ecs_http_request_preflight_count = 0; int64_t ecs_http_send_ok_count = 0; int64_t ecs_http_send_error_count = 0; int64_t ecs_http_busy_count = 0; /* Send request queue */ typedef struct ecs_http_send_request_t { ecs_http_socket_t sock; char *headers; int32_t header_length; char *content; int32_t content_length; } ecs_http_send_request_t; typedef struct ecs_http_send_queue_t { ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; int32_t head; int32_t tail; ecs_os_thread_t thread; int32_t wait_ms; } ecs_http_send_queue_t; typedef struct ecs_http_request_key_t { const char *array; ecs_size_t count; } ecs_http_request_key_t; typedef struct ecs_http_request_entry_t { char *content; int32_t content_length; ecs_ftime_t time; } ecs_http_request_entry_t; /* HTTP server struct */ struct ecs_http_server_t { bool should_run; bool running; ecs_http_socket_t sock; ecs_os_mutex_t lock; ecs_os_thread_t thread; ecs_http_reply_action_t callback; void *ctx; ecs_sparse_t connections; /* sparse */ ecs_sparse_t requests; /* sparse */ bool initialized; uint16_t port; const char *ipaddr; double dequeue_timeout; /* used to not lock request queue too often */ double stats_timeout; /* used for periodic reporting of statistics */ double request_time; /* time spent on requests in last stats interval */ double request_time_total; /* total time spent on requests */ int32_t requests_processed; /* requests processed in last stats interval */ int32_t requests_processed_total; /* total requests processed */ int32_t dequeue_count; /* number of dequeues in last stats interval */ ecs_http_send_queue_t send_queue; ecs_hashmap_t request_cache; }; /** Fragment state, used by HTTP request parser */ typedef enum { HttpFragStateBegin, HttpFragStateMethod, HttpFragStatePath, HttpFragStateVersion, HttpFragStateHeaderStart, HttpFragStateHeaderName, HttpFragStateHeaderValueStart, HttpFragStateHeaderValue, HttpFragStateCR, HttpFragStateCRLF, HttpFragStateCRLFCR, HttpFragStateBody, HttpFragStateDone } HttpFragState; /** A fragment is a partially received HTTP request */ typedef struct { HttpFragState state; ecs_strbuf_t buf; ecs_http_method_t method; int32_t body_offset; int32_t query_offset; int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; int32_t header_count; int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; int32_t param_count; int32_t content_length; char *header_buf_ptr; char header_buf[32]; bool parse_content_length; bool invalid; } ecs_http_fragment_t; /** Extend public connection type with fragment data */ typedef struct { ecs_http_connection_t pub; ecs_http_socket_t sock; /* Connection is purged after both timeout expires and connection has * exceeded retry count. This ensures that a connection does not immediately * timeout when a frame takes longer than usual */ double dequeue_timeout; int32_t dequeue_retries; } ecs_http_connection_impl_t; typedef struct { ecs_http_request_t pub; uint64_t conn_id; /* for sanity check */ char *res; int32_t req_len; } ecs_http_request_impl_t; static ecs_size_t http_send( ecs_http_socket_t sock, const void *buf, ecs_size_t size, int flags) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); #ifdef ECS_TARGET_POSIX ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags | MSG_NOSIGNAL); return flecs_itoi32(send_bytes); #else int send_bytes = send(sock, buf, size, flags); return flecs_itoi32(send_bytes); #endif } static ecs_size_t http_recv( ecs_http_socket_t sock, void *buf, ecs_size_t size, int flags) { ecs_size_t ret; #ifdef ECS_TARGET_POSIX ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); ret = flecs_itoi32(recv_bytes); #else int recv_bytes = recv(sock, buf, size, flags); ret = flecs_itoi32(recv_bytes); #endif if (ret == -1) { ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); } else if (ret == 0) { ecs_dbg("recv: received 0 bytes (sock = %d)", sock); } return ret; } static void http_sock_set_timeout( ecs_http_socket_t sock, int32_t timeout_ms) { int r; #ifdef ECS_TARGET_POSIX struct timeval tv; tv.tv_sec = timeout_ms * 1000; tv.tv_usec = 0; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); #else DWORD t = (DWORD)timeout_ms; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); #endif if (r) { ecs_warn("http: failed to set socket timeout: %s", ecs_os_strerror(errno)); } } static void http_sock_keep_alive( ecs_http_socket_t sock) { int v = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { ecs_warn("http: failed to set socket KEEPALIVE: %s", ecs_os_strerror(errno)); } } static void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { (void)sock; (void)enable; #ifdef ECS_TARGET_POSIX int flags; flags = fcntl(sock,F_GETFL,0); if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } if (enable) { flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); } else { flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); } if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } #endif } static int http_getnameinfo( const struct sockaddr* addr, ecs_size_t addr_len, char *host, ecs_size_t host_len, char *port, ecs_size_t port_len, int flags) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return getnameinfo(addr, addr_len, host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #else return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #endif } static int http_bind( ecs_http_socket_t sock, const struct sockaddr* addr, ecs_size_t addr_len) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return bind(sock, addr, addr_len); #else return bind(sock, addr, flecs_ito(uint32_t, addr_len)); #endif } static bool http_socket_is_valid( ecs_http_socket_t sock) { #if defined(ECS_TARGET_WINDOWS) return sock != INVALID_SOCKET; #else return sock >= 0; #endif } #if defined(ECS_TARGET_WINDOWS) #define HTTP_SOCKET_INVALID INVALID_SOCKET #else #define HTTP_SOCKET_INVALID (-1) #endif static void http_close( ecs_http_socket_t *sock) { ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) closesocket(*sock); #else ecs_dbg_2("http: closing socket %u", *sock); shutdown(*sock, SHUT_RDWR); close(*sock); #endif *sock = HTTP_SOCKET_INVALID; } static ecs_http_socket_t http_accept( ecs_http_socket_t sock, struct sockaddr* addr, ecs_size_t *addr_len) { socklen_t len = (socklen_t)addr_len[0]; ecs_http_socket_t result = accept(sock, addr, &len); addr_len[0] = (ecs_size_t)len; return result; } static void http_reply_fini(ecs_http_reply_t* reply) { ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_free(reply->body.content); } static void http_request_fini(ecs_http_request_impl_t *req) { ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); ecs_os_free(req->res); flecs_sparse_remove_t(&req->pub.conn->server->requests, ecs_http_request_impl_t, req->pub.id); } static void http_connection_free(ecs_http_connection_impl_t *conn) { ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); uint64_t conn_id = conn->pub.id; if (http_socket_is_valid(conn->sock)) { http_close(&conn->sock); } flecs_sparse_remove_t(&conn->pub.server->connections, ecs_http_connection_impl_t, conn_id); } // https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int static char http_hex_2_int(char a, char b){ a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); return (char)((a << 4) + b); } static void http_decode_url_str( char *str) { char ch, *ptr, *dst = str; for (ptr = str; (ch = *ptr); ptr++) { if (ch == '%') { dst[0] = http_hex_2_int(ptr[1], ptr[2]); dst ++; ptr += 2; } else { dst[0] = ptr[0]; dst ++; } } dst[0] = '\0'; } static void http_parse_method( ecs_http_fragment_t *frag) { char *method = ecs_strbuf_get_small(&frag->buf); if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; else { frag->method = EcsHttpMethodUnsupported; frag->invalid = true; } ecs_strbuf_reset(&frag->buf); } static bool http_header_writable( ecs_http_fragment_t *frag) { return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; } static void http_header_buf_reset( ecs_http_fragment_t *frag) { frag->header_buf[0] = '\0'; frag->header_buf_ptr = frag->header_buf; } static void http_header_buf_append( ecs_http_fragment_t *frag, char ch) { if ((frag->header_buf_ptr - frag->header_buf) < ECS_SIZEOF(frag->header_buf)) { frag->header_buf_ptr[0] = ch; frag->header_buf_ptr ++; } else { frag->header_buf_ptr[0] = '\0'; } } static uint64_t http_request_key_hash(const void *ptr) { const ecs_http_request_key_t *key = ptr; const char *array = key->array; int32_t count = key->count; return flecs_hash(array, count * ECS_SIZEOF(char)); } static int http_request_key_compare(const void *ptr_1, const void *ptr_2) { const ecs_http_request_key_t *type_1 = ptr_1; const ecs_http_request_key_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } return ecs_os_memcmp(type_1->array, type_2->array, count_1); } static ecs_http_request_entry_t* http_find_request_entry( ecs_http_server_t *srv, const char *array, int32_t count) { ecs_http_request_key_t key; key.array = array; key.count = count; ecs_time_t t = {0, 0}; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (entry) { ecs_ftime_t tf = (ecs_ftime_t)ecs_time_measure(&t); if ((tf - entry->time) < ECS_HTTP_CACHE_TIMEOUT) { return entry; } } return NULL; } static void http_insert_request_entry( ecs_http_server_t *srv, ecs_http_request_impl_t *req, ecs_http_reply_t *reply) { int32_t content_length = ecs_strbuf_written(&reply->body); if (!content_length) { return; } ecs_http_request_key_t key; key.array = req->res; key.count = req->req_len; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (!entry) { flecs_hashmap_result_t elem = flecs_hashmap_ensure( &srv->request_cache, &key, ecs_http_request_entry_t); ecs_http_request_key_t *elem_key = elem.key; elem_key->array = ecs_os_memdup_n(key.array, char, key.count); entry = elem.value; } else { ecs_os_free(entry->content); } ecs_time_t t = {0, 0}; entry->time = (ecs_ftime_t)ecs_time_measure(&t); entry->content_length = ecs_strbuf_written(&reply->body); entry->content = ecs_strbuf_get(&reply->body); 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) { return NULL; } req->pub.method = frag->method; req->pub.path = res + 1; http_decode_url_str(req->pub.path); if (frag->body_offset) { req->pub.body = &res[frag->body_offset]; } int32_t i, count = frag->header_count; for (i = 0; i < count; i ++) { req->pub.headers[i].key = &res[frag->header_offsets[i]]; req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; } count = frag->param_count; for (i = 0; i < count; i ++) { req->pub.params[i].key = &res[frag->param_offsets[i]]; req->pub.params[i].value = &res[frag->param_value_offsets[i]]; /* Safe, member is only const so that end-user can't change it */ http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); } req->pub.header_count = frag->header_count; req->pub.param_count = frag->param_count; req->res = res; req->req_len = frag->header_offsets[0]; return res; } static ecs_http_request_entry_t* http_enqueue_request( ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_fragment_t *frag) { ecs_http_server_t *srv = conn->pub.server; ecs_os_mutex_lock(srv->lock); bool is_alive = conn->pub.id == conn_id; if (!is_alive || frag->invalid) { /* Don't enqueue invalid requests or requests for purged connections */ ecs_strbuf_reset(&frag->buf); } else { ecs_http_request_impl_t req; char *res = http_decode_request(&req, frag); if (res) { req.pub.conn = (ecs_http_connection_t*)conn; /* Check cache for GET requests */ if (frag->method == EcsHttpGet) { ecs_http_request_entry_t *entry = http_find_request_entry(srv, res, frag->header_offsets[0]); if (entry) { /* If an entry is found, don't enqueue a request. Instead * return the cached response immediately. */ ecs_os_free(res); return entry; } } ecs_http_request_impl_t *req_ptr = flecs_sparse_add_t( &srv->requests, ecs_http_request_impl_t); *req_ptr = req; req_ptr->pub.id = flecs_sparse_last_id(&srv->requests); req_ptr->conn_id = conn->pub.id; ecs_os_linc(&ecs_http_request_received_count); } } ecs_os_mutex_unlock(srv->lock); return NULL; } static bool http_parse_request( ecs_http_fragment_t *frag, const char* req_frag, ecs_size_t req_frag_len) { int32_t i; for (i = 0; i < req_frag_len; i++) { char c = req_frag[i]; switch (frag->state) { case HttpFragStateBegin: ecs_os_memset_t(frag, 0, ecs_http_fragment_t); frag->buf.max = ECS_HTTP_METHOD_LEN_MAX; frag->state = HttpFragStateMethod; frag->header_buf_ptr = frag->header_buf; /* fall through */ case HttpFragStateMethod: if (c == ' ') { http_parse_method(frag); frag->state = HttpFragStatePath; frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; } else { ecs_strbuf_appendch(&frag->buf, c); } break; case HttpFragStatePath: if (c == ' ') { frag->state = HttpFragStateVersion; ecs_strbuf_appendch(&frag->buf, '\0'); } else { if (c == '?' || c == '=' || c == '&') { ecs_strbuf_appendch(&frag->buf, '\0'); int32_t offset = ecs_strbuf_written(&frag->buf); if (c == '?' || c == '&') { frag->param_offsets[frag->param_count] = offset; } else { frag->param_value_offsets[frag->param_count] = offset; frag->param_count ++; } } else { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateVersion: if (c == '\r') { frag->state = HttpFragStateCR; } /* version is not stored */ break; case HttpFragStateHeaderStart: if (http_header_writable(frag)) { frag->header_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } http_header_buf_reset(frag); frag->state = HttpFragStateHeaderName; /* fall through */ case HttpFragStateHeaderName: if (c == ':') { frag->state = HttpFragStateHeaderValueStart; http_header_buf_append(frag, '\0'); frag->parse_content_length = !ecs_os_strcmp( frag->header_buf, "Content-Length"); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_value_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } } else if (c == '\r') { frag->state = HttpFragStateCR; } else { http_header_buf_append(frag, c); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateHeaderValueStart: http_header_buf_reset(frag); frag->state = HttpFragStateHeaderValue; if (c == ' ') { /* skip first space */ break; } /* fall through */ case HttpFragStateHeaderValue: if (c == '\r') { if (frag->parse_content_length) { http_header_buf_append(frag, '\0'); int32_t len = atoi(frag->header_buf); if (len < 0) { frag->invalid = true; } else { frag->content_length = len; } frag->parse_content_length = false; } if (http_header_writable(frag)) { int32_t cur = ecs_strbuf_written(&frag->buf); if (frag->header_offsets[frag->header_count] < cur && frag->header_value_offsets[frag->header_count] < cur) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_count ++; } } frag->state = HttpFragStateCR; } else { if (frag->parse_content_length) { http_header_buf_append(frag, c); } if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateCR: if (c == '\n') { frag->state = HttpFragStateCRLF; } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateCRLF: if (c == '\r') { frag->state = HttpFragStateCRLFCR; } else { frag->state = HttpFragStateHeaderStart; i--; } break; case HttpFragStateCRLFCR: if (c == '\n') { if (frag->content_length != 0) { frag->body_offset = ecs_strbuf_written(&frag->buf); frag->state = HttpFragStateBody; } else { frag->state = HttpFragStateDone; } } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateBody: { ecs_strbuf_appendch(&frag->buf, c); if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == frag->content_length) { frag->state = HttpFragStateDone; } } break; case HttpFragStateDone: break; } } if (frag->state == HttpFragStateDone) { return true; } else { return false; } } static ecs_http_send_request_t* http_send_queue_post( ecs_http_server_t *srv) { /* This function should only be called while the server is locked. Before * the lock is released, the returned element should be populated. */ ecs_http_send_queue_t *sq = &srv->send_queue; int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; if (next == sq->tail) { return NULL; } /* Don't enqueue new requests if server is shutting down */ if (!srv->should_run) { return NULL; } /* Return element at end of the queue */ ecs_http_send_request_t *result = &sq->requests[sq->head]; sq->head = next; return result; } static ecs_http_send_request_t* http_send_queue_get( ecs_http_server_t *srv) { ecs_os_mutex_lock(srv->lock); ecs_http_send_queue_t *sq = &srv->send_queue; if (sq->tail == sq->head) { return NULL; } int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; ecs_http_send_request_t *result = &sq->requests[sq->tail]; sq->tail = next; return result; } static void* http_server_send_queue(void* arg) { ecs_http_server_t *srv = arg; int32_t wait_ms = srv->send_queue.wait_ms; /* Run for as long as the server is running or there are messages. When the * server is stopping, no new messages will be enqueued */ while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { ecs_http_send_request_t* r = http_send_queue_get(srv); if (!r) { ecs_os_mutex_unlock(srv->lock); /* If the queue is empty, wait so we don't run too fast */ if (srv->should_run) { ecs_os_sleep(0, wait_ms * 1000 * 1000); } } else { ecs_http_socket_t sock = r->sock; char *headers = r->headers; int32_t headers_length = r->header_length; char *content = r->content; int32_t content_length = r->content_length; ecs_os_mutex_unlock(srv->lock); if (http_socket_is_valid(sock)) { bool error = false; http_sock_nonblock(sock, false); /* Write headers */ ecs_size_t written = http_send(sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to write HTTP response headers: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } else if (content_length >= 0) { /* Write content */ written = http_send(sock, content, content_length, 0); if (written != content_length) { ecs_err("http: failed to write HTTP response body: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } } if (!error) { ecs_os_linc(&ecs_http_send_ok_count); } http_close(&sock); } else { ecs_err("http: invalid socket\n"); } ecs_os_free(content); ecs_os_free(headers); } } return NULL; } static void http_append_send_headers( ecs_strbuf_t *hdrs, int code, const char* status, const char* content_type, ecs_strbuf_t *extra_headers, ecs_size_t content_len, bool preflight) { ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); ecs_strbuf_appendint(hdrs, code); ecs_strbuf_appendch(hdrs, ' '); ecs_strbuf_appendstr(hdrs, status); ecs_strbuf_appendlit(hdrs, "\r\n"); if (content_type) { ecs_strbuf_appendlit(hdrs, "Content-Type: "); ecs_strbuf_appendstr(hdrs, content_type); ecs_strbuf_appendlit(hdrs, "\r\n"); } if (content_len >= 0) { ecs_strbuf_appendlit(hdrs, "Content-Length: "); ecs_strbuf_append(hdrs, "%d", content_len); ecs_strbuf_appendlit(hdrs, "\r\n"); } ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); if (preflight) { ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, OPTIONS\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); } ecs_strbuf_mergebuff(hdrs, extra_headers); ecs_strbuf_appendlit(hdrs, "\r\n"); } static void http_send_reply( ecs_http_connection_impl_t* conn, ecs_http_reply_t* reply, bool preflight) { ecs_strbuf_t hdrs = ECS_STRBUF_INIT; char *content = ecs_strbuf_get(&reply->body); int32_t content_length = reply->body.length - 1; /* 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); char *headers = ecs_strbuf_get(&hdrs); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* Second, enqueue send request for response body */ req->sock = conn->sock; req->headers = headers; req->header_length = headers_length; req->content = content; req->content_length = content_length; /* Take ownership of values */ reply->body.content = NULL; conn->sock = HTTP_SOCKET_INVALID; } static void http_recv_connection( ecs_http_server_t *srv, ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_socket_t sock) { ecs_size_t bytes_read; char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; ecs_http_fragment_t frag = {0}; int32_t retries = 0; do { if ((bytes_read = http_recv( sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { /* Connection has been purged by main thread */ goto done; } if (http_parse_request(&frag, recv_buf, bytes_read)) { if (frag.method == EcsHttpOptions) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = 200; reply.content_type = NULL; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; http_send_reply(conn, &reply, true); ecs_os_linc(&ecs_http_request_preflight_count); } else { ecs_http_request_entry_t *entry = http_enqueue_request(conn, conn_id, &frag); if (entry) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = 200; reply.content_type = NULL; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; ecs_strbuf_appendstrn(&reply.body, entry->content, entry->content_length); http_send_reply(conn, &reply, false); http_connection_free(conn); /* Lock was transferred from enqueue_request */ ecs_os_mutex_unlock(srv->lock); } } } else { ecs_os_linc(&ecs_http_request_invalid_count); } } ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { http_close(&sock); } done: ecs_strbuf_reset(&frag.buf); } typedef struct { ecs_http_connection_impl_t *conn; uint64_t id; } http_conn_res_t; static http_conn_res_t http_init_connection( ecs_http_server_t *srv, ecs_http_socket_t sock_conn, struct sockaddr_storage *remote_addr, ecs_size_t remote_addr_len) { http_sock_set_timeout(sock_conn, 100); http_sock_keep_alive(sock_conn); http_sock_nonblock(sock_conn, true); /* Create new connection */ ecs_os_mutex_lock(srv->lock); ecs_http_connection_impl_t *conn = flecs_sparse_add_t( &srv->connections, ecs_http_connection_impl_t); uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); conn->pub.server = srv; conn->sock = sock_conn; ecs_os_mutex_unlock(srv->lock); char *remote_host = conn->pub.host; char *remote_port = conn->pub.port; /* Fetch name & port info */ if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, remote_host, ECS_SIZEOF(conn->pub.host), remote_port, ECS_SIZEOF(conn->pub.port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(remote_host, "unknown"); ecs_os_strcpy(remote_port, "unknown"); } ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", remote_host, remote_port, sock_conn); return (http_conn_res_t){ .conn = conn, .id = conn_id }; } static void http_accept_connections( ecs_http_server_t* srv, const struct sockaddr* addr, ecs_size_t addr_len) { #ifdef ECS_TARGET_WINDOWS /* If on Windows, test if winsock needs to be initialized */ SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ WSADATA data = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &data); if (result) { ecs_warn("http: WSAStartup failed with GetLastError = %d\n", GetLastError()); return; } } else { http_close(&testsocket); } #endif /* Resolve name + port (used for logging) */ char addr_host[256]; char addr_port[20]; ecs_http_socket_t sock = HTTP_SOCKET_INVALID; ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); if (http_getnameinfo( addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(addr_host, "unknown"); ecs_os_strcpy(addr_port, "unknown"); } ecs_os_mutex_lock(srv->lock); if (srv->should_run) { ecs_dbg_2("http: initializing connection socket"); sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); if (!http_socket_is_valid(sock)) { ecs_err("http: unable to create new connection socket: %s", ecs_os_strerror(errno)); ecs_os_mutex_unlock(srv->lock); goto done; } int reuse = 1, result; result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, ECS_SIZEOF(reuse)); if (result) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } if (addr->sa_family == AF_INET6) { int ipv6only = 0; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, ECS_SIZEOF(ipv6only))) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } } result = http_bind(sock, addr, addr_len); if (result) { ecs_err("http: failed to bind to '%s:%s': %s", addr_host, addr_port, ecs_os_strerror(errno)); ecs_os_mutex_unlock(srv->lock); goto done; } http_sock_set_timeout(sock, 1000); srv->sock = sock; result = listen(srv->sock, SOMAXCONN); if (result) { ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", SOMAXCONN, ecs_os_strerror(errno)); } ecs_trace("http: listening for incoming connections on '%s:%s'", addr_host, addr_port); } else { ecs_dbg_2("http: server shut down while initializing"); } ecs_os_mutex_unlock(srv->lock); struct sockaddr_storage remote_addr; ecs_size_t remote_addr_len = 0; while (srv->should_run) { remote_addr_len = ECS_SIZEOF(remote_addr); ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, &remote_addr_len); if (!http_socket_is_valid(sock_conn)) { if (srv->should_run) { ecs_dbg("http: connection attempt failed: %s", ecs_os_strerror(errno)); } continue; } http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); http_recv_connection(srv, conn.conn, conn.id, sock_conn); } done: ecs_os_mutex_lock(srv->lock); if (http_socket_is_valid(sock) && errno != EBADF) { http_close(&sock); srv->sock = sock; } ecs_os_mutex_unlock(srv->lock); ecs_trace("http: no longer accepting connections on '%s:%s'", addr_host, addr_port); } static void* http_server_thread(void* arg) { ecs_http_server_t *srv = arg; struct sockaddr_in addr; ecs_os_zeromem(&addr); addr.sin_family = AF_INET; addr.sin_port = htons(srv->port); if (!srv->ipaddr) { addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); return NULL; } static void http_do_request( ecs_http_server_t *srv, ecs_http_reply_t *reply, const ecs_http_request_impl_t *req) { if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { reply->code = 404; reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply->code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } } static void http_handle_request( ecs_http_server_t *srv, ecs_http_request_impl_t *req) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; if (req->pub.method != EcsHttpOptions) { if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { reply.code = 404; reply.status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply.code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } if (req->pub.method == EcsHttpGet) { http_insert_request_entry(srv, req, &reply); } http_send_reply(conn, &reply, false); ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); } else { /* Already taken care of */ } http_reply_fini(&reply); http_request_fini(req); http_connection_free(conn); } static void http_purge_request_cache( ecs_http_server_t *srv, bool fini) { ecs_time_t t = {0, 0}; ecs_ftime_t time = (ecs_ftime_t)ecs_time_measure(&t); ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&bucket->values); ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); for (i = count - 1; i >= 0; i --) { ecs_http_request_entry_t *entry = &entries[i]; if (fini || ((time - entry->time) > ECS_HTTP_CACHE_PURGE_TIMEOUT)) { ecs_http_request_key_t *key = &keys[i]; /* Safe, code owns the value */ ecs_os_free(ECS_CONST_CAST(char*, key->array)); ecs_os_free(entry->content); flecs_hm_bucket_remove(&srv->request_cache, bucket, ecs_map_key(&it), i); } } } if (fini) { flecs_hashmap_fini(&srv->request_cache); } } static int32_t http_dequeue_requests( ecs_http_server_t *srv, double delta_time) { ecs_os_mutex_lock(srv->lock); int32_t i, request_count = flecs_sparse_count(&srv->requests); for (i = request_count - 1; i >= 1; i --) { ecs_http_request_impl_t *req = flecs_sparse_get_dense_t( &srv->requests, ecs_http_request_impl_t, i); http_handle_request(srv, req); } int32_t connections_count = flecs_sparse_count(&srv->connections); for (i = connections_count - 1; i >= 1; i --) { ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i); conn->dequeue_timeout += delta_time; conn->dequeue_retries ++; if ((conn->dequeue_timeout > (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) { ecs_dbg("http: purging connection '%s:%s' (sock = %d)", conn->pub.host, conn->pub.port, conn->sock); http_connection_free(conn); } } http_purge_request_cache(srv, false); ecs_os_mutex_unlock(srv->lock); return request_count - 1; } const char* ecs_http_get_header( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->header_count; i++) { if (!ecs_os_strcmp(req->headers[i].key, name)) { return req->headers[i].value; } } return NULL; } const char* ecs_http_get_param( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->param_count; i++) { if (!ecs_os_strcmp(req->params[i].key, name)) { return req->params[i].value; } } return NULL; } ecs_http_server_t* ecs_http_server_init( const ecs_http_server_desc_t *desc) { ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); srv->lock = ecs_os_mutex_new(); srv->sock = HTTP_SOCKET_INVALID; srv->should_run = false; srv->initialized = true; srv->callback = desc->callback; srv->ctx = desc->ctx; srv->port = desc->port; srv->ipaddr = desc->ipaddr; srv->send_queue.wait_ms = desc->send_queue_wait_ms; if (!srv->send_queue.wait_ms) { srv->send_queue.wait_ms = 1; } flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); flecs_sparse_init_t(&srv->requests, NULL, NULL, ecs_http_request_impl_t); /* Start at id 1 */ flecs_sparse_new_id(&srv->connections); flecs_sparse_new_id(&srv->requests); /* Initialize request cache */ flecs_hashmap_init(&srv->request_cache, ecs_http_request_key_t, ecs_http_request_entry_t, http_request_key_hash, http_request_key_compare, NULL); #ifndef ECS_TARGET_WINDOWS /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client * but te client already disconnected. */ signal(SIGPIPE, SIG_IGN); #endif return srv; error: return NULL; } void ecs_http_server_fini( ecs_http_server_t* srv) { if (srv->should_run) { ecs_http_server_stop(srv); } ecs_os_mutex_free(srv->lock); http_purge_request_cache(srv, true); flecs_sparse_fini(&srv->requests); flecs_sparse_fini(&srv->connections); ecs_os_free(srv); } int ecs_http_server_start( ecs_http_server_t *srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); srv->should_run = true; ecs_dbg("http: starting server thread"); srv->thread = ecs_os_thread_new(http_server_thread, srv); if (!srv->thread) { goto error; } srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); if (!srv->send_queue.thread) { goto error; } return 0; error: return -1; } void ecs_http_server_stop( ecs_http_server_t* srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_OPERATION, NULL); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); /* Stop server thread */ ecs_dbg("http: shutting down server thread"); ecs_os_mutex_lock(srv->lock); srv->should_run = false; if (http_socket_is_valid(srv->sock)) { http_close(&srv->sock); } ecs_os_mutex_unlock(srv->lock); ecs_os_thread_join(srv->thread); ecs_os_thread_join(srv->send_queue.thread); ecs_trace("http: server threads shut down"); /* Cleanup all outstanding requests */ int i, count = flecs_sparse_count(&srv->requests); for (i = count - 1; i >= 1; i --) { http_request_fini(flecs_sparse_get_dense_t( &srv->requests, ecs_http_request_impl_t, i)); } /* Close all connections */ count = flecs_sparse_count(&srv->connections); for (i = count - 1; i >= 1; i --) { http_connection_free(flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i)); } ecs_assert(flecs_sparse_count(&srv->connections) == 1, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_sparse_count(&srv->requests) == 1, ECS_INTERNAL_ERROR, NULL); srv->thread = 0; error: return; } void ecs_http_server_dequeue( ecs_http_server_t* srv, ecs_ftime_t delta_time) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); srv->dequeue_timeout += (double)delta_time; srv->stats_timeout += (double)delta_time; if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { srv->dequeue_timeout = 0; ecs_time_t t = {0}; ecs_time_measure(&t); int32_t request_count = http_dequeue_requests(srv, srv->dequeue_timeout); srv->requests_processed += request_count; srv->requests_processed_total += request_count; double time_spent = ecs_time_measure(&t); srv->request_time += time_spent; srv->request_time_total += time_spent; srv->dequeue_count ++; } if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { srv->stats_timeout = 0; ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", srv->requests_processed, srv->request_time, (srv->request_time / (double)srv->dequeue_count)); srv->requests_processed = 0; srv->request_time = 0; srv->dequeue_count = 0; } error: return; } int ecs_http_server_http_request( ecs_http_server_t* srv, const char *req, ecs_size_t len, ecs_http_reply_t *reply_out) { if (!len) { len = ecs_os_strlen(req); } ecs_http_fragment_t frag = {0}; if (!http_parse_request(&frag, req, len)) { ecs_strbuf_reset(&frag.buf); reply_out->code = 400; return -1; } ecs_http_request_impl_t request; char *res = http_decode_request(&request, &frag); if (!res) { reply_out->code = 400; return -1; } http_do_request(srv, reply_out, &request); ecs_os_free(res); return (reply_out->code >= 400) ? -1 : 0; } int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, ecs_http_reply_t *reply_out) { ecs_strbuf_t reqbuf = ECS_STRBUF_INIT; ecs_strbuf_appendstr_zerocpy_const(&reqbuf, method); ecs_strbuf_appendlit(&reqbuf, " "); ecs_strbuf_appendstr_zerocpy_const(&reqbuf, req); ecs_strbuf_appendlit(&reqbuf, " HTTP/1.1\r\n\r\n"); int32_t len = ecs_strbuf_written(&reqbuf); char *reqstr = ecs_strbuf_get(&reqbuf); int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); ecs_os_free(reqstr); return result; } void* ecs_http_server_ctx( ecs_http_server_t* srv) { return srv->ctx; } #endif /** * @file addons/journal.c * @brief Journal addon. */ #ifdef FLECS_JOURNAL static char* flecs_journal_entitystr( ecs_world_t *world, ecs_entity_t entity) { char *path; const char *_path = ecs_get_symbol(world, entity); if (_path && !strchr(_path, '.')) { path = ecs_asprintf("#[blue]%s", _path); } else { uint32_t gen = entity >> 32; if (gen) { path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); } else { path = ecs_asprintf("#[normal]_%u", (uint32_t)entity); } } return path; } static char* flecs_journal_idstr( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *second_path = flecs_journal_entitystr(world, ecs_pair_second(world, id)); char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", first_path, second_path); ecs_os_free(first_path); ecs_os_free(second_path); return result; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_journal_entitystr(world, id); } else { return ecs_id_str(world, id); } } static int flecs_journal_sp = 0; void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove) { flecs_journal_sp ++; if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { return; } char *path = NULL; char *var_id = NULL; if (entity) { path = ecs_get_fullpath(world, entity); var_id = flecs_journal_entitystr(world, entity); } if (kind == EcsJournalNew) { ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); ecs_print(4, "#[green]ecs_entity_t %s;", var_id); ecs_print(4, "#[magenta]#endif"); ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " "#[grey] // %s = new()", var_id, path); } if (add) { for (int i = 0; i < add->count; i ++) { char *jidstr = flecs_journal_idstr(world, add->array[i]); char *idstr = ecs_id_str(world, add->array[i]); ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " "#[grey] // add(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (remove) { for (int i = 0; i < remove->count; i ++) { char *jidstr = flecs_journal_idstr(world, remove->array[i]); char *idstr = ecs_id_str(world, remove->array[i]); ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " "#[grey] // remove(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (kind == EcsJournalClear) { ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " "#[grey] // clear(%s)", var_id, path); } else if (kind == EcsJournalDelete) { ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " "#[grey] // delete(%s)", var_id, path); } else if (kind == EcsJournalDeleteWith) { ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " "#[grey] // delete_with(%s)", var_id, path); } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); } else if (kind == EcsJournalTableEvents) { ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); ecs_log_push(); } void flecs_journal_end(void) { flecs_journal_sp --; ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } #endif /** * @file addons/log.c * @brief Log addon. */ #ifdef FLECS_LOG #include void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf) { ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); char *ptr, ch, prev = '\0'; bool isNum = false; char isStr = '\0'; bool isVar = false; bool overrideColor = false; bool autoColor = true; bool dontAppend = false; for (ptr = msg; (ch = *ptr); ptr++) { dontAppend = false; if (!overrideColor) { if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isNum = false; } if (isStr && (isStr == ch) && prev != '\\') { isStr = '\0'; } else if (((ch == '\'') || (ch == '"')) && !isStr && !isalpha(prev) && (prev != '\\')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isStr = ch; } if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && !isalpha(prev) && !isdigit(prev) && (prev != '_') && (prev != '.')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); isNum = true; } if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isVar = false; } if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isVar = true; } } if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { bool isColor = true; overrideColor = true; /* Custom colors */ if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { autoColor = false; } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { overrideColor = false; if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else { isColor = false; overrideColor = false; } if (isColor) { ptr += 2; while ((ch = *ptr) != ']') ptr ++; dontAppend = true; } if (!autoColor) { overrideColor = true; } } if (ch == '\n') { if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); overrideColor = false; isNum = false; isStr = false; isVar = false; } } if (!dontAppend) { ecs_strbuf_appendstrn(buf, ptr, 1); } if (!overrideColor) { if (((ch == '\'') || (ch == '"')) && !isStr) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } prev = ch; } if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } void ecs_printv_( int level, const char *file, int32_t line, const char *fmt, va_list args) { (void)level; (void)line; ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; /* Apply color. Even if we don't want color, we still need to call the * colorize function to get rid of the color tags (e.g. #[green]) */ char *msg_nocolor = ecs_vasprintf(fmt, args); flecs_colorize_buf(msg_nocolor, ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); ecs_os_free(msg_nocolor); char *msg = ecs_strbuf_get(&msg_buf); if (msg) { ecs_os_api.log_(level, file, line, msg); ecs_os_free(msg); } else { ecs_os_api.log_(level, file, line, ""); } } void ecs_print_( int level, const char *file, int32_t line, const char *fmt, ...) { va_list args; va_start(args, fmt); ecs_printv_(level, file, line, fmt, args); va_end(args); } void ecs_logv_( int level, const char *file, int32_t line, const char *fmt, va_list args) { if (level > ecs_os_api.log_level_) { return; } ecs_printv_(level, file, line, fmt, args); } void ecs_log_( int level, const char *file, int32_t line, const char *fmt, ...) { if (level > ecs_os_api.log_level_) { return; } va_list args; va_start(args, fmt); ecs_printv_(level, file, line, fmt, args); va_end(args); } void ecs_log_push_( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ ++; } } void ecs_log_pop_( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ --; ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); } } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the * function is called with (expr - ptr), and expr is NULL. */ column_arg = 0; } int32_t column = flecs_itoi32(column_arg); if (ecs_os_api.log_level_ >= -2) { ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; ecs_strbuf_vappend(&msg_buf, fmt, args); if (expr) { ecs_strbuf_appendch(&msg_buf, '\n'); /* Find start of line by taking column and looking for the * last occurring newline */ if (column != -1) { const char *ptr = &expr[column]; while (ptr[0] != '\n' && ptr > expr) { ptr --; } if (ptr == expr) { /* ptr is already at start of line */ } else { column -= (int32_t)(ptr - expr + 1); expr = ptr + 1; } } /* Strip newlines from current statement, if any */ char *newline_ptr = strchr(expr, '\n'); if (newline_ptr) { /* Strip newline from expr */ ecs_strbuf_appendstrn(&msg_buf, expr, (int32_t)(newline_ptr - expr)); } else { ecs_strbuf_appendstr(&msg_buf, expr); } ecs_strbuf_appendch(&msg_buf, '\n'); if (column != -1) { int32_t c; for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } ecs_strbuf_appendch(&msg_buf, '^'); } } char *msg = ecs_strbuf_get(&msg_buf); ecs_os_err(name, 0, msg); ecs_os_free(msg); } } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); ecs_parser_errorv_(name, expr, column, fmt, args); va_end(args); } } void ecs_abort_( int32_t err, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = ecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "%s (%s)", msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "%s", ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } bool ecs_assert_( bool condition, int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (!condition) { if (fmt) { va_list args; va_start(args, fmt); char *msg = ecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "assert: %s %s (%s)", cond_str, msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "assert: %s %s", cond_str, ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } return condition; } void ecs_deprecated_( const char *file, int32_t line, const char *msg) { ecs_err_(file, line, "%s", msg); } bool ecs_should_log(int32_t level) { # if !defined(FLECS_LOG_3) if (level == 3) { return false; } # endif # if !defined(FLECS_LOG_2) if (level == 2) { return false; } # endif # if !defined(FLECS_LOG_1) if (level == 1) { return false; } # endif return level <= ecs_os_api.log_level_; } #define ECS_ERR_STR(code) case code: return &(#code[4]) const char* ecs_strerror( int32_t error_code) { switch (error_code) { ECS_ERR_STR(ECS_INVALID_PARAMETER); ECS_ERR_STR(ECS_NOT_A_COMPONENT); ECS_ERR_STR(ECS_INTERNAL_ERROR); ECS_ERR_STR(ECS_ALREADY_DEFINED); ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); ECS_ERR_STR(ECS_NAME_IN_USE); ECS_ERR_STR(ECS_OUT_OF_MEMORY); ECS_ERR_STR(ECS_DOUBLE_FREE); ECS_ERR_STR(ECS_OPERATION_FAILED); ECS_ERR_STR(ECS_INVALID_CONVERSION); ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_LEAK_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); ECS_ERR_STR(ECS_INVALID_FROM_WORKER); ECS_ERR_STR(ECS_OUT_OF_RANGE); ECS_ERR_STR(ECS_MISSING_OS_API); ECS_ERR_STR(ECS_UNSUPPORTED); ECS_ERR_STR(ECS_ACCESS_VIOLATION); ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); ECS_ERR_STR(ECS_INCONSISTENT_NAME); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); ECS_ERR_STR(ECS_INVALID_OPERATION); ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); ECS_ERR_STR(ECS_LOCKED_STORAGE); ECS_ERR_STR(ECS_ID_IN_USE); } return "unknown error code"; } #else /* Empty bodies for when logging is disabled */ void ecs_log_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_abort_( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } bool ecs_assert_( bool condition, int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)condition; (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; return true; } #endif int ecs_log_get_level(void) { return ecs_os_api.log_level_; } int ecs_log_set_level( int level) { int prev = level; ecs_os_api.log_level_ = level; return prev; } bool ecs_log_enable_colors( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); return prev; } bool ecs_log_enable_timestamp( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); return prev; } bool ecs_log_enable_timedelta( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); return prev; } int ecs_log_last_error(void) { int result = ecs_os_api.log_last_error_; ecs_os_api.log_last_error_ = 0; return result; } /** * @file addons/meta_c.c * @brief C utilities for meta addon. */ #ifdef FLECS_META_C #include #define ECS_META_IDENTIFIER_LENGTH (256) #define ecs_meta_error(ctx, ptr, ...)\ ecs_parser_error((ctx)->name, (ctx)->desc, ptr - (ctx)->desc, __VA_ARGS__); typedef char ecs_meta_token_t[ECS_META_IDENTIFIER_LENGTH]; typedef struct meta_parse_ctx_t { const char *name; const char *desc; } meta_parse_ctx_t; typedef struct meta_type_t { ecs_meta_token_t type; ecs_meta_token_t params; bool is_const; bool is_ptr; } meta_type_t; typedef struct meta_member_t { meta_type_t type; ecs_meta_token_t name; int64_t count; bool is_partial; } meta_member_t; typedef struct meta_constant_t { ecs_meta_token_t name; int64_t value; bool is_value_set; } meta_constant_t; typedef struct meta_params_t { meta_type_t key_type; meta_type_t type; int64_t count; bool is_key_value; bool is_fixed_size; } meta_params_t; static const char* skip_scope(const char *ptr, meta_parse_ctx_t *ctx) { /* Keep track of which characters were used to open the scope */ char stack[256]; int32_t sp = 0; char ch; while ((ch = *ptr)) { if (ch == '(' || ch == '<') { stack[sp] = ch; sp ++; if (sp >= 256) { ecs_meta_error(ctx, ptr, "maximum level of nesting reached"); goto error; } } else if (ch == ')' || ch == '>') { sp --; if ((sp < 0) || (ch == '>' && stack[sp] != '<') || (ch == ')' && stack[sp] != '(')) { ecs_meta_error(ctx, ptr, "mismatching %c in identifier", ch); goto error; } } ptr ++; if (!sp) { break; } } return ptr; error: return NULL; } static const char* parse_c_digit( const char *ptr, int64_t *value_out) { char token[24]; ptr = ecs_parse_ws_eol(ptr); ptr = ecs_parse_digit(ptr, token); if (!ptr) { goto error; } *value_out = strtol(token, NULL, 0); return ecs_parse_ws_eol(ptr); error: return NULL; } static const char* parse_c_identifier( const char *ptr, char *buff, char *params, meta_parse_ctx_t *ctx) { ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); char *bptr = buff, ch; if (params) { params[0] = '\0'; } /* Ignore whitespaces */ ptr = ecs_parse_ws_eol(ptr); ch = *ptr; if (!isalpha(ch) && (ch != '_')) { ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch); goto error; } while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>' && ch != '}' && ch != '*') { /* Type definitions can contain macros or templates */ if (ch == '(' || ch == '<') { if (!params) { ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); goto error; } const char *end = skip_scope(ptr, ctx); ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); params[end - ptr] = '\0'; ptr = end; } else { *bptr = ch; bptr ++; ptr ++; } } *bptr = '\0'; if (!ch) { ecs_meta_error(ctx, ptr, "unexpected end of token"); goto error; } return ptr; error: return NULL; } static const char * meta_open_scope( const char *ptr, meta_parse_ctx_t *ctx) { /* Skip initial whitespaces */ ptr = ecs_parse_ws_eol(ptr); /* Is this the start of the type definition? */ if (ctx->desc == ptr) { if (*ptr != '{') { ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); goto error; } ptr ++; ptr = ecs_parse_ws_eol(ptr); } /* Is this the end of the type definition? */ if (!*ptr) { ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); goto error; } /* Is this the end of the type definition? */ if (*ptr == '}') { ptr = ecs_parse_ws_eol(ptr + 1); if (*ptr) { ecs_meta_error(ctx, ptr, "stray characters after struct definition"); goto error; } return NULL; } return ptr; error: return NULL; } static const char* meta_parse_constant( const char *ptr, meta_constant_t *token, meta_parse_ctx_t *ctx) { ptr = meta_open_scope(ptr, ctx); if (!ptr) { return NULL; } token->is_value_set = false; /* Parse token, constant identifier */ ptr = parse_c_identifier(ptr, token->name, NULL, ctx); if (!ptr) { return NULL; } ptr = ecs_parse_ws_eol(ptr); if (!ptr) { return NULL; } /* Explicit value assignment */ if (*ptr == '=') { int64_t value = 0; ptr = parse_c_digit(ptr + 1, &value); token->value = value; token->is_value_set = true; } /* Expect a ',' or '}' */ if (*ptr != ',' && *ptr != '}') { ecs_meta_error(ctx, ptr, "missing , after enum constant"); goto error; } if (*ptr == ',') { return ptr + 1; } else { return ptr; } error: return NULL; } static const char* meta_parse_type( const char *ptr, meta_type_t *token, meta_parse_ctx_t *ctx) { token->is_ptr = false; token->is_const = false; ptr = ecs_parse_ws_eol(ptr); /* Parse token, expect type identifier or ECS_PROPERTY */ ptr = parse_c_identifier(ptr, token->type, token->params, ctx); if (!ptr) { goto error; } if (!strcmp(token->type, "ECS_PRIVATE")) { /* Members from this point are not stored in metadata */ ptr += ecs_os_strlen(ptr); goto done; } /* If token is const, set const flag and continue parsing type */ if (!strcmp(token->type, "const")) { token->is_const = true; /* Parse type after const */ ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); } /* Check if type is a pointer */ ptr = ecs_parse_ws_eol(ptr); if (*ptr == '*') { token->is_ptr = true; ptr ++; } done: return ptr; error: return NULL; } static const char* meta_parse_member( const char *ptr, meta_member_t *token, meta_parse_ctx_t *ctx) { ptr = meta_open_scope(ptr, ctx); if (!ptr) { return NULL; } token->count = 1; token->is_partial = false; /* Parse member type */ ptr = meta_parse_type(ptr, &token->type, ctx); if (!ptr) { token->is_partial = true; goto error; } if (!ptr[0]) { return ptr; } /* Next token is the identifier */ ptr = parse_c_identifier(ptr, token->name, NULL, ctx); if (!ptr) { goto error; } /* Skip whitespace between member and [ or ; */ ptr = ecs_parse_ws_eol(ptr); /* Check if this is an array */ char *array_start = strchr(token->name, '['); if (!array_start) { /* If the [ was separated by a space, it will not be parsed as part of * the name */ if (*ptr == '[') { /* safe, will not be modified */ array_start = ECS_CONST_CAST(char*, ptr); } } if (array_start) { /* Check if the [ matches with a ] */ char *array_end = strchr(array_start, ']'); if (!array_end) { ecs_meta_error(ctx, ptr, "missing ']'"); goto error; } else if (array_end - array_start == 0) { ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); goto error; } token->count = atoi(array_start + 1); if (array_start == ptr) { /* If [ was found after name, continue parsing after ] */ ptr = array_end + 1; } else { /* If [ was fonud in name, replace it with 0 terminator */ array_start[0] = '\0'; } } /* Expect a ; */ if (*ptr != ';') { ecs_meta_error(ctx, ptr, "missing ; after member declaration"); goto error; } return ptr + 1; error: return NULL; } static int meta_parse_desc( const char *ptr, meta_params_t *token, meta_parse_ctx_t *ctx) { token->is_key_value = false; token->is_fixed_size = false; ptr = ecs_parse_ws_eol(ptr); if (*ptr != '(' && *ptr != '<') { ecs_meta_error(ctx, ptr, "expected '(' at start of collection definition"); goto error; } ptr ++; /* Parse type identifier */ ptr = meta_parse_type(ptr, &token->type, ctx); if (!ptr) { goto error; } ptr = ecs_parse_ws_eol(ptr); /* If next token is a ',' the first type was a key type */ if (*ptr == ',') { ptr = ecs_parse_ws_eol(ptr + 1); if (isdigit(*ptr)) { int64_t value; ptr = parse_c_digit(ptr, &value); if (!ptr) { goto error; } token->count = value; token->is_fixed_size = true; } else { token->key_type = token->type; /* Parse element type */ ptr = meta_parse_type(ptr, &token->type, ctx); ptr = ecs_parse_ws_eol(ptr); token->is_key_value = true; } } if (*ptr != ')' && *ptr != '>') { ecs_meta_error(ctx, ptr, "expected ')' at end of collection definition"); goto error; } return 0; error: return -1; } static ecs_entity_t meta_lookup( ecs_world_t *world, meta_type_t *token, const char *ptr, int64_t count, meta_parse_ctx_t *ctx); static ecs_entity_t meta_lookup_array( ecs_world_t *world, ecs_entity_t e, const char *params_decl, meta_parse_ctx_t *ctx) { meta_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; meta_params_t params; if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { goto error; } if (!params.is_fixed_size) { ecs_meta_error(ctx, params_decl, "missing size for array"); goto error; } if (!params.count) { ecs_meta_error(ctx, params_decl, "invalid array size"); goto error; } ecs_entity_t element_type = ecs_lookup_symbol( world, params.type.type, true, true); if (!element_type) { ecs_meta_error(ctx, params_decl, "unknown element type '%s'", params.type.type); } if (!e) { e = ecs_new_id(world); } ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); error: return 0; } static ecs_entity_t meta_lookup_vector( ecs_world_t *world, ecs_entity_t e, const char *params_decl, meta_parse_ctx_t *ctx) { meta_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; meta_params_t params; if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { goto error; } if (params.is_key_value) { ecs_meta_error(ctx, params_decl, "unexpected key value parameters for vector"); goto error; } ecs_entity_t element_type = meta_lookup( world, ¶ms.type, params_decl, 1, ¶m_ctx); if (!e) { e = ecs_new_id(world); } return ecs_set(world, e, EcsVector, { element_type }); error: return 0; } static ecs_entity_t meta_lookup_bitmask( ecs_world_t *world, ecs_entity_t e, const char *params_decl, meta_parse_ctx_t *ctx) { (void)e; meta_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; meta_params_t params; if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { goto error; } if (params.is_key_value) { ecs_meta_error(ctx, params_decl, "unexpected key value parameters for bitmask"); goto error; } if (params.is_fixed_size) { ecs_meta_error(ctx, params_decl, "unexpected size for bitmask"); goto error; } ecs_entity_t bitmask_type = meta_lookup( world, ¶ms.type, params_decl, 1, ¶m_ctx); ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); #ifndef FLECS_NDEBUG /* Make sure this is a bitmask type */ const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); #endif return bitmask_type; error: return 0; } static ecs_entity_t meta_lookup( ecs_world_t *world, meta_type_t *token, const char *ptr, int64_t count, meta_parse_ctx_t *ctx) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); const char *typename = token->type; ecs_entity_t type = 0; /* Parse vector type */ if (!token->is_ptr) { if (!ecs_os_strcmp(typename, "ecs_array")) { type = meta_lookup_array(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "ecs_vector") || !ecs_os_strcmp(typename, "flecs::vector")) { type = meta_lookup_vector(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { type = meta_lookup_bitmask(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::byte")) { type = ecs_id(ecs_byte_t); } else if (!ecs_os_strcmp(typename, "char")) { type = ecs_id(ecs_char_t); } else if (!ecs_os_strcmp(typename, "bool") || !ecs_os_strcmp(typename, "_Bool")) { type = ecs_id(ecs_bool_t); } else if (!ecs_os_strcmp(typename, "int8_t")) { type = ecs_id(ecs_i8_t); } else if (!ecs_os_strcmp(typename, "int16_t")) { type = ecs_id(ecs_i16_t); } else if (!ecs_os_strcmp(typename, "int32_t")) { type = ecs_id(ecs_i32_t); } else if (!ecs_os_strcmp(typename, "int64_t")) { type = ecs_id(ecs_i64_t); } else if (!ecs_os_strcmp(typename, "uint8_t")) { type = ecs_id(ecs_u8_t); } else if (!ecs_os_strcmp(typename, "uint16_t")) { type = ecs_id(ecs_u16_t); } else if (!ecs_os_strcmp(typename, "uint32_t")) { type = ecs_id(ecs_u32_t); } else if (!ecs_os_strcmp(typename, "uint64_t")) { type = ecs_id(ecs_u64_t); } else if (!ecs_os_strcmp(typename, "float")) { type = ecs_id(ecs_f32_t); } else if (!ecs_os_strcmp(typename, "double")) { type = ecs_id(ecs_f64_t); } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { type = ecs_id(ecs_entity_t); } else if (!ecs_os_strcmp(typename, "char*")) { type = ecs_id(ecs_string_t); } else { type = ecs_lookup_symbol(world, typename, true, true); } } else { if (!ecs_os_strcmp(typename, "char")) { typename = "flecs.meta.string"; } else if (token->is_ptr) { typename = "flecs.meta.uptr"; } else if (!ecs_os_strcmp(typename, "char*") || !ecs_os_strcmp(typename, "flecs::string")) { typename = "flecs.meta.string"; } type = ecs_lookup_symbol(world, typename, true, true); } if (count != 1) { ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); type = ecs_set(world, 0, EcsArray, {type, (int32_t)count}); } if (!type) { ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); goto error; } return type; error: return 0; } static int meta_parse_struct( ecs_world_t *world, ecs_entity_t t, const char *desc) { const char *ptr = desc; const char *name = ecs_get_name(world, t); meta_member_t token; meta_parse_ctx_t ctx = { .name = name, .desc = ptr }; ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { ecs_entity_t m = ecs_entity(world, { .name = token.name }); ecs_entity_t type = meta_lookup( world, &token.type, ptr, 1, &ctx); if (!type) { goto error; } ecs_set(world, m, EcsMember, { .type = type, .count = (ecs_size_t)token.count }); } ecs_set_scope(world, old_scope); return 0; error: return -1; } static int meta_parse_constants( ecs_world_t *world, ecs_entity_t t, const char *desc, bool is_bitmask) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); const char *ptr = desc; const char *name = ecs_get_name(world, t); int32_t name_len = ecs_os_strlen(name); const ecs_world_info_t *info = ecs_get_world_info(world); const char *name_prefix = info->name_prefix; int32_t name_prefix_len = name_prefix ? ecs_os_strlen(name_prefix) : 0; meta_parse_ctx_t ctx = { .name = name, .desc = ptr }; meta_constant_t token; int64_t last_value = 0; ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { if (token.is_value_set) { last_value = token.value; } else if (is_bitmask) { ecs_meta_error(&ctx, ptr, "bitmask requires explicit value assignment"); goto error; } if (name_prefix) { if (!ecs_os_strncmp(token.name, name_prefix, name_prefix_len)) { ecs_os_memmove(token.name, token.name + name_prefix_len, ecs_os_strlen(token.name) - name_prefix_len + 1); } } if (!ecs_os_strncmp(token.name, name, name_len)) { ecs_os_memmove(token.name, token.name + name_len, ecs_os_strlen(token.name) - name_len + 1); } ecs_entity_t c = ecs_entity(world, { .name = token.name }); if (!is_bitmask) { ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, {(ecs_i32_t)last_value}); } else { ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, {(ecs_u32_t)last_value}); } last_value ++; } ecs_set_scope(world, old_scope); return 0; error: return -1; } static int meta_parse_enum( ecs_world_t *world, ecs_entity_t t, const char *desc) { ecs_add(world, t, EcsEnum); return meta_parse_constants(world, t, desc, false); } static int meta_parse_bitmask( ecs_world_t *world, ecs_entity_t t, const char *desc) { ecs_add(world, t, EcsBitmask); return meta_parse_constants(world, t, desc, true); } int ecs_meta_from_desc( ecs_world_t *world, ecs_entity_t component, ecs_type_kind_t kind, const char *desc) { switch(kind) { case EcsStructType: if (meta_parse_struct(world, component, desc)) { goto error; } break; case EcsEnumType: if (meta_parse_enum(world, component, desc)) { goto error; } break; case EcsBitmaskType: if (meta_parse_bitmask(world, component, desc)) { goto error; } break; case EcsPrimitiveType: case EcsArrayType: case EcsVectorType: case EcsOpaqueType: break; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); } return 0; error: return -1; } #endif /** * @file addons/metrics.c * @brief Metrics addon. */ #ifdef FLECS_METRICS /* Public components */ ECS_COMPONENT_DECLARE(FlecsMetrics); ECS_TAG_DECLARE(EcsMetricInstance); ECS_COMPONENT_DECLARE(EcsMetricValue); ECS_COMPONENT_DECLARE(EcsMetricSource); ECS_TAG_DECLARE(EcsMetric); ECS_TAG_DECLARE(EcsCounter); ECS_TAG_DECLARE(EcsCounterIncrement); ECS_TAG_DECLARE(EcsCounterId); ECS_TAG_DECLARE(EcsGauge); /* Internal components */ static ECS_COMPONENT_DECLARE(EcsMetricMember); static ECS_COMPONENT_DECLARE(EcsMetricId); static ECS_COMPONENT_DECLARE(EcsMetricOneOf); static ECS_COMPONENT_DECLARE(EcsMetricCountIds); static ECS_COMPONENT_DECLARE(EcsMetricCountTargets); static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); static ECS_COMPONENT_DECLARE(EcsMetricIdInstance); static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); /** Context for metric */ typedef struct { ecs_entity_t metric; /**< Metric entity */ ecs_entity_t kind; /**< Metric kind (gauge, counter) */ } ecs_metric_ctx_t; /** Context for metric that monitors member */ typedef struct { ecs_metric_ctx_t metric; ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */ uint16_t offset; /**< Offset of member in component */ } ecs_member_metric_ctx_t; /** Context for metric that monitors whether entity has id */ typedef struct { ecs_metric_ctx_t metric; ecs_id_record_t *idr; /**< Id record for monitored component */ } ecs_id_metric_ctx_t; /** Context for metric that monitors whether entity has pair target */ typedef struct { ecs_metric_ctx_t metric; ecs_id_record_t *idr; /**< Id record for monitored component */ ecs_size_t size; /**< Size of metric type */ ecs_map_t target_offset; /**< Pair target to metric type offset */ } ecs_oneof_metric_ctx_t; /** Context for metric that monitors how many entities have a pair target */ typedef struct { ecs_metric_ctx_t metric; ecs_id_record_t *idr; /**< Id record for monitored component */ ecs_map_t targets; /**< Map of counters for each target */ } ecs_count_targets_metric_ctx_t; /** Stores context shared for all instances of member metric */ typedef struct { ecs_member_metric_ctx_t *ctx; } EcsMetricMember; /** Stores context shared for all instances of id metric */ typedef struct { ecs_id_metric_ctx_t *ctx; } EcsMetricId; /** Stores context shared for all instances of oneof metric */ typedef struct { ecs_oneof_metric_ctx_t *ctx; } EcsMetricOneOf; /** Stores context shared for all instances of id counter metric */ typedef struct { ecs_id_t id; } EcsMetricCountIds; /** Stores context shared for all instances of target counter metric */ typedef struct { ecs_count_targets_metric_ctx_t *ctx; } EcsMetricCountTargets; /** Instance of member metric */ typedef struct { ecs_ref_t ref; ecs_member_metric_ctx_t *ctx; } EcsMetricMemberInstance; /** Instance of id metric */ typedef struct { ecs_record_t *r; ecs_id_metric_ctx_t *ctx; } EcsMetricIdInstance; /** Instance of oneof metric */ typedef struct { ecs_record_t *r; ecs_oneof_metric_ctx_t *ctx; } EcsMetricOneOfInstance; /** Component lifecycle */ static ECS_DTOR(EcsMetricMember, ptr, { ecs_os_free(ptr->ctx); }) static ECS_MOVE(EcsMetricMember, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricId, ptr, { ecs_os_free(ptr->ctx); }) static ECS_MOVE(EcsMetricId, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricOneOf, ptr, { if (ptr->ctx) { ecs_map_fini(&ptr->ctx->target_offset); ecs_os_free(ptr->ctx); } }) static ECS_MOVE(EcsMetricOneOf, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricCountTargets, ptr, { if (ptr->ctx) { ecs_map_fini(&ptr->ctx->targets); ecs_os_free(ptr->ctx); } }) static ECS_MOVE(EcsMetricCountTargets, dst, src, { *dst = *src; src->ctx = NULL; }) /** Observer used for creating new instances of member metric */ static void flecs_metrics_on_member_metric(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_member_metric_ctx_t *ctx = it->ctx; ecs_id_t id = ecs_field_id(it, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricMemberInstance *src = ecs_emplace( world, m, EcsMetricMemberInstance); src->ref = ecs_ref_init_id(world, e, id); src->ctx = ctx; ecs_modified(world, m, EcsMetricMemberInstance); ecs_set(world, m, EcsMetricValue, { 0 }); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Observer used for creating new instances of id metric */ static void flecs_metrics_on_id_metric(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_id_metric_ctx_t *ctx = it->ctx; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricIdInstance *src = ecs_emplace(world, m, EcsMetricIdInstance); src->r = ecs_record_find(world, e); src->ctx = ctx; ecs_modified(world, m, EcsMetricIdInstance); ecs_set(world, m, EcsMetricValue, { 0 }); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Observer used for creating new instances of oneof metric */ static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { if (it->event == EcsOnRemove) { return; } ecs_world_t *world = it->world; ecs_oneof_metric_ctx_t *ctx = it->ctx; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricOneOfInstance *src = ecs_emplace(world, m, EcsMetricOneOfInstance); src->r = ecs_record_find(world, e); src->ctx = ctx; ecs_modified(world, m, EcsMetricOneOfInstance); ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue)); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Set doc name of metric instance to name of source entity */ #ifdef FLECS_DOC static void SetMetricDocName(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t src_e = src[i].entity; const char *name = ecs_get_name(world, src_e); if (name) { ecs_doc_set_name(world, it->entities[i], name); } } } #endif /** Delete metric instances for entities that are no longer alive */ static void ClearMetricInstance(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMetricSource *src = ecs_field(it, EcsMetricSource, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t src_e = src[i].entity; if (!ecs_is_alive(world, src_e)) { ecs_delete(world, it->entities[i]); } } } /** Update member metric */ static void UpdateMemberInstance(ecs_iter_t *it, bool counter) { ecs_world_t *world = it->real_world; EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1); EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 2); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_member_metric_ctx_t *ctx = mi[i].ctx; ecs_ref_t *ref = &mi[i].ref; const void *ptr = ecs_ref_get_id(world, ref, ref->id); if (ptr) { ptr = ECS_OFFSET(ptr, ctx->offset); if (!counter) { m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr); } else { m[i].value += ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; } } else { ecs_delete(it->world, it->entities[i]); } } } static void UpdateGaugeMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, false); } static void UpdateCounterMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, false); } static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, true); } /** Update id metric */ static void UpdateIdInstance(ecs_iter_t *it, bool counter) { ecs_world_t *world = it->real_world; EcsMetricValue *m = ecs_field(it, EcsMetricValue, 1); EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 2); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_table_t *table = mi[i].r->table; if (!table) { ecs_delete(it->world, it->entities[i]); continue; } ecs_id_metric_ctx_t *ctx = mi[i].ctx; ecs_id_record_t *idr = ctx->idr; if (flecs_search_w_idr(world, table, idr->id, NULL, idr) != -1) { if (!counter) { m[i].value = 1.0; } else { m[i].value += 1.0 * (double)dt; } } else { ecs_delete(it->world, it->entities[i]); } } } static void UpdateGaugeIdInstance(ecs_iter_t *it) { UpdateIdInstance(it, false); } static void UpdateCounterIdInstance(ecs_iter_t *it) { UpdateIdInstance(it, true); } /** Update oneof metric */ static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { ecs_world_t *world = it->real_world; ecs_table_t *table = it->table; void *m = ecs_table_get_column(table, ecs_table_type_to_column_index(table, it->columns[0] - 1), it->offset); EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 2); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; ecs_table_t *mtable = mi[i].r->table; double *value = ECS_ELEM(m, ctx->size, i); if (!counter) { ecs_os_memset(value, 0, ctx->size); } if (!mtable) { ecs_delete(it->world, it->entities[i]); continue; } ecs_id_record_t *idr = ctx->idr; ecs_id_t id; if (flecs_search_w_idr(world, mtable, idr->id, &id, idr) == -1) { ecs_delete(it->world, it->entities[i]); continue; } ecs_entity_t tgt = ECS_PAIR_SECOND(id); uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt); if (!offset) { ecs_err("unexpected relationship target for metric"); continue; } value = ECS_OFFSET(value, *offset); if (!counter) { *value = 1.0; } else { *value += 1.0 * (double)dt; } } } static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { UpdateOneOfInstance(it, false); } static void UpdateCounterOneOfInstance(ecs_iter_t *it) { UpdateOneOfInstance(it, true); } static void UpdateCountTargets(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_count_targets_metric_ctx_t *ctx = m[i].ctx; ecs_id_record_t *cur = ctx->idr; while ((cur = cur->first.next)) { ecs_id_t id = cur->id; ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); if (!mi[0]) { mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); ecs_entity_t tgt = ecs_pair_second(world, cur->id); const char *name = ecs_get_name(world, tgt); if (name) { ecs_set_name(world, mi[0], name); } EcsMetricSource *source = ecs_get_mut( world, mi[0], EcsMetricSource); source->entity = tgt; } EcsMetricValue *value = ecs_get_mut(world, mi[0], EcsMetricValue); value->value += (double)ecs_count_id(world, cur->id) * (double)it->delta_system_time; } } } static void UpdateCountIds(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 1); EcsMetricValue *v = ecs_field(it, EcsMetricValue, 2); int32_t i, count = it->count; for (i = 0; i < count; i ++) { v[i].value += (double)ecs_count_id(world, m[i].id) * (double)it->delta_system_time; } } /** Initialize member metric */ static int flecs_member_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { const EcsMember *m = ecs_get(world, desc->member, EcsMember); if (!m) { char *metric_name = ecs_get_fullpath(world, metric); char *member_name = ecs_get_fullpath(world, desc->member); ecs_err("entity '%s' provided for metric '%s' is not a member", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } const EcsPrimitive *p = ecs_get(world, m->type, EcsPrimitive); if (!p) { char *metric_name = ecs_get_fullpath(world, metric); char *member_name = ecs_get_fullpath(world, desc->member); ecs_err("member '%s' provided for metric '%s' must have primitive type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } ecs_entity_t type = ecs_get_parent(world, desc->member); if (!type) { char *metric_name = ecs_get_fullpath(world, metric); char *member_name = ecs_get_fullpath(world, desc->member); ecs_err("member '%s' provided for metric '%s' is not part of a type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } const EcsMetaType *mt = ecs_get(world, type, EcsMetaType); if (!mt) { char *metric_name = ecs_get_fullpath(world, metric); char *member_name = ecs_get_fullpath(world, desc->member); ecs_err("parent of member '%s' for metric '%s' is not a type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } if (mt->kind != EcsStructType) { char *metric_name = ecs_get_fullpath(world, metric); char *member_name = ecs_get_fullpath(world, desc->member); ecs_err("parent of member '%s' for metric '%s' is not a struct", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->type_kind = p->kind; ctx->offset = flecs_ito(uint16_t, m->offset); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .filter.terms[0] = { .id = type, .src.flags = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_member_metric, .yield_existing = true, .ctx = ctx }); ecs_set_pair(world, metric, EcsMetricMember, desc->member, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** Update id metric */ static int flecs_id_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .filter.terms[0] = { .id = desc->id, .src.flags = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_id_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** Update oneof metric */ static int flecs_oneof_metric_init( ecs_world_t *world, ecs_entity_t metric, ecs_entity_t scope, const ecs_metric_desc_t *desc) { ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_map_init(&ctx->target_offset, NULL); /* Add member for each child of oneof to metric, so it can be used as metric * instance type that holds values for all targets */ ecs_iter_t it = ecs_children(world, scope); uint64_t offset = 0; while (ecs_children_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_entity_t tgt = it.entities[i]; const char *name = ecs_get_name(world, tgt); if (!name) { /* Member must have name */ continue; } char *to_snake_case = flecs_to_snake_case(name); ecs_entity_t mbr = ecs_entity(world, { .name = to_snake_case, .add = { ecs_childof(metric) } }); ecs_os_free(to_snake_case); ecs_set(world, mbr, EcsMember, { .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }); /* Truncate upper 32 bits of target so we can lookup the offset * with the id we get from the pair */ ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; offset += sizeof(double); } } ctx->size = flecs_uto(ecs_size_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsMonitor }, .filter.terms[0] = { .id = desc->id, .src.flags = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_oneof_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_id_targets_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_map_init(&ctx->targets, NULL); ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_ids_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); ecs_set(world, metric, EcsMetricValue, { .value = 0 }); return 0; } ecs_entity_t ecs_metric_init( ecs_world_t *world, const ecs_metric_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_poly_assert(world, ecs_world_t); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new_id(world); } ecs_entity_t kind = desc->kind; if (!kind) { ecs_err("missing metric kind"); goto error; } if (kind != EcsGauge && kind != EcsCounter && kind != EcsCounterId && kind != EcsCounterIncrement) { ecs_err("invalid metric kind %s", ecs_get_fullpath(world, kind)); goto error; } if (kind == EcsCounterIncrement && !desc->member) { ecs_err("CounterIncrement can only be used in combination with member"); goto error; } if (kind == EcsCounterId && desc->member) { ecs_err("CounterIncrement 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) { if (desc->id) { ecs_err("cannot specify both member and id for metric"); goto error; } if (flecs_member_metric_init(world, result, desc)) { goto error; } } else if (desc->id) { if (desc->targets) { if (!ecs_id_is_pair(desc->id)) { ecs_err("cannot specify targets for id that is not a pair"); goto error; } if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { ecs_err("first element of pair cannot be wildcard with " " targets enabled"); goto error; } if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { ecs_err("second element of pair must be wildcard with " " targets enabled"); goto error; } if (kind == EcsCounterId) { if (flecs_count_id_targets_metric_init(world, result, desc)) { goto error; } } else { ecs_entity_t first = ecs_pair_first(world, desc->id); ecs_entity_t scope = flecs_get_oneof(world, first); if (!scope) { ecs_err("first element of pair must have OneOf with " " targets enabled"); goto error; } if (flecs_oneof_metric_init(world, result, scope, desc)) { goto error; } } } else { if (kind == EcsCounterId) { if (flecs_count_ids_metric_init(world, result, desc)) { goto error; } } else { if (flecs_id_metric_init(world, result, desc)) { goto error; } } } } else { ecs_err("missing source specified for metric"); goto error; } return result; error: if (result && result != desc->entity) { ecs_delete(world, result); } return 0; } void FlecsMetricsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMetrics); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); ECS_TAG_DEFINE(world, EcsMetric); ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); ECS_TAG_DEFINE(world, EcsCounter); ECS_TAG_DEFINE(world, EcsCounterIncrement); ECS_TAG_DEFINE(world, EcsCounterId); ECS_TAG_DEFINE(world, EcsGauge); ecs_set_scope(world, old_scope); ecs_set_name_prefix(world, "EcsMetric"); ECS_TAG_DEFINE(world, EcsMetricInstance); ECS_COMPONENT_DEFINE(world, EcsMetricValue); ECS_COMPONENT_DEFINE(world, EcsMetricSource); ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); ECS_COMPONENT_DEFINE(world, EcsMetricMember); ECS_COMPONENT_DEFINE(world, EcsMetricId); ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); ecs_struct(world, { .entity = ecs_id(EcsMetricValue), .members = { { .name = "value", .type = ecs_id(ecs_f64_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsMetricSource), .members = { { .name = "entity", .type = ecs_id(ecs_entity_t) } } }); ecs_set_hooks(world, EcsMetricMember, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsMetricMember), .move = ecs_move(EcsMetricMember) }); ecs_set_hooks(world, EcsMetricId, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsMetricId), .move = ecs_move(EcsMetricId) }); ecs_set_hooks(world, EcsMetricOneOf, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsMetricOneOf), .move = ecs_move(EcsMetricOneOf) }); ecs_set_hooks(world, EcsMetricCountTargets, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsMetricCountTargets), .move = ecs_move(EcsMetricCountTargets) }); ecs_add_id(world, EcsMetric, EcsOneOf); #ifdef FLECS_DOC ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, EcsMetricSource); #endif ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, [in] Source); ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, CounterIncrement)); ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, [out] Value, [in] IdInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, [inout] Value, [in] IdInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, [inout] CountIds, Value); ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, [inout] CountTargets); } #endif /** * @file addons/module.c * @brief Module addon. */ #ifdef FLECS_MODULE #include char* ecs_module_path_from_c( const char *c_name) { ecs_strbuf_t str = ECS_STRBUF_INIT; const char *ptr; char ch; for (ptr = c_name; (ch = *ptr); ptr++) { if (isupper(ch)) { ch = flecs_ito(char, tolower(ch)); if (ptr != c_name) { ecs_strbuf_appendstrn(&str, ".", 1); } } ecs_strbuf_appendstrn(&str, &ch, 1); } return ecs_strbuf_get(&str); } ecs_entity_t ecs_import( ecs_world_t *world, ecs_module_action_t module, const char *module_name) { ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = ecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup_fullpath(world, path); ecs_os_free(path); if (!e) { ecs_trace("#[magenta]import#[reset] %s", module_name); ecs_log_push(); /* Load module */ module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup_fullpath(world, module_name); ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); ecs_log_pop(); } /* Restore to previous state */ ecs_set_scope(world, old_scope); world->info.name_prefix = old_name_prefix; return e; error: return 0; } ecs_entity_t ecs_import_c( ecs_world_t *world, ecs_module_action_t module, const char *c_name) { char *name = ecs_module_path_from_c(c_name); ecs_entity_t e = ecs_import(world, module, name); ecs_os_free(name); return e; } ecs_entity_t ecs_import_from_library( ecs_world_t *world, const char *library_name, const char *module_name) { ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); char *import_func = ECS_CONST_CAST(char*, module_name); char *module = ECS_CONST_CAST(char*, module_name); if (!ecs_os_has_modules() || !ecs_os_has_dl()) { ecs_err( "library loading not supported, set module_to_dl, dlopen, dlclose " "and dlproc os API callbacks first"); return 0; } /* If no module name is specified, try default naming convention for loading * the main module from the library */ if (!import_func) { import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); const char *ptr; char ch, *bptr = import_func; bool capitalize = true; for (ptr = library_name; (ch = *ptr); ptr ++) { if (ch == '.') { capitalize = true; } else { if (capitalize) { *bptr = flecs_ito(char, toupper(ch)); bptr ++; capitalize = false; } else { *bptr = flecs_ito(char, tolower(ch)); bptr ++; } } } *bptr = '\0'; module = ecs_os_strdup(import_func); ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcat(bptr, "Import"); } char *library_filename = ecs_os_module_to_dl(library_name); if (!library_filename) { ecs_err("failed to find library file for '%s'", library_name); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("found file '%s' for library '%s'", library_filename, library_name); } ecs_os_dl_t dl = ecs_os_dlopen(library_filename); if (!dl) { ecs_err("failed to load library '%s' ('%s')", library_name, library_filename); ecs_os_free(library_filename); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("library '%s' ('%s') loaded", library_name, library_filename); } ecs_module_action_t action = (ecs_module_action_t) ecs_os_dlproc(dl, import_func); if (!action) { ecs_err("failed to load import function %s from library %s", import_func, library_name); ecs_os_free(library_filename); ecs_os_dlclose(dl); return 0; } else { ecs_trace("found import function '%s' in library '%s' for module '%s'", import_func, library_name, module); } /* Do not free id, as it will be stored as the component identifier */ ecs_entity_t result = ecs_import(world, action, module); if (import_func != module_name) { ecs_os_free(import_func); } if (module != module_name) { ecs_os_free(module); } ecs_os_free(library_filename); return result; error: return 0; } ecs_entity_t ecs_module_init( ecs_world_t *world, const char *c_name, const ecs_component_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_poly_assert(world, ecs_world_t); ecs_entity_t old_scope = ecs_set_scope(world, 0); ecs_entity_t e = desc->entity; if (!e) { char *module_path = ecs_module_path_from_c(c_name); e = ecs_new_from_fullpath(world, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } else if (!ecs_exists(world, e)) { char *module_path = ecs_module_path_from_c(c_name); ecs_ensure(world, e); ecs_add_fullpath(world, e, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } ecs_add_id(world, e, EcsModule); ecs_component_desc_t private_desc = *desc; private_desc.entity = e; if (desc->type.size) { ecs_entity_t result = ecs_component_init(world, &private_desc); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); (void)result; } ecs_set_scope(world, old_scope); return e; error: return 0; } #endif /** * @file addons/monitor.c * @brief Monitor addon. */ #ifdef FLECS_MONITOR ECS_COMPONENT_DECLARE(FlecsMonitor); ECS_COMPONENT_DECLARE(EcsWorldStats); ECS_COMPONENT_DECLARE(EcsWorldSummary); ECS_COMPONENT_DECLARE(EcsPipelineStats); ecs_entity_t EcsPeriod1s = 0; ecs_entity_t EcsPeriod1m = 0; ecs_entity_t EcsPeriod1h = 0; ecs_entity_t EcsPeriod1d = 0; ecs_entity_t EcsPeriod1w = 0; static int32_t flecs_day_interval_count = 24; static int32_t flecs_week_interval_count = 168; static ECS_COPY(EcsPipelineStats, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); }) static ECS_MOVE(EcsPipelineStats, dst, src, { ecs_os_memcpy_t(dst, src, EcsPipelineStats); ecs_os_zeromem(src); }) static ECS_DTOR(EcsPipelineStats, ptr, { ecs_pipeline_stats_fini(&ptr->stats); }) static void UpdateWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 1); const ecs_world_info_t *info = ecs_get_world_info(it->world); int32_t i, count = it->count; for (i = 0; i < count; i ++) { summary[i].target_fps = (double)info->target_fps; summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total; summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total; summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total; summary[i].frame_time_total = (double)info->frame_time_total; summary[i].system_time_total = (double)info->system_time_total; summary[i].merge_time_total = (double)info->merge_time_total; } } static void MonitorStats(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1); ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader); ecs_ftime_t elapsed = hdr->elapsed; hdr->elapsed += it->delta_time; int32_t t_last = (int32_t)(elapsed * 60); int32_t t_next = (int32_t)(hdr->elapsed * 60); int32_t i, dif = t_last - t_next; ecs_world_stats_t last_world = {0}; ecs_pipeline_stats_t last_pipeline = {0}; void *last = NULL; if (!dif) { /* Copy last value so we can pass it to reduce_last */ if (kind == ecs_id(EcsWorldStats)) { last = &last_world; ecs_world_stats_copy_last(&last_world, stats); } else if (kind == ecs_id(EcsPipelineStats)) { last = &last_pipeline; ecs_pipeline_stats_copy_last(&last_pipeline, stats); } } if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_get(world, stats); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats); } if (!dif) { /* Still in same interval, combine with last measurement */ hdr->reduce_count ++; if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce_last(stats, last, hdr->reduce_count); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count); } } else if (dif > 1) { /* More than 16ms has passed, backfill */ for (i = 1; i < dif; i ++) { if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_repeat_last(stats); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_world_stats_repeat_last(stats); } } hdr->reduce_count = 0; } if (last && kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_fini(last); } } static void ReduceStats(ecs_iter_t *it) { void *dst = ecs_field_w_size(it, 0, 1); void *src = ecs_field_w_size(it, 0, 2); ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); dst = ECS_OFFSET_T(dst, EcsStatsHeader); src = ECS_OFFSET_T(src, EcsStatsHeader); if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce(dst, src); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce(dst, src); } } static void AggregateStats(ecs_iter_t *it) { int32_t interval = *(int32_t*)it->ctx; EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); ecs_world_stats_t last_world = {0}; ecs_pipeline_stats_t last_pipeline = {0}; void *last = NULL; if (dst_hdr->reduce_count != 0) { /* Copy last value so we can pass it to reduce_last */ if (kind == ecs_id(EcsWorldStats)) { last_world.t = 0; ecs_world_stats_copy_last(&last_world, dst); last = &last_world; } else if (kind == ecs_id(EcsPipelineStats)) { last_pipeline.t = 0; ecs_pipeline_stats_copy_last(&last_pipeline, dst); last = &last_pipeline; } } /* Reduce from minutes to the current day */ if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce(dst, src); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce(dst, src); } if (dst_hdr->reduce_count != 0) { if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count); } } /* A day has 60 24 minute intervals */ dst_hdr->reduce_count ++; if (dst_hdr->reduce_count >= interval) { dst_hdr->reduce_count = 0; } if (last && kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_fini(last); } } static void flecs_stats_monitor_import( ecs_world_t *world, ecs_id_t kind, size_t size) { ecs_entity_t prev = ecs_set_scope(world, kind); // Called each frame, collects 60 measurements per second ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1s), .src.id = EcsWorld }}, .callback = MonitorStats }); // Called each second, reduces into 60 measurements per minute ecs_entity_t mw1m = ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1s), .src.id = EcsWorld }}, .callback = ReduceStats, .interval = 1.0 }); // Called each minute, reduces into 60 measurements per hour ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1h), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }}, .callback = ReduceStats, .rate = 60, .tick_source = mw1m }); // Called each minute, reduces into 60 measurements per day ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1d), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }}, .callback = AggregateStats, .rate = 60, .tick_source = mw1m, .ctx = &flecs_day_interval_count }); // Called each hour, reduces into 60 measurements per week ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1w), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1h), .src.id = EcsWorld }}, .callback = AggregateStats, .rate = 60, .tick_source = mw1m, .ctx = &flecs_week_interval_count }); ecs_set_scope(world, prev); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL); } static void flecs_world_monitor_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldStats); flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), sizeof(EcsWorldStats)); } static void flecs_pipeline_monitor_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsPipelineStats); ecs_set_hooks(world, EcsPipelineStats, { .ctor = ecs_default_ctor, .copy = ecs_copy(EcsPipelineStats), .move = ecs_move(EcsPipelineStats), .dtor = ecs_dtor(EcsPipelineStats) }); flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), sizeof(EcsPipelineStats)); } void FlecsMonitorImport( ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMonitor); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif ecs_set_name_prefix(world, "Ecs"); EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s"); EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m"); EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h"); EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); ECS_COMPONENT_DEFINE(world, EcsWorldSummary); #ifdef FLECS_META ecs_struct(world, { .entity = ecs_id(EcsWorldSummary), .members = { { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds } } }); #endif ecs_system(world, { .entity = ecs_entity(world, { .name = "UpdateWorldSummary", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms[0] = { .id = ecs_id(EcsWorldSummary) }, .callback = UpdateWorldSummary }); ECS_SYSTEM(world, UpdateWorldSummary, EcsPreFrame, WorldSummary); ecs_set(world, EcsWorld, EcsWorldSummary, {0}); flecs_world_monitor_import(world); flecs_pipeline_monitor_import(world); if (ecs_os_has_time()) { ecs_measure_frame_time(world, true); ecs_measure_system_time(world, true); } } #endif /** * @file addons/parser.c * @brief Parser addon. */ #ifdef FLECS_PARSER #include #define TOK_COLON ':' #define TOK_AND ',' #define TOK_OR "||" #define TOK_NOT '!' #define TOK_OPTIONAL '?' #define TOK_BITWISE_OR '|' #define TOK_BRACKET_OPEN '[' #define TOK_BRACKET_CLOSE ']' #define TOK_SCOPE_OPEN '{' #define TOK_SCOPE_CLOSE '}' #define TOK_VARIABLE '$' #define TOK_PAREN_OPEN '(' #define TOK_PAREN_CLOSE ')' #define TOK_EQ "==" #define TOK_NEQ "!=" #define TOK_MATCH "~=" #define TOK_EXPR_STRING '"' #define TOK_SELF "self" #define TOK_UP "up" #define TOK_DOWN "down" #define TOK_CASCADE "cascade" #define TOK_PARENT "parent" #define TOK_OVERRIDE "OVERRIDE" #define TOK_ROLE_AND "AND" #define TOK_ROLE_OR "OR" #define TOK_ROLE_NOT "NOT" #define TOK_ROLE_TOGGLE "TOGGLE" #define TOK_IN "in" #define TOK_OUT "out" #define TOK_INOUT "inout" #define TOK_INOUT_NONE "none" static const ecs_id_t ECS_OR = (1ull << 59); static const ecs_id_t ECS_NOT = (1ull << 58); #define ECS_MAX_TOKEN_SIZE (256) typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; const char* ecs_parse_ws_eol( const char *ptr) { while (isspace(*ptr)) { ptr ++; } return ptr; } const char* ecs_parse_ws( const char *ptr) { while ((*ptr != '\n') && isspace(*ptr)) { ptr ++; } return ptr; } const char* ecs_parse_digit( const char *ptr, char *token) { char *tptr = token; char ch = ptr[0]; if (!isdigit(ch) && ch != '-') { ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); return NULL; } tptr[0] = ch; tptr ++; ptr ++; for (; (ch = *ptr); ptr ++) { if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { break; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; return ptr; } /* -- Private functions -- */ bool flecs_isident( char ch) { return isalpha(ch) || (ch == '_'); } static bool flecs_valid_identifier_start_char( char ch) { if (ch && (flecs_isident(ch) || (ch == '*') || (ch == '0') || (ch == TOK_VARIABLE) || isdigit(ch))) { return true; } return false; } static bool flecs_valid_token_start_char( char ch) { if ((ch == '"') || (ch == '{') || (ch == '}') || (ch == ',') || (ch == '-') || (ch == '[') || (ch == ']') || (ch == '`') || flecs_valid_identifier_start_char(ch)) { return true; } return false; } static bool flecs_valid_token_char( char ch) { if (ch && (flecs_isident(ch) || isdigit(ch) || ch == '.' || ch == '"')) { return true; } return false; } static bool flecs_valid_operator_char( char ch) { if (ch == TOK_OPTIONAL || ch == TOK_NOT) { return true; } return false; } const char* ecs_parse_token( const char *name, const char *expr, const char *ptr, char *token_out, char delim) { int64_t column = ptr - expr; ptr = ecs_parse_ws(ptr); char *tptr = token_out, ch = ptr[0]; if (!flecs_valid_token_start_char(ch)) { if (ch == '\0' || ch == '\n') { ecs_parser_error(name, expr, column, "unexpected end of expression"); } else { ecs_parser_error(name, expr, column, "invalid start of token '%s'", ptr); } return NULL; } tptr[0] = ch; tptr ++; ptr ++; if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { tptr[0] = 0; return ptr; } int tmpl_nesting = 0; bool in_str = ch == '"'; for (; (ch = *ptr); ptr ++) { if (ch == '<') { tmpl_nesting ++; } else if (ch == '>') { if (!tmpl_nesting) { break; } tmpl_nesting --; } else if (ch == '"') { in_str = !in_str; } else if (!flecs_valid_token_char(ch) && !in_str) { break; } if (delim && (ch == delim)) { break; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; if (tmpl_nesting != 0) { ecs_parser_error(name, expr, column, "identifier '%s' has mismatching < > pairs", ptr); return NULL; } const char *next_ptr = ecs_parse_ws(ptr); if (next_ptr[0] == ':' && next_ptr != ptr) { /* Whitespace between token and : is significant */ ptr = next_ptr - 1; } else { ptr = next_ptr; } return ptr; } const char* ecs_parse_identifier( const char *name, const char *expr, const char *ptr, char *token_out) { if (!flecs_valid_identifier_start_char(ptr[0]) && (ptr[0] != '"')) { ecs_parser_error(name, expr, (ptr - expr), "expected start of identifier"); return NULL; } ptr = ecs_parse_token(name, expr, ptr, token_out, 0); return ptr; } static int flecs_parse_identifier( const char *token, ecs_term_id_t *out) { const char *tptr = token; if (tptr[0] == TOK_VARIABLE && tptr[1]) { out->flags |= EcsIsVariable; tptr ++; } if (tptr[0] == TOK_EXPR_STRING && tptr[1]) { out->flags |= EcsIsName; tptr ++; if (tptr[0] == TOK_NOT) { /* Already parsed */ tptr ++; } } char *name = ecs_os_strdup(tptr); out->name = name; ecs_size_t len = ecs_os_strlen(name); if (out->flags & EcsIsName) { if (name[len - 1] != TOK_EXPR_STRING) { ecs_parser_error(NULL, token, 0, "missing '\"' at end of string"); return -1; } else { name[len - 1] = '\0'; } } return 0; } static ecs_entity_t flecs_parse_role( const char *name, const char *sig, int64_t column, const char *token) { if (!ecs_os_strcmp(token, TOK_ROLE_AND)) { return ECS_AND; } else if (!ecs_os_strcmp(token, TOK_ROLE_OR)) { return ECS_OR; } else if (!ecs_os_strcmp(token, TOK_ROLE_NOT)) { return ECS_NOT; } else if (!ecs_os_strcmp(token, TOK_OVERRIDE)) { return ECS_OVERRIDE; } else if (!ecs_os_strcmp(token, TOK_ROLE_TOGGLE)) { return ECS_TOGGLE; } else { ecs_parser_error(name, sig, column, "invalid role '%s'", token); return 0; } } static ecs_oper_kind_t flecs_parse_operator( char ch) { if (ch == TOK_OPTIONAL) { return EcsOptional; } else if (ch == TOK_NOT) { return EcsNot; } else { ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } static const char* flecs_parse_annotation( const char *name, const char *sig, int64_t column, const char *ptr, ecs_inout_kind_t *inout_kind_out) { char token[ECS_MAX_TOKEN_SIZE]; ptr = ecs_parse_identifier(name, sig, ptr, token); if (!ptr) { return NULL; } if (!ecs_os_strcmp(token, TOK_IN)) { *inout_kind_out = EcsIn; } else if (!ecs_os_strcmp(token, TOK_OUT)) { *inout_kind_out = EcsOut; } else if (!ecs_os_strcmp(token, TOK_INOUT)) { *inout_kind_out = EcsInOut; } else if (!ecs_os_strcmp(token, TOK_INOUT_NONE)) { *inout_kind_out = EcsInOutNone; } ptr = ecs_parse_ws(ptr); if (ptr[0] != TOK_BRACKET_CLOSE) { ecs_parser_error(name, sig, column, "expected ]"); return NULL; } return ptr + 1; } static uint8_t flecs_parse_set_token( const char *token) { if (!ecs_os_strcmp(token, TOK_SELF)) { return EcsSelf; } else if (!ecs_os_strcmp(token, TOK_UP)) { return EcsUp; } else if (!ecs_os_strcmp(token, TOK_DOWN)) { return EcsDown; } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { return EcsCascade; } else if (!ecs_os_strcmp(token, TOK_PARENT)) { return EcsParent; } else { return 0; } } static const char* flecs_parse_term_flags( const ecs_world_t *world, const char *name, const char *expr, int64_t column, const char *ptr, char *token, ecs_term_id_t *id, char tok_end) { char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; if (!token) { token = token_buf; ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } } do { uint8_t tok = flecs_parse_set_token(token); if (!tok) { ecs_parser_error(name, expr, column, "invalid set token '%s'", token); return NULL; } if (id->flags & tok) { ecs_parser_error(name, expr, column, "duplicate set token '%s'", token); return NULL; } id->flags |= tok; if (ptr[0] == TOK_PAREN_OPEN) { ptr ++; /* Relationship (overrides IsA default) */ if (!isdigit(ptr[0]) && flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } id->trav = ecs_lookup_fullpath(world, token); if (!id->trav) { ecs_parser_error(name, expr, column, "unresolved identifier '%s'", token); return NULL; } if (ptr[0] == TOK_AND) { ptr = ecs_parse_ws(ptr + 1); } else if (ptr[0] != TOK_PAREN_CLOSE) { ecs_parser_error(name, expr, column, "expected ',' or ')'"); return NULL; } } if (ptr[0] != TOK_PAREN_CLOSE) { ecs_parser_error(name, expr, column, "expected ')', got '%c'", ptr[0]); return NULL; } else { ptr = ecs_parse_ws(ptr + 1); if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { ecs_parser_error(name, expr, column, "expected end of set expr"); return NULL; } } } /* Next token in set expression */ if (ptr[0] == TOK_BITWISE_OR) { ptr ++; if (flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } } /* End of set expression */ } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { break; } } while (true); return ptr; } static const char* flecs_parse_arguments( const ecs_world_t *world, const char *name, const char *expr, int64_t column, const char *ptr, char *token, ecs_term_t *term) { (void)column; int32_t arg = 0; do { if (flecs_valid_token_start_char(ptr[0])) { if (arg == 2) { ecs_parser_error(name, expr, (ptr - expr), "too many arguments in term"); return NULL; } ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } ecs_term_id_t *term_id = NULL; if (arg == 0) { term_id = &term->src; } else if (arg == 1) { term_id = &term->second; } /* If token is a colon, the token is an identifier followed by a * set expression. */ if (ptr[0] == TOK_COLON) { if (flecs_parse_identifier(token, term_id)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); return NULL; } ptr = ecs_parse_ws(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, term_id, TOK_PAREN_CLOSE); if (!ptr) { return NULL; } /* Check for term flags */ } else if (!ecs_os_strcmp(token, TOK_CASCADE) || !ecs_os_strcmp(token, TOK_SELF) || !ecs_os_strcmp(token, TOK_UP) || !ecs_os_strcmp(token, TOK_DOWN) || !(ecs_os_strcmp(token, TOK_PARENT))) { ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, token, term_id, TOK_PAREN_CLOSE); if (!ptr) { return NULL; } /* Regular identifier */ } else if (flecs_parse_identifier(token, term_id)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); return NULL; } if (ptr[0] == TOK_AND) { ptr = ecs_parse_ws(ptr + 1); term->id_flags = ECS_PAIR; } else if (ptr[0] == TOK_PAREN_CLOSE) { ptr = ecs_parse_ws(ptr + 1); break; } else { ecs_parser_error(name, expr, (ptr - expr), "expected ',' or ')'"); return NULL; } } else { ecs_parser_error(name, expr, (ptr - expr), "expected identifier or set expression"); return NULL; } arg ++; } while (true); return ptr; } static void flecs_parser_unexpected_char( const char *name, const char *expr, const char *ptr, char ch) { if (ch && (ch != '\n')) { ecs_parser_error(name, expr, (ptr - expr), "unexpected character '%c'", ch); } else { ecs_parser_error(name, expr, (ptr - expr), "unexpected end of term"); } } static const char* flecs_parse_term( const ecs_world_t *world, const char *name, const char *expr, ecs_term_t *term_out) { const char *ptr = expr; char token[ECS_MAX_TOKEN_SIZE] = {0}; ecs_term_t term = { .move = true /* parser never owns resources */ }; ptr = ecs_parse_ws(ptr); /* Inout specifiers always come first */ if (ptr[0] == TOK_BRACKET_OPEN) { ptr = flecs_parse_annotation(name, expr, (ptr - expr), ptr + 1, &term.inout); if (!ptr) { goto error; } ptr = ecs_parse_ws(ptr); } if (flecs_valid_operator_char(ptr[0])) { term.oper = flecs_parse_operator(ptr[0]); ptr = ecs_parse_ws(ptr + 1); } /* If next token is the start of an identifier, it could be either a type * role, source or component identifier */ if (flecs_valid_identifier_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { goto error; } /* Is token a type role? */ if (ptr[0] == TOK_BITWISE_OR && ptr[1] != TOK_BITWISE_OR) { ptr ++; goto flecs_parse_role; } /* Is token a predicate? */ if (ptr[0] == TOK_PAREN_OPEN) { goto parse_predicate; } /* Next token must be a predicate */ goto parse_predicate; /* Pair with implicit subject */ } else if (ptr[0] == TOK_PAREN_OPEN) { goto parse_pair; /* Open query scope */ } else if (ptr[0] == TOK_SCOPE_OPEN) { term.first.id = EcsScopeOpen; term.src.id = 0; term.src.flags = EcsIsEntity; term.inout = EcsInOutNone; goto parse_done; /* Close query scope */ } else if (ptr[0] == TOK_SCOPE_CLOSE) { term.first.id = EcsScopeClose; term.src.id = 0; term.src.flags = EcsIsEntity; term.inout = EcsInOutNone; ptr = ecs_parse_ws(ptr + 1); goto parse_done; /* Nothing else expected here */ } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); goto error; } flecs_parse_role: term.id_flags = flecs_parse_role(name, expr, (ptr - expr), token); if (!term.id_flags) { goto error; } ptr = ecs_parse_ws(ptr); /* If next token is the source token, this is an empty source */ if (flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { goto error; } /* If not, it's a predicate */ goto parse_predicate; } else if (ptr[0] == TOK_PAREN_OPEN) { goto parse_pair; } else { ecs_parser_error(name, expr, (ptr - expr), "expected identifier after role"); goto error; } parse_predicate: if (flecs_parse_identifier(token, &term.first)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); goto error; } /* Set expression */ if (ptr[0] == TOK_COLON) { ptr = ecs_parse_ws(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, &term.first, TOK_COLON); if (!ptr) { goto error; } ptr = ecs_parse_ws(ptr); if (ptr[0] == TOK_AND || !ptr[0]) { goto parse_done; } if (ptr[0] != TOK_COLON) { ecs_parser_error(name, expr, (ptr - expr), "unexpected token '%c' after predicate set expression", ptr[0]); goto error; } ptr = ecs_parse_ws(ptr + 1); } else if (!ecs_os_strncmp(ptr, TOK_EQ, 2)) { ptr = ecs_parse_ws(ptr + 2); goto parse_eq; } else if (!ecs_os_strncmp(ptr, TOK_NEQ, 2)) { ptr = ecs_parse_ws(ptr + 2); goto parse_neq; } else if (!ecs_os_strncmp(ptr, TOK_MATCH, 2)) { ptr = ecs_parse_ws(ptr + 2); goto parse_match; } else { ptr = ecs_parse_ws(ptr); } if (ptr[0] == TOK_PAREN_OPEN) { ptr ++; if (ptr[0] == TOK_PAREN_CLOSE) { term.src.flags = EcsIsEntity; term.src.id = 0; ptr ++; ptr = ecs_parse_ws(ptr); } else { ptr = flecs_parse_arguments( world, name, expr, (ptr - expr), ptr, token, &term); } goto parse_done; } goto parse_done; parse_eq: term.src = term.first; term.first = (ecs_term_id_t){0}; term.first.id = EcsPredEq; goto parse_right_operand; parse_neq: term.src = term.first; term.first = (ecs_term_id_t){0}; term.first.id = EcsPredEq; if (term.oper != EcsAnd) { ecs_parser_error(name, expr, (ptr - expr), "invalid operator combination"); goto error; } term.oper = EcsNot; goto parse_right_operand; parse_match: term.src = term.first; term.first = (ecs_term_id_t){0}; term.first.id = EcsPredMatch; goto parse_right_operand; parse_right_operand: if (flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { goto error; } if (term.first.id == EcsPredMatch) { if (token[0] == '"' && token[1] == '!') { term.oper = EcsNot; } } if (flecs_parse_identifier(token, &term.second)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); goto error; } term.src.flags &= ~EcsTraverseFlags; term.src.flags |= EcsSelf; term.inout = EcsInOutNone; } else { ecs_parser_error(name, expr, (ptr - expr), "expected identifier"); goto error; } goto parse_done; parse_pair: ptr = ecs_parse_identifier(name, expr, ptr + 1, token); if (!ptr) { goto error; } if (ptr[0] == TOK_COLON) { ptr = ecs_parse_ws(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, &term.first, TOK_PAREN_CLOSE); if (!ptr) { goto error; } } if (ptr[0] == TOK_AND) { ptr = ecs_parse_ws(ptr + 1); if (ptr[0] == TOK_PAREN_CLOSE) { ecs_parser_error(name, expr, (ptr - expr), "expected identifier for second element of pair"); goto error; } term.src.id = EcsThis; term.src.flags |= EcsIsVariable; goto parse_pair_predicate; } else if (ptr[0] == TOK_PAREN_CLOSE) { term.src.id = EcsThis; term.src.flags |= EcsIsVariable; goto parse_pair_predicate; } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); goto error; } parse_pair_predicate: if (flecs_parse_identifier(token, &term.first)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); goto error; } ptr = ecs_parse_ws(ptr); if (flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { goto error; } if (ptr[0] == TOK_COLON) { ptr = ecs_parse_ws(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, &term.second, TOK_PAREN_CLOSE); if (!ptr) { goto error; } } if (ptr[0] == TOK_PAREN_CLOSE) { ptr ++; goto parse_pair_object; } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); goto error; } } else if (ptr[0] == TOK_PAREN_CLOSE) { /* No object */ ptr ++; goto parse_done; } else { ecs_parser_error(name, expr, (ptr - expr), "expected pair object or ')'"); goto error; } parse_pair_object: if (flecs_parse_identifier(token, &term.second)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); goto error; } if (term.id_flags == 0) { term.id_flags = ECS_PAIR; } ptr = ecs_parse_ws(ptr); goto parse_done; parse_done: *term_out = term; return ptr; error: ecs_term_fini(&term); *term_out = (ecs_term_t){0}; return NULL; } static bool flecs_is_valid_end_of_term( const char *ptr) { if ((ptr[0] == TOK_AND) || /* another term with And operator */ (ptr[0] == TOK_OR[0]) || /* another term with Or operator */ (ptr[0] == '\n') || /* newlines are valid */ (ptr[0] == '\0') || /* end of string */ (ptr[0] == '/') || /* comment (in plecs) */ (ptr[0] == '{') || /* scope (in plecs) */ (ptr[0] == '}') || (ptr[0] == ':') || /* inheritance (in plecs) */ (ptr[0] == '=')) /* assignment (in plecs) */ { return true; } return false; } char* ecs_parse_term( const ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_term_t *term) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); ecs_term_id_t *src = &term->src; if (ptr != expr) { if (ptr[0]) { if (ptr[0] == ',') { ptr ++; } else if (ptr[0] == '|') { ptr += 2; } else if (ptr[0] == '{') { ptr ++; } else if (ptr[0] == '}') { /* nothing to be done */ } else { ecs_parser_error(name, expr, (ptr - expr), "invalid preceding token"); } } } ptr = ecs_parse_ws_eol(ptr); if (!ptr[0]) { *term = (ecs_term_t){0}; return ECS_CONST_CAST(char*, ptr); } if (ptr == expr && !strcmp(expr, "0")) { return ECS_CONST_CAST(char*, &ptr[1]); } /* Parse next element */ ptr = flecs_parse_term(world, name, ptr, term); if (!ptr) { goto error; } /* Check for $() notation */ if (term->first.name && !ecs_os_strcmp(term->first.name, "$")) { if (term->src.name) { /* Safe, parser owns name */ ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); term->first = term->src; if (term->second.name) { term->src = term->second; } else { term->src.id = EcsThis; term->src.name = NULL; term->src.flags |= EcsIsVariable; } term->second.name = ecs_os_strdup(term->first.name); term->second.flags |= EcsIsVariable; } } /* Post-parse consistency checks */ /* If next token is OR, term is part of an OR expression */ if (!ecs_os_strncmp(ptr, TOK_OR, 2)) { /* An OR operator must always follow an AND or another OR */ if (term->oper != EcsAnd) { ecs_parser_error(name, expr, (ptr - expr), "cannot combine || with other operators"); goto error; } term->oper = EcsOr; } /* Term must either end in end of expression, AND or OR token */ if (!flecs_is_valid_end_of_term(ptr)) { if (!flecs_isident(ptr[0]) || ((ptr != expr) && (ptr[-1] != ' '))) { ecs_parser_error(name, expr, (ptr - expr), "expected end of expression or next term"); goto error; } } /* If the term just contained a 0, the expression has nothing. Ensure * that after the 0 nothing else follows */ if (term->first.name && !ecs_os_strcmp(term->first.name, "0")) { if (ptr[0]) { ecs_parser_error(name, expr, (ptr - expr), "unexpected term after 0"); goto error; } if (src->flags != 0) { ecs_parser_error(name, expr, (ptr - expr), "invalid combination of 0 with non-default subject"); goto error; } src->flags = EcsIsEntity; src->id = 0; /* Safe, parser owns string */ ecs_os_free(ECS_CONST_CAST(char*, term->first.name)); term->first.name = NULL; } /* Cannot combine EcsIsEntity/0 with operators other than AND */ if (term->oper != EcsAnd && ecs_term_match_0(term)) { if (term->first.id != EcsScopeOpen && term->first.id != EcsScopeClose) { ecs_parser_error(name, expr, (ptr - expr), "invalid operator for empty source"); goto error; } } /* Automatically assign This if entity is not assigned and the set is * nothing */ if (!(src->flags & EcsIsEntity)) { if (!src->name) { if (!src->id) { src->id = EcsThis; src->flags |= EcsIsVariable; } } } if (src->name && !ecs_os_strcmp(src->name, "0")) { src->id = 0; src->flags = EcsIsEntity; } /* Process role */ if (term->id_flags == ECS_AND) { term->oper = EcsAndFrom; term->id_flags = 0; } else if (term->id_flags == ECS_OR) { term->oper = EcsOrFrom; term->id_flags = 0; } else if (term->id_flags == ECS_NOT) { term->oper = EcsNotFrom; term->id_flags = 0; } ptr = ecs_parse_ws(ptr); return ECS_CONST_CAST(char*, ptr); error: if (term) { ecs_term_fini(term); } return NULL; } #endif /** * @file addons/plecs.c * @brief Plecs addon. */ #ifdef FLECS_PLECS ECS_COMPONENT_DECLARE(EcsScript); #include #define TOK_NEWLINE '\n' #define TOK_USING "using" #define TOK_MODULE "module" #define TOK_WITH "with" #define TOK_CONST "const" #define TOK_PROP "prop" #define TOK_ASSEMBLY "assembly" #define STACK_MAX_SIZE (64) typedef struct { ecs_value_t value; bool owned; } plecs_with_value_t; typedef struct { const char *name; const char *code; ecs_entity_t last_predicate; ecs_entity_t last_subject; ecs_entity_t last_object; ecs_id_t last_assign_id; ecs_entity_t assign_to; ecs_entity_t scope[STACK_MAX_SIZE]; ecs_entity_t default_scope_type[STACK_MAX_SIZE]; ecs_entity_t with[STACK_MAX_SIZE]; ecs_entity_t using[STACK_MAX_SIZE]; int32_t with_frames[STACK_MAX_SIZE]; plecs_with_value_t with_value_frames[STACK_MAX_SIZE]; int32_t using_frames[STACK_MAX_SIZE]; int32_t sp; int32_t with_frame; int32_t using_frame; ecs_entity_t global_with; ecs_entity_t assembly; const char *assembly_start, *assembly_stop; char *annot[STACK_MAX_SIZE]; int32_t annot_count; ecs_vars_t vars; char var_name[256]; ecs_entity_t var_type; bool with_stmt; bool scope_assign_stmt; bool assign_stmt; bool assembly_stmt; bool assembly_instance; bool isa_stmt; bool decl_stmt; bool decl_type; bool var_stmt; bool var_is_prop; bool is_module; int32_t errors; } plecs_state_t; static int flecs_plecs_parse( ecs_world_t *world, const char *name, const char *expr, ecs_vars_t *vars, ecs_entity_t script, ecs_entity_t instance); static void flecs_dtor_script(EcsScript *ptr) { ecs_os_free(ptr->script); ecs_vec_fini_t(NULL, &ptr->using_, ecs_entity_t); int i, count = ptr->prop_defaults.count; ecs_value_t *values = ptr->prop_defaults.array; for (i = 0; i < count; i ++) { ecs_value_free(ptr->world, values[i].type, values[i].ptr); } ecs_vec_fini_t(NULL, &ptr->prop_defaults, ecs_value_t); } static ECS_MOVE(EcsScript, dst, src, { flecs_dtor_script(dst); dst->using_ = src->using_; dst->prop_defaults = src->prop_defaults; dst->script = src->script; dst->world = src->world; ecs_os_zeromem(&src->using_); ecs_os_zeromem(&src->prop_defaults); src->script = NULL; src->world = NULL; }) static ECS_DTOR(EcsScript, ptr, { flecs_dtor_script(ptr); }) /* Assembly ctor to initialize with default property values */ static void flecs_assembly_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.ctx; ecs_entity_t assembly = ti->component; const EcsStruct *st = ecs_get(world, assembly, EcsStruct); if (!st) { ecs_err("assembly '%s' is not a struct, cannot construct", ti->name); return; } const EcsScript *script = ecs_get(world, assembly, EcsScript); if (!script) { ecs_err("assembly '%s' is not a script, cannot construct", ti->name); return; } if (st->members.count != script->prop_defaults.count) { ecs_err("number of props (%d) of assembly '%s' does not match members" " (%d), cannot construct", script->prop_defaults.count, ti->name, st->members.count); return; } const ecs_member_t *members = st->members.array; int32_t i, m, member_count = st->members.count; ecs_value_t *values = script->prop_defaults.array; for (m = 0; m < member_count; m ++) { const ecs_member_t *member = &members[m]; ecs_value_t *value = &values[m]; const ecs_type_info_t *mti = ecs_get_type_info(world, member->type); if (!mti) { ecs_err("failed to get type info for prop '%s' of assembly '%s'", member->name, ti->name); return; } for (i = 0; i < count; i ++) { void *el = ECS_ELEM(ptr, ti->size, i); ecs_value_copy_w_type_info(world, mti, ECS_OFFSET(el, member->offset), value->ptr); } } } /* Assembly on_set handler to update contents for new property values */ static void flecs_assembly_on_set( ecs_iter_t *it) { if (it->table->flags & EcsTableIsPrefab) { /* Don't instantiate assemblies for prefabs */ return; } ecs_world_t *world = it->world; ecs_entity_t assembly = ecs_field_id(it, 1); const char *name = ecs_get_name(world, assembly); ecs_record_t *r = ecs_record_find(world, assembly); const EcsComponent *ct = ecs_record_get(world, r, EcsComponent); ecs_get(world, assembly, EcsComponent); if (!ct) { ecs_err("assembly '%s' is not a component", name); return; } const EcsStruct *st = ecs_record_get(world, r, EcsStruct); if (!st) { ecs_err("assembly '%s' is not a struct", name); return; } const EcsScript *script = ecs_record_get(world, r, EcsScript); if (!script) { ecs_err("assembly '%s' is missing a script", name); return; } void *data = ecs_field_w_size(it, flecs_ito(size_t, ct->size), 1); int32_t i, m; for (i = 0; i < it->count; i ++) { /* Create variables to hold assembly properties */ ecs_vars_t vars = {0}; ecs_vars_init(world, &vars); /* Populate properties from assembly members */ const ecs_member_t *members = st->members.array; for (m = 0; m < st->members.count; m ++) { const ecs_member_t *member = &members[m]; ecs_value_t v = {0}; /* Prevent allocating value */ ecs_expr_var_t *var = ecs_vars_declare_w_value( &vars, member->name, &v); if (var == NULL) { ecs_err("could not create prop '%s' for assembly '%s'", member->name, name); break; } /* Assign assembly property from assembly instance */ var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); var->owned = false; } /* Populate $this variable with instance entity */ ecs_entity_t instance = it->entities[i]; ecs_value_t v = {0}; ecs_expr_var_t *var = ecs_vars_declare_w_value( &vars, "this", &v); var->value.type = ecs_id(ecs_entity_t); var->value.ptr = &instance; var->owned = false; /* Update script with new code/properties */ ecs_script_update(world, assembly, instance, script->script, &vars); ecs_vars_fini(&vars); if (ecs_record_has_id(world, r, EcsFlatten)) { ecs_flatten(it->real_world, ecs_childof(instance), NULL); } data = ECS_OFFSET(data, ct->size); } } /* Delete contents of assembly instance */ static void flecs_assembly_on_remove( ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { ecs_entity_t instance = it->entities[i]; ecs_script_clear(it->world, 0, instance); } } /* Set default property values on assembly Script component */ static int flecs_assembly_init_defaults( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_entity_t assembly, EcsScript *script, plecs_state_t *state) { const EcsStruct *st = ecs_get(world, assembly, EcsStruct); int32_t i, count = st->members.count; const ecs_member_t *members = st->members.array; ecs_vec_init_t(NULL, &script->prop_defaults, ecs_value_t, count); for (i = 0; i < count; i ++) { const ecs_member_t *member = &members[i]; ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, member->name); if (!var) { char *assembly_name = ecs_get_fullpath(world, assembly); ecs_parser_error(name, expr, ptr - expr, "missing property '%s' for assembly '%s'", member->name, assembly_name); ecs_os_free(assembly_name); return -1; } if (member->type != var->value.type) { char *assembly_name = ecs_get_fullpath(world, assembly); ecs_parser_error(name, expr, ptr - expr, "property '%s' for assembly '%s' has mismatching type", member->name, assembly_name); ecs_os_free(assembly_name); return -1; } ecs_value_t *pv = ecs_vec_append_t(NULL, &script->prop_defaults, ecs_value_t); pv->type = member->type; pv->ptr = var->value.ptr; var->owned = false; /* Transfer ownership */ } return 0; } /* Create new assembly */ static int flecs_assembly_create( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_entity_t assembly, char *script_code, plecs_state_t *state) { const EcsStruct *st = ecs_get(world, assembly, EcsStruct); if (!st || !st->members.count) { char *assembly_name = ecs_get_fullpath(world, assembly); ecs_parser_error(name, expr, ptr - expr, "assembly '%s' has no properties", assembly_name); ecs_os_free(assembly_name); ecs_os_free(script_code); return -1; } ecs_add_id(world, assembly, EcsAlwaysOverride); EcsScript *script = ecs_get_mut(world, assembly, EcsScript); flecs_dtor_script(script); script->world = world; script->script = script_code; ecs_vec_reset_t(NULL, &script->using_, ecs_entity_t); ecs_entity_t scope = ecs_get_scope(world); if (scope && (scope = ecs_get_target(world, scope, EcsChildOf, 0))) { ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = scope; } int i, count = state->using_frame; for (i = 0; i < count; i ++) { ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = state->using[i]; } if (flecs_assembly_init_defaults( world, name, expr, ptr, assembly, script, state)) { return -1; } ecs_modified(world, assembly, EcsScript); ecs_set_hooks_id(world, assembly, &(ecs_type_hooks_t) { .ctor = flecs_assembly_ctor, .on_set = flecs_assembly_on_set, .on_remove = flecs_assembly_on_remove, .ctx = world }); return 0; } /* Parser */ static bool plecs_is_newline_comment( const char *ptr) { if (ptr[0] == '/' && ptr[1] == '/') { return true; } return false; } static const char* plecs_parse_fluff( const char *ptr) { do { /* Skip whitespaces before checking for a comment */ ptr = ecs_parse_ws(ptr); /* Newline comment, skip until newline character */ if (plecs_is_newline_comment(ptr)) { ptr += 2; while (ptr[0] && ptr[0] != TOK_NEWLINE) { ptr ++; } } /* If a newline character is found, skip it */ if (ptr[0] == TOK_NEWLINE) { ptr ++; } } while (isspace(ptr[0]) || plecs_is_newline_comment(ptr)); return ptr; } static ecs_entity_t plecs_lookup( const ecs_world_t *world, const char *path, plecs_state_t *state, ecs_entity_t rel, bool is_subject) { ecs_entity_t e = 0; if (!is_subject) { ecs_entity_t oneof = 0; if (rel) { if (ecs_has_id(world, rel, EcsOneOf)) { oneof = rel; } else { oneof = ecs_get_target(world, rel, EcsOneOf, 0); } if (oneof) { return ecs_lookup_path_w_sep( world, oneof, path, NULL, NULL, false); } } int using_scope = state->using_frame - 1; for (; using_scope >= 0; using_scope--) { e = ecs_lookup_path_w_sep( world, state->using[using_scope], path, NULL, NULL, false); if (e) { break; } } } if (!e) { e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); } return e; } /* Lookup action used for deserializing entity refs in component values */ static ecs_entity_t plecs_lookup_action( const ecs_world_t *world, const char *path, void *ctx) { return plecs_lookup(world, path, ctx, 0, false); } static void plecs_apply_with_frame( ecs_world_t *world, plecs_state_t *state, ecs_entity_t e) { int32_t i, frame_count = state->with_frames[state->sp]; for (i = 0; i < frame_count; i ++) { ecs_id_t id = state->with[i]; plecs_with_value_t *v = &state->with_value_frames[i]; if (v->value.type) { void *ptr = ecs_get_mut_id(world, e, id); ecs_value_copy(world, v->value.type, ptr, v->value.ptr); ecs_modified_id(world, e, id); } else { ecs_add_id(world, e, id); } } } static ecs_entity_t plecs_ensure_entity( ecs_world_t *world, plecs_state_t *state, const char *path, ecs_entity_t rel, bool is_subject) { if (!path) { return 0; } ecs_entity_t e = 0; bool is_anonymous = !ecs_os_strcmp(path, "_"); bool is_new = false; if (is_anonymous) { path = NULL; e = ecs_new_id(world); is_new = true; } if (!e) { e = plecs_lookup(world, path, state, rel, is_subject); } if (!e) { is_new = true; if (rel && flecs_get_oneof(world, rel)) { /* If relationship has oneof and entity was not found, don't proceed * with creating an entity as this can cause asserts later on */ char *relstr = ecs_get_fullpath(world, rel); ecs_parser_error(state->name, 0, 0, "invalid identifier '%s' for relationship '%s'", path, relstr); ecs_os_free(relstr); return 0; } ecs_entity_t prev_scope = 0; ecs_entity_t prev_with = 0; if (!is_subject) { /* Don't apply scope/with for non-subject entities */ prev_scope = ecs_set_scope(world, 0); prev_with = ecs_set_with(world, 0); } e = ecs_add_path(world, e, 0, path); ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); if (prev_scope) { ecs_set_scope(world, prev_scope); } if (prev_with) { ecs_set_with(world, prev_with); } } else { /* If entity exists, make sure it gets the right scope and with */ if (is_subject) { ecs_entity_t scope = ecs_get_scope(world); if (scope) { ecs_add_pair(world, e, EcsChildOf, scope); } ecs_entity_t with = ecs_get_with(world); if (with) { ecs_add_id(world, e, with); } } } if (is_new) { if (state->assembly && !state->assembly_instance) { ecs_add_id(world, e, EcsPrefab); } if (state->global_with) { ecs_add_id(world, e, state->global_with); } } return e; } static ecs_entity_t plecs_ensure_term_id( ecs_world_t *world, plecs_state_t *state, ecs_term_id_t *term_id, const char *expr, int64_t column, ecs_entity_t pred, bool is_subject) { ecs_entity_t result = 0; const char *name = term_id->name; if (term_id->flags & EcsIsVariable) { if (name != NULL) { ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, name); if (!var) { ecs_parser_error(name, expr, column, "unresolved variable '%s'", name); return 0; } if (var->value.type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, column, "variable '%s' is not an entity", name); return 0; } result = *(ecs_entity_t*)var->value.ptr; if (!result) { ecs_parser_error(name, expr, column, "variable '%s' is not initialized with valid entity", name); return 0; } } else if (term_id->id) { result = term_id->id; } else { ecs_parser_error(name, expr, column, "invalid variable in term"); return 0; } } else { result = plecs_ensure_entity(world, state, name, pred, is_subject); } return result; } static bool plecs_pred_is_subj( ecs_term_t *term, plecs_state_t *state) { if (term->src.name != NULL) { return false; } if (term->second.name != NULL) { return false; } if (ecs_term_match_0(term)) { return false; } if (state->with_stmt) { return false; } if (state->assign_stmt) { return false; } if (state->isa_stmt) { return false; } if (state->decl_type) { return false; } return true; } /* Set masks aren't useful in plecs, so translate them back to entity names */ static const char* plecs_set_mask_to_name( ecs_flags32_t flags) { flags &= EcsTraverseFlags; if (flags == EcsSelf) { return "self"; } else if (flags == EcsUp) { return "up"; } else if (flags == EcsDown) { return "down"; } else if (flags == EcsCascade || flags == (EcsUp|EcsCascade)) { return "cascade"; } else if (flags == EcsParent) { return "parent"; } return NULL; } static char* plecs_trim_annot( char *annot) { annot = ECS_CONST_CAST(char*, ecs_parse_ws(annot)); int32_t len = ecs_os_strlen(annot) - 1; while (isspace(annot[len]) && (len > 0)) { annot[len] = '\0'; len --; } return annot; } static void plecs_apply_annotations( ecs_world_t *world, ecs_entity_t subj, plecs_state_t *state) { (void)world; (void)subj; (void)state; #ifdef FLECS_DOC int32_t i = 0, count = state->annot_count; for (i = 0; i < count; i ++) { char *annot = state->annot[i]; if (!ecs_os_strncmp(annot, "@brief ", 7)) { annot = plecs_trim_annot(annot + 7); ecs_doc_set_brief(world, subj, annot); } else if (!ecs_os_strncmp(annot, "@link ", 6)) { annot = plecs_trim_annot(annot + 6); ecs_doc_set_link(world, subj, annot); } else if (!ecs_os_strncmp(annot, "@name ", 6)) { annot = plecs_trim_annot(annot + 6); ecs_doc_set_name(world, subj, annot); } else if (!ecs_os_strncmp(annot, "@color ", 7)) { annot = plecs_trim_annot(annot + 7); ecs_doc_set_color(world, subj, annot); } } #else ecs_warn("cannot apply annotations, doc addon is missing"); #endif } static int plecs_create_term( ecs_world_t *world, ecs_term_t *term, const char *name, const char *expr, int64_t column, plecs_state_t *state) { state->last_subject = 0; state->last_predicate = 0; state->last_object = 0; state->last_assign_id = 0; const char *subj_name = term->src.name; if (!subj_name) { subj_name = plecs_set_mask_to_name(term->src.flags); } if (!ecs_term_id_is_set(&term->first)) { ecs_parser_error(name, expr, column, "missing term in expression"); return -1; } if (state->assign_stmt && !ecs_term_match_this(term)) { ecs_parser_error(name, expr, column, "invalid statement in assign statement"); return -1; } bool pred_as_subj = plecs_pred_is_subj(term, state); ecs_entity_t subj = 0, obj = 0, pred = plecs_ensure_term_id( world, state, &term->first, expr, column, 0, pred_as_subj); if (!pred) { return -1; } subj = plecs_ensure_entity(world, state, subj_name, pred, true); if (ecs_term_id_is_set(&term->second)) { obj = plecs_ensure_term_id(world, state, &term->second, expr, column, pred, !state->assign_stmt && !state->with_stmt); if (!obj) { return -1; } } if (state->assign_stmt || state->isa_stmt) { subj = state->assign_to; } if (state->isa_stmt && obj) { ecs_parser_error(name, expr, column, "invalid object in inheritance statement"); return -1; } if (state->isa_stmt) { pred = ecs_pair(EcsIsA, pred); } if (subj == EcsVariable) { subj = pred; } if (subj) { ecs_id_t id; if (!obj) { id = term->id_flags | pred; } else { id = term->id_flags | ecs_pair(pred, obj); state->last_object = obj; } state->last_assign_id = id; state->last_predicate = pred; state->last_subject = subj; ecs_add_id(world, subj, id); pred_as_subj = false; } else { if (!obj) { /* If no subject or object were provided, use predicate as subj * unless the expression explictly excluded the subject */ if (pred_as_subj) { state->last_subject = pred; subj = pred; } else { state->last_predicate = pred; pred_as_subj = false; } } else { state->last_predicate = pred; state->last_object = obj; pred_as_subj = false; } } /* If this is a with clause (the list of entities between 'with' and scope * open), add subject to the array of with frames */ if (state->with_stmt) { ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); ecs_id_t id; if (obj) { id = ecs_pair(pred, obj); } else { id = pred; } state->with[state->with_frame ++] = id; } else { if (subj && !state->scope_assign_stmt) { plecs_apply_with_frame(world, state, subj); } } /* If an id was provided by itself, add default scope type to it */ ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; if (pred_as_subj && default_scope_type) { ecs_add_id(world, subj, default_scope_type); } /* If annotations preceded the statement, append */ if (!state->decl_type && state->annot_count) { if (!subj) { ecs_parser_error(name, expr, column, "missing subject for annotations"); return -1; } plecs_apply_annotations(world, subj, state); } return 0; } static const char* plecs_parse_inherit_stmt( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "cannot nest inheritance"); return NULL; } if (!state->last_subject) { ecs_parser_error(name, expr, ptr - expr, "missing entity to assign inheritance to"); return NULL; } state->isa_stmt = true; state->assign_to = state->last_subject; return ptr; } static const char* plecs_parse_assign_var_expr( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state, ecs_expr_var_t *var) { ecs_value_t value = {0}; if (state->last_assign_id) { value.type = state->last_assign_id; value.ptr = ecs_value_new(world, state->last_assign_id); if (!var && state->assembly_instance) { var = ecs_vars_lookup(&state->vars, state->var_name); } } ptr = ecs_parse_expr(world, ptr, &value, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { if (state->last_assign_id) { ecs_value_free(world, value.type, value.ptr); } goto error; } if (var) { bool ignore = state->var_is_prop && state->assembly_instance; if (!ignore) { if (var->value.ptr) { ecs_value_free(world, var->value.type, var->value.ptr); var->value.ptr = value.ptr; var->value.type = value.type; } } else { ecs_value_free(world, value.type, value.ptr); } } else { var = ecs_vars_declare_w_value( &state->vars, state->var_name, &value); if (!var) { goto error; } } state->var_is_prop = false; return ptr; error: return NULL; } static const char* plecs_parse_assign_expr( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state, ecs_expr_var_t *var) { (void)world; if (state->var_stmt) { return plecs_parse_assign_var_expr(world, name, expr, ptr, state, var); } if (!state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "unexpected value outside of assignment statement"); return NULL; } ecs_id_t assign_id = state->last_assign_id; if (!assign_id) { ecs_parser_error(name, expr, ptr - expr, "missing type for assignment statement"); return NULL; } ecs_entity_t assign_to = state->assign_to; if (!assign_to) { assign_to = state->last_subject; } if (!assign_to) { ecs_parser_error(name, expr, ptr - expr, "missing entity to assign to"); return NULL; } ecs_entity_t type = ecs_get_typeid(world, assign_id); if (!type) { char *id_str = ecs_id_str(world, assign_id); ecs_parser_error(name, expr, ptr - expr, "invalid assignment, '%s' is not a type", id_str); ecs_os_free(id_str); return NULL; } if (assign_to == EcsVariable) { assign_to = type; } void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); ptr = ecs_parse_expr(world, ptr, &(ecs_value_t){type, value_ptr}, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { return NULL; } ecs_modified_id(world, assign_to, assign_id); return ptr; } static const char* plecs_parse_assign_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { (void)world; state->isa_stmt = false; /* Component scope (add components to entity) */ if (!state->assign_to) { if (!state->last_subject) { ecs_parser_error(name, expr, ptr - expr, "missing entity to assign to"); return NULL; } state->assign_to = state->last_subject; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid assign statement in assign statement"); return NULL; } state->assign_stmt = true; /* Assignment without a preceding component */ if (ptr[0] == '{') { ecs_entity_t type = 0; /* If we're in a scope & last_subject is a type, assign to scope */ if (ecs_get_scope(world) != 0) { type = ecs_get_typeid(world, state->last_subject); if (type != 0) { type = state->last_subject; } } /* If type hasn't been set yet, check if scope has default type */ if (!type && !state->scope_assign_stmt) { type = state->default_scope_type[state->sp]; } /* If no type has been found still, check if last with id is a type */ if (!type && !state->scope_assign_stmt) { int32_t with_frame_count = state->with_frames[state->sp]; if (with_frame_count) { type = state->with[with_frame_count - 1]; } } if (!type) { ecs_parser_error(name, expr, ptr - expr, "missing type for assignment"); return NULL; } state->last_assign_id = type; } return ptr; } static const char* plecs_parse_assign_with_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { int32_t with_frame = state->with_frame - 1; if (with_frame < 0) { ecs_parser_error(name, expr, ptr - expr, "missing type in with value"); return NULL; } ecs_id_t id = state->with[with_frame]; ecs_id_record_t *idr = flecs_id_record_get(world, id); const ecs_type_info_t *ti = idr ? idr->type_info : NULL; if (!ti) { char *typename = ecs_id_str(world, id); ecs_parser_error(name, expr, ptr - expr, "id '%s' in with value is not a type", typename); ecs_os_free(typename); return NULL; } plecs_with_value_t *v = &state->with_value_frames[with_frame]; v->value.type = ti->component; v->value.ptr = ecs_value_new(world, ti->component); v->owned = true; if (!v->value.ptr) { char *typename = ecs_id_str(world, id); ecs_parser_error(name, expr, ptr - expr, "failed to create value for '%s'", typename); ecs_os_free(typename); return NULL; } ptr = ecs_parse_expr(world, ptr, &v->value, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { return NULL; } return ptr; } static const char* plecs_parse_assign_with_var( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); ecs_assert(state->with_stmt, ECS_INTERNAL_ERROR, NULL); char var_name[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr; ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "unresolved variable '%s'", var_name); return NULL; } ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", var_name); return NULL; } int32_t with_frame = state->with_frame; state->with[with_frame] = var->value.type; state->with_value_frames[with_frame].value = var->value; state->with_value_frames[with_frame].owned = false; state->with_frame ++; return ptr; } static const char* plecs_parse_var_as_component( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); ecs_assert(!state->var_stmt, ECS_INTERNAL_ERROR, NULL); char var_name[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr; ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "unresolved variable '%s'", var_name); return NULL; } ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", var_name); return NULL; } if (!state->assign_to) { ecs_parser_error(name, expr, ptr - expr, "missing lvalue for variable assignment '%s'", var_name); return NULL; } /* Use type of variable as component */ ecs_entity_t type = var->value.type; ecs_entity_t assign_to = state->assign_to; if (!assign_to) { assign_to = state->last_subject; } void *dst = ecs_get_mut_id(world, assign_to, type); if (!dst) { char *type_name = ecs_get_fullpath(world, type); ecs_parser_error(name, expr, ptr - expr, "failed to obtain component for type '%s' of variable '%s'", type_name, var_name); ecs_os_free(type_name); return NULL; } if (ecs_value_copy(world, type, dst, var->value.ptr)) { char *type_name = ecs_get_fullpath(world, type); ecs_parser_error(name, expr, ptr - expr, "failed to copy value for variable '%s' of type '%s'", var_name, type_name); ecs_os_free(type_name); return NULL; } ecs_modified_id(world, assign_to, type); return ptr; } static void plecs_push_using( ecs_entity_t scope, plecs_state_t *state) { for (int i = 0; i < state->using_frame; i ++) { if (state->using[i] == scope) { return; } } state->using[state->using_frame ++] = scope; } static const char* plecs_parse_using_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt || state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid usage of using keyword"); return NULL; } char using_path[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr + 1; ptr = ecs_parse_token(name, expr, ptr + 5, using_path, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "expected identifier for using statement"); return NULL; } ecs_size_t len = ecs_os_strlen(using_path); if (!len) { ecs_parser_error(name, expr, tmp - expr, "missing identifier for using statement"); return NULL; } /* Lookahead as * is not matched by parse_token */ if (ptr[0] == '*') { using_path[len] = '*'; using_path[len + 1] = '\0'; len ++; ptr ++; } ecs_entity_t scope; if (len > 2 && !ecs_os_strcmp(&using_path[len - 2], ".*")) { using_path[len - 2] = '\0'; scope = ecs_lookup_fullpath(world, using_path); if (!scope) { ecs_parser_error(name, expr, ptr - expr, "unresolved identifier '%s' in using statement", using_path); return NULL; } /* Add each child of the scope to using stack */ ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(scope) }); while (ecs_term_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { plecs_push_using(it.entities[i], state); } } } else { scope = plecs_ensure_entity(world, state, using_path, 0, false); if (!scope) { ecs_parser_error(name, expr, ptr - expr, "unresolved identifier '%s' in using statement", using_path); return NULL; } plecs_push_using(scope, state); } state->using_frames[state->sp] = state->using_frame; return ptr; } static const char* plecs_parse_module_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { const char *expr_start = ecs_parse_ws_eol(expr); if (expr_start != ptr) { ecs_parser_error(name, expr, ptr - expr, "module must be first statement of script"); return NULL; } char module_path[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr + 1; ptr = ecs_parse_token(name, expr, ptr + 6, module_path, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "expected identifier for module statement"); return NULL; } ecs_component_desc_t desc = {0}; desc.entity = ecs_entity(world, { .name = module_path }); ecs_entity_t module = ecs_module_init(world, NULL, &desc); if (!module) { return NULL; } state->is_module = true; state->sp ++; state->scope[state->sp] = module; ecs_set_scope(world, module); return ptr; } static const char* plecs_parse_with_stmt( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with after inheritance"); return NULL; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with in assign_stmt"); return NULL; } /* Add following expressions to with list */ state->with_stmt = true; return ptr + 5; } static const char* plecs_parse_assembly_stmt( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with after inheritance"); return NULL; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with in assign_stmt"); return NULL; } state->assembly_stmt = true; return ptr + 9; } static const char* plecs_parse_var_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state, ecs_entity_t *type_out) { char prop_type_name[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr + 1; ptr = ecs_parse_token(name, expr, ptr + 1, prop_type_name, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "expected type for prop declaration"); return NULL; } ecs_entity_t prop_type = plecs_lookup(world, prop_type_name, state, 0, false); if (!prop_type) { ecs_parser_error(name, expr, ptr - expr, "unresolved property type '%s'", prop_type_name); return NULL; } *type_out = prop_type; return ptr; } static const char* plecs_parse_const_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name, 0); if (!ptr) { return NULL; } ptr = ecs_parse_ws(ptr); if (ptr[0] == ':') { ptr = plecs_parse_var_type( world, name, expr, ptr, state, &state->last_assign_id); if (!ptr) { return NULL; } ptr = ecs_parse_ws(ptr); } if (ptr[0] != '=') { ecs_parser_error(name, expr, ptr - expr, "expected '=' after const declaration"); return NULL; } state->var_stmt = true; return ptr + 1; } static const char* plecs_parse_prop_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { char prop_name[ECS_MAX_TOKEN_SIZE]; ptr = ecs_parse_token(name, expr, ptr + 5, prop_name, 0); if (!ptr) { return NULL; } ptr = ecs_parse_ws(ptr); if (ptr[0] != ':') { ecs_parser_error(name, expr, ptr - expr, "expected ':' after prop declaration"); return NULL; } ecs_entity_t prop_type; ptr = plecs_parse_var_type(world, name, expr, ptr, state, &prop_type); if (!ptr) { return NULL; } ecs_entity_t assembly = state->assembly; if (!assembly) { ecs_parser_error(name, expr, ptr - expr, "unexpected prop '%s' outside of assembly", prop_name); return NULL; } if (!state->assembly_instance) { ecs_entity_t prop_member = ecs_entity(world, { .name = prop_name, .add = { ecs_childof(assembly) } }); if (!prop_member) { return NULL; } ecs_set(world, prop_member, EcsMember, { .type = prop_type }); } if (ptr[0] != '=') { ecs_parser_error(name, expr, ptr - expr, "expected '=' after prop type"); return NULL; } ecs_os_strcpy(state->var_name, prop_name); state->last_assign_id = prop_type; state->var_stmt = true; state->var_is_prop = true; return plecs_parse_fluff(ptr + 1); } static const char* plecs_parse_scope_open( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { state->isa_stmt = false; if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid scope in assign_stmt"); return NULL; } state->sp ++; ecs_entity_t scope = 0; ecs_entity_t default_scope_type = 0; bool assembly_stmt = false; if (!state->with_stmt) { if (state->last_subject) { scope = state->last_subject; ecs_set_scope(world, state->last_subject); /* Check if scope has a default child component */ ecs_entity_t def_type_src = ecs_get_target_for_id(world, scope, 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); if (def_type_src) { default_scope_type = ecs_get_target( world, def_type_src, EcsDefaultChildComponent, 0); } } else { if (state->last_object) { scope = ecs_pair( state->last_predicate, state->last_object); ecs_set_with(world, scope); } else { if (state->last_predicate) { scope = ecs_pair(EcsChildOf, state->last_predicate); } ecs_set_scope(world, state->last_predicate); } } state->scope[state->sp] = scope; state->default_scope_type[state->sp] = default_scope_type; if (state->assembly_stmt) { assembly_stmt = true; if (state->assembly) { ecs_parser_error(name, expr, ptr - expr, "invalid nested assembly"); return NULL; } state->assembly = scope; state->assembly_stmt = false; state->assembly_start = ptr; } } else { state->scope[state->sp] = state->scope[state->sp - 1]; state->default_scope_type[state->sp] = state->default_scope_type[state->sp - 1]; state->assign_to = 0; } state->using_frames[state->sp] = state->using_frame; state->with_frames[state->sp] = state->with_frame; state->with_stmt = false; ecs_vars_push(&state->vars); /* Declare variable to hold assembly instance during instantiation */ if (assembly_stmt) { ecs_value_t val = {0}; ecs_expr_var_t *var = ecs_vars_declare_w_value( &state->vars, "this", &val); var->value.ptr = ECS_CONST_CAST(void*, &EcsThis); /* Dummy value */ var->value.type = ecs_id(ecs_entity_t); var->owned = false; } return ptr; } static void plecs_free_with_frame( ecs_world_t *world, plecs_state_t *state) { int32_t i, prev_with = state->with_frames[state->sp]; for (i = prev_with; i < state->with_frame; i ++) { plecs_with_value_t *v = &state->with_value_frames[i]; if (!v->owned) { continue; } if (v->value.type) { ecs_value_free(world, v->value.type, v->value.ptr); v->value.type = 0; v->value.ptr = NULL; v->owned = false; } } } static void plecs_free_all_with_frames( ecs_world_t *world, plecs_state_t *state) { int32_t i; for (i = state->sp - 1; i >= 0; i --) { state->sp = i; plecs_free_with_frame(world, state); } } static const char* plecs_parse_scope_close( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid '}' after inheritance statement"); return NULL; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "unfinished assignment before }"); return NULL; } ecs_entity_t cur = state->scope[state->sp], assembly = state->assembly; if (state->sp && (cur == state->scope[state->sp - 1])) { /* Previous scope is also from the assembly, not found the end yet */ cur = 0; } if (cur && cur == assembly) { ecs_size_t assembly_len = flecs_ito(ecs_size_t, ptr - state->assembly_start); if (assembly_len) { assembly_len --; char *script = ecs_os_malloc_n(char, assembly_len + 1); ecs_os_memcpy(script, state->assembly_start, assembly_len); script[assembly_len] = '\0'; state->assembly = 0; state->assembly_start = NULL; if (flecs_assembly_create(world, name, expr, ptr, assembly, script, state)) { return NULL; } } else { ecs_parser_error(name, expr, ptr - expr, "empty assembly"); return NULL; } } state->scope[state->sp] = 0; state->default_scope_type[state->sp] = 0; state->sp --; if (state->sp < 0) { ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); return NULL; } ecs_id_t id = state->scope[state->sp]; if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { ecs_set_with(world, id); } if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { ecs_set_scope(world, id); } plecs_free_with_frame(world, state); state->with_frame = state->with_frames[state->sp]; state->using_frame = state->using_frames[state->sp]; state->last_subject = 0; state->assign_stmt = false; ecs_vars_pop(&state->vars); return plecs_parse_fluff(ptr + 1); } static const char *plecs_parse_plecs_term( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_term_t term = {0}; ecs_entity_t decl_id = 0; if (state->decl_stmt) { decl_id = state->last_predicate; } ptr = ecs_parse_term(world, name, expr, ptr, &term); if (!ptr) { return NULL; } if (flecs_isident(ptr[0])) { state->decl_type = true; } if (!ecs_term_is_initialized(&term)) { ecs_parser_error(name, expr, ptr - expr, "expected identifier"); return NULL; /* No term found */ } if (plecs_create_term(world, &term, name, expr, (ptr - expr), state)) { ecs_term_fini(&term); return NULL; /* Failed to create term */ } if (decl_id && state->last_subject) { ecs_add_id(world, state->last_subject, decl_id); } state->decl_type = false; ecs_term_fini(&term); return ptr; } static const char* plecs_parse_annotation( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { do { if(state->annot_count >= STACK_MAX_SIZE) { ecs_parser_error(name, expr, ptr - expr, "max number of annotations reached"); return NULL; } char ch; const char *start = ptr; for (; (ch = *ptr) && ch != '\n'; ptr ++) { } int32_t len = (int32_t)(ptr - start); char *annot = ecs_os_malloc_n(char, len + 1); ecs_os_memcpy_n(annot, start, char, len); annot[len] = '\0'; state->annot[state->annot_count] = annot; state->annot_count ++; ptr = plecs_parse_fluff(ptr); } while (ptr[0] == '@'); return ptr; } static void plecs_clear_annotations( plecs_state_t *state) { int32_t i, count = state->annot_count; for (i = 0; i < count; i ++) { ecs_os_free(state->annot[i]); } state->annot_count = 0; } static const char* plecs_parse_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { state->assign_stmt = false; state->scope_assign_stmt = false; state->isa_stmt = false; state->with_stmt = false; state->decl_stmt = false; state->var_stmt = false; state->last_subject = 0; state->last_predicate = 0; state->last_object = 0; state->assign_to = 0; state->last_assign_id = 0; plecs_clear_annotations(state); ptr = plecs_parse_fluff(ptr); char ch = ptr[0]; if (!ch) { goto done; } else if (ch == '{') { ptr = plecs_parse_fluff(ptr + 1); goto scope_open; } else if (ch == '}') { goto scope_close; } else if (ch == '-') { ptr = plecs_parse_fluff(ptr + 1); state->assign_to = ecs_get_scope(world); state->scope_assign_stmt = true; goto assign_stmt; } else if (ch == '@') { ptr = plecs_parse_annotation(name, expr, ptr, state); if (!ptr) goto error; goto term_expr; } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { ptr = plecs_parse_using_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto done; } else if (!ecs_os_strncmp(ptr, TOK_MODULE " ", 6)) { ptr = plecs_parse_module_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto done; } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { ptr = plecs_parse_with_stmt(name, expr, ptr, state); if (!ptr) goto error; goto term_expr; } else if (!ecs_os_strncmp(ptr, TOK_CONST " ", 6)) { ptr = plecs_parse_const_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto assign_expr; } else if (!ecs_os_strncmp(ptr, TOK_ASSEMBLY " ", 9)) { ptr = plecs_parse_assembly_stmt(name, expr, ptr, state); if (!ptr) goto error; goto decl_stmt; } else if (!ecs_os_strncmp(ptr, TOK_PROP " ", 5)) { ptr = plecs_parse_prop_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto assign_expr; } else { goto term_expr; } term_expr: if (!ptr[0]) { goto done; } if (ptr[0] == '$' && !isspace(ptr[1])) { if (state->with_stmt) { ptr = plecs_parse_assign_with_var(name, expr, ptr, state); if (!ptr) { return NULL; } } else if (!state->var_stmt) { goto assign_var_as_component; } } else if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { goto error; } const char *tptr = ecs_parse_ws(ptr); if (flecs_isident(tptr[0])) { if (state->decl_stmt) { ecs_parser_error(name, expr, (ptr - expr), "unexpected ' ' in declaration statement"); goto error; } ptr = tptr; goto decl_stmt; } next_term: ptr = plecs_parse_fluff(ptr); if (ptr[0] == ':' && ptr[1] == '-') { ptr = plecs_parse_fluff(ptr + 2); goto assign_stmt; } else if (ptr[0] == ':') { ptr = plecs_parse_fluff(ptr + 1); goto inherit_stmt; } else if (ptr[0] == ',') { ptr = plecs_parse_fluff(ptr + 1); goto term_expr; } else if (ptr[0] == '{') { if (state->assign_stmt) { goto assign_expr; } else if (state->with_stmt && !isspace(ptr[-1])) { /* If this is a { in a with statement which directly follows a * non-whitespace character, the with id has a value */ ptr = plecs_parse_assign_with_stmt(world, name, expr, ptr, state); if (!ptr) { goto error; } goto next_term; } else { ptr = plecs_parse_fluff(ptr + 1); goto scope_open; } } state->assign_stmt = false; goto done; decl_stmt: state->decl_stmt = true; goto term_expr; inherit_stmt: ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); if (!ptr) goto error; /* Expect base identifier */ goto term_expr; assign_stmt: ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); if (!ptr) goto error; ptr = plecs_parse_fluff(ptr); /* Assignment without a preceding component */ if (ptr[0] == '{') { goto assign_expr; } /* Expect component identifiers */ goto term_expr; assign_expr: ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, NULL); if (!ptr) goto error; ptr = plecs_parse_fluff(ptr); if (ptr[0] == ',') { ptr ++; goto term_expr; } else if (ptr[0] == '{') { if (state->var_stmt) { ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, state->var_name); if (var && var->value.type == ecs_id(ecs_entity_t)) { ecs_assert(var->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); /* The code contained an entity{...} variable assignment, use * the assigned entity id as type for parsing the expression */ state->last_assign_id = *(ecs_entity_t*)var->value.ptr; ptr = plecs_parse_assign_expr(world, name, expr, ptr, state, var); goto done; } } ecs_parser_error(name, expr, (ptr - expr), "unexpected '{' after assignment"); goto error; } state->assign_stmt = false; state->assign_to = 0; goto done; assign_var_as_component: { ptr = plecs_parse_var_as_component(world, name, expr, ptr, state); if (!ptr) { goto error; } state->assign_stmt = false; state->assign_to = 0; goto done; } scope_open: ptr = plecs_parse_scope_open(world, name, expr, ptr, state); if (!ptr) goto error; goto done; scope_close: ptr = plecs_parse_scope_close(world, name, expr, ptr, state); if (!ptr) goto error; goto done; done: return ptr; error: return NULL; } static int flecs_plecs_parse( ecs_world_t *world, const char *name, const char *expr, ecs_vars_t *vars, ecs_entity_t script, ecs_entity_t instance) { const char *ptr = expr; ecs_term_t term = {0}; plecs_state_t state = {0}; if (!expr) { return 0; } state.scope[0] = 0; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t prev_with = ecs_set_with(world, 0); if (ECS_IS_PAIR(prev_with) && ECS_PAIR_FIRST(prev_with) == EcsChildOf) { ecs_set_scope(world, ECS_PAIR_SECOND(prev_with)); state.scope[0] = ecs_pair_second(world, prev_with); } else { state.global_with = prev_with; } ecs_vars_init(world, &state.vars); if (script) { const EcsScript *s = ecs_get(world, script, EcsScript); if (!s) { ecs_err("%s: provided script entity is not a script", name); goto error; } if (s && ecs_has(world, script, EcsStruct)) { state.assembly = script; state.assembly_instance = true; if (s->using_.count) { ecs_os_memcpy_n(state.using, s->using_.array, ecs_entity_t, s->using_.count); state.using_frame = s->using_.count; state.using_frames[0] = s->using_.count; } if (instance) { ecs_set_scope(world, instance); } } } if (vars) { state.vars.root.parent = vars->cur; } do { expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); if (!ptr) { goto error; } if (!ptr[0]) { break; /* End of expression */ } } while (true); ecs_set_scope(world, prev_scope); ecs_set_with(world, prev_with); plecs_clear_annotations(&state); if (state.is_module) { state.sp --; } if (state.sp != 0) { ecs_parser_error(name, expr, 0, "missing end of scope"); goto error; } if (state.assign_stmt) { ecs_parser_error(name, expr, 0, "unfinished assignment"); goto error; } if (state.errors) { goto error; } ecs_vars_fini(&state.vars); return 0; error: plecs_free_all_with_frames(world, &state); ecs_vars_fini(&state.vars); ecs_set_scope(world, state.scope[0]); ecs_set_with(world, prev_with); ecs_term_fini(&term); return -1; } int ecs_plecs_from_str( ecs_world_t *world, const char *name, const char *expr) { return flecs_plecs_parse(world, name, expr, NULL, 0, 0); } static char* flecs_load_from_file( const char *filename) { FILE* file; char* content = NULL; int32_t bytes; size_t size; /* Open file for reading */ ecs_os_fopen(&file, filename, "r"); if (!file) { ecs_err("%s (%s)", ecs_os_strerror(errno), filename); goto error; } /* Determine file size */ fseek(file, 0 , SEEK_END); bytes = (int32_t)ftell(file); if (bytes == -1) { goto error; } rewind(file); /* Load contents in memory */ content = ecs_os_malloc(bytes + 1); size = (size_t)bytes; if (!(size = fread(content, 1, size, file)) && bytes) { ecs_err("%s: read zero bytes instead of %d", filename, size); ecs_os_free(content); content = NULL; goto error; } else { content[size] = '\0'; } fclose(file); return content; error: ecs_os_free(content); return NULL; } int ecs_plecs_from_file( ecs_world_t *world, const char *filename) { char *script = flecs_load_from_file(filename); if (!script) { return -1; } int result = ecs_plecs_from_str(world, filename, script); ecs_os_free(script); return result; } static ecs_id_t flecs_script_tag( ecs_entity_t script, ecs_entity_t instance) { if (!instance) { return ecs_pair_t(EcsScript, script); } else { return ecs_pair(EcsChildOf, instance); } } void ecs_script_clear( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance) { ecs_delete_with(world, flecs_script_tag(script, instance)); } int ecs_script_update( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *script, ecs_vars_t *vars) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); int result = 0; bool is_defer = ecs_is_deferred(world); ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { ecs_assert(ecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_script_clear(world, e, instance); EcsScript *s = ecs_get_mut(world, e, EcsScript); if (!s->script || ecs_os_strcmp(s->script, script)) { s->script = ecs_os_strdup(script); ecs_modified(world, e, EcsScript); } ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); if (flecs_plecs_parse(world, ecs_get_name(world, e), script, vars, e, instance)) { ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } ecs_set_with(world, prev); if (is_defer) { flecs_resume_readonly(real_world, &srs); } return result; } ecs_entity_t ecs_script_init( ecs_world_t *world, const ecs_script_desc_t *desc) { const char *script = NULL; ecs_entity_t e = desc->entity; ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); if (!e) { if (desc->filename) { e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); } else { e = ecs_new_id(world); } } script = desc->str; if (!script && desc->filename) { script = flecs_load_from_file(desc->filename); if (!script) { goto error; } } if (ecs_script_update(world, e, 0, script, NULL)) { goto error; } if (script != desc->str) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free(ECS_CONST_CAST(char*, script)); } return e; error: if (script != desc->str) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free(ECS_CONST_CAST(char*, script)); } if (!desc->entity) { ecs_delete(world, e); } return 0; } void FlecsScriptImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScript); ECS_IMPORT(world, FlecsMeta); ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); ecs_set_hooks(world, EcsScript, { .ctor = ecs_default_ctor, .move = ecs_move(EcsScript), .dtor = ecs_dtor(EcsScript) }); ecs_add_id(world, ecs_id(EcsScript), EcsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_struct(world, { .entity = ecs_id(EcsScript), .members = { { .name = "using", .type = ecs_vector(world, { .entity = ecs_entity(world, { .name = "UsingVector" }), .type = ecs_id(ecs_entity_t) }), .count = 0 }, { .name = "script", .type = ecs_id(ecs_string_t), .count = 0 } } }); } #endif /** * @file addons/rest.c * @brief Rest addon. */ #ifdef FLECS_REST static ECS_TAG_DECLARE(EcsRestPlecs); typedef struct { ecs_world_t *world; ecs_http_server_t *srv; int32_t rc; } ecs_rest_ctx_t; /* Global statistics */ int64_t ecs_rest_request_count = 0; int64_t ecs_rest_entity_count = 0; int64_t ecs_rest_entity_error_count = 0; int64_t ecs_rest_query_count = 0; int64_t ecs_rest_query_error_count = 0; int64_t ecs_rest_query_name_count = 0; int64_t ecs_rest_query_name_error_count = 0; int64_t ecs_rest_query_name_from_cache_count = 0; int64_t ecs_rest_enable_count = 0; int64_t ecs_rest_enable_error_count = 0; int64_t ecs_rest_delete_count = 0; int64_t ecs_rest_delete_error_count = 0; int64_t ecs_rest_world_stats_count = 0; int64_t ecs_rest_pipeline_stats_count = 0; int64_t ecs_rest_stats_error_count = 0; static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_http_server_fini(impl->srv); ecs_os_free(impl); } } ecs_os_free(ptr->ipaddr); }) static char *rest_last_err; static ecs_os_api_log_t rest_prev_log; static void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; if (level < 0) { if (rest_prev_log) { // Also log to previous log function ecs_log_enable_colors(true); rest_prev_log(level, file, line, msg); ecs_log_enable_colors(false); } } if (!rest_last_err && level < 0) { rest_last_err = ecs_os_strdup(msg); } } static char* flecs_rest_get_captured_log(void) { char *result = rest_last_err; rest_last_err = NULL; return result; } static void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) { ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); ecs_strbuf_vappend(&reply->body, fmt, args); ecs_strbuf_appendlit(&reply->body, "\"}"); } static void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); flecs_reply_verror(reply, fmt, args); va_end(args); } static void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { if (!ecs_os_strcmp(value, "true")) { value_out[0] = true; } else { value_out[0] = false; } } } static void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = atoi(value); } } static void flecs_rest_string_param( const ecs_http_request_t *req, const char *name, char **value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = ECS_CONST_CAST(char*, value); } } static void flecs_rest_parse_json_ser_entity_params( ecs_world_t *world, ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "path", &desc->serialize_path); flecs_rest_bool_param(req, "label", &desc->serialize_label); flecs_rest_bool_param(req, "brief", &desc->serialize_brief); flecs_rest_bool_param(req, "link", &desc->serialize_link); flecs_rest_bool_param(req, "color", &desc->serialize_color); flecs_rest_bool_param(req, "ids", &desc->serialize_ids); flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); flecs_rest_bool_param(req, "base", &desc->serialize_base); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "private", &desc->serialize_private); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "matches", &desc->serialize_matches); flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); char *rel = NULL; flecs_rest_string_param(req, "refs", &rel); if (rel) { desc->serialize_refs = ecs_lookup_fullpath(world, rel); } } static void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); flecs_rest_bool_param(req, "term_labels", &desc->serialize_term_labels); flecs_rest_bool_param(req, "ids", &desc->serialize_ids); flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); flecs_rest_bool_param(req, "sources", &desc->serialize_sources); flecs_rest_bool_param(req, "variables", &desc->serialize_variables); flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "private", &desc->serialize_private); flecs_rest_bool_param(req, "entities", &desc->serialize_entities); flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids); flecs_rest_bool_param(req, "colors", &desc->serialize_colors); flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "table", &desc->serialize_table); } static bool flecs_rest_reply_entity( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *path = &req->path[7]; ecs_dbg_2("rest: request entity '%s'", path); ecs_os_linc(&ecs_rest_entity_count); ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { ecs_dbg_2("rest: entity '%s' not found", path); flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; ecs_os_linc(&ecs_rest_entity_error_count); return true; } ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; flecs_rest_parse_json_ser_entity_params(world, &desc, req); if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static bool flecs_rest_reply_world( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static ecs_entity_t flecs_rest_entity_from_path( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; } return e; } static bool flecs_rest_set( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *data = ecs_http_get_param(req, "data"); ecs_from_json_desc_t desc = {0}; desc.expr = data; desc.name = path; if (ecs_entity_from_json(world, e, data, &desc) == NULL) { flecs_reply_error(reply, "invalid request"); reply->code = 400; return true; } return true; } static bool flecs_rest_delete( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_delete_error_count); return true; } ecs_delete(world, e); return true; } static bool flecs_rest_enable( ecs_world_t *world, ecs_http_reply_t *reply, const char *path, bool enable) { ecs_os_linc(&ecs_rest_enable_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_enable_error_count); return true; } ecs_enable(world, e, enable); return true; } static bool flecs_rest_script( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; #ifdef FLECS_PLECS const char *data = ecs_http_get_param(req, "data"); if (!data) { flecs_reply_error(reply, "missing data parameter"); return true; } bool prev_color = ecs_log_enable_colors(false); rest_prev_log = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; ecs_entity_t script = ecs_script(world, { .entity = ecs_entity(world, { .name = "scripts.main" }), .str = data }); if (!script) { char *err = flecs_rest_get_captured_log(); char *escaped_err = ecs_astresc('"', err); flecs_reply_error(reply, escaped_err); ecs_os_linc(&ecs_rest_query_error_count); reply->code = 400; /* bad request */ ecs_os_free(escaped_err); ecs_os_free(err); } ecs_os_api.log_ = rest_prev_log; ecs_log_enable_colors(prev_color); return true; #else return false; #endif } static void flecs_rest_reply_set_captured_log( ecs_http_reply_t *reply) { char *err = flecs_rest_get_captured_log(); if (err) { char *escaped_err = ecs_astresc('"', err); flecs_reply_error(reply, escaped_err); reply->code = 400; ecs_os_free(escaped_err); ecs_os_free(err); } } static int flecs_rest_iter_to_reply( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = {false}; desc.serialize_entities = true; desc.serialize_variables = true; flecs_rest_parse_json_ser_iter_params(&desc, req); int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); if (offset < 0 || limit < 0) { flecs_reply_error(reply, "invalid offset/limit parameter"); reply->code = 400; return -1; } ecs_iter_t pit = ecs_page_iter(it, offset, limit); if (ecs_iter_to_json_buf(world, &pit, &reply->body, &desc)) { flecs_rest_reply_set_captured_log(reply); return -1; } return 0; } static 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_os_linc(&ecs_rest_query_name_count); ecs_entity_t q = ecs_lookup_fullpath(world, name); if (!q) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); if (!poly) { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } ecs_iter_t it; ecs_iter_poly(world, poly->poly, &it, NULL); ecs_dbg_2("rest: request query '%s'", q); bool prev_color = ecs_log_enable_colors(false); rest_prev_log = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; const char *vars = ecs_http_get_param(req, "vars"); if (vars) { if (!ecs_poly_is(poly->poly, ecs_rule_t)) { flecs_reply_error(reply, "variables are only supported for rule queries"); reply->code = 400; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) { flecs_rest_reply_set_captured_log(reply); ecs_os_linc(&ecs_rest_query_name_error_count); return true; } } flecs_rest_iter_to_reply(world, req, reply, &it); ecs_os_api.log_ = rest_prev_log; ecs_log_enable_colors(prev_color); return true; } static bool flecs_rest_reply_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { const char *q_name = ecs_http_get_param(req, "name"); if (q_name) { return flecs_rest_reply_existing_query(world, req, reply, q_name); } ecs_os_linc(&ecs_rest_query_count); const char *q = ecs_http_get_param(req, "q"); if (!q) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); reply->code = 400; /* bad request */ ecs_os_linc(&ecs_rest_query_error_count); return true; } ecs_dbg_2("rest: request query '%s'", q); bool prev_color = ecs_log_enable_colors(false); rest_prev_log = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ .expr = q }); if (!r) { flecs_rest_reply_set_captured_log(reply); ecs_os_linc(&ecs_rest_query_error_count); } else { ecs_iter_t it = ecs_rule_iter(world, r); flecs_rest_iter_to_reply(world, req, reply, &it); ecs_rule_fini(r); } ecs_os_api.log_ = rest_prev_log; ecs_log_enable_colors(prev_color); return true; } #ifdef FLECS_MONITOR static void flecs_rest_array_append_( ecs_strbuf_t *reply, const char *field, int32_t field_len, const ecs_float_t *values, int32_t t) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "[", ","); int32_t i; for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { int32_t index = i % ECS_STAT_WINDOW; ecs_strbuf_list_next(reply); ecs_strbuf_appendflt(reply, (double)values[index], '"'); } ecs_strbuf_list_pop(reply, "]"); } #define flecs_rest_array_append(reply, field, values, t)\ flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) static void flecs_rest_gauge_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "{", ","); flecs_rest_array_append(reply, "avg", m->gauge.avg, t); flecs_rest_array_append(reply, "min", m->gauge.min, t); flecs_rest_array_append(reply, "max", m->gauge.max, t); if (brief) { ecs_strbuf_list_appendlit(reply, "\"brief\":\""); ecs_strbuf_appendstrn(reply, brief, brief_len); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_counter_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); } #define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_GAUGE_APPEND(reply, s, field, brief)\ ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) #define ECS_COUNTER_APPEND(reply, s, field, brief)\ ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) static void flecs_world_stats_to_json( ecs_strbuf_t *reply, const EcsWorldStats *monitor_stats) { const ecs_world_stats_t *stats = &monitor_stats->stats; ecs_strbuf_list_push(reply, "{", ","); ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.get_mut_count, "Get_mut commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); ECS_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags"); ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)"); ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices"); ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables"); ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, ids.count, "Component, tag and pair ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, ids.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, rest.request_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache"); ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests"); ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests"); ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); ecs_strbuf_appendch(reply, '"'); if (!stats->task) { ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); } ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); ecs_strbuf_list_pop(reply, "}"); } static void flecs_pipeline_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, const EcsPipelineStats *stats) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = ecs_vec_count(&stats->stats.systems), sync_cur = 0; ecs_entity_t *ids = ecs_vec_first_t(&stats->stats.systems, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; ecs_strbuf_list_next(reply); if (id) { ecs_system_stats_t *sys_stats = ecs_map_get_deref( &stats->stats.system_stats, ecs_system_stats_t, id); flecs_system_stats_to_json(world, reply, id, sys_stats); } else { /* Sync point */ ecs_strbuf_list_push(reply, "{", ","); ecs_sync_stats_t *sync_stats = ecs_vec_get_t( &stats->stats.sync_points, ecs_sync_stats_t, sync_cur); ecs_strbuf_list_appendlit(reply, "\"system_count\":"); ecs_strbuf_appendint(reply, sync_stats->system_count); ecs_strbuf_list_appendlit(reply, "\"multi_threaded\":"); ecs_strbuf_appendbool(reply, sync_stats->multi_threaded); ecs_strbuf_list_appendlit(reply, "\"no_readonly\":"); ecs_strbuf_appendbool(reply, sync_stats->no_readonly); ECS_GAUGE_APPEND_T(reply, sync_stats, time_spent, stats->stats.t, ""); ECS_GAUGE_APPEND_T(reply, sync_stats, commands_enqueued, stats->stats.t, ""); ecs_strbuf_list_pop(reply, "}"); sync_cur ++; } } ecs_strbuf_list_pop(reply, "]"); } static bool flecs_rest_reply_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *period_str = NULL; flecs_rest_string_param(req, "period", &period_str); char *category = &req->path[6]; ecs_entity_t period = EcsPeriod1s; if (period_str) { char *period_name = ecs_asprintf("Period%s", period_str); period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); ecs_os_free(period_name); if (!period) { flecs_reply_error(reply, "bad request (invalid period string)"); reply->code = 400; ecs_os_linc(&ecs_rest_stats_error_count); return false; } } if (!ecs_os_strcmp(category, "world")) { const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, EcsWorldStats, period); flecs_world_stats_to_json(&reply->body, stats); ecs_os_linc(&ecs_rest_world_stats_count); return true; } else if (!ecs_os_strcmp(category, "pipeline")) { const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); flecs_pipeline_stats_to_json(world, &reply->body, stats); ecs_os_linc(&ecs_rest_pipeline_stats_count); return true; } else { flecs_reply_error(reply, "bad request (unsupported category)"); reply->code = 400; ecs_os_linc(&ecs_rest_stats_error_count); return false; } return true; } #else static bool flecs_rest_reply_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; return false; } #endif static void flecs_rest_reply_table_append_type( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = table->type.count; ecs_id_t *ids = table->type.array; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(reply); ecs_strbuf_appendch(reply, '"'); ecs_id_str_buf(world, ids[i], reply); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "]"); } static void flecs_rest_reply_table_append_memory( ecs_strbuf_t *reply, const ecs_table_t *table) { int32_t used = 0, allocated = 0; used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*); int32_t i, storage_count = table->column_count; ecs_column_t *columns = table->data.columns; for (i = 0; i < storage_count; i ++) { used += columns[i].data.count * columns[i].ti->size; allocated += columns[i].data.size * columns[i].ti->size; } ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_append(reply, "\"used\":%d", used); ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated); ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_reply_table_append( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_next(reply); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id); ecs_strbuf_list_appendstr(reply, "\"type\":"); flecs_rest_reply_table_append_type(world, reply, table); ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); ecs_strbuf_list_append(reply, "\"memory\":"); flecs_rest_reply_table_append_memory(reply, table); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_reply_tables( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_rest_reply_table_append(world, &reply->body, table); } ecs_strbuf_list_pop(&reply->body, "]"); return true; } static bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) { ecs_rest_ctx_t *impl = ctx; ecs_world_t *world = impl->world; ecs_os_linc(&ecs_rest_request_count); if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } if (req->method == EcsHttpGet) { /* Entity endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_reply_entity(world, req, reply); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_reply_query(world, req, reply); /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_reply_world(world, req, reply); /* Stats endpoint */ } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { return flecs_rest_reply_stats(world, req, reply); /* Tables endpoint */ } else if (!ecs_os_strncmp(req->path, "tables", 6)) { return flecs_rest_reply_tables(world, req, reply); } } else if (req->method == EcsHttpPut) { /* Set endpoint */ if (!ecs_os_strncmp(req->path, "set/", 4)) { return flecs_rest_set(world, req, reply, &req->path[4]); /* Delete endpoint */ } else if (!ecs_os_strncmp(req->path, "delete/", 7)) { return flecs_rest_delete(world, reply, &req->path[7]); /* Enable endpoint */ } else if (!ecs_os_strncmp(req->path, "enable/", 7)) { return flecs_rest_enable(world, reply, &req->path[7], true); /* Disable endpoint */ } else if (!ecs_os_strncmp(req->path, "disable/", 8)) { return flecs_rest_enable(world, reply, &req->path[8], false); /* Script endpoint */ } else if (!ecs_os_strncmp(req->path, "script", 6)) { return flecs_rest_script(world, req, reply); } } return false; } ecs_http_server_t* ecs_rest_server_init( ecs_world_t *world, const ecs_http_server_desc_t *desc) { ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); ecs_http_server_desc_t private_desc = {0}; if (desc) { private_desc = *desc; } private_desc.callback = flecs_rest_reply; private_desc.ctx = srv_ctx; ecs_http_server_t *srv = ecs_http_server_init(&private_desc); if (!srv) { ecs_os_free(srv_ctx); return NULL; } srv_ctx->world = world; srv_ctx->srv = srv; srv_ctx->rc = 1; srv_ctx->srv = srv; return srv; } void ecs_rest_server_fini( ecs_http_server_t *srv) { ecs_rest_ctx_t *srv_ctx = ecs_http_server_ctx(srv); ecs_os_free(srv_ctx); ecs_http_server_fini(srv); } static void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = it->ptrs[0]; int i; for(i = 0; i < it->count; i ++) { if (!rest[i].port) { rest[i].port = ECS_REST_DEFAULT_PORT; } ecs_http_server_t *srv = ecs_rest_server_init(it->real_world, &(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port }); if (!srv) { const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; ecs_err("failed to create REST server on %s:%u", ipaddr, rest[i].port); continue; } rest[i].impl = ecs_http_server_ctx(srv); ecs_http_server_start(srv); } } static void DequeueRest(ecs_iter_t *it) { EcsRest *rest = ecs_field(it, EcsRest, 1); if (it->delta_system_time > (ecs_ftime_t)1.0) { ecs_warn( "detected large progress interval (%.2fs), REST request may timeout", (double)it->delta_system_time); } int32_t i; for(i = 0; i < it->count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; if (ctx) { ecs_http_server_dequeue(ctx->srv, it->delta_time); } } } static void DisableRest(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_id(EcsRest), .src.flags = EcsSelf }); if (it->event == EcsOnAdd) { /* REST module was disabled */ while (ecs_term_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 1); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_stop(ctx->srv); } } } else if (it->event == EcsOnRemove) { /* REST module was enabled */ while (ecs_term_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 1); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_start(ctx->srv); } } } } void FlecsRestImport( ecs_world_t *world) { ECS_MODULE(world, FlecsRest); ECS_IMPORT(world, FlecsPipeline); #ifdef FLECS_PLECS ECS_IMPORT(world, FlecsScript); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsRest); ecs_set_hooks(world, EcsRest, { .ctor = ecs_default_ctor, .move = ecs_move(EcsRest), .copy = ecs_copy(EcsRest), .dtor = ecs_dtor(EcsRest), .on_set = flecs_on_set_rest }); ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); ecs_system(world, { .entity = ecs_id(DequeueRest), .no_readonly = true }); ecs_observer(world, { .filter = { .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} }, .events = {EcsOnAdd, EcsOnRemove}, .callback = DisableRest }); ecs_set_name_prefix(world, "EcsRest"); ECS_TAG_DEFINE(world, EcsRestPlecs); } #endif /** * @file addons/snapshot.c * @brief Snapshot addon. */ #ifdef FLECS_SNAPSHOT /* World snapshot */ struct ecs_snapshot_t { ecs_world_t *world; ecs_entity_index_t entity_index; ecs_vec_t tables; uint64_t last_id; }; /** Small footprint data structure for storing data associated with a table. */ typedef struct ecs_table_leaf_t { ecs_table_t *table; ecs_type_t type; ecs_data_t *data; } ecs_table_leaf_t; static ecs_data_t* flecs_duplicate_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *main_data) { if (!ecs_table_count(table)) { return NULL; } ecs_data_t *result = ecs_os_calloc_t(ecs_data_t); int32_t i, column_count = table->column_count; result->columns = flecs_wdup_n(world, ecs_column_t, column_count, main_data->columns); /* Copy entities and records */ ecs_allocator_t *a = &world->allocator; result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t); result->records = ecs_vec_copy_t(a, &main_data->records, ecs_record_t*); /* Copy each column */ for (i = 0; i < column_count; i ++) { ecs_column_t *column = &result->columns[i]; ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); int32_t size = ti->size; ecs_copy_t copy = ti->hooks.copy; if (copy) { ecs_vec_t dst = ecs_vec_copy(a, &column->data, size); int32_t count = ecs_vec_count(&column->data); void *dst_ptr = ecs_vec_first(&dst); void *src_ptr = ecs_vec_first(&column->data); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(dst_ptr, count, ti); } copy(dst_ptr, src_ptr, count, ti); column->data = dst; } else { column->data = ecs_vec_copy(a, &column->data, size); } } return result; } static void snapshot_table( const ecs_world_t *world, ecs_snapshot_t *snapshot, ecs_table_t *table) { if (table->flags & EcsTableHasBuiltins) { return; } ecs_table_leaf_t *l = ecs_vec_get_t( &snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); l->table = table; l->type = flecs_type_copy( ECS_CONST_CAST(ecs_world_t*, world), &table->type); l->data = flecs_duplicate_data( ECS_CONST_CAST(ecs_world_t*, world), table, &table->data); } static ecs_snapshot_t* snapshot_create( const ecs_world_t *world, const ecs_entity_index_t *entity_index, ecs_iter_t *iter, ecs_iter_next_action_t next) { ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_run_aperiodic(ECS_CONST_CAST(ecs_world_t*, world), 0); result->world = ECS_CONST_CAST(ecs_world_t*, world); /* If no iterator is provided, the snapshot will be taken of the entire * world, and we can simply copy the entity index as it will be restored * entirely upon snapshote restore. */ if (!iter && entity_index) { flecs_entities_copy(&result->entity_index, entity_index); } /* Create vector with as many elements as tables, so we can store the * snapshot tables at their element ids. When restoring a snapshot, the code * will run a diff between the tables in the world and the snapshot, to see * which of the world tables still exist, no longer exist, or need to be * deleted. */ uint64_t t, table_count = flecs_sparse_last_id(&world->store.tables) + 1; ecs_vec_init_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); ecs_vec_set_count_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); ecs_table_leaf_t *arr = ecs_vec_first_t(&result->tables, ecs_table_leaf_t); /* Array may have holes, so initialize with 0 */ ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); /* Iterate tables in iterator */ if (iter) { while (next(iter)) { ecs_table_t *table = iter->table; snapshot_table(world, result, table); } } else { for (t = 1; t < table_count; t ++) { ecs_table_t *table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, t); snapshot_table(world, result, table); } } return result; } /** Create a snapshot */ ecs_snapshot_t* ecs_snapshot_take( ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); ecs_snapshot_t *result = snapshot_create( world, ecs_eis(world), NULL, NULL); result->last_id = flecs_entities_max_id(world); return result; } /** Create a filtered snapshot */ ecs_snapshot_t* ecs_snapshot_take_w_iter( ecs_iter_t *iter) { ecs_world_t *world = iter->world; ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_snapshot_t *result = snapshot_create( world, ecs_eis(world), iter, iter ? iter->next : NULL); result->last_id = flecs_entities_max_id(world); return result; } /* Restoring an unfiltered snapshot restores the world to the exact state it was * when the snapshot was taken. */ static void restore_unfiltered( ecs_world_t *world, ecs_snapshot_t *snapshot) { flecs_entity_index_restore(ecs_eis(world), &snapshot->entity_index); flecs_entity_index_fini(&snapshot->entity_index); flecs_entities_max_id(world) = snapshot->last_id; ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); int32_t snapshot_count = ecs_vec_count(&snapshot->tables); for (i = 1; i <= count; i ++) { ecs_table_t *world_table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, (uint32_t)i); if (world_table && (world_table->flags & EcsTableHasBuiltins)) { continue; } ecs_table_leaf_t *snapshot_table = NULL; if (i < snapshot_count) { snapshot_table = &leafs[i]; if (!snapshot_table->table) { snapshot_table = NULL; } } /* If the world table no longer exists but the snapshot table does, * reinsert it */ if (!world_table && snapshot_table) { ecs_table_t *table = flecs_table_find_or_create(world, &snapshot_table->type); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (snapshot_table->data) { flecs_table_replace_data(world, table, snapshot_table->data); } /* If the world table still exists, replace its data */ } else if (world_table && snapshot_table) { ecs_assert(snapshot_table->table == world_table, ECS_INTERNAL_ERROR, NULL); if (snapshot_table->data) { flecs_table_replace_data( world, world_table, snapshot_table->data); } else { flecs_table_clear_data( world, world_table, &world_table->data); flecs_table_init_data(world, world_table); } /* If the snapshot table doesn't exist, this table was created after the * snapshot was taken and needs to be deleted */ } else if (world_table && !snapshot_table) { /* Deleting a table invokes OnRemove triggers & updates the entity * index. That is not what we want, since entities may no longer be * valid (if they don't exist in the snapshot) or may have been * restored in a different table. Therefore first clear the data * from the table (which doesn't invoke triggers), and then delete * the table. */ flecs_table_clear_data(world, world_table, &world_table->data); flecs_delete_table(world, world_table); /* If there is no world & snapshot table, nothing needs to be done */ } else { } if (snapshot_table) { ecs_os_free(snapshot_table->data); flecs_type_free(world, &snapshot_table->type); } } /* Now that all tables have been restored and world is in a consistent * state, run OnSet systems */ int32_t world_count = flecs_sparse_count(&world->store.tables); for (i = 0; i < world_count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( &world->store.tables, ecs_table_t, i); if (table->flags & EcsTableHasBuiltins) { continue; } int32_t tcount = ecs_table_count(table); if (tcount) { int32_t j, storage_count = table->column_count; for (j = 0; j < storage_count; j ++) { ecs_type_t type = { .array = &table->data.columns[j].id, .count = 1 }; flecs_notify_on_set(world, table, 0, tcount, &type, true); } } } } /* Restoring a filtered snapshots only restores the entities in the snapshot * to their previous state. */ static void restore_filtered( ecs_world_t *world, ecs_snapshot_t *snapshot) { ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); int32_t l = 0, snapshot_count = ecs_vec_count(&snapshot->tables); for (l = 0; l < snapshot_count; l ++) { ecs_table_leaf_t *snapshot_table = &leafs[l]; ecs_table_t *table = snapshot_table->table; if (!table) { continue; } ecs_data_t *data = snapshot_table->data; if (!data) { flecs_type_free(world, &snapshot_table->type); continue; } /* Delete entity from storage first, so that when we restore it to the * current table we can be sure that there won't be any duplicates */ int32_t i, entity_count = ecs_vec_count(&data->entities); ecs_entity_t *entities = ecs_vec_first( &snapshot_table->data->entities); for (i = 0; i < entity_count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *r = flecs_entities_try(world, e); if (r && r->table) { flecs_table_delete(world, r->table, ECS_RECORD_TO_ROW(r->row), true); } else { /* Make sure that the entity has the same generation count */ flecs_entities_set_generation(world, e); } } /* Merge data from snapshot table with world table */ int32_t old_count = ecs_table_count(snapshot_table->table); int32_t new_count = flecs_table_data_count(snapshot_table->data); flecs_table_merge(world, table, table, &table->data, snapshot_table->data); /* Run OnSet systems for merged entities */ if (new_count) { int32_t j, storage_count = table->column_count; for (j = 0; j < storage_count; j ++) { ecs_type_t type = { .array = &table->data.columns[j].id, .count = 1 }; flecs_notify_on_set( world, table, old_count, new_count, &type, true); } } flecs_wfree_n(world, ecs_column_t, table->column_count, snapshot_table->data->columns); ecs_os_free(snapshot_table->data); flecs_type_free(world, &snapshot_table->type); } } /** Restore a snapshot */ void ecs_snapshot_restore( ecs_world_t *world, ecs_snapshot_t *snapshot) { ecs_run_aperiodic(world, 0); if (flecs_entity_index_count(&snapshot->entity_index) > 0) { /* Unfiltered snapshots have a copy of the entity index which is * copied back entirely when the snapshot is restored */ restore_unfiltered(world, snapshot); } else { restore_filtered(world, snapshot); } ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); ecs_os_free(snapshot); } ecs_iter_t ecs_snapshot_iter( ecs_snapshot_t *snapshot) { ecs_snapshot_iter_t iter = { .tables = snapshot->tables, .index = 0 }; return (ecs_iter_t){ .world = snapshot->world, .table_count = ecs_vec_count(&snapshot->tables), .priv.iter.snapshot = iter, .next = ecs_snapshot_next }; } bool ecs_snapshot_next( ecs_iter_t *it) { ecs_snapshot_iter_t *iter = &it->priv.iter.snapshot; ecs_table_leaf_t *tables = ecs_vec_first_t(&iter->tables, ecs_table_leaf_t); int32_t count = ecs_vec_count(&iter->tables); int32_t i; for (i = iter->index; i < count; i ++) { ecs_table_t *table = tables[i].table; if (!table) { continue; } ecs_data_t *data = tables[i].data; it->table = table; it->count = ecs_table_count(table); if (data) { it->entities = ecs_vec_first(&data->entities); } else { it->entities = NULL; } ECS_BIT_SET(it->flags, EcsIterIsValid); iter->index = i + 1; goto yield; } ECS_BIT_CLEAR(it->flags, EcsIterIsValid); return false; yield: ECS_BIT_CLEAR(it->flags, EcsIterIsValid); return true; } /** Cleanup snapshot */ void ecs_snapshot_free( ecs_snapshot_t *snapshot) { flecs_entity_index_fini(&snapshot->entity_index); ecs_table_leaf_t *tables = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); int32_t i, count = ecs_vec_count(&snapshot->tables); for (i = 0; i < count; i ++) { ecs_table_leaf_t *snapshot_table = &tables[i]; ecs_table_t *table = snapshot_table->table; if (table) { ecs_data_t *data = snapshot_table->data; if (data) { flecs_table_clear_data(snapshot->world, table, data); ecs_os_free(data); } flecs_type_free(snapshot->world, &snapshot_table->type); } } ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); ecs_os_free(snapshot); } #endif /** * @file addons/stats.c * @brief Stats addon. */ #ifdef FLECS_SYSTEM /** * @file addons/system/system.h * @brief Internal types and functions for system addon. */ #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #ifdef FLECS_SYSTEM #define ecs_system_t_magic (0x65637383) #define ecs_system_t_tag EcsSystem extern ecs_mixins_t ecs_system_t_mixins; typedef struct ecs_system_t { ecs_header_t hdr; ecs_run_action_t run; /* See ecs_system_desc_t */ ecs_iter_action_t action; /* See ecs_system_desc_t */ ecs_query_t *query; /* System query */ ecs_entity_t query_entity; /* Entity associated with query */ ecs_entity_t tick_source; /* Tick source associated with system */ /* Schedule parameters */ bool multi_threaded; bool no_readonly; int64_t invoke_count; /* Number of times system is invoked */ ecs_ftime_t time_spent; /* Time spent on running system */ ecs_ftime_t time_passed; /* Time passed since last invocation */ int64_t last_frame; /* Last frame for which the system was considered */ void *ctx; /* Userdata for system */ void *binding_ctx; /* Optional language binding context */ ecs_ctx_free_t ctx_free; ecs_ctx_free_t binding_ctx_free; /* Mixins */ ecs_world_t *world; ecs_entity_t entity; ecs_poly_dtor_t dtor; } ecs_system_t; /* Invoked when system becomes active / inactive */ void ecs_system_activate( ecs_world_t *world, ecs_entity_t system, bool activate, const ecs_system_t *system_data); /* Internal function to run a system */ ecs_entity_t ecs_run_intern( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_current, int32_t stage_count, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param); #endif #endif #endif #ifdef FLECS_PIPELINE /** * @file addons/pipeline/pipeline.h * @brief Internal functions/types for pipeline addon. */ #ifndef FLECS_PIPELINE_PRIVATE_H #define FLECS_PIPELINE_PRIVATE_H /** Instruction data for pipeline. * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { int32_t offset; /* Offset in systems vector */ int32_t count; /* Number of systems to run before next op */ double time_spent; /* Time spent merging commands for sync point */ int64_t commands_enqueued; /* Number of commands enqueued for sync point */ bool multi_threaded; /* Whether systems can be ran multi threaded */ bool no_readonly; /* Whether systems are staged or not */ } ecs_pipeline_op_t; struct ecs_pipeline_state_t { ecs_query_t *query; /* Pipeline query */ ecs_vec_t ops; /* Pipeline schedule */ ecs_vec_t systems; /* Vector with system ids */ ecs_entity_t last_system; /* Last system ran by pipeline */ ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ int32_t match_count; /* Used to track of rebuild is necessary */ int32_t rebuild_count; /* Number of pipeline rebuilds */ ecs_iter_t *iters; /* Iterator for worker(s) */ int32_t iter_count; /* Members for continuing pipeline iteration after pipeline rebuild */ ecs_pipeline_op_t *cur_op; /* Current pipeline op */ int32_t cur_i; /* Index in current result */ int32_t ran_since_merge; /* Index in current op */ bool no_readonly; /* Is pipeline in readonly mode */ }; typedef struct EcsPipeline { /* Stable ptr so threads can safely access while entity/components move */ ecs_pipeline_state_t *state; } EcsPipeline; //////////////////////////////////////////////////////////////////////////////// //// Pipeline API //////////////////////////////////////////////////////////////////////////////// bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame); void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); void flecs_create_worker_threads( ecs_world_t *world); void flecs_join_worker_threads( ecs_world_t *world); void flecs_signal_workers( ecs_world_t *world); void flecs_wait_for_sync( ecs_world_t *world); #endif #endif #ifdef FLECS_STATS #define ECS_GAUGE_RECORD(m, t, value)\ flecs_gauge_record(m, t, (ecs_float_t)(value)) #define ECS_COUNTER_RECORD(m, t, value)\ flecs_counter_record(m, t, (double)(value)) #define ECS_METRIC_FIRST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) #define ECS_METRIC_LAST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) static int32_t t_next( int32_t t) { return (t + 1) % ECS_STAT_WINDOW; } static int32_t t_prev( int32_t t) { return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } static void flecs_gauge_record( ecs_metric_t *m, int32_t t, ecs_float_t value) { m->gauge.avg[t] = value; m->gauge.min[t] = value; m->gauge.max[t] = value; } static double flecs_counter_record( ecs_metric_t *m, int32_t t, double value) { int32_t tp = t_prev(t); double prev = m->counter.value[tp]; m->counter.value[t] = value; double gauge_value = value - prev; if (gauge_value < 0) { gauge_value = 0; /* Counters are monotonically increasing */ } flecs_gauge_record(m, t, (ecs_float_t)gauge_value); return gauge_value; } static void flecs_metric_print( const char *name, ecs_float_t value) { ecs_size_t len = ecs_os_strlen(name); ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); } static void flecs_gauge_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->gauge.avg[t]); } static void flecs_counter_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->counter.rate.avg[t]); } void ecs_metric_reduce( ecs_metric_t *dst, const ecs_metric_t *src, int32_t t_dst, int32_t t_src) { ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); bool min_set = false; dst->gauge.avg[t_dst] = 0; dst->gauge.min[t_dst] = 0; dst->gauge.max[t_dst] = 0; ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; int32_t i; for (i = 0; i < ECS_STAT_WINDOW; i ++) { int32_t t = (t_src + i) % ECS_STAT_WINDOW; dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { dst->gauge.min[t_dst] = src->gauge.min[t]; min_set = true; } if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { dst->gauge.max[t_dst] = src->gauge.max[t]; } } dst->counter.value[t_dst] = src->counter.value[t_src]; error: return; } void ecs_metric_reduce_last( ecs_metric_t *m, int32_t prev, int32_t count) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); int32_t t = t_next(prev); if (m->gauge.min[t] < m->gauge.min[prev]) { m->gauge.min[prev] = m->gauge.min[t]; } if (m->gauge.max[t] > m->gauge.max[prev]) { m->gauge.max[prev] = m->gauge.max[t]; } ecs_float_t fcount = (ecs_float_t)(count + 1); ecs_float_t cur = m->gauge.avg[prev]; ecs_float_t next = m->gauge.avg[t]; cur *= ((fcount - 1) / fcount); next *= 1 / fcount; m->gauge.avg[prev] = cur + next; m->counter.value[prev] = m->counter.value[t]; error: return; } void ecs_metric_copy( ecs_metric_t *m, int32_t dst, int32_t src) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); m->gauge.avg[dst] = m->gauge.avg[src]; m->gauge.min[dst] = m->gauge.min[src]; m->gauge.max[dst] = m->gauge.max[src]; m->counter.value[dst] = m->counter.value[src]; error: return; } static void flecs_stats_reduce( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } } static void flecs_stats_reduce_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src, int32_t count) { int32_t t_dst_next = t_next(t_dst); for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { /* Reduce into previous value */ ecs_metric_reduce_last(dst_cur, t_dst, count); /* Restore old value */ dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; } } static void flecs_stats_repeat_last( ecs_metric_t *cur, ecs_metric_t *last, int32_t t) { int32_t prev = t_prev(t); for (; cur <= last; cur ++) { ecs_metric_copy(cur, t, prev); } } static void flecs_stats_copy_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; } } void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t t = s->t = t_next(s->t); double delta_frame_count = ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); double delta_world_time = ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); } else { ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); ECS_GAUGE_RECORD(&s->ids.count, t, world->info.id_count); ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count); ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count); ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count); ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count); ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info)); ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->ids.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQ