/** * @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_make_alive( ecs_entity_index_t *index, uint64_t entity); /* Get current generation of entity */ uint64_t flecs_entity_index_get_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is alive */ bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is valid */ bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity exists */ bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity); /* Create or recycle entity id */ uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index); /* Bulk create or recycle new entity ids */ uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count); /* Set size of index */ void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size); /* Return number of entities in index */ int32_t flecs_entity_index_count( const ecs_entity_index_t *index); /* Return number of allocated entities in index */ int32_t flecs_entity_index_size( const ecs_entity_index_t *index); /* Return number of not alive entities in index */ int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index); /* Clear entity index */ void flecs_entity_index_clear( ecs_entity_index_t *index); /* 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_make_alive(world, entity) flecs_entity_index_make_alive(ecs_eis(world), entity) #define flecs_entities_get_alive(world, entity) flecs_entity_index_get_alive(ecs_eis(world), entity) #define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) #define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) #define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) #define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) #define flecs_entities_max_id(world) (ecs_eis(world)->max_id) #define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) #define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) #define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) #define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) #define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) #define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) #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); /** Deinitialize 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 number of fields gets out of hand, this can be turned into a union * but since events are very temporary objects, this works for now and makes * initializing an event a bit simpler. */ } ecs_table_event_t; /** 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_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, 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); ecs_vec_t* flecs_table_entities( ecs_table_t *table); ecs_entity_t* flecs_table_entities_array( ecs_table_t *table); #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) #define ECS_MAX_DEFER_STACK (8) /* 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 term */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ /* 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 { EcsCmdClone, EcsCmdBulkNew, EcsCmdAdd, EcsCmdRemove, EcsCmdSet, EcsCmdEmplace, EcsCmdEnsure, EcsCmdModified, EcsCmdModifiedNoHook, EcsCmdAddModified, EcsCmdPath, EcsCmdDelete, EcsCmdClear, EcsCmdOnDeleteAction, EcsCmdEnable, EcsCmdDisable, EcsCmdEvent, EcsCmdSkip } ecs_cmd_kind_t; /* Entity specific metadata for command in queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / ensure) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ ecs_cmd_entry_t *entry; ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; ecs_entity_t system; /* System that enqueued the command */ } ecs_cmd_t; /* Data structures that store the command queue */ typedef struct ecs_commands_t { ecs_vec_t queue; ecs_stack_t stack; /* Temp memory used by deferred commands */ ecs_sparse_t entries; /* - command batching */ } ecs_commands_t; /** Callback used to capture commands of a frame */ typedef void (*ecs_on_commands_action_t)( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx); /** 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; /* Zero if not deferred, positive if deferred, negative if suspended */ int32_t defer; /* Command queue stack, for nested execution */ ecs_commands_t *cmd; ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; int32_t cmd_sp; /* 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 */ /* Running system */ ecs_entity_t system; /* 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 */ /* Internal callback for command inspection. Only one callback can be set at * a time. After assignment the action will become active at the start of * the next frame, set by ecs_frame_begin, and will be reset by * ecs_frame_end. */ ecs_on_commands_action_t on_commands; ecs_on_commands_action_t on_commands_active; void *on_commands_ctx; void *on_commands_ctx_active; /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ /* -- 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 data structures 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); 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 #define ecs_poly_assert(object, ty)\ do {\ ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\ const ecs_header_t *hdr = (const ecs_header_t *)object;\ const char *type_name = hdr->mixins->type_name;\ ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, type_name);\ ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\ } while (0) #else #define ecs_poly_assert(object, ty) #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 flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage); void flecs_enqueue( ecs_world_t *world, ecs_stage_t *stage, ecs_event_desc_t *desc); void flecs_commands_push( ecs_stage_t *stage); void flecs_commands_pop( ecs_stage_t *stage); ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system); #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_make_alive(world, name);\ ecs_add_id(world, name, EcsFinal);\ ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ ecs_set_symbol(world, name, #name); #define flecs_bootstrap_trait(world, name)\ flecs_bootstrap_tag(world, name)\ ecs_add_id(world, name, EcsTrait) /* 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 used for order_by */ int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); /* Compare function for component ids used for qsort */ int flecs_id_qsort_cmp( const void *a, const void *b); /* Load file contents into string */ char* flecs_load_from_file( const char *filename); 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_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); 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); #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; if (!ecs_has_id(world, e, EcsRelationship) && !ecs_has_id(world, e, EcsTarget)) { idr = flecs_id_record_ensure(world, e); changed |= flecs_set_id_flag(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 != EcsOnRemove) { ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } if (it->event == EcsOnSet) { if (flecs_type_info_init_id( world, e, c[i].size, c[i].alignment, NULL)) { flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); } } else if (it->event == EcsOnRemove) { 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); } } } static void flecs_observer_set_disable_bit( ecs_world_t *world, ecs_entity_t e, ecs_flags32_t bit, bool cond) { const EcsPoly *poly = ecs_get_pair(world, e, EcsPoly, EcsObserver); if (!poly || !poly->poly) { return; } ecs_observer_t *o = poly->poly; if (o->flags & EcsObserverIsMulti) { /* If this is a multi-component observer, set flag on single-term * observer children. */ ecs_iter_t child_it = ecs_children(world, e); while (ecs_children_next(&child_it)) { ecs_table_t *table = child_it.table; if (ecs_table_has_id(world, table, EcsObserver)) { int32_t i; for (i = 0; i < child_it.count; i ++) { flecs_observer_set_disable_bit( world, child_it.entities[i], bit, cond); } } } } else { ecs_poly_assert(o, ecs_observer_t); ECS_BIT_COND(o->flags, bit, cond); } } static void flecs_disable_observer( ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_entity_t evt = it->event; int32_t i, count = it->count; for (i = 0; i < count; i ++) { flecs_observer_set_disable_bit(world, it->entities[i], EcsObserverIsDisabled, evt == EcsOnAdd); } } static void flecs_disable_module_observers( ecs_world_t *world, ecs_entity_t module, bool should_disable) { ecs_iter_t child_it = ecs_children(world, module); while (ecs_children_next(&child_it)) { ecs_table_t *table = child_it.table; bool table_disabled = table->flags & EcsTableIsDisabled; int32_t i; /* Recursively walk modules, don't propagate to disabled modules */ if (ecs_table_has_id(world, table, EcsModule) && !table_disabled) { for (i = 0; i < child_it.count; i ++) { flecs_disable_module_observers( world, child_it.entities[i], should_disable); } continue; } /* Only disable observers */ if (!ecs_table_has_id(world, table, EcsObserver)) { continue; } for (i = 0; i < child_it.count; i ++) { flecs_observer_set_disable_bit(world, child_it.entities[i], EcsObserverIsParentDisabled, should_disable); } } } static void flecs_disable_module(ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { flecs_disable_module_observers( it->world, it->entities[i], it->event == EcsOnAdd); } } /* -- 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); } /* -- 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, 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->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_make_alive(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(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_make_alive(world, ecs_id(EcsComponent)); ecs_make_alive(world, EcsFinal); ecs_make_alive(world, ecs_id(EcsIdentifier)); ecs_make_alive(world, EcsName); ecs_make_alive(world, EcsSymbol); ecs_make_alive(world, EcsAlias); ecs_make_alive(world, EcsChildOf); ecs_make_alive(world, EcsFlecs); ecs_make_alive(world, EcsFlecsCore); ecs_make_alive(world, EcsOnAdd); ecs_make_alive(world, EcsOnRemove); ecs_make_alive(world, EcsOnSet); ecs_make_alive(world, EcsUnSet); ecs_make_alive(world, EcsOnDelete); ecs_make_alive(world, EcsPanic); ecs_make_alive(world, EcsFlag); ecs_make_alive(world, EcsIsA); ecs_make_alive(world, EcsWildcard); ecs_make_alive(world, EcsAny); ecs_make_alive(world, 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, EcsFlattenTarget, { 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, EcsFlattenTarget); /* Patch up symbol of EcsIterable. The type is a typedef, which causes a * symbol mismatch when registering the type with the C++ API. */ ecs_set_symbol(world, ecs_id(EcsIterable), "ecs_iterable_t"); /* 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_trait(world, EcsTransitive); flecs_bootstrap_trait(world, EcsReflexive); flecs_bootstrap_trait(world, EcsSymmetric); flecs_bootstrap_trait(world, EcsFinal); flecs_bootstrap_trait(world, EcsDontInherit); flecs_bootstrap_trait(world, EcsAlwaysOverride); flecs_bootstrap_trait(world, EcsTag); flecs_bootstrap_trait(world, EcsUnion); flecs_bootstrap_trait(world, EcsExclusive); flecs_bootstrap_trait(world, EcsAcyclic); flecs_bootstrap_trait(world, EcsTraversable); flecs_bootstrap_trait(world, EcsWith); flecs_bootstrap_trait(world, EcsOneOf); flecs_bootstrap_trait(world, EcsTrait); flecs_bootstrap_trait(world, EcsRelationship); flecs_bootstrap_trait(world, EcsTarget); flecs_bootstrap_trait(world, EcsOnDelete); flecs_bootstrap_trait(world, EcsOnDeleteTarget); ecs_add_id(world, ecs_id(EcsFlattenTarget), EcsTrait); 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); /* Relationships */ ecs_add_id(world, EcsChildOf, EcsRelationship); ecs_add_id(world, EcsIsA, EcsRelationship); ecs_add_id(world, EcsSlotOf, EcsRelationship); ecs_add_id(world, EcsDependsOn, EcsRelationship); ecs_add_id(world, EcsWith, EcsRelationship); ecs_add_id(world, EcsOnDelete, EcsRelationship); ecs_add_id(world, EcsOnDeleteTarget, EcsRelationship); ecs_add_id(world, ecs_id(EcsIdentifier), EcsRelationship); /* 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 }); /* Observer that tracks whether observers are disabled */ ecs_observer(world, { .filter.terms = { { .id = EcsObserver, .src.flags = EcsSelf|EcsFilter }, { .id = EcsDisabled, .src.flags = EcsSelf }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_observer }); /* Observer that tracks whether modules are disabled */ ecs_observer(world, { .filter.terms = { { .id = EcsModule, .src.flags = EcsSelf|EcsFilter }, { .id = EcsDisabled, .src.flags = EcsSelf }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_module }); /* 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); ecs_add_id(world, ecs_id(EcsComponent), EcsDontInherit); ecs_add_id(world, EcsOnDelete, 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_assert(world->idr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(world->idr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, 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 int32_t flecs_child_type_insert( ecs_type_t *type, void **component_data, ecs_id_t id) { int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t cur = type->array[i]; if (cur == id) { /* Id is already part of type */ return -1; } if (cur > id) { /* A larger id was found so id can't be part of the type. */ break; } } /* Assumes that the array has enough memory to store the new element. */ int32_t to_move = type->count - i; if (to_move) { ecs_os_memmove(&type->array[i + 1], &type->array[i], to_move * ECS_SIZEOF(ecs_id_t)); ecs_os_memmove(&component_data[i + 1], &component_data[i], to_move * ECS_SIZEOF(void*)); } component_data[i] = NULL; type->array[i] = id; type->count ++; return i; } 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_table_diff_t diff = { .added = {0}}; diff.added.array = ecs_os_alloca_n(ecs_entity_t, type_count + 1); void **component_data = ecs_os_alloca_n(void*, type_count + 1); /* Copy in component identifiers. Find the base index in the component * array, since we'll need this to replace the base with the instance id */ int j, i, childof_base_index = -1; for (i = 0; i < type_count; i ++) { ecs_id_t id = ids[i]; /* If id has DontInherit flag don't inherit it, except for the name * and ChildOf pairs. The name is preserved so applications can lookup * the instantiated children by name. The ChildOf pair is replaced later * with the instance parent. */ if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && ECS_PAIR_FIRST(id) != EcsChildOf) { 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) == (uint32_t)base)) { childof_base_index = diff.added.count; } 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[diff.added.count] = ecs_vec_first(column); } else { component_data[diff.added.count] = NULL; } diff.added.array[diff.added.count] = id; diff.added.count ++; } /* Table must contain children of base */ ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); /* If children are added to a prefab, make sure they are prefabs too */ if (table->flags & EcsTableIsPrefab) { if (flecs_child_type_insert( &diff.added, component_data, EcsPrefab) != -1) { childof_base_index ++; } } /* Instantiate the prefab child table for each new instance */ ecs_entity_t *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_t *i_table = NULL; /* Replace ChildOf element in the component array with instance id */ diff.added.array[childof_base_index] = ecs_pair(EcsChildOf, instance); /* Find or create table */ i_table = flecs_table_find_or_create(world, &diff.added); ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == diff.added.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself * which would cause infinite recursion. */ 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; const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, &diff.added, child_count, component_data, false, &child_row, &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, 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, 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; 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); } 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_ensure( 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) { int32_t defer = world->stages[0].defer; if (defer < 0) { world->stages[0].defer *= -1; } ecs_iter_t it = { .field_count = 1}; it.entities = entities; 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); world->stages[0].defer = defer; } 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, NULL, NULL, false))) { 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, NULL, NULL, false))) { 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_make_alive(world, result); name_assigned = ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName); if (name && name_assigned) { /* If entity has name, verify that name matches. The name provided * to the function could either have been relative to the current * scope, or fully qualified. */ char *path; ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { /* Fully qualified name was provided, so make sure to * compare with fully qualified name */ path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); } else { /* Relative name was provided, so make sure to compare with * relative name */ if (!sep || sep[0]) { path = ecs_get_path_w_sep(world, scope, result, sep, ""); } else { /* Safe, only freed when sep is valid */ path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); } } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); if (!sep || sep[0]) { ecs_os_free(path); } return 0; } if (!sep || sep[0]) { ecs_os_free(path); } } } } ecs_assert(name_assigned == ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); if (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_make_alive(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; ecs_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_make_alive(world, result); new_component = ecs_has(world, result, EcsComponent); } if (desc->type.name && new_component) { ecs_add_path(world, result, 0, desc->type.name); } EcsComponent *ptr = ecs_ensure(world, result, EcsComponent); if (!ptr->size) { ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; if (!new_component || ptr->size != desc->type.size) { if (!ptr->size) { ecs_trace("#[green]tag#[reset] %s created", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s created", ecs_get_name(world, result)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } ecs_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_err("(OnDelete, Panic) constraint violated while deleting %s", id_str); ecs_os_free(id_str); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, ecs_entity_t action, bool delete_id) { ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, &world->store.marked_ids, ecs_marked_id_t); m->idr = idr; m->id = idr->id; m->action = action; m->delete_id = delete_id; flecs_id_record_claim(world, idr); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id); 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); int32_t i, count = ecs_vec_count(&table->data.entities); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[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 = { .added = {0} }; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst_table = table; /* To find the dst table, remove all ids that are marked for deletion */ int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); const ecs_table_record_t *tr; for (i = 0; i < count; i ++) { const ecs_id_record_t *idr = ids[i].idr; if (!(tr = flecs_id_record_get_table(idr, dst_table))) { continue; } t = tr->index; do { ecs_id_t id = dst_table->type.array[t]; ecs_table_t *tgt_table = flecs_table_traverse_remove( world, dst_table, &id, &temp_diff); ecs_assert(tgt_table != dst_table, ECS_INTERNAL_ERROR, NULL); dst_table = tgt_table; flecs_table_diff_build_append_table(world, &diff, &temp_diff); } while (dst_table->type.count && (t = ecs_search_offset( world, dst_table, t, idr->id, NULL)) != -1); } ecs_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_table_t *dst_table = src_table; if (src_table->flags & EcsTableHasName) { dst_table = ecs_table_remove_id(world, src_table, ecs_pair_t(EcsIdentifier, EcsName)); } ecs_type_t dst_type = dst_table->type; ecs_table_diff_t diff = { .added = dst_type }; ecs_record_t *dst_r = flecs_entities_get(world, dst); flecs_new_entity(world, dst, dst_r, dst_table, &diff, true, true); int32_t row = ECS_RECORD_TO_ROW(dst_r->row); if (copy_value) { flecs_table_move(world, dst, src, dst_table, row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); int32_t i, count = dst_table->column_count; for (i = 0; i < count; i ++) { ecs_type_t type = { .array = &dst_table->data.columns[i].id, .count = 1 }; flecs_notify_on_set(world, dst_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( 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 || (tr->column == -1)) { return 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_ensure_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, EcsCmdEnsure, entity, id, 0, NULL); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_ensure(world, entity, id, r).ptr; ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return result; error: return NULL; } void* ecs_ensure_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, EcsCmdSet, entity, id, 0, NULL); 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( const 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_ensure_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); result.table_id = table->id; } return result; error: return (ecs_ref_t){0}; } static bool flecs_ref_needs_sync( ecs_ref_t *ref, ecs_table_record_t *tr, const ecs_table_t *table) { ecs_assert(ref != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return !tr || tr->hdr.table != table || ref->table_id != table->id; } 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 (flecs_ref_needs_sync(ref, tr, 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); ref->table_id = table->id; } error: return; } void* ecs_ref_get_id( const ecs_world_t *world, ecs_ref_t *ref, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, 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 (flecs_ref_needs_sync(ref, tr, table)) { tr = ref->tr = flecs_table_record_get(world, table, id); if (!tr) { return NULL; } ref->table_id = table->id; 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, EcsCmdEmplace, entity, id, 0, NULL); } 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, bool owned) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } 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, owned); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { 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, EcsCmdSet, entity, id, flecs_utosize(size), ptr); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(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); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind != EcsCmdEmplace) { /* ctor will have happened by ensure */ move = ti->hooks.move_dtor; } else { move = ti->hooks.ctor_move_dtor; } if (move) { move(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, id); if (cmd_kind == EcsCmdSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } 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) { EcsFlattenTarget *tf = ecs_table_get_id(world, table, ecs_pair_t(EcsFlattenTarget, 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(EcsFlattenTarget, 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_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->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); EcsFlattenTarget *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_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->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_ensure_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (tag == EcsName) { /* Insert command after ensure, but before the name is potentially * freed. Even though the name is a const char*, it is possible that the * application passed in the existing name of the entity which could * still cause it to be freed. */ flecs_defer_path(stage, 0, entity, name); } 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_alive(world, entity); if (!current || !flecs_entities_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_make_alive( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); /* 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_make_alive(world, entity); /* Ensure id exists. The underlying data structure will verify that the * generation count matches the provided one. */ flecs_entities_ensure(world, entity); error: return; } void ecs_make_alive_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_alive(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); flecs_entities_ensure(world, r); } if (flecs_entities_get_alive(world, o) == 0) { ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, "second element of pair is not alive"); flecs_entities_ensure(world, o); } } else { flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); } 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); ecs_check(id != 0, 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) { ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); 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_free_cmd_event( ecs_world_t *world, ecs_event_desc_t *desc) { if (desc->ids) { flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); } /* Safe const cast, command makes a copy of type object */ flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), ecs_type_t); if (desc->param) { flecs_dtor_value(world, desc->event, /* Safe const cast, command makes copy of value */ ECS_CONST_CAST(void*, desc->param), 1); } } static void flecs_discard_cmd( ecs_world_t *world, ecs_cmd_t *cmd) { if (cmd->kind == EcsCmdBulkNew) { ecs_os_free(cmd->is._n.entities); } else if (cmd->kind == EcsCmdEvent) { flecs_free_cmd_event(world, cmd->is._1.value); } else { ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); void *value = cmd->is._1.value; if (value) { flecs_dtor_value(world, cmd->id, value, 1); flecs_stack_free(value, cmd->is._1.size); } } } 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 ecs_table_t* flecs_cmd_batch_add_diff( ecs_world_t *world, ecs_table_t *dst, ecs_table_t *cur, ecs_table_t *prev) { int32_t p = 0, p_count = prev->type.count; int32_t c = 0, c_count = cur->type.count; ecs_id_t *p_ids = prev->type.array; ecs_id_t *c_ids = cur->type.array; for (; c < c_count;) { ecs_id_t c_id = c_ids[c]; ecs_id_t p_id = p_ids[p]; if (p_id < c_id) { /* Previous id no longer exists in new table, so it was removed */ dst = ecs_table_remove_id(world, dst, p_id); } if (c_id < p_id) { /* Current id didn't exist in previous table, so it was added */ dst = ecs_table_add_id(world, dst, c_id); } c += c_id <= p_id; p += p_id <= c_id; if (p == p_count) { break; } } /* Remainder */ for (; p < p_count; p ++) { ecs_id_t p_id = p_ids[p]; dst = ecs_table_remove_id(world, dst, p_id); } for (; c < c_count; c ++) { ecs_id_t c_id = c_ids[c]; dst = ecs_table_add_id(world, dst, c_id); } return dst; } 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 = EcsCmdSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsCmdSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsCmdAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsCmdModified; /* fall through */ case EcsCmdAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsCmdModified: if (start_table) { /* If a modified was inserted for an existing component, the value * of the component could have been changed. If this is the case, * call on_set hooks before the OnAdd/OnRemove observers are invoked * when moving the entity to a different table. * This ensures that if OnAdd/OnRemove observers access the modified * component value, the on_set hook has had the opportunity to * run first to set any computed values of the component. */ int32_t row = ECS_RECORD_TO_ROW(r->row); 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)) { ecs_table_t *prev_table = r->table; ecs_defer_begin(world); flecs_invoke_hook(world, start_table, 1, row, &entity, ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); ecs_defer_end(world); /* Don't run on_set hook twice, but make sure to still * invoke observers. */ cmd->kind = EcsCmdModifiedNoHook; /* If entity changed tables in hook, add difference to * destination table, so we don't lose the side effects * of the hook. */ if (r->table != prev_table) { table = flecs_cmd_batch_add_diff( world, table, r->table, prev_table); } } } } break; case EcsCmdSet: case EcsCmdEnsure: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; has_set = true; break; case EcsCmdEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsCmdRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsCmdClear: if (table) { ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, &diff->removed, ecs_id_t, table->type.count); ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, table->type.count); } table = &world->store.root; world->info.cmd.batched_command_count ++; break; case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdPath: case EcsCmdDelete: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: case EcsCmdModifiedNoHook: break; } /* Add, remove and clear operations can be skipped since they have no * side effects besides adding/removing components */ if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { cmd->kind = EcsCmdSkip; } } while ((cur = next_for_entity)); /* 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 the batch contains set commands, copy the component value from the * temporary command storage to the actual component storage before OnSet * observers are invoked. This ensures that for multi-component OnSet * observers all components are assigned a valid value before the observer * is invoked. * This only happens for entities that didn't have the assigned component * yet, as for entities that did have the component already the value will * have been assigned directly to the component storage. */ if (has_set) { cur = start; do { cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsCmdSet: case EcsCmdEnsure: { flecs_component_ptr_t ptr = {0}; if (r->table) { ptr = flecs_get_component_ptr(world, r->table, ECS_RECORD_TO_ROW(r->row), cmd->id); } /* It's possible that even though the component was set, the * command queue also contained a remove command, so before we * do anything ensure the entity actually has the component. */ if (ptr.ptr) { const ecs_type_info_t *ti = ptr.ti; ecs_move_t move = ti->hooks.move; if (move) { move(ptr.ptr, cmd->is._1.value, 1, ti); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { dtor(cmd->is._1.value, 1, ti); cmd->is._1.value = NULL; } } else { ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); } if (cmd->kind == EcsCmdSet) { /* A set operation is add + copy + modified. We just did * the add the copy, so the only thing that's left is a * modified command, which will call the OnSet * observers. */ cmd->kind = EcsCmdModified; } else { /* If this was a ensure, nothing's left to be done */ cmd->kind = EcsCmdSkip; } } else { /* The entity no longer has the component which means that * there was a remove command for the component in the * command queue. In that case skip the command. */ cmd->kind = EcsCmdSkip; } break; } case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdRemove: case EcsCmdEmplace: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdPath: case EcsCmdDelete: case EcsCmdClear: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: break; } } while ((cur = next_for_entity)); } } /* 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); ecs_commands_t *commands = stage->cmd; ecs_vec_t *queue = &commands->queue; if (ecs_vec_count(queue)) { /* Internal callback for capturing commands */ if (world->on_commands_active) { world->on_commands_active(stage, queue, world->on_commands_ctx_active); } ecs_cmd_t *cmds = ecs_vec_first(queue); int32_t i, count = ecs_vec_count(queue); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_commands_push(stage); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_alive(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* Invalidate entry */ if (cmd->entry) { cmd->entry->first = -1; } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsCmdAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsCmdRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsCmdClone: ecs_clone(world, e, id, cmd->is._1.clone_value); world->info.cmd.other_count ++; break; case EcsCmdSet: flecs_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 EcsCmdEmplace: 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.ensure_count ++; break; case EcsCmdEnsure: 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.ensure_count ++; break; case EcsCmdModified: flecs_modified_id_if(world, e, id, true); world->info.cmd.modified_count ++; break; case EcsCmdModifiedNoHook: flecs_modified_id_if(world, e, id, false); world->info.cmd.modified_count ++; break; case EcsCmdAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id, true); world->info.cmd.set_count ++; break; case EcsCmdDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsCmdClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsCmdOnDeleteAction: ecs_defer_begin(world); flecs_on_delete(world, id, e, false); ecs_defer_end(world); world->info.cmd.other_count ++; break; case EcsCmdEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsCmdDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsCmdBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsCmdPath: { bool keep_alive = true; ecs_make_alive(world, e); if (cmd->id) { if (ecs_is_alive(world, cmd->id)) { ecs_add_pair(world, e, EcsChildOf, cmd->id); } else { ecs_delete(world, e); keep_alive = false; } } if (keep_alive) { ecs_set_name(world, e, cmd->is._1.value); } ecs_os_free(cmd->is._1.value); cmd->is._1.value = NULL; world->info.cmd.other_count ++; break; } case EcsCmdEvent: { ecs_event_desc_t *desc = cmd->is._1.value; ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_emit((ecs_world_t*)stage, desc); flecs_free_cmd_event(world, desc); world->info.cmd.event_count ++; break; } case EcsCmdSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); flecs_commands_pop(stage); flecs_table_diff_builder_fini(world, &diff); /* Internal callback for capturing commands, signal queue is done */ if (world->on_commands_active) { world->on_commands_active(stage, NULL, world->on_commands_ctx_active); } } 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->cmd->queue; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->cmd->stack); flecs_sparse_clear(&stage->cmd->entries); } return true; } error: return false; } /** * @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, EcsFlattenTarget *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); EcsFlattenTarget *next = table->data.columns[col].data.array; next = ECS_ELEM_T(next, EcsFlattenTarget, 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(EcsFlattenTarget, 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; EcsFlattenTarget *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); EcsFlattenTarget *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 *path) { return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); } ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, const char *name, bool lookup_as_path, bool recursive) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = 0; if (lookup_as_path) { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); } if (!e) { e = flecs_name_index_find(&world->symbols, name, 0, 0); } return e; error: return 0; } ecs_entity_t ecs_lookup_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, const char *path, const char *sep, const char *prefix, bool recursive) { if (!path) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); 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) { ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_resume_readonly(real_world, &srs); flecs_defer_path(stage, parent, entity, name); } } ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); 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_is_deferred(world) && (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->name && term_id->name[0] == '$' && term_id->name[1]) { term_id->flags |= EcsIsVariable; } 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; if (scope) { e = ecs_lookup_child(world, scope, name); if (!e) { /* Support wildcards and fully qualified paths */ e = ecs_lookup_symbol(world, name, true, true); } } else { e = ecs_lookup_symbol(world, name, true, true); } 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)) { /* Only if the source is not EcsAny */ if (!(term->src.id == EcsAny && (term->src.flags & EcsIsVariable))) { term->oper = EcsAnd; term->id = ecs_pair(EcsChildOf, 0); second->id = 0; second->flags |= EcsIsEntity; second->flags &= ~EcsIsVariable; } } ecs_entity_t first_id = 0; if (first->flags & EcsIsEntity) { first_id = 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; } } /* Is term trivial */ bool trivial_term = true; if (term->oper != EcsAnd) { trivial_term = false; } if (ecs_id_is_wildcard(term->id)) { if (!(term->idr && term->idr->flags & EcsIdExclusive)) { trivial_term = false; } if (first->flags & EcsIsVariable) { if (!ecs_id_is_wildcard(first->id) || first->id == EcsAny) { trivial_term = false; } } if (second->flags & EcsIsVariable) { if (!ecs_id_is_wildcard(second->id) || second->id == EcsAny) { trivial_term = false; } } } if (!ecs_term_match_this(term)) { trivial_term = false; } if (term->flags & EcsTermIdInherited) { trivial_term = false; } if (src->trav && src->trav != EcsIsA) { trivial_term = false; } if (first->trav && first->trav != EcsIsA) { trivial_term = false; } if (second->trav && second->trav != EcsIsA) { trivial_term = false; } if (!(src->flags & EcsSelf)) { trivial_term = false; } if (((term->first.id == EcsPredEq) || (term->first.id == EcsPredMatch)) && (term->first.flags & EcsIsEntity)) { trivial_term = false; } if (trivial_term) { ECS_BIT_SET(term->flags, EcsTermIsTrivial); } 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 ++) { bool filter_term = false; 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 ++; } term->field_index = field_count - 1; if (ecs_id_is_wildcard(term->id)) { f->flags |= EcsFilterHasWildcards; } 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->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->inout == EcsInOutNone) { filter_term = true; } else if (term->idr) { if (!term->idr->type_info && !(term->idr->flags & EcsIdUnion)) { filter_term = true; } } else 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 */ if (ecs_id_is_tag(world, term->id)) { filter_term = true; } else if ((ECS_PAIR_SECOND(term->id) == EcsWildcard) || (ECS_PAIR_SECOND(term->id) == EcsAny)) { /* If the second element of a pair is a wildcard and the first * element is not a type, we can't know in advance what the * type of the term is, so it can't provide data. */ if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { filter_term = true; } } } if (!filter_term) { if (term->oper == EcsOr || (i && term[-1].oper == EcsOr)) { ecs_term_t *first = flecs_filter_or_other_type(f, i); if (first) { if (first == &term[-1]) { filter_terms ++; } filter_term = true; } } } if (filter_term) { filter_terms ++; term->flags |= EcsTermNoData; } else { f->data_fields |= (1llu << term->field_index); } 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 = flecs_ito(int8_t, 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; f->ids[field] = term->id; if (term->oper == EcsOr || (i && (term[-1].oper == EcsOr))) { if (flecs_filter_or_other_type(f, i)) { f->sizes[field] = 0; f->ids[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); #ifdef FLECS_META f->ids[field] = ecs_id(ecs_entity_t); #endif } else if (idr->type_info) { f->sizes[field] = idr->type_info->size; f->ids[field] = idr->id; } } } 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); #ifdef FLECS_META f->ids[field] = ecs_id(ecs_entity_t); #endif } else { const ecs_type_info_t *ti = ecs_get_type_info( world, term->id); if (ti) { f->sizes[field] = ti->size; f->ids[field] = term->id; } } } } } else { f->sizes = NULL; f->ids = NULL; } ecs_assert(filter_terms <= term_count, ECS_INTERNAL_ERROR, NULL); if (filter_terms == term_count) { ECS_BIT_SET(f->flags, EcsFilterNoData); } ECS_BIT_COND(f->flags, EcsFilterHasCondSet, cond_set); /* Check if this is a trivial filter */ if ((f->flags & EcsFilterMatchOnlyThis)) { if (!(f->flags & (EcsFilterHasPred|EcsFilterMatchDisabled|EcsFilterMatchPrefab))) { ECS_BIT_SET(f->flags, EcsFilterMatchOnlySelf); for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_id_t *src = &term->src; if (src->flags & EcsUp) { ECS_BIT_CLEAR(f->flags, EcsFilterMatchOnlySelf); } if (!(term->flags & EcsTermIsTrivial)) { break; } if (!(f->flags & EcsFilterNoData)) { if (term->inout == EcsInOutNone) { break; } } } if (term_count && (i == term_count)) { ECS_BIT_SET(f->flags, EcsFilterIsTrivial); } } } 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->flags & EcsFilterOwnsTermsStorage) { /* Memory allocated for both terms & sizes */ ecs_os_free(filter->terms); } else { ecs_os_free(filter->sizes); ecs_os_free(filter->ids); } } filter->terms = NULL; if (filter->flags & EcsFilterOwnsStorage) { ecs_os_free(filter); } } void ecs_filter_fini( ecs_filter_t *filter) { if ((filter->flags & EcsFilterOwnsStorage) && filter->entity) { /* If filter is associated with entity, use poly dtor path */ ecs_delete(filter->world, filter->entity); } else { flecs_filter_fini(filter); } } static void flecs_normalize_term_name( ecs_term_id_t *ref) { if (ref->name && ref->name[0] == '$' && ref->name[1]) { ecs_assert(ref->flags & EcsIsVariable, ECS_INTERNAL_ERROR, NULL); const char *old = ref->name; ref->name = ecs_os_strdup(&old[1]); ecs_os_free(ECS_CONST_CAST(char*, old)); /* safe, filter owns name */ } } 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->flags |= EcsFilterOwnsStorage; } if (!storage_terms) { f->flags |= EcsFilterOwnsTermsStorage; } 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}; ecs_term_id_t extra_args[ECS_PARSER_MAX_ARGS]; ecs_oper_kind_t extra_oper = 0; int32_t expr_size = 0; ecs_os_zeromem(extra_args); if (entity) { name = ecs_get_name(world, entity); } while (ptr[0] && (ptr = ecs_parse_term( world, name, expr, ptr, &term, &extra_oper, extra_args, true))) { if (!ecs_term_is_initialized(&term)) { break; } int32_t arg = 0; do { ecs_assert(arg <= ECS_PARSER_MAX_ARGS, ECS_INTERNAL_ERROR, NULL); 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); } ecs_term_t *expr_term = &expr_terms[expr_count ++]; *expr_term = term; if (arg) { if (extra_oper == EcsAnd) { expr_term->src = expr_term[-1].second; expr_term->second = extra_args[arg - 1]; } else if (extra_oper == EcsOr) { expr_term->src = expr_term[-1].src; expr_term->second = extra_args[arg - 1]; expr_term[-1].oper = EcsOr; } if (expr_term->first.name != NULL) { expr_term->first.name = ecs_os_strdup( expr_term->first.name); } if (expr_term->src.name != NULL) { expr_term->src.name = ecs_os_strdup( expr_term->src.name); } } } while (ecs_term_id_is_set(&extra_args[arg ++])); if (ptr[0] == '\n') { break; } ecs_os_zeromem(extra_args); } if (!ptr) { /* Set terms in filter object to make sur they get cleaned up */ f->terms = expr_terms; f->term_count = flecs_ito(int8_t, expr_count); f->flags |= EcsFilterOwnsTermsStorage; 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->flags & EcsFilterOwnsTermsStorage, ECS_INTERNAL_ERROR, NULL); f->term_count = flecs_ito(int8_t, term_count + expr_count); ecs_size_t terms_size = ECS_SIZEOF(ecs_term_t) * f->term_count; ecs_size_t ids_size = ECS_SIZEOF(ecs_id_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 + ids_size); f->ids = ECS_OFFSET(f->terms, terms_size); f->sizes = ECS_OFFSET(f->terms, terms_size + ids_size); } else { f->terms = storage_terms; f->term_count = flecs_ito(int8_t, storage_count); f->sizes = ecs_os_calloc_n(ecs_size_t, term_count); f->ids = ecs_os_calloc_n(ecs_id_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 ++) { ecs_term_t *term = &f->terms[i]; /* Post process term names in case they were used to create variables */ flecs_normalize_term_name(&term->first); flecs_normalize_term_name(&term->second); flecs_normalize_term_name(&term->src); term->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; f->eval_count = 0; if (entity && (f->flags & EcsFilterOwnsStorage)) { 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; ecs_size_t ids_size = ECS_SIZEOF(ecs_id_t) * term_count; dst->terms = ecs_os_malloc(terms_size + sizes_size + ids_size); dst->ids = ECS_OFFSET(dst->terms, terms_size); dst->sizes = ECS_OFFSET(dst->terms, terms_size + ids_size); dst->flags |= EcsFilterOwnsTermsStorage; ecs_os_memcpy_n(dst->sizes, src->sizes, int32_t, term_count); ecs_os_memcpy_n(dst->ids, src->ids, 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->flags & EcsFilterOwnsTermsStorage) { dst->terms = src->terms; dst->sizes = src->sizes; dst->ids = src->ids; dst->flags |= EcsFilterOwnsTermsStorage; } else { ecs_filter_copy(dst, src); } src->terms = NULL; src->sizes = NULL; src->ids = 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 (((term->first.id == EcsPredEq) || (term->first.id == EcsPredMatch)) && (term->first.flags & EcsIsEntity)) { ecs_strbuf_appendlit(buf, "$"); if (term->src.id == EcsThis && term->src.flags & EcsIsVariable) { ecs_strbuf_appendlit(buf, "this"); } else if (term->src.flags & EcsIsVariable) { ecs_strbuf_appendstr(buf, term->src.name); } else { /* Shouldn't happen */ } if (term->first.id == EcsPredEq) { if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, " != "); } else { ecs_strbuf_appendlit(buf, " == "); } } else if (term->first.id == EcsPredMatch) { ecs_strbuf_appendlit(buf, " ~= "); } if (term->second.flags & EcsIsEntity) { if (term->second.id != 0) { ecs_get_path_w_sep_buf( world, 0, term->second.id, ".", NULL, buf); } } else { if (term->second.flags & EcsIsVariable) { ecs_strbuf_appendlit(buf, "$"); if (term->second.name) { ecs_strbuf_appendstr(buf, term->second.name); } else if (term->second.id == EcsThis) { ecs_strbuf_appendlit(buf, "this"); } } else if (term->second.flags & EcsIsName) { ecs_strbuf_appendlit(buf, "\""); if (term->first.id == EcsPredMatch && term->oper == EcsNot) { ecs_strbuf_appendlit(buf, "!"); } ecs_strbuf_appendstr(buf, term->second.name); ecs_strbuf_appendlit(buf, "\""); } } return; } if (!t || !(term[-1].oper == EcsOr)) { if (term->inout == EcsIn) { ecs_strbuf_appendlit(buf, "[in] "); } else if (term->inout == EcsInOut) { ecs_strbuf_appendlit(buf, "[inout] "); } else if (term->inout == EcsOut) { ecs_strbuf_appendlit(buf, "[out] "); } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { ecs_strbuf_appendlit(buf, "[none] "); } } if (term->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; } if (ecs_is_alive(world, src_id)) { match_table = ecs_get_table(world, src_id); } else { match_table = NULL; } 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); ecs_id_record_t *idr = term->idr; if (world->flags & EcsWorldQuit) { /* During world cleanup the keep_alive assert for id records is no * longer enforced */ idr = NULL; } column = flecs_search_relation_w_idr(world, match_table, column, id, src->trav, src->flags, &source, id_out, &tr, 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; } ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); 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)) { if (ecs_is_alive(world, src_id)) { match_table = ecs_get_table(world, src_id); } else { match_table = NULL; } } 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 accommodate 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 */ ecs_id_record_t *idr = term->idr; if (world->flags & EcsWorldQuit) { /* During world cleanup the keep_alive assert for id records is no * longer enforced */ idr = NULL; } int32_t index = flecs_search_relation_w_idr(world, table, 0, term->id, src->trav, src->flags, source, id, 0, 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), .query = filter, .terms = filter ? filter->terms : NULL, .next = ecs_filter_next, .flags = flags, .sizes = filter->sizes, .system = filter->entity }; 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) { if (filter) { // Ok, only for stats ECS_CONST_CAST(ecs_filter_t*, filter)->eval_count ++; } 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 = { .query = filter, .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, .system = filter->entity }; 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 ++) { ecs_entity_t src = it->sources[t]; if (!src) { 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) { if (!(it->flags & EcsIterIsValid)) { return NULL; } 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 && it->variable_names) { int32_t actual_count = 0; for (i = 0; i < it->variable_count; i ++) { const char *var_name = it->variable_names[i]; if (!var_name || var_name[0] == '_' || !strcmp(var_name, "This")) { /* Skip anonymous variables */ continue; } ecs_var_t var = it->variables[i]; if (!var.entity) { /* Skip table variables */ continue; } if (!actual_count) { ecs_strbuf_list_push(&buf, "var: ", ","); } char *str = ecs_get_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 && !var_id) { table = it->table; } if (table) { if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { ecs_assert(ecs_table_count(table) > var->range.offset, ECS_INTERNAL_ERROR, NULL); e = ecs_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 && !var_id) { table = it->table; } if (!table) { /* If table is not set, try to get table from entity */ ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { table = r->table; if (ecs_table_count(table) != 1) { /* If table contains more than the entity, make sure not to * return a partial table. */ return NULL; } } } } if (table) { if (var->range.offset) { /* Don't return whole table if only partial table is matched */ return NULL; } if (!var->range.count || ecs_table_count(table) == var->range.count) { /* Return table if count matches */ return table; } } error: return NULL; } ecs_table_range_t ecs_iter_get_var_as_range( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, 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 && !var_id) { table = it->table; } if (!table) { ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { result.table = r->table; result.offset = ECS_RECORD_TO_ROW(r->row); result.count = 1; } } } else { result.table = table; result.offset = var->range.offset; result.count = var->range.count; if (!result.count) { result.count = ecs_table_count(table); } } return result; error: return (ecs_table_range_t){0}; } 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 Miscellaneous functions. */ #include #include #ifndef FLECS_NDEBUG static int64_t flecs_s_min[] = { [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; static int64_t flecs_s_max[] = { [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; static uint64_t flecs_u_max[] = { [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; uint64_t flecs_ito_( size_t size, bool is_signed, bool lt_zero, uint64_t u, const char *err) { union { uint64_t u; int64_t s; } v; v.u = u; if (is_signed) { ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); } else { ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); } return u; } #endif int32_t flecs_next_pow_of_2( int32_t n) { n --; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n ++; return n; } /** Convert time to double */ double ecs_time_to_double( ecs_time_t t) { double result; result = t.sec; return result + (double)t.nanosec / (double)1000000000; } ecs_time_t ecs_time_sub( ecs_time_t t1, ecs_time_t t2) { ecs_time_t result; if (t1.nanosec >= t2.nanosec) { result.nanosec = t1.nanosec - t2.nanosec; result.sec = t1.sec - t2.sec; } else { result.nanosec = t1.nanosec - t2.nanosec + 1000000000; result.sec = t1.sec - t2.sec - 1; } return result; } void ecs_sleepf( double t) { if (t > 0) { int sec = (int)t; int nsec = (int)((t - sec) * 1000000000); ecs_os_sleep(sec, nsec); } } double ecs_time_measure( ecs_time_t *start) { ecs_time_t stop, temp; ecs_os_get_time(&stop); temp = stop; stop = ecs_time_sub(stop, *start); *start = temp; return ecs_time_to_double(stop); } void* ecs_os_memdup( const void *src, ecs_size_t size) { if (!src) { return NULL; } void *dst = ecs_os_malloc(size); ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_memcpy(dst, src, size); return dst; } int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2) { (void)ptr1; (void)ptr2; return (e1 > e2) - (e1 < e2); } int flecs_id_qsort_cmp(const void *a, const void *b) { ecs_id_t id_a = *(const ecs_id_t*)a; ecs_id_t id_b = *(const ecs_id_t*)b; return (id_a > id_b) - (id_a < id_b); } 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; } 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; } fseek(file, 0, SEEK_SET); /* Load contents in memory */ content = ecs_os_malloc(bytes + 1); size = (size_t)bytes; if (!(size = fread(content, 1, size, file)) && bytes) { ecs_err("%s: read zero bytes instead of %d", filename, size); ecs_os_free(content); content = NULL; goto error; } else { content[size] = '\0'; } fclose(file); return content; error: ecs_os_free(content); return NULL; } /** * @file observable.c * @brief Observable implementation. * * The observable implementation contains functions that find the set of * observers to invoke for an event. The code also contains the implementation * of a reachable id cache, which is used to speedup event propagation when * relationships are added/removed to/from entities. */ 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; if (id != EcsAny) { iders[0] = flecs_event_id_record_get_if(er, EcsAny); count += iders[count] != 0; } iders[count] = flecs_event_id_record_get_if(er, id); count += iders[count] != 0; if (id != EcsAny) { if (ECS_IS_PAIR(id)) { ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); 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_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count); static void flecs_emit_propagate_id( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *cur, ecs_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { return; } const ecs_table_record_t *tr; int32_t event_cur = it->event_cur; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table)) { continue; } 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. */ it->event_cur = ++ world->event_id; int32_t ider_i; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); if (!owned) { /* Owned takes precedence */ flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } if (!table->_->traversable_count) { continue; } ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (e = 0; e < entity_count; e ++) { ecs_record_t *r = flecs_entities_get(world, entities[e]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *idr_t = r->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate(world, it, idr, idr_t, trav, iders, ider_count); } } } it->event_cur = event_cur; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *tgt_idr, ecs_entity_t propagate_trav, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); 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 */ /* Get traversed relationship */ ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); if (propagate_trav && propagate_trav != trav) { if (propagate_trav != EcsIsA) { continue; } } flecs_emit_propagate_id( world, it, idr, cur, trav, 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_entity_t *entities = ecs_vec_first(&table->data.entities); for (e = 0; e < entity_count; e ++) { ecs_record_t *r = flecs_entities_get(world, entities[e]); ecs_id_record_t *idr_t = r->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } } } void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count) { ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities, ecs_entity_t, offset); int32_t i; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_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_propagate_entities( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_entity_t *entities, int32_t count, ecs_entity_t src, ecs_event_id_record_t **iders, int32_t ider_count) { if (!count) { return; } ecs_entity_t old_src = it->sources[0]; ecs_table_t *old_table = it->table; ecs_table_t *old_other_table = it->other_table; ecs_entity_t *old_entities = it->entities; int32_t old_count = it->count; int32_t old_offset = it->offset; int32_t i; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Entity is used as target in traversable pairs, propagate */ ecs_entity_t e = src ? src : entities[i]; it->sources[0] = e; flecs_emit_propagate( world, it, idr, idr_t, 0, iders, ider_count); } } it->table = old_table; it->other_table = old_other_table; it->entities = old_entities; it->count = old_count; it->offset = old_offset; it->sources[0] = old_src; } 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, 1, ti); ptr = ECS_OFFSET(ptr, size); } } else { for (i = 0; i < count; i ++) { ecs_os_memcpy(ptr, src, size); ptr = ECS_OFFSET(ptr, size); } } 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); 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) { 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); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } /* Emit OnSet events for newly inherited components */ if (storage_i != -1) { 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); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke( world, &ider->self_up, it, table, trav); } else if (override) { ecs_entity_t src = it->sources[0]; it->sources[0] = 0; flecs_observers_invoke(world, &ider->self, it, table, 0); flecs_observers_invoke(world, &ider->self_up, it, table, 0); 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) { /* 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); } 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) { 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); } } 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) { 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_PAIR_FIRST(id) == EcsIsA) { 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); 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); 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); } 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) { ecs_id_t id = idr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *tgt_record = flecs_entities_try(world, tgt); ecs_table_t *tgt_table; if (!tgt_record || !(tgt_table = tgt_record->table)) { return; } flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, tgt, tgt_table, tgt_record, idr, stack, reachable_ids); } static void flecs_emit_forward( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr) { 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); 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); } } /* Propagate events for new reachable ids downwards */ if (table->_->traversable_count) { int32_t i; ecs_entity_t *entities = flecs_table_entities_array(table); entities = ECS_ELEM_T(entities, ecs_entity_t, it->offset); for (i = 0; i < it->count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->idr) { break; } } if (i != it->count) { ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *elem = &elems[i]; const ecs_table_record_t *tr = elem->tr; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; ecs_record_t *r = elem->record; ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, elem->src) == r, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); (void)r; ecs_event_id_record_t *iders[5] = {0}; int32_t ider_count = flecs_event_observers_get( er, rc_idr->id, iders); flecs_propagate_entities(world, it, rc_idr, it->entities, it->count, elem->src, iders, ider_count); } } } } /* 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, count = desc->count; ecs_flags32_t table_flags = table->flags; /* Deferring cannot be suspended for observers */ int32_t defer = world->stages[0].defer; if (defer < 0) { world->stages[0].defer *= -1; } /* Table events are emitted for internal table operations only, and do not * provide component data and/or entity ids. */ bool table_event = desc->flags & EcsEventTableOnly; if (!count && !table_event) { /* If no count is provided, forward event for all entities in table */ count = ecs_table_count(table) - offset; } /* 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; /* The world event id is used to determine if an observer has already been * triggered for an event. Observers for multiple components are split up * into multiple observers for a single component, and this counter is used * to make sure a multi observer only triggers once, even if multiple of its * single-component observers trigger. */ int32_t evtx = ++world->event_id; ecs_id_t ids_cache = 0; 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, .event_cur = evtx, .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 }; 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; /* 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; if (count && can_forward && has_observed) { flecs_emit_propagate_invalidate(world, table, offset, count); } 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); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } 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); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); flecs_observers_invoke( world, &ider->up, &it, table, EcsIsA); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } 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); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); flecs_observers_invoke(world, &ider->self_up, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } if (!ider_count || !count || !has_observed) { continue; } /* The table->traversable_count value indicates if the table contains any * entities that are used as targets of traversable relationships. If the * entity/entities for which the event was generated is used as such a * target, events must be propagated downwards. */ flecs_propagate_entities( world, &it, idr, it.entities, count, 0, iders, ider_count); } can_override = false; /* Don't override twice */ can_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: world->stages[0].defer = defer; if (measure_time) { world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); } return; } void ecs_emit( ecs_world_t *stage, ecs_event_desc_t *desc) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, "cannot set param and const_param at the same time"); if (desc->entity) { ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, desc->entity); ecs_table_t *table; if (!r || !(table = r->table)) { /* Empty entities can't trigger observers */ return; } desc->table = table; desc->offset = ECS_RECORD_TO_ROW(r->row); desc->count = 1; } if (!desc->observable) { desc->observable = world; } ecs_type_t default_ids = (ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ EcsAny } }; if (!desc->ids || !desc->ids->count) { desc->ids = &default_ids; } if (desc->const_param) { desc->param = ECS_CONST_CAST(void*, desc->const_param); desc->const_param = NULL; } flecs_emit(world, stage, desc); if (desc->ids == &default_ids) { desc->ids = NULL; } error: return; } void ecs_enqueue( ecs_world_t *world, ecs_event_desc_t *desc) { ecs_check(ecs_is_deferred(world), ECS_INVALID_PARAMETER, "can't enqueue if not in deferred mode"); ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_enqueue(world, stage, desc); error: return; } /** * @file observer.c * @brief Observer implementation. * * The observer implementation contains functions for creating, deleting and * invoking observers. The code is split up into single-term observers and * multi-term observers. Multi-term observers are created from multiple single- * term observers. */ #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 ecs_id_t flecs_observer_id( ecs_id_t id) { if (ECS_IS_PAIR(id)) { if (ECS_PAIR_FIRST(id) == EcsAny) { id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); } if (ECS_PAIR_SECOND(id) == EcsAny) { id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); } } return id; } static void flecs_register_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer, size_t offset) { ecs_id_t term_id = flecs_observer_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 = flecs_observer_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, ecs_iter_t *it) { 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] == it->event_cur) { return true; } if (observer->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { 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(); ecs_entity_t old_system = flecs_stage_set_system( &world->stages[0], observer->filter.entity); 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); filter->eval_count ++; } 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); filter->eval_count ++; } else if (observer_src == e) { ecs_entity_t dummy = 0; it->entities = &dummy; if (!src) { it->sources[0] = e; } callback(it); filter->eval_count ++; it->sources[0] = src; break; } } it->entities = entities; it->count = count; } flecs_stage_set_system(&world->stages[0], old_system); 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, bool simple_result) { ecs_filter_t *filter = &observer->filter; ecs_term_t *term = &filter->terms[0]; if (flecs_ignore_observer(observer, table, it)) { 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->query = &observer->filter; it->terms = observer->filter.terms; ecs_entity_t event = it->event; int32_t event_cur = it->event_cur; 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; it->event_cur = event_cur; } void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { if (ecs_map_is_init(observers)) { ecs_table_lock(it->world, table); 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, 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] == it->event_cur) { /* 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.query = &o->filter; 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->flags & EcsObserverIsMonitor) { 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->flags & EcsObserverIsMulti) { 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, &observer->filter, &it, NULL); 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]; it.event_cur = ++ world->event_id; 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) { pivot_term = 0; /* Observer has no this terms */ } /* 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.query = &observer->filter; 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]; it.event_cur = ++ world->event_id; run(&it); } } 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; bool has_on_add = false; bool has_on_remove = false; for (e = 0; e < count; e ++) { if (observer->events[e] == EcsOnAdd) { has_on_add = true; } if (observer->events[e] == EcsOnRemove) { has_on_remove = true; } } for (e = 0; e < count; e ++) { if (observer->events[e] == EcsOnSet) { if (has_on_add) { /* Already registered */ observer->events[e] = 0; } else { observer->events[e] = EcsOnAdd; } } else if (observer->events[e] == EcsUnSet) { if (has_on_remove) { /* Already registered */ observer->events[e] = 0; } else { 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->flags |= EcsObserverIsMulti; /* 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->flags |= EcsObserverIsMonitor; } 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; } bool is_monitor = observer->flags & EcsObserverIsMonitor; if (filter->term_count == 1 && !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_poly_assert(poly->poly, ecs_observer_t); ecs_observer_t *observer = (ecs_observer_t*)poly->poly; 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; } } const ecs_filter_t* ecs_observer_get_filter( 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)->filter; } else { return NULL; } } void flecs_observer_fini( ecs_observer_t *observer) { if (observer->flags & EcsObserverIsMulti) { 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; ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; int64_t ecs_os_api_malloc_count = 0; int64_t ecs_os_api_realloc_count = 0; int64_t ecs_os_api_calloc_count = 0; int64_t ecs_os_api_free_count = 0; void ecs_os_set_api( ecs_os_api_t *os_api) { if (!ecs_os_api_initialized) { ecs_os_api = *os_api; ecs_os_api_initialized = true; } } ecs_os_api_t ecs_os_get_api(void) { return ecs_os_api; } void ecs_os_init(void) { if (!ecs_os_api_initialized) { ecs_os_set_api_defaults(); } if (!(ecs_os_api_init_count ++)) { if (ecs_os_api.init_) { ecs_os_api.init_(); } } } void ecs_os_fini(void) { if (!--ecs_os_api_init_count) { if (ecs_os_api.fini_) { ecs_os_api.fini_(); } } } /* Assume every non-glibc Linux target has no execinfo. This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ #if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__) #define HAVE_EXECINFO 0 #elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) #define HAVE_EXECINFO 1 #else #define HAVE_EXECINFO 0 #endif #if HAVE_EXECINFO #include #define ECS_BT_BUF_SIZE 100 void flecs_dump_backtrace( void *stream) { int nptrs; void *buffer[ECS_BT_BUF_SIZE]; char **strings; nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { return; } for (int j = 1; j < nptrs; j++) { fprintf(stream, "%s\n", strings[j]); } free(strings); } #else void flecs_dump_backtrace( void *stream) { (void)stream; } #endif #undef HAVE_EXECINFO_H static void flecs_log_msg( int32_t level, const char *file, int32_t line, const char *msg) { FILE *stream = ecs_os_api.log_out_; if (!stream) { stream = stdout; } bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; time_t now = 0; if (deltatime) { now = time(NULL); int64_t delta = 0; if (ecs_os_api.log_last_timestamp_) { delta = now - ecs_os_api.log_last_timestamp_; } ecs_os_api.log_last_timestamp_ = (int64_t)now; if (delta) { if (delta < 10) { fputs(" ", stream); } if (delta < 100) { fputs(" ", stream); } char time_buf[20]; ecs_os_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_ensure_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; } 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; bool desc = false; if (query->cascade_by) { desc = (query->filter.terms[ query->cascade_by - 1].src.flags & EcsDesc) != 0; } /* Find closest smaller group id */ while (ecs_map_next(&it)) { id = ecs_map_key(&it); if (!desc) { if (id >= group_id) { continue; } } else { if (id <= group_id) { continue; } } list = ecs_map_ptr(&it); if (!list->last) { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); continue; } bool comp; if (!desc) { comp = ((group_id - id) < (group_id - closest_id)); } else { comp = ((group_id - id) > (group_id - closest_id)); } if (!closest_list || comp) { 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 = flecs_filter_iter_w_flags(world, &query->filter, 0); 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)) { tables_sorted = true; /* Ensure ranges are rebuilt */ 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 = flecs_filter_iter_w_flags(world, &query->filter, 0); } 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); query->filter.eval_count ++; /* 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), .query = &query->filter, .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, .system = query->filter.entity }; 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; } 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(EcsFlattenTarget, 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_new( ecs_stage_t *stage) { ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); cmd->is._1.value = NULL; cmd->next_for_entity = 0; cmd->entry = NULL; cmd->system = stage->system; return cmd; } static ecs_cmd_t* flecs_cmd_new_batched( ecs_stage_t *stage, ecs_entity_t e) { ecs_vec_t *cmds = &stage->cmd->queue; ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( &stage->cmd->entries, ecs_cmd_entry_t, e); int32_t cur = ecs_vec_count(cmds); ecs_cmd_t *cmd = flecs_cmd_new(stage); if (entry) { if (entry->first == -1) { /* Existing but invalidated entry */ entry->first = cur; cmd->entry = entry; } else { int32_t last = entry->last; ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *last_op = &arr[last]; last_op->next_for_entity = cur; if (last == entry->first) { /* Flip sign bit so flush logic can tell which command * is the first for an entity */ last_op->next_for_entity *= -1; } } } else { cmd->entry = entry = flecs_sparse_ensure_fast_t( &stage->cmd->entries, ecs_cmd_entry_t, e); entry->first = cur; } entry->last = cur; return cmd; } 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_batched(stage, entity); if (cmd) { cmd->kind = EcsCmdModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; return true; } return false; } bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name) { if (stage->defer > 0) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdPath; cmd->entity = entity; cmd->id = parent; cmd->is._1.value = ecs_os_strdup(name); return true; } return false; } bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdDelete; cmd->entity = entity; return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdClear; cmd->entity = entity; return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; cmd->entity = entity; cmd->id = id; return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new_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_new(stage); cmd->kind = EcsCmdBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; cmd->entity = 0; return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdAdd; cmd->id = id; cmd->entity = entity; return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdRemove; cmd->id = id; cmd->entity = entity; 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) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); /* Find type info for id */ const ecs_type_info_t *ti = NULL; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If idr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (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 == EcsCmdEmplace; /* If the component does not yet exist, create a temporary value. This is * necessary so we can store a component value in the deferred command, * without adding the component to the entity which is not allowed in * deferred mode. */ if (!existing) { ecs_stack_t *stack = &stage->cmd->stack; cmd_value = flecs_stack_alloc(stack, size, ti->alignment); /* 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_new(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 == EcsCmdSet) { cmd->kind = EcsCmdAddModified; } else { cmd->kind = EcsCmdAdd; } cmd->id = id; cmd->entity = entity; } return cmd_value; error: return NULL; } void flecs_enqueue( ecs_world_t *world, ecs_stage_t *stage, ecs_event_desc_t *desc) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdEvent; cmd->entity = desc->entity; ecs_stack_t *stack = &stage->cmd->stack; ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); if (desc->ids && desc->ids->count != 0) { ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); int32_t id_count = desc->ids->count; type_cmd->count = id_count; type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); desc_cmd->ids = type_cmd; } else { desc_cmd->ids = NULL; } cmd->is._1.value = desc_cmd; cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); if (desc->param || desc->const_param) { ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, "cannot set param and const_param at the same time"); const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "can only enqueue events with data for events that are components"); void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); if (desc->param) { if (ti->hooks.move_ctor) { ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); } else { ecs_os_memcpy(param_cmd, desc->param, ti->size); } } else { if (ti->hooks.copy_ctor) { ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); } else { ecs_os_memcpy(param_cmd, desc->const_param, ti->size); } } desc_cmd->param = param_cmd; desc_cmd->const_param = 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); } static void flecs_commands_init( ecs_stage_t *stage, ecs_commands_t *cmd) { flecs_stack_init(&cmd->stack); ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); flecs_sparse_init_t(&cmd->entries, &stage->allocator, &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } static void flecs_commands_fini( ecs_stage_t *stage, ecs_commands_t *cmd) { /* Make sure stage has no unmerged data */ ecs_assert(ecs_vec_count(&stage->cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); flecs_stack_fini(&cmd->stack); ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); flecs_sparse_fini(&cmd->entries); } void flecs_commands_push( ecs_stage_t *stage) { int32_t sp = ++ stage->cmd_sp; ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); stage->cmd = &stage->cmd_stack[sp]; } void flecs_commands_pop( ecs_stage_t *stage) { int32_t sp = -- stage->cmd_sp; ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); stage->cmd = &stage->cmd_stack[sp]; } 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->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->post_frame_actions, ecs_action_elem_t, 0); int32_t i; for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { flecs_commands_init(stage, &stage->cmd_stack[i]); } stage->cmd = &stage->cmd_stack[0]; } 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); ecs_poly_fini(stage, ecs_stage_t); ecs_allocator_t *a = &stage->allocator; ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); ecs_vec_fini(NULL, &stage->variables, 0); ecs_vec_fini(NULL, &stage->operations, 0); int32_t i; for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { flecs_commands_fini(stage, &stage->cmd_stack[i]); } 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, bool multi_threaded) { 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); ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, multi_threaded); 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; } ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system) { ecs_entity_t old = stage->system; stage->system = system; return old; } /** * @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; const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 29; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 32; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 35; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 38; const ecs_entity_t EcsUnSet = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 42; const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 44; 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(EcsFlattenTarget) = 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; const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 97; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsMetaType) = FLECS_HI_COMPONENT_ID + 98; const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; /* Doc module components */ const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; /* REST module components */ const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 118; /* 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); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (!is_readonly && !stage->defer) { state->is_readonly = false; state->is_deferred = false; return world; } ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = stage->defer != 0; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; /* Hack around safety checks (this ought to look ugly) */ state->defer_count = stage->defer; state->commands = stage->cmd->queue; state->defer_stack = stage->cmd->stack; flecs_stack_init(&stage->cmd->stack); state->scope = stage->scope; state->with = stage->with; stage->defer = 0; ecs_vec_init_t(NULL, &stage->cmd->queue, 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->cmd->queue, ecs_cmd_t); stage->cmd->queue = state->commands; flecs_stack_fini(&stage->cmd->stack); stage->cmd->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_root_tables( ecs_world_t *world, ecs_id_record_t *idr, bool fini_targets) { 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; 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; if (fini_targets) { /* Only delete entities that are used as pair target. Iterate * backwards to minimize moving entities around in table. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { ecs_delete(world, entities[i]); } } } else { /* Delete remaining entities that are not in use (added to another * entity). This limits table moves during cleanup and delays * cleanup of tags. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { ecs_delete(world, entities[i]); } } } } } 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); /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, &world->stages[0]); flecs_fini_root_tables(world, idr, true); flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); flecs_fini_root_tables(world, idr, false); flecs_defer_end(world, &world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_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); } #define FLECS_COMPILER_INFO_MAX (64) static char flecs_compiler_info[FLECS_COMPILER_INFO_MAX] = {0}; static bool flecs_compiler_info_initialized = false; static const char *flecs_addons_info[] = { #ifdef FLECS_CPP "FLECS_CPP", #endif #ifdef FLECS_MODULE "FLECS_MODULE", #endif #ifdef FLECS_PARSER "FLECS_PARSER", #endif #ifdef FLECS_PLECS "FLECS_PLECS", #endif #ifdef FLECS_RULES "FLECS_RULES", #endif #ifdef FLECS_SNAPSHOT "FLECS_SNAPSHOT", #endif #ifdef FLECS_STATS "FLECS_STATS", #endif #ifdef FLECS_MONITOR "FLECS_MONITOR", #endif #ifdef FLECS_METRICS "FLECS_METRICS", #endif #ifdef FLECS_ALERTS "FLECS_ALERTS", #endif #ifdef FLECS_SYSTEM "FLECS_SYSTEM", #endif #ifdef FLECS_PIPELINE "FLECS_PIPELINE", #endif #ifdef FLECS_TIMER "FLECS_TIMER", #endif #ifdef FLECS_META "FLECS_META", #endif #ifdef FLECS_META_C "FLECS_META_C", #endif #ifdef FLECS_UNITS "FLECS_UNITS", #endif #ifdef FLECS_EXPR "FLECS_EXPR", #endif #ifdef FLECS_JSON "FLECS_JSON", #endif #ifdef FLECS_DOC "FLECS_DOC", #endif #ifdef FLECS_LOG "FLECS_LOG", #endif #ifdef FLECS_JOURNAL "FLECS_JOURNAL", #endif #ifdef FLECS_APP "FLECS_APP", #endif #ifdef FLECS_OS_API_IMPL "FLECS_OS_API_IMPL", #endif #ifdef FLECS_SCRIPT "FLECS_SCRIPT", #endif #ifdef FLECS_HTTP "FLECS_HTTP", #endif #ifdef FLECS_REST "FLECS_REST", #endif NULL }; static ecs_build_info_t flecs_build_info = { .compiler = flecs_compiler_info, .addons = flecs_addons_info, #ifdef FLECS_DEBUG .debug = true, #endif #ifdef FLECS_SANITIZE .sanitize = true, #endif #ifdef FLECS_PERF_TRACE .perf_trace = true, #endif .version = FLECS_VERSION, .version_major = FLECS_VERSION_MAJOR, .version_minor = FLECS_VERSION_MINOR, .version_patch = FLECS_VERSION_PATCH }; static void flecs_log_build_info(void) { const ecs_build_info_t *bi = ecs_get_build_info(); ecs_assert(bi != NULL, ECS_INTERNAL_ERROR, NULL); ecs_trace("flecs version %s", bi->version); ecs_trace("addons included in build:"); ecs_log_push(); const char **addon = bi->addons; do { ecs_trace(addon[0]); } while ((++ addon)[0]); ecs_log_pop(); if (bi->sanitize) { ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); } else if (bi->debug) { ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for " "improved performance"); } else { ecs_trace("#[green]release#[reset] build"); } ecs_trace("compiled with %s", bi->compiler); } /* -- Public functions -- */ const ecs_build_info_t* ecs_get_build_info(void) { if (!flecs_compiler_info_initialized) { #ifdef __clang__ ecs_os_snprintf(flecs_compiler_info, FLECS_COMPILER_INFO_MAX, "clang %s", __clang_version__); #elif defined(__GNUC__) ecs_os_snprintf(flecs_compiler_info, FLECS_COMPILER_INFO_MAX, "gcc %d.%d", __GNUC__, __GNUC_MINOR__); #elif defined (_MSC_VER) ecs_os_snprintf(flecs_compiler_info, FLECS_COMPILER_INFO_MAX, "msvc %d", _MSC_VER); #elif defined (__TINYC__) ecs_os_snprintf(flecs_compiler_info, FLECS_COMPILER_INFO_MAX, "tcc %d", __TINYC__); #endif flecs_compiler_info_initialized = true; } return &flecs_build_info; } const ecs_world_info_t* ecs_get_world_info( const ecs_world_t *world) { world = ecs_get_world(world); return &world->info; } ecs_world_t *ecs_mini(void) { #ifdef FLECS_OS_API_IMPL ecs_set_os_api_impl(); #endif ecs_os_init(); ecs_trace("#[bold]bootstrapping world"); ecs_log_push(); ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); if (!ecs_os_has_heap()) { ecs_abort(ECS_MISSING_OS_API, NULL); } if (!ecs_os_has_threading()) { ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } if (!ecs_os_has_time()) { ecs_trace("time management not available"); } flecs_log_build_info(); ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); 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_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 data structures"); 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_make_alive(world, entity_with_generation); ecs_record_t *r = flecs_entities_get(world, entity_with_generation); if (r && r->table) { int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_entity_t *entities = r->table->data.entities.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; if (ECS_EQZERO(delta_time)) { delta_time = user_delta_time; break; } } } /* Keep trying while delta_time is zero */ } while (ECS_EQZERO(delta_time)); world->frame_start_time = t; /* Keep track of total time passed in world */ world->info.world_time_total_raw += (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; /* Command buffer capturing */ world->on_commands_active = world->on_commands; world->on_commands = NULL; world->on_commands_ctx_active = world->on_commands_ctx; world->on_commands_ctx = NULL; ecs_run_aperiodic(world, 0); 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); /* Reset command handler each frame */ world->on_commands_active = NULL; world->on_commands_ctx_active = NULL; error: return; } 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 data structures 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_ensure( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count ++; } else if (severity == EcsAlertWarning) { active->warning_count ++; } else if (severity == EcsAlertError) { active->error_count ++; } ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr[0] = alert_instance; ecs_modified(world, source, EcsAlertsActive); } static void flecs_alerts_remove_alert_from_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert) { EcsAlertsActive *active = ecs_ensure( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_remove(&active->alerts, alert); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count --; } else if (severity == EcsAlertWarning) { active->warning_count --; } else if (severity == EcsAlertError) { active->error_count --; } if (!ecs_map_count(&active->alerts)) { ecs_remove(world, source, EcsAlertsActive); } else { ecs_modified(world, source, EcsAlertsActive); } } static ecs_entity_t flecs_alert_get_severity( ecs_world_t *world, ecs_iter_t *it, EcsAlert *alert) { int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); ecs_alert_severity_filter_t *filters = ecs_vec_first(&alert->severity_filters); for (i = 0; i < filter_count; i ++) { ecs_alert_severity_filter_t *filter = &filters[i]; if (!filter->var) { if (ecs_table_has_id(world, it->table, filters[i].with)) { return filters[i].severity; } } else { ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); if (src && src != EcsWildcard) { if (ecs_has_id(world, src, filters[i].with)) { return filters[i].severity; } } } } return 0; } static ecs_entity_t flecs_alert_out_of_range_kind( EcsAlert *alert, const EcsMemberRanges *ranges, const void *value_ptr) { double value = 0; switch(alert->kind) { case EcsU8: value = *(const uint8_t*)value_ptr; break; case EcsU16: value = *(const uint16_t*)value_ptr; break; case EcsU32: value = *(const uint32_t*)value_ptr; break; case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; case EcsI8: value = *(const int8_t*)value_ptr; break; case EcsI16: value = *(const int16_t*)value_ptr; break; case EcsI32: value = *(const int32_t*)value_ptr; break; case EcsI64: value = (double)*(const int64_t*)value_ptr; break; case EcsF32: value = (double)*(const float*)value_ptr; break; case EcsF64: value = *(const double*)value_ptr; break; case EcsBool: case EcsChar: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: 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; if (!rule) { continue; } 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_ensure(world, parent, EcsAlert); const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); ecs_assert(poly != NULL, ECS_INVALID_OPERATION, "alert entity does not have (Poly, Query) component"); ecs_rule_t *rule = poly->poly; if (!rule) { return; } 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_ensure(world, result, EcsAlert); ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); alert->message = ecs_os_strdup(desc->message); alert->retain_period = desc->retain_period; /* Initialize severity filters */ int32_t i; for (i = 0; i < 4; i ++) { if (desc->severity_filters[i].with) { if (!desc->severity_filters[i].severity) { ecs_err("severity filter must have severity"); goto error; } ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, &alert->severity_filters, ecs_alert_severity_filter_t); *sf = desc->severity_filters[i]; if (sf->var) { sf->_var_index = ecs_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 = (ecs_ftime_t)0.5 }); ecs_system(world, { .entity = ecs_id(MonitorAlertInstances), .interval = (ecs_ftime_t)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. */ #ifndef __EMSCRIPTEN__ ecs_quit(world); #endif if (result == 1) { return 0; /* Normal exit */ } else { return result; /* Error code */ } } static int flecs_default_frame_action( ecs_world_t *world, const ecs_app_desc_t *desc) { return !ecs_progress(world, desc->delta_time); } static ecs_app_run_action_t run_action = flecs_default_run_action; static ecs_app_frame_action_t frame_action = flecs_default_frame_action; static ecs_app_desc_t ecs_app_desc; /* Serve REST API from wasm image when running in emscripten */ #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/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); }) static void flecs_doc_set( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t kind, const char *value) { if (value) { ecs_set_pair(world, entity, EcsDocDescription, kind, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, value) }); } else { ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); } } void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_doc_set(world, entity, EcsName, name); } void ecs_doc_set_brief( ecs_world_t *world, ecs_entity_t entity, const char *brief) { flecs_doc_set(world, entity, EcsDocBrief, brief); } void ecs_doc_set_detail( ecs_world_t *world, ecs_entity_t entity, const char *detail) { flecs_doc_set(world, entity, EcsDocDetail, detail); } void ecs_doc_set_link( ecs_world_t *world, ecs_entity_t entity, const char *link) { flecs_doc_set(world, entity, EcsDocLink, link); } void ecs_doc_set_color( ecs_world_t *world, ecs_entity_t entity, const char *color) { flecs_doc_set(world, entity, EcsDocColor, color); } const char* ecs_doc_get_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; } } /* Doc definitions for core components */ static void flecs_doc_import_core_definitions( ecs_world_t *world) { 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, "Module with builtin components"); ecs_doc_set_brief(world, EcsFlecsInternals, "Module with internal entities"); ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to 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, EcsPrivate, "Tag that is added to private components"); ecs_doc_set_brief(world, EcsFlag, "Internal tag for tracking ids with special id flags"); ecs_doc_set_brief(world, ecs_id(EcsIterable), "Internal component to make (query) entities iterable"); ecs_doc_set_brief(world, ecs_id(EcsPoly), "Internal component that stores pointer to poly objects"); ecs_doc_set_brief(world, ecs_id(EcsFlattenTarget), "Internal component that stores information for flattened trees"); ecs_doc_set_brief(world, EcsFlatten, "Tag that when added to assembly automatically flattens tree"); ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to store entity name"); ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to store entity symbol"); ecs_doc_set_brief(world, EcsAlias, "Tag used with EcsIdentifier to store entity alias"); ecs_doc_set_brief(world, EcsQuery, "Tag added to query entities"); ecs_doc_set_brief(world, EcsObserver, "Tag added to observer entities"); ecs_doc_set_brief(world, EcsTransitive, "Trait that enables transitive evaluation of relationships"); ecs_doc_set_brief(world, EcsReflexive, "Trait that enables reflexive evaluation of relationships"); ecs_doc_set_brief(world, EcsFinal, "Trait that indicates an entity cannot be inherited from"); ecs_doc_set_brief(world, EcsDontInherit, "Trait that indicates it should not be inherited"); ecs_doc_set_brief(world, EcsTag, "Trait that ensures a pair cannot contain a value"); ecs_doc_set_brief(world, EcsAcyclic, "Trait that indicates a relationship is acyclic"); ecs_doc_set_brief(world, EcsTraversable, "Trait that indicates a relationship is traversable"); ecs_doc_set_brief(world, EcsExclusive, "Trait that ensures a relationship can only have one target"); ecs_doc_set_brief(world, EcsSymmetric, "Trait that causes a relationship to be two-way"); ecs_doc_set_brief(world, EcsWith, "Trait for adding additional components when a component is added"); ecs_doc_set_brief(world, EcsAlwaysOverride, "Trait that indicates a component should always be overridden"); ecs_doc_set_brief(world, EcsUnion, "Trait for creating a non-fragmenting relationship"); ecs_doc_set_brief(world, EcsOneOf, "Trait that enforces target of relationship is a child of "); ecs_doc_set_brief(world, EcsOnDelete, "Cleanup trait for specifying what happens when component is deleted"); ecs_doc_set_brief(world, EcsOnDeleteTarget, "Cleanup trait for specifying what happens when pair target is deleted"); ecs_doc_set_brief(world, EcsRemove, "Cleanup action used with OnDelete/OnDeleteTarget"); ecs_doc_set_brief(world, EcsDelete, "Cleanup action used with OnDelete/OnDeleteTarget"); ecs_doc_set_brief(world, EcsPanic, "Cleanup action used with OnDelete/OnDeleteTarget"); ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); ecs_doc_set_brief(world, EcsIsA, "Relationship used for expressing inheritance"); ecs_doc_set_brief(world, EcsChildOf, "Relationship used for expressing hierarchies"); ecs_doc_set_brief(world, EcsDependsOn, "Relationship used for expressing dependencies"); ecs_doc_set_brief(world, EcsSlotOf, "Relationship used for expressing prefab slots"); ecs_doc_set_brief(world, EcsOnAdd, "Event emitted when component is added"); ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); ecs_doc_set_brief(world, EcsUnSet, "Event emitted when component is unset"); ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); ecs_doc_set_brief(world, EcsOnTableFill, "Event emitted when table becomes non-empty"); ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); ecs_doc_set_brief(world, EcsThis, "Query marker to express $this variable"); ecs_doc_set_brief(world, EcsWildcard, "Query marker to express match all wildcard"); ecs_doc_set_brief(world, EcsAny, "Query marker to express match at least one wildcard"); ecs_doc_set_brief(world, EcsPredEq, "Query marker to express == operator"); ecs_doc_set_brief(world, EcsPredMatch, "Query marker to express ~= operator"); ecs_doc_set_brief(world, EcsPredLookup, "Query marker to express by-name lookup"); ecs_doc_set_brief(world, EcsScopeOpen, "Query marker to express scope open"); ecs_doc_set_brief(world, EcsScopeClose, "Query marker to express scope close"); ecs_doc_set_brief(world, EcsEmpty, "Tag used to indicate a query has no results"); } /* Doc definitions for doc components */ static void flecs_doc_import_doc_definitions( ecs_world_t *world) { ecs_entity_t doc = ecs_lookup(world, "flecs.doc"); ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); ecs_doc_set_brief(world, EcsDocBrief, "Brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Link to additional documentation"); ecs_doc_set_brief(world, EcsDocColor, "Color hint for entity"); ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); } void 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); flecs_doc_import_core_definitions(world); flecs_doc_import_doc_definitions(world); } #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) { ecs_size_t len = ecs_os_strlen(path); if (!ecs_os_strncmp(path, type_name, len)) { // Type is a child of current parent, trim name of parent type_name += len; ecs_assert(type_name[0], ECS_INVALID_PARAMETER, NULL); ecs_assert(type_name[0] == ':', ECS_INVALID_PARAMETER, NULL); ecs_assert(type_name[1] == ':', ECS_INVALID_PARAMETER, NULL); type_name += 2; } else { // Type is not a child of current parent, trim entire path char *ptr = strrchr(type_name, ':'); if (ptr) { type_name = ptr + 1; } } } ecs_os_free(path); return type_name; } // 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_make_alive(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) /* 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; int code; double time; } ecs_http_request_entry_t; /* HTTP server struct */ struct ecs_http_server_t { bool should_run; bool running; ecs_http_socket_t sock; ecs_os_mutex_t lock; ecs_os_thread_t thread; ecs_http_reply_action_t callback; void *ctx; double cache_timeout; double cache_purge_timeout; ecs_sparse_t connections; /* sparse */ 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) { double tf = ecs_time_measure(&t); if ((tf - entry->time) < srv->cache_timeout) { return entry; } } return NULL; } static void http_insert_request_entry( ecs_http_server_t *srv, ecs_http_request_impl_t *req, ecs_http_reply_t *reply) { int32_t content_length = ecs_strbuf_written(&reply->body); if (!content_length) { return; } ecs_http_request_key_t key; key.array = req->res; key.count = req->req_len; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (!entry) { flecs_hashmap_result_t elem = flecs_hashmap_ensure( &srv->request_cache, &key, ecs_http_request_entry_t); ecs_http_request_key_t *elem_key = elem.key; elem_key->array = ecs_os_memdup_n(key.array, char, key.count); entry = elem.value; } else { ecs_os_free(entry->content); } ecs_time_t t = {0, 0}; entry->time = ecs_time_measure(&t); entry->content_length = ecs_strbuf_written(&reply->body); entry->content = ecs_strbuf_get(&reply->body); entry->code = reply->code; ecs_strbuf_appendstrn(&reply->body, entry->content, entry->content_length); } static char* http_decode_request( ecs_http_request_impl_t *req, ecs_http_fragment_t *frag) { ecs_os_zeromem(req); ecs_size_t req_len = frag->buf.length; 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]; if (!req->req_len) { req->req_len = req_len; } 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->state = HttpFragStateMethod; frag->header_buf_ptr = frag->header_buf; /* fall through */ case HttpFragStateMethod: if (c == ' ') { http_parse_method(frag); ecs_strbuf_reset(&frag->buf); frag->state = HttpFragStatePath; frag->buf.content = NULL; } 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; int32_t content_length = reply->body.length; char *content = ecs_strbuf_get(&reply->body); /* Use asynchronous send queue for outgoing data so send operations won't * hold up main thread */ ecs_http_send_request_t *req = NULL; if (!preflight) { req = http_send_queue_post(conn->pub.server); if (!req) { reply->code = 503; /* queue full, server is busy */ ecs_os_linc(&ecs_http_busy_count); } } http_append_send_headers(&hdrs, reply->code, reply->status, reply->content_type, &reply->headers, content_length, preflight); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); char *headers = ecs_strbuf_get(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* 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 = entry->code; reply.content_type = "application/json"; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; ecs_strbuf_appendstrn(&reply.body, entry->content, entry->content_length); http_send_reply(conn, &reply, false); http_connection_free(conn); /* Lock was transferred from enqueue_request */ ecs_os_mutex_unlock(srv->lock); } } } else { ecs_os_linc(&ecs_http_request_invalid_count); } } ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); if (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) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, "missing request handler for server"); if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { reply->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); } } error: return; } static void http_handle_request( ecs_http_server_t *srv, ecs_http_request_impl_t *req) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; if (req->pub.method != EcsHttpOptions) { if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { reply.code = 404; reply.status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply.code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } if (req->pub.method == EcsHttpGet) { http_insert_request_entry(srv, req, &reply); } http_send_reply(conn, &reply, false); ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); } else { /* Already taken care of */ } http_reply_fini(&reply); http_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}; double time = ecs_time_measure(&t); ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); 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) > srv->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->cache_timeout = desc->cache_timeout; srv->cache_purge_timeout = desc->cache_purge_timeout; if (!ECS_EQZERO(srv->cache_timeout) && ECS_EQZERO(srv->cache_purge_timeout)) { srv->cache_purge_timeout = srv->cache_timeout * 10; } srv->callback = desc->callback; srv->ctx = desc->ctx; srv->port = desc->port; srv->ipaddr = desc->ipaddr; srv->send_queue.wait_ms = desc->send_queue_wait_ms; if (!srv->send_queue.wait_ms) { srv->send_queue.wait_ms = 1; } flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); 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; } ecs_http_request_entry_t *entry = http_find_request_entry(srv, request.res, request.req_len); if (entry) { reply_out->body = ECS_STRBUF_INIT; reply_out->code = entry->code; reply_out->content_type = "application/json"; reply_out->headers = ECS_STRBUF_INIT; reply_out->status = "OK"; ecs_strbuf_appendstrn(&reply_out->body, entry->content, entry->content_length); } else { http_do_request(srv, reply_out, &request); if (request.pub.method == EcsHttpGet) { http_insert_request_entry(srv, &request, reply_out); } } ecs_os_free(res); http_purge_request_cache(srv, false); return (reply_out->code >= 400) ? -1 : 0; } int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, ecs_http_reply_t *reply_out) { const char *http_ver = " HTTP/1.1\r\n\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); int32_t http_ver_len = ecs_os_strlen(http_ver); int32_t len = method_len + req_len + http_ver_len + 1; if (method_len + req_len + http_ver_len >= 1024) { ecs_err("HTTP request too long"); return -1; } char reqstr[1024]; char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; ptr[0] = '\n'; return ecs_http_server_http_request(srv, reqstr, len, reply_out); } 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) { if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { path = ecs_get_fullpath(world, entity); var_id = flecs_journal_entitystr(world, entity); } else { path = ecs_id_str(world, entity); var_id = flecs_journal_idstr(world, entity); } } if (kind == EcsJournalNew) { ecs_print(4, "#[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; } void ecs_assert_log_( int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = 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; } 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; } void ecs_assert_log_( int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; } #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, "ecs_id_t")) { type = ecs_id(ecs_id_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; if (!ref->entity) { continue; } 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_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *table = 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_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *mtable = r->table; double *value = ECS_ELEM(m, ctx->size, i); if (!counter) { ecs_os_memset(value, 0, ctx->size); } if (!mtable) { ecs_delete(it->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_ensure( world, mi[0], EcsMetricSource); source->entity = tgt; } EcsMetricValue *value = ecs_ensure(world, mi[0], EcsMetricValue); value->value += (double)ecs_count_id(world, cur->id) * (double)it->delta_system_time; } } } static void UpdateCountIds(ecs_iter_t *it) { ecs_world_t *world = it->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) { ecs_entity_t type = 0, member_type = 0, member = 0, id = 0; uintptr_t offset = 0; if (desc->dotmember) { if (!desc->id) { char *metric_name = ecs_get_fullpath(world, metric); ecs_err("missing id for metric '%s' with member '%s", metric_name, desc->dotmember); ecs_os_free(metric_name); goto error; } if (desc->member) { char *metric_name = ecs_get_fullpath(world, metric); ecs_err("cannot set both member and dotmember for metric '%s'", metric_name); ecs_os_free(metric_name); goto error; } type = ecs_get_typeid(world, desc->id); ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); if (ecs_meta_push(&cur)) { char *metric_name = ecs_get_fullpath(world, metric); ecs_err("invalid type for metric '%s'", metric_name); ecs_os_free(metric_name); goto error; } if (ecs_meta_dotmember(&cur, desc->dotmember)) { char *metric_name = ecs_get_fullpath(world, metric); ecs_err("invalid dotmember '%s' for metric '%s'", desc->dotmember, metric_name); ecs_os_free(metric_name); goto error; } id = desc->id; member_type = ecs_meta_get_type(&cur); offset = (uintptr_t)ecs_meta_get_ptr(&cur); member = ecs_meta_get_member_id(&cur); } else { const EcsMember *m = ecs_get(world, desc->member, EcsMember); if (!m) { char *metric_name = ecs_get_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; } 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; } id = type; if (desc->id) { if (type != ecs_get_typeid(world, desc->id)) { char *metric_name = ecs_get_fullpath(world, metric); char *member_name = ecs_get_fullpath(world, desc->member); char *id_name = ecs_get_fullpath(world, desc->id); ecs_err("member '%s' for metric '%s' is not of type '%s'", member_name, metric_name, id_name); ecs_os_free(id_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } id = desc->id; } member = desc->member; member_type = m->type; offset = flecs_ito(uintptr_t, m->offset); } const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive); if (!p) { char *metric_name = ecs_get_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; } 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_uto(uint16_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .filter.terms[0] = { .id = id, .src.flags = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_member_metric, .yield_existing = true, .ctx = ctx }); ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** 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 && !desc->dotmember) { ecs_err("CounterIncrement can only be used in combination with member"); goto error; } if (kind == EcsCounterId && (desc->member || desc->dotmember)) { ecs_err("CounterId cannot be used in combination with member"); goto error; } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); #endif } if (desc->member || desc->dotmember) { if (flecs_member_metric_init(world, result, desc)) { goto error; } } else if (desc->id) { if (desc->targets) { if (!ecs_id_is_pair(desc->id)) { ecs_err("cannot specify targets for id that is not a pair"); goto error; } if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { ecs_err("first element of pair cannot be wildcard with " " targets enabled"); goto error; } if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { ecs_err("second element of pair must be wildcard with " " targets enabled"); goto error; } if (kind == EcsCounterId) { if (flecs_count_id_targets_metric_init(world, result, desc)) { goto error; } } else { ecs_entity_t first = ecs_pair_first(world, desc->id); ecs_entity_t scope = flecs_get_oneof(world, first); if (!scope) { ecs_err("first element of pair must have OneOf with " " targets enabled"); goto error; } if (flecs_oneof_metric_init(world, result, scope, desc)) { goto error; } } } else { if (kind == EcsCounterId) { if (flecs_count_ids_metric_init(world, result, desc)) { goto error; } } else { if (flecs_id_metric_init(world, result, desc)) { goto error; } } } } else { ecs_err("missing source specified for metric"); goto error; } return result; error: if (result && result != desc->entity) { ecs_delete(world, result); } return 0; } void FlecsMetricsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMetrics); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); ECS_TAG_DEFINE(world, EcsMetric); ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); ECS_TAG_DEFINE(world, EcsCounter); ECS_TAG_DEFINE(world, EcsCounterIncrement); ECS_TAG_DEFINE(world, EcsCounterId); ECS_TAG_DEFINE(world, EcsGauge); ecs_set_scope(world, old_scope); ecs_set_name_prefix(world, "EcsMetric"); ECS_TAG_DEFINE(world, EcsMetricInstance); ECS_COMPONENT_DEFINE(world, EcsMetricValue); ECS_COMPONENT_DEFINE(world, EcsMetricSource); ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); ECS_COMPONENT_DEFINE(world, EcsMetricMember); ECS_COMPONENT_DEFINE(world, EcsMetricId); ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); ecs_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(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(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_make_alive(world, e); ecs_add_fullpath(world, e, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } ecs_add_id(world, e, EcsModule); ecs_component_desc_t private_desc = *desc; private_desc.entity = e; if (desc->type.size) { ecs_entity_t result = ecs_component_init(world, &private_desc); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); (void)result; } ecs_set_scope(world, old_scope); return e; error: return 0; } #endif /** * @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; summary[i].frame_count ++; summary[i].command_count += info->cmd.add_count + info->cmd.remove_count + info->cmd.delete_count + info->cmd.clear_count + info->cmd.set_count + info->cmd.ensure_count + info->cmd.modified_count + info->cmd.discard_count + info->cmd.event_count + info->cmd.other_count; summary[i].build_info = *ecs_get_build_info(); } } 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 #ifdef FLECS_UNITS ECS_IMPORT(world, FlecsUnits); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsMonitor), "Module that automatically monitors statistics for the world & systems"); #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); #if defined(FLECS_META) && defined(FLECS_UNITS) ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); 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 }, { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, { .name = "command_count", .type = ecs_id(ecs_u64_t) }, { .name = "build_info", .type = build_info } } }); #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_DESC "desc" #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 (ch == '\\') { ptr ++; tptr[0] = ptr[0]; tptr ++; continue; } 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_DESC)) { return EcsDesc; } 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(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, ecs_oper_kind_t *extra_oper, ecs_term_id_t *extra_args) { (void)column; int32_t i, arg = 0; if (extra_args) { ecs_os_memset_n(extra_args, 0, ecs_term_id_t, ECS_PARSER_MAX_ARGS); } if (!term) { arg = 2; } do { if (flecs_valid_token_start_char(ptr[0])) { if ((arg == ECS_PARSER_MAX_ARGS) || (!extra_args && arg == 2)) { ecs_parser_error(name, expr, (ptr - expr), "too many arguments in term"); goto error; } ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { goto error; } ptr = ecs_parse_ws_eol(ptr); ecs_term_id_t *term_id = NULL; if (arg == 0) { term_id = &term->src; } else if (arg == 1) { term_id = &term->second; } else { term_id = &extra_args[arg - 2]; } /* 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); goto error; } ptr = ecs_parse_ws_eol(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, term_id, TOK_PAREN_CLOSE); if (!ptr) { goto error; } /* Check for term flags */ } else if (!ecs_os_strcmp(token, TOK_CASCADE) || !ecs_os_strcmp(token, TOK_DESC) || !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) { goto error; } /* Regular identifier */ } else if (flecs_parse_identifier(token, term_id)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); goto error; } if (ptr[0] == TOK_AND) { if (extra_oper && *extra_oper != EcsAnd) { ecs_parser_error(name, expr, (ptr - expr), "cannot mix ',' and '||' in term arguments"); goto error; } ptr = ecs_parse_ws_eol(ptr + 1); if (term) { term->id_flags = ECS_PAIR; } } else if (ptr[0] == TOK_PAREN_CLOSE) { ptr = ecs_parse_ws(ptr + 1); break; } else if (extra_oper && ptr[0] == TOK_OR[0] && ptr[1] == TOK_OR[1]){ if (arg >= 2 && *extra_oper != EcsOr) { ecs_parser_error(name, expr, (ptr - expr), "cannot mix ',' and '||' in term arguments"); goto error; } *extra_oper = EcsOr; ptr = ecs_parse_ws_eol(ptr + 2); if (term) { term->id_flags = ECS_PAIR; } } else { ecs_parser_error(name, expr, (ptr - expr), "expected ',' or ')'"); goto error; } } else { ecs_parser_error(name, expr, (ptr - expr), "expected identifier or set expression"); goto error; } arg ++; } while (true); return ptr; error: if (term && term->src.name) { ecs_os_free(ECS_CONST_CAST(char*, term->src.name)); term->src.name = NULL; } if (term && term->second.name) { ecs_os_free(ECS_CONST_CAST(char*, term->second.name)); term->second.name = NULL; } if (extra_args) { for (i = 2; i < arg + 1; i ++) { if (extra_args[i - 2].name) { ecs_os_free(ECS_CONST_CAST(char*, extra_args[i - 2].name)); extra_args[i - 2].name = NULL; } } } return NULL; } 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, ecs_oper_kind_t *extra_oper, ecs_term_id_t *extra_args) { 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_eol(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 = ecs_parse_ws_eol(ptr + 1); 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, extra_oper, extra_args); } 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[0] == TOK_AND || ptr[0] == TOK_OR[0]) { 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; } if (ptr[0] == TOK_AND) { ptr = ecs_parse_ws(ptr + 1); ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, token, NULL, extra_oper, extra_args); if (!ptr) { goto error; } } else if (extra_oper && ptr[0] == TOK_OR[0] && ptr[1] == TOK_OR[1]) { ptr = ecs_parse_ws_eol(ptr + 2); *extra_oper = EcsOr; ptr = flecs_parse_arguments(world, name, expr, (ptr - expr), ptr, token, NULL, extra_oper, extra_args); if (!ptr) { goto error; } } else { ptr ++; } 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] == '[') || /* collection 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_oper_kind_t *extra_oper, ecs_term_id_t *extra_args, bool allow_newline) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); if (extra_oper) { *extra_oper = EcsAnd; } 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, extra_oper, extra_args); 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; } const char *var_name = strrchr(term->first.name, '.'); if (var_name) { var_name ++; } else { var_name = term->first.name; } term->second.name = ecs_os_strdup(var_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; } if (allow_newline) { ptr = ecs_parse_ws_eol(ptr); } else { 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_ensure(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_ensure_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 explicitly 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_ensure_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_ensure_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(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, NULL, NULL, false); 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] == '{' || 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] == '{' || 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); } 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_ensure(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); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsScript), "Module with components for managing Flecs scripts"); #endif 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 /* Retain captured commands for one minute at 60 FPS */ #define FLECS_REST_COMMAND_RETAIN_COUNT (60 * 60) static ECS_TAG_DECLARE(EcsRestPlecs); typedef struct { ecs_world_t *world; ecs_http_server_t *srv; int32_t rc; ecs_map_t cmd_captures; } ecs_rest_ctx_t; typedef struct { char *cmds; ecs_time_t start_time; ecs_strbuf_t buf; } ecs_rest_cmd_sync_capture_t; typedef struct { ecs_vec_t syncs; } ecs_rest_cmd_capture_t; static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_rest_server_fini(impl->srv); } } ecs_os_free(ptr->ipaddr); }) static 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; #ifdef FLECS_DEBUG if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { ecs_log_enable_colors(true); rest_prev_log(level, file, line, msg); ecs_log_enable_colors(false); } } #endif 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(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, "field_info", &desc->serialize_field_info); flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); flecs_rest_bool_param(req, "query_profile", &desc->serialize_query_profile); flecs_rest_bool_param(req, "table", &desc->serialize_table); flecs_rest_bool_param(req, "rows", &desc->serialize_rows); bool results = true; flecs_rest_bool_param(req, "results", &results); desc->dont_serialize_results = !results; } 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_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; 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))) { 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_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { 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); 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); ecs_os_free(escaped_err); ecs_os_free(err); } reply->code = 400; } static void flecs_rest_iter_to_reply( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_poly_t *query, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = {0}; desc.serialize_entities = true; desc.serialize_variables = true; flecs_rest_parse_json_ser_iter_params(&desc, req); desc.query = query; int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); if (offset < 0 || limit < 0) { flecs_reply_error(reply, "invalid offset/limit parameter"); return; } 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); } flecs_rest_int_param(req, "offset", &offset); } static bool flecs_rest_reply_existing_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *name) { ecs_entity_t q = ecs_lookup(world, name); if (!q) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; return true; } ecs_poly_t *poly = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, q, EcsPoly, EcsQuery); if (!poly_comp) { poly_comp = ecs_get_pair(world, q, EcsPoly, EcsObserver); if (poly_comp) { poly = &((ecs_observer_t*)poly_comp->poly)->filter; } else { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } } else { poly = poly_comp->poly; } if (!poly) { flecs_reply_error(reply, "query '%s' is not initialized", name); reply->code = 400; return true; } ecs_iter_t it; ecs_iter_poly(world, 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, ecs_rule_t)) { flecs_reply_error(reply, "variables are only supported for rule queries"); reply->code = 400; return true; } if (ecs_rule_parse_vars(poly, &it, vars) == NULL) { flecs_rest_reply_set_captured_log(reply); return true; } } flecs_rest_iter_to_reply(world, req, reply, poly, &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); } const char *q = ecs_http_get_param(req, "q"); if (!q) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); reply->code = 400; /* bad request */ return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); 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); if (try) { /* If client is trying queries, don't spam console with errors */ reply->code = 200; } } else { ecs_iter_t it = ecs_rule_iter(world, r); flecs_rest_iter_to_reply(world, req, reply, r, &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.ensure_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_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); 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; return false; } } if (!ecs_os_strcmp(category, "world")) { const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, EcsWorldStats, period); flecs_world_stats_to_json(&reply->body, stats); return true; } else if (!ecs_os_strcmp(category, "pipeline")) { const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); flecs_pipeline_stats_to_json(world, &reply->body, stats); return true; } else { flecs_reply_error(reply, "bad request (unsupported category)"); reply->code = 400; return false; } } #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); allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_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 const char* flecs_rest_cmd_kind_to_str( ecs_cmd_kind_t kind) { switch(kind) { case EcsCmdClone: return "Clone"; case EcsCmdBulkNew: return "BulkNew"; case EcsCmdAdd: return "Add"; case EcsCmdRemove: return "Remove"; case EcsCmdSet: return "Set"; case EcsCmdEmplace: return "Emplace"; case EcsCmdEnsure: return "Ensure"; case EcsCmdModified: return "Modified"; case EcsCmdModifiedNoHook: return "ModifiedNoHook"; case EcsCmdAddModified: return "AddModified"; case EcsCmdPath: return "Path"; case EcsCmdDelete: return "Delete"; case EcsCmdClear: return "Clear"; case EcsCmdOnDeleteAction: return "OnDeleteAction"; case EcsCmdEnable: return "Enable"; case EcsCmdDisable: return "Disable"; case EcsCmdEvent: return "Event"; case EcsCmdSkip: return "Skip"; default: return "Unknown"; } } static bool flecs_rest_cmd_has_id( const ecs_cmd_t *cmd) { switch(cmd->kind) { case EcsCmdClear: case EcsCmdDelete: case EcsCmdClone: case EcsCmdDisable: case EcsCmdPath: return false; case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdRemove: case EcsCmdSet: case EcsCmdEmplace: case EcsCmdEnsure: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdEvent: case EcsCmdSkip: default: return true; } } static void flecs_rest_server_garbage_collect_all( ecs_rest_ctx_t *impl) { ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); while (ecs_map_next(&it)) { ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_os_free(sync->cmds); } ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_os_free(capture); } ecs_map_fini(&impl->cmd_captures); } static void flecs_rest_server_garbage_collect( ecs_world_t *world, ecs_rest_ctx_t *impl) { const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); ecs_vec_t removed_frames = {0}; while (ecs_map_next(&it)) { int64_t frame = flecs_uto(int64_t, ecs_map_key(&it)); if ((wi->frame_count_total - frame) > FLECS_REST_COMMAND_RETAIN_COUNT) { ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_os_free(sync->cmds); } ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_os_free(capture); ecs_vec_init_if_t(&removed_frames, int64_t); ecs_vec_append_t(NULL, &removed_frames, int64_t)[0] = frame; } } int32_t i, count = ecs_vec_count(&removed_frames); if (count) { int64_t *frames = ecs_vec_first(&removed_frames); if (count) { for (i = 0; i < count; i ++) { ecs_map_remove(&impl->cmd_captures, flecs_ito(uint64_t, frames[i])); } } ecs_vec_fini_t(NULL, &removed_frames, int64_t); } } static void flecs_rest_cmd_to_json( ecs_world_t *world, ecs_strbuf_t *buf, ecs_cmd_t *cmd) { ecs_strbuf_list_push(buf, "{", ","); ecs_strbuf_list_appendlit(buf, "\"kind\":\""); ecs_strbuf_appendstr(buf, flecs_rest_cmd_kind_to_str(cmd->kind)); ecs_strbuf_appendlit(buf, "\""); if (flecs_rest_cmd_has_id(cmd)) { ecs_strbuf_list_appendlit(buf, "\"id\":\""); char *idstr = ecs_id_str(world, cmd->id); ecs_strbuf_appendstr(buf, idstr); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(idstr); } if (cmd->system) { ecs_strbuf_list_appendlit(buf, "\"system\":\""); char *sysstr = ecs_get_fullpath(world, cmd->system); ecs_strbuf_appendstr(buf, sysstr); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(sysstr); } if (cmd->kind == EcsCmdBulkNew) { /* Todo */ } else if (cmd->kind == EcsCmdEvent) { /* Todo */ } else { if (cmd->entity) { ecs_strbuf_list_appendlit(buf, "\"entity\":\""); char *path = ecs_get_path_w_sep(world, 0, cmd->entity, ".", ""); ecs_strbuf_appendstr(buf, path); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(path); ecs_strbuf_list_appendlit(buf, "\"is_alive\":\""); if (ecs_is_alive(world, cmd->entity)) { ecs_strbuf_appendlit(buf, "true"); } else { ecs_strbuf_appendlit(buf, "false"); } ecs_strbuf_appendlit(buf, "\""); ecs_strbuf_list_appendlit(buf, "\"next_for_entity\":"); ecs_strbuf_appendint(buf, cmd->next_for_entity); } } ecs_strbuf_list_pop(buf, "}"); } static void flecs_rest_on_commands( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx) { ecs_world_t *world = stage->world; ecs_rest_cmd_capture_t *capture = ctx; ecs_assert(capture != NULL, ECS_INTERNAL_ERROR, NULL); if (commands) { ecs_vec_init_if_t(&capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_rest_cmd_sync_capture_t *sync = ecs_vec_append_t( NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); int32_t i, count = ecs_vec_count(commands); ecs_cmd_t *cmds = ecs_vec_first(commands); sync->buf = ECS_STRBUF_INIT; ecs_strbuf_list_push(&sync->buf, "{", ","); ecs_strbuf_list_appendlit(&sync->buf, "\"commands\":"); ecs_strbuf_list_push(&sync->buf, "[", ","); for (i = 0; i < count; i ++) { ecs_strbuf_list_next(&sync->buf); flecs_rest_cmd_to_json(world, &sync->buf, &cmds[i]); } ecs_strbuf_list_pop(&sync->buf, "]"); /* Measure how long it takes to process queue */ sync->start_time = (ecs_time_t){0}; ecs_time_measure(&sync->start_time); } else { /* Finished processing queue, measure duration */ ecs_rest_cmd_sync_capture_t *sync = ecs_vec_last_t( &capture->syncs, ecs_rest_cmd_sync_capture_t); double duration = ecs_time_measure(&sync->start_time); ecs_strbuf_list_appendlit(&sync->buf, "\"duration\":"); ecs_strbuf_appendflt(&sync->buf, duration, '"'); ecs_strbuf_list_pop(&sync->buf, "}"); sync->cmds = ecs_strbuf_get(&sync->buf); } } static bool flecs_rest_reply_commands_capture( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_append(&reply->body, "\"frame\":%u", wi->frame_count_total); ecs_strbuf_appendstr(&reply->body, "}"); ecs_map_init_if(&impl->cmd_captures, &world->allocator); ecs_rest_cmd_capture_t *capture = ecs_map_ensure_alloc_t( &impl->cmd_captures, ecs_rest_cmd_capture_t, flecs_ito(uint64_t, wi->frame_count_total)); world->on_commands = flecs_rest_on_commands; world->on_commands_ctx = capture; /* Run garbage collection so that requests don't linger */ flecs_rest_server_garbage_collect(world, impl); return true; } static bool flecs_rest_reply_commands_request( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; char *frame_str = &req->path[15]; int32_t frame = atoi(frame_str); ecs_map_init_if(&impl->cmd_captures, &world->allocator); const ecs_rest_cmd_capture_t *capture = ecs_map_get_deref( &impl->cmd_captures, ecs_rest_cmd_capture_t, flecs_ito(uint64_t, frame)); if (!capture) { ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_append(&reply->body, "\"error\": \"no capture for frame %u\"", frame); ecs_strbuf_appendstr(&reply->body, "}"); reply->code = 404; return true; } ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_list_append(&reply->body, "\"syncs\":"); ecs_strbuf_list_push(&reply->body, "[", ","); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_strbuf_list_appendstr(&reply->body, sync->cmds); } ecs_strbuf_list_pop(&reply->body, "]"); ecs_strbuf_appendstr(&reply->body, "}"); return true; } static bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) { ecs_rest_ctx_t *impl = ctx; ecs_world_t *world = impl->world; if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } if (req->method == EcsHttpGet) { /* 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); /* Commands capture endpoint */ } else if (!ecs_os_strncmp(req->path, "commands/capture", 16)) { return flecs_rest_reply_commands_capture(world, impl, req, reply); /* Commands request endpoint (request commands from specific frame) */ } else if (!ecs_os_strncmp(req->path, "commands/frame/", 15)) { return flecs_rest_reply_commands_request(world, impl, 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 *impl = ecs_http_server_ctx(srv); flecs_rest_server_garbage_collect_all(impl); ecs_os_free(impl); ecs_http_server_fini(srv); } static void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = 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, .cache_timeout = 0.2 }); if (!srv) { const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; ecs_err("failed to create REST server on %s:%u", ipaddr, rest[i].port); continue; } 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); flecs_rest_server_garbage_collect(it->world, ctx); } } } 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 #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsRest), "Module that implements Flecs REST API"); #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) { int32_t count = ecs_table_count(table); if (!count) { 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 */ ecs_allocator_t *a = &world->allocator; result->entities = ecs_vec_copy_shrink_t(a, &main_data->entities, ecs_entity_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_ctor; if (copy) { void *src_ptr = ecs_vec_first(&column->data); ecs_vec_t dst; ecs_vec_init(a, &dst, size, count); ecs_vec_set_count(a, &dst, size, count); void *dst_ptr = ecs_vec_first(&dst); copy(dst_ptr, src_ptr, count, ti); column->data = dst; } else { column->data = ecs_vec_copy_shrink(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_make_alive(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; 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->components.tag_count, t, world->info.tag_id_count); ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_sparse_count(&world->type_info)); ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); if (ecs_is_alive(world, EcsSystem)) { ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); int64_t outstanding_allocs = ecs_os_api_malloc_count + ecs_os_api_calloc_count - ecs_os_api_free_count; ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); #ifdef FLECS_HTTP ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif error: return; } void ecs_world_stats_reduce( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_world_stats_reduce_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_world_stats_repeat_last( ecs_world_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_world_stats_copy_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; int32_t t = s->t = t_next(s->t); if (query->filter.flags & EcsFilterMatchThis) { ECS_GAUGE_RECORD(&s->matched_entity_count, t, ecs_query_entity_count(query)); ECS_GAUGE_RECORD(&s->matched_table_count, t, ecs_query_table_count(query)); ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, ecs_query_empty_table_count(query)); } else { ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0); ECS_GAUGE_RECORD(&s->matched_table_count, t, 0); ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0); } const ecs_filter_t *f = ecs_query_get_filter(query); ECS_COUNTER_RECORD(&s->eval_count, t, f->eval_count); error: return; } void ecs_query_stats_reduce( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_query_stats_reduce_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_query_stats_repeat_last( ecs_query_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_query_stats_copy_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } #ifdef FLECS_SYSTEM bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); if (!ptr) { return false; } ecs_query_stats_get(world, ptr->query, &s->query); int32_t t = s->query.t; ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); return true; error: return false; } void ecs_system_stats_reduce( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_stats_reduce(&dst->query, &src->query); dst->task = src->task; flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } void ecs_system_stats_reduce_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src, int32_t count) { ecs_query_stats_reduce_last(&dst->query, &src->query, count); dst->task = src->task; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } void ecs_system_stats_repeat_last( ecs_system_stats_t *stats) { ecs_query_stats_repeat_last(&stats->query); flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->query.t)); } void ecs_system_stats_copy_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_stats_copy_last(&dst->query, &src->query); dst->task = src->task; flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } #endif #ifdef FLECS_PIPELINE bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); if (!pqc) { return false; } ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); int32_t sys_count = 0, active_sys_count = 0; /* Count number of active systems */ ecs_iter_t it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { continue; } active_sys_count += it.count; } /* Count total number of systems in pipeline */ it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { sys_count += it.count; } /* Also count synchronization points */ ecs_vec_t *ops = &pq->ops; ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); int32_t pip_count = active_sys_count + ecs_vec_count(ops); if (!sys_count) { return false; } if (ecs_map_is_init(&s->system_stats) && !sys_count) { ecs_map_fini(&s->system_stats); } ecs_map_init_if(&s->system_stats, NULL); if (op) { ecs_entity_t *systems = NULL; if (pip_count) { ecs_vec_init_if_t(&s->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); systems = ecs_vec_first_t(&s->systems, ecs_entity_t); /* Populate systems vector, keep track of sync points */ it = ecs_query_iter(stage, pq->query); int32_t i, i_system = 0, ran_since_merge = 0; while (ecs_query_next(&it)) { if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { continue; } for (i = 0; i < it.count; i ++) { systems[i_system ++] = it.entities[i]; ran_since_merge ++; if (op != op_last && ran_since_merge == op->count) { ran_since_merge = 0; op++; systems[i_system ++] = 0; /* 0 indicates a merge point */ } } } systems[i_system ++] = 0; /* Last merge */ ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); } /* Get sync point statistics */ int32_t i, count = ecs_vec_count(ops); if (count) { ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); op = ecs_vec_first_t(ops, ecs_pipeline_op_t); for (i = 0; i < count; i ++) { ecs_pipeline_op_t *cur = &op[i]; ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, ecs_sync_stats_t, i); ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, cur->commands_enqueued); el->system_count = cur->count; el->multi_threaded = cur->multi_threaded; el->no_readonly = cur->no_readonly; } } } /* Separately populate system stats map from build query, which includes * systems that aren't currently active */ it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { int32_t i; for (i = 0; i < it.count; i ++) { ecs_system_stats_t *stats = ecs_map_ensure_alloc_t(&s->system_stats, ecs_system_stats_t, it.entities[i]); stats->query.t = s->t; ecs_system_stats_get(world, it.entities[i], stats); } } s->t = t_next(s->t); return true; error: return false; } void ecs_pipeline_stats_fini( ecs_pipeline_stats_t *stats) { ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *elem = ecs_map_ptr(&it); ecs_os_free(elem); } ecs_map_fini(&stats->system_stats); ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); } void ecs_pipeline_stats_reduce( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t system_count = ecs_vec_count(&src->systems); ecs_vec_init_if_t(&dst->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, src->t); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->no_readonly = src_el->no_readonly; } ecs_map_init_if(&dst->system_stats, NULL); ecs_map_iter_t it = ecs_map_iter(&src->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys_src = ecs_map_ptr(&it); ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, ecs_system_stats_t, ecs_map_key(&it)); sys_dst->query.t = dst->t; ecs_system_stats_reduce(sys_dst, sys_src); } dst->t = t_next(dst->t); } void ecs_pipeline_stats_reduce_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src, int32_t count) { int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, src->t, count); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->no_readonly = src_el->no_readonly; } ecs_map_init_if(&dst->system_stats, NULL); ecs_map_iter_t it = ecs_map_iter(&src->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys_src = ecs_map_ptr(&it); ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, ecs_system_stats_t, ecs_map_key(&it)); sys_dst->query.t = dst->t; ecs_system_stats_reduce_last(sys_dst, sys_src, count); } dst->t = t_prev(dst->t); } void ecs_pipeline_stats_repeat_last( ecs_pipeline_stats_t *stats) { int32_t i, sync_count = ecs_vec_count(&stats->sync_points); ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *el = &syncs[i]; flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), (stats->t)); } ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys = ecs_map_ptr(&it); sys->query.t = stats->t; ecs_system_stats_repeat_last(sys); } stats->t = t_next(stats->t); } void ecs_pipeline_stats_copy_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->no_readonly = src_el->no_readonly; } ecs_map_init_if(&dst->system_stats, NULL); ecs_map_iter_t it = ecs_map_iter(&src->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys_src = ecs_map_ptr(&it); ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, ecs_system_stats_t, ecs_map_key(&it)); sys_dst->query.t = dst->t; ecs_system_stats_copy_last(sys_dst, sys_src); } } #endif void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { int32_t t = s->t; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_counter_print("Frame", t, &s->frame.frame_count); ecs_trace("-------------------------------------"); flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); flecs_counter_print("systems ran", t, &s->frame.systems_ran); ecs_trace(""); flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); ecs_trace(""); flecs_gauge_print("actual FPS", t, &s->performance.fps); flecs_counter_print("frame time", t, &s->performance.frame_time); flecs_counter_print("system time", t, &s->performance.system_time); flecs_counter_print("merge time", t, &s->performance.merge_time); flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); ecs_trace(""); flecs_gauge_print("tag id count", t, &s->components.tag_count); flecs_gauge_print("component id count", t, &s->components.component_count); flecs_gauge_print("pair id count", t, &s->components.pair_count); flecs_gauge_print("type count", t, &s->components.type_count); flecs_counter_print("id create count", t, &s->components.create_count); flecs_counter_print("id delete count", t, &s->components.delete_count); ecs_trace(""); flecs_gauge_print("alive entity count", t, &s->entities.count); flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); ecs_trace(""); flecs_gauge_print("query count", t, &s->queries.query_count); flecs_gauge_print("observer count", t, &s->queries.observer_count); flecs_gauge_print("system count", t, &s->queries.system_count); ecs_trace(""); flecs_gauge_print("table count", t, &s->tables.count); flecs_gauge_print("empty table count", t, &s->tables.empty_count); flecs_counter_print("table create count", t, &s->tables.create_count); flecs_counter_print("table delete count", t, &s->tables.delete_count); ecs_trace(""); flecs_counter_print("add commands", t, &s->commands.add_count); flecs_counter_print("remove commands", t, &s->commands.remove_count); flecs_counter_print("delete commands", t, &s->commands.delete_count); flecs_counter_print("clear commands", t, &s->commands.clear_count); flecs_counter_print("set commands", t, &s->commands.set_count); flecs_counter_print("ensure commands", t, &s->commands.ensure_count); flecs_counter_print("modified commands", t, &s->commands.modified_count); flecs_counter_print("other commands", t, &s->commands.other_count); flecs_counter_print("discarded commands", t, &s->commands.discard_count); flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); flecs_counter_print("batched commands", t, &s->commands.batched_count); ecs_trace(""); error: return; } #endif /** * @file addons/timer.c * @brief Timer addon. */ #ifdef FLECS_TIMER static void AddTickSource(ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { ecs_set(it->world, it->entities[i], EcsTickSource, {0}); } } static void ProgressTimers(ecs_iter_t *it) { EcsTimer *timer = ecs_field(it, EcsTimer, 1); EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 2); ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); int i; for (i = 0; i < it->count; i ++) { tick_source[i].tick = false; if (!timer[i].active) { continue; } const ecs_world_info_t *info = ecs_get_world_info(it->world); ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; ecs_ftime_t timeout = timer[i].timeout; if (time_elapsed >= timeout) { ecs_ftime_t t = time_elapsed - timeout; if (t > timeout) { t = 0; } timer[i].time = t; /* Initialize with remainder */ tick_source[i].tick = true; tick_source[i].time_elapsed = time_elapsed - timer[i].overshoot; timer[i].overshoot = t; if (timer[i].single_shot) { timer[i].active = false; } } else { timer[i].time = time_elapsed; } } } static void ProgressRateFilters(ecs_iter_t *it) { EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 1); EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 2); int i; for (i = 0; i < it->count; i ++) { ecs_entity_t src = filter[i].src; bool inc = false; filter[i].time_elapsed += it->delta_time; if (src) { const EcsTickSource *tick_src = ecs_get( it->world, src, EcsTickSource); if (tick_src) { inc = tick_src->tick; } else { inc = true; } } else { inc = true; } if (inc) { filter[i].tick_count ++; bool triggered = !(filter[i].tick_count % filter[i].rate); tick_dst[i].tick = triggered; tick_dst[i].time_elapsed = filter[i].time_elapsed; if (triggered) { filter[i].time_elapsed = 0; } } else { tick_dst[i].tick = false; } } } static void ProgressTickSource(ecs_iter_t *it) { EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); /* If tick source has no filters, tick unconditionally */ int i; for (i = 0; i < it->count; i ++) { tick_src[i].tick = true; tick_src[i].time_elapsed = it->delta_time; } } ecs_entity_t ecs_set_timeout( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t timeout) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); timer = ecs_set(world, timer, EcsTimer, { .timeout = timeout, .single_shot = true, .active = true }); ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_timeout( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } ecs_entity_t ecs_set_interval( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t interval) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { timer = ecs_new(world, EcsTimer); } EcsTimer *t = ecs_ensure(world, timer, EcsTimer); ecs_check(t != NULL, ECS_INVALID_PARAMETER, NULL); t->timeout = interval; t->active = true; ecs_modified(world, timer, EcsTimer); ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_interval( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { return 0; } const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } void ecs_start_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ptr->active = true; ptr->time = 0; error: return; } void ecs_stop_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ptr->active = false; error: return; } void ecs_reset_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ptr->time = 0; error: return; } ecs_entity_t ecs_set_rate( ecs_world_t *world, ecs_entity_t filter, int32_t rate, ecs_entity_t source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); filter = ecs_set(world, filter, EcsRateFilter, { .rate = rate, .src = source }); ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); if (system_data) { system_data->tick_source = filter; } error: return filter; } void ecs_set_tick_source( ecs_world_t *world, ecs_entity_t system, ecs_entity_t tick_source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); system_data->tick_source = tick_source; error: return; } static void RandomizeTimers(ecs_iter_t *it) { EcsTimer *timer = ecs_field(it, EcsTimer, 1); int32_t i; for (i = 0; i < it->count; i ++) { timer[i].time = ((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout; } } void ecs_randomize_timers( ecs_world_t *world) { ecs_observer(world, { .entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }), .filter.terms = {{ .id = ecs_id(EcsTimer) }}, .events = {EcsOnSet}, .yield_existing = true, .callback = RandomizeTimers }); } void FlecsTimerImport( ecs_world_t *world) { ECS_MODULE(world, FlecsTimer); ECS_IMPORT(world, FlecsPipeline); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsTimer), "Module that implements system timers (used by .interval)"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTimer); flecs_bootstrap_component(world, EcsRateFilter); ecs_set_hooks(world, EcsTimer, { .ctor = ecs_default_ctor }); /* Add EcsTickSource to timers and rate filters */ ecs_system(world, { .entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(EcsPreFrame) }}), .query.filter.terms = { { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, { .id = ecs_id(EcsRateFilter), .oper = EcsAnd, .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} }, .callback = AddTickSource }); /* Timer handling */ ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsTimer) }, { .id = ecs_id(EcsTickSource) } }, .callback = ProgressTimers }); /* Rate filter handling */ ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .inout = EcsOut } }, .callback = ProgressRateFilters }); /* TickSource without a timer or rate filter just increases each frame */ ecs_system(world, { .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsTickSource), .inout = EcsOut }, { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, { .id = ecs_id(EcsTimer), .oper = EcsNot } }, .callback = ProgressTickSource }); } #endif /** * @file addons/units.c * @brief Units addon. */ #ifdef FLECS_UNITS void FlecsUnitsImport( ecs_world_t *world) { ECS_MODULE(world, FlecsUnits); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsUnits), "Module with (amongst others) SI units for annotating component members"); #endif ecs_set_name_prefix(world, "Ecs"); EcsUnitPrefixes = ecs_entity(world, { .name = "prefixes", .add = { EcsModule } }); /* Initialize unit prefixes */ ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Yocto" }), .symbol = "y", .translation = { .factor = 10, .power = -24 } }); EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Zepto" }), .symbol = "z", .translation = { .factor = 10, .power = -21 } }); EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Atto" }), .symbol = "a", .translation = { .factor = 10, .power = -18 } }); EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Femto" }), .symbol = "a", .translation = { .factor = 10, .power = -15 } }); EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Pico" }), .symbol = "p", .translation = { .factor = 10, .power = -12 } }); EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Nano" }), .symbol = "n", .translation = { .factor = 10, .power = -9 } }); EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Micro" }), .symbol = "μ", .translation = { .factor = 10, .power = -6 } }); EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Milli" }), .symbol = "m", .translation = { .factor = 10, .power = -3 } }); EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Centi" }), .symbol = "c", .translation = { .factor = 10, .power = -2 } }); EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Deci" }), .symbol = "d", .translation = { .factor = 10, .power = -1 } }); EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Deca" }), .symbol = "da", .translation = { .factor = 10, .power = 1 } }); EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Hecto" }), .symbol = "h", .translation = { .factor = 10, .power = 2 } }); EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Kilo" }), .symbol = "k", .translation = { .factor = 10, .power = 3 } }); EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Mega" }), .symbol = "M", .translation = { .factor = 10, .power = 6 } }); EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Giga" }), .symbol = "G", .translation = { .factor = 10, .power = 9 } }); EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Tera" }), .symbol = "T", .translation = { .factor = 10, .power = 12 } }); EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Peta" }), .symbol = "P", .translation = { .factor = 10, .power = 15 } }); EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Exa" }), .symbol = "E", .translation = { .factor = 10, .power = 18 } }); EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Zetta" }), .symbol = "Z", .translation = { .factor = 10, .power = 21 } }); EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Yotta" }), .symbol = "Y", .translation = { .factor = 10, .power = 24 } }); EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Kibi" }), .symbol = "Ki", .translation = { .factor = 1024, .power = 1 } }); EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Mebi" }), .symbol = "Mi", .translation = { .factor = 1024, .power = 2 } }); EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Gibi" }), .symbol = "Gi", .translation = { .factor = 1024, .power = 3 } }); EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Tebi" }), .symbol = "Ti", .translation = { .factor = 1024, .power = 4 } }); EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Pebi" }), .symbol = "Pi", .translation = { .factor = 1024, .power = 5 } }); EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Exbi" }), .symbol = "Ei", .translation = { .factor = 1024, .power = 6 } }); EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Zebi" }), .symbol = "Zi", .translation = { .factor = 1024, .power = 7 } }); EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Yobi" }), .symbol = "Yi", .translation = { .factor = 1024, .power = 8 } }); ecs_set_scope(world, prev_scope); /* Duration units */ EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Duration" }); prev_scope = ecs_set_scope(world, EcsDuration); EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Seconds" }), .quantity = EcsDuration, .symbol = "s" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsSeconds, .kind = EcsF32 }); EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "PicoSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsPico }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPicoSeconds, .kind = EcsF32 }); EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "NanoSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsNano }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsNanoSeconds, .kind = EcsF32 }); EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MicroSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsMicro }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMicroSeconds, .kind = EcsF32 }); EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MilliSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsMilli }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMilliSeconds, .kind = EcsF32 }); EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Minutes" }), .quantity = EcsDuration, .base = EcsSeconds, .symbol = "min", .translation = { .factor = 60, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMinutes, .kind = EcsU32 }); EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hours" }), .quantity = EcsDuration, .base = EcsMinutes, .symbol = "h", .translation = { .factor = 60, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsHours, .kind = EcsU32 }); EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Days" }), .quantity = EcsDuration, .base = EcsHours, .symbol = "d", .translation = { .factor = 24, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDays, .kind = EcsU32 }); ecs_set_scope(world, prev_scope); /* Time units */ EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Time" }); prev_scope = ecs_set_scope(world, EcsTime); EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Date" }), .quantity = EcsTime }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDate, .kind = EcsU32 }); ecs_set_scope(world, prev_scope); /* Mass units */ EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Mass" }); prev_scope = ecs_set_scope(world, EcsMass); EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Grams" }), .quantity = EcsMass, .symbol = "g" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGrams, .kind = EcsF32 }); EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloGrams" }), .quantity = EcsMass, .prefix = EcsKilo, .base = EcsGrams }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloGrams, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Electric current units */ EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "ElectricCurrent" }); prev_scope = ecs_set_scope(world, EcsElectricCurrent); EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Ampere" }), .quantity = EcsElectricCurrent, .symbol = "A" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsAmpere, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Amount of substance units */ EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Amount" }); prev_scope = ecs_set_scope(world, EcsAmount); EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Mole" }), .quantity = EcsAmount, .symbol = "mol" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMole, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Luminous intensity units */ EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "LuminousIntensity" }); prev_scope = ecs_set_scope(world, EcsLuminousIntensity); EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Candela" }), .quantity = EcsLuminousIntensity, .symbol = "cd" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsCandela, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Force units */ EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Force" }); prev_scope = ecs_set_scope(world, EcsForce); EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Newton" }), .quantity = EcsForce, .symbol = "N" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsNewton, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Length units */ EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Length" }); prev_scope = ecs_set_scope(world, EcsLength); EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Meters" }), .quantity = EcsLength, .symbol = "m" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMeters, .kind = EcsF32 }); EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "PicoMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsPico }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPicoMeters, .kind = EcsF32 }); EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "NanoMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsNano }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsNanoMeters, .kind = EcsF32 }); EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MicroMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsMicro }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMicroMeters, .kind = EcsF32 }); EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MilliMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsMilli }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMilliMeters, .kind = EcsF32 }); EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "CentiMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsCenti }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsCentiMeters, .kind = EcsF32 }); EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsKilo }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloMeters, .kind = EcsF32 }); EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Miles" }), .quantity = EcsLength, .symbol = "mi" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMiles, .kind = EcsF32 }); EcsPixels = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Pixels" }), .quantity = EcsLength, .symbol = "px" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPixels, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Pressure units */ EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Pressure" }); prev_scope = ecs_set_scope(world, EcsPressure); EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Pascal" }), .quantity = EcsPressure, .symbol = "Pa" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPascal, .kind = EcsF32 }); EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bar" }), .quantity = EcsPressure, .symbol = "bar" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBar, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Speed units */ EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Speed" }); prev_scope = ecs_set_scope(world, EcsSpeed); EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MetersPerSecond" }), .quantity = EcsSpeed, .base = EcsMeters, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMetersPerSecond, .kind = EcsF32 }); EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), .quantity = EcsSpeed, .base = EcsKiloMeters, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloMetersPerSecond, .kind = EcsF32 }); EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), .quantity = EcsSpeed, .base = EcsKiloMeters, .over = EcsHours }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloMetersPerHour, .kind = EcsF32 }); EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MilesPerHour" }), .quantity = EcsSpeed, .base = EcsMiles, .over = EcsHours }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMilesPerHour, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Acceleration */ EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Acceleration" }), .base = EcsMetersPerSecond, .over = EcsSeconds }); ecs_quantity_init(world, &(ecs_entity_desc_t){ .id = EcsAcceleration }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsAcceleration, .kind = EcsF32 }); /* Temperature units */ EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Temperature" }); prev_scope = ecs_set_scope(world, EcsTemperature); EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Kelvin" }), .quantity = EcsTemperature, .symbol = "K" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKelvin, .kind = EcsF32 }); EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Celsius" }), .quantity = EcsTemperature, .symbol = "°C" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsCelsius, .kind = EcsF32 }); EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Fahrenheit" }), .quantity = EcsTemperature, .symbol = "F" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsFahrenheit, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Data units */ EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Data" }); prev_scope = ecs_set_scope(world, EcsData); EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bits" }), .quantity = EcsData, .symbol = "bit" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBits, .kind = EcsU64 }); EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBits" }), .quantity = EcsData, .base = EcsBits, .prefix = EcsKilo }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBits, .kind = EcsU64 }); EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBits" }), .quantity = EcsData, .base = EcsBits, .prefix = EcsMega }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBits, .kind = EcsU64 }); EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBits" }), .quantity = EcsData, .base = EcsBits, .prefix = EcsGiga }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBits, .kind = EcsU64 }); EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bytes" }), .quantity = EcsData, .symbol = "B", .base = EcsBits, .translation = { .factor = 8, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBytes, .kind = EcsU64 }); EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsKilo }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBytes, .kind = EcsU64 }); EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsMega }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBytes, .kind = EcsU64 }); EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsGiga }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBytes, .kind = EcsU64 }); EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KibiBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsKibi }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKibiBytes, .kind = EcsU64 }); EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MebiBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsMebi }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMebiBytes, .kind = EcsU64 }); EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GibiBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsGibi }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGibiBytes, .kind = EcsU64 }); ecs_set_scope(world, prev_scope); /* DataRate units */ EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "DataRate" }); prev_scope = ecs_set_scope(world, EcsDataRate); EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "BitsPerSecond" }), .quantity = EcsDataRate, .base = EcsBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBitsPerSecond, .kind = EcsU64 }); EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), .quantity = EcsDataRate, .base = EcsKiloBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBitsPerSecond, .kind = EcsU64 }); EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), .quantity = EcsDataRate, .base = EcsMegaBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBitsPerSecond, .kind = EcsU64 }); EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), .quantity = EcsDataRate, .base = EcsGigaBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBitsPerSecond, .kind = EcsU64 }); EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "BytesPerSecond" }), .quantity = EcsDataRate, .base = EcsBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBytesPerSecond, .kind = EcsU64 }); EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), .quantity = EcsDataRate, .base = EcsKiloBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBytesPerSecond, .kind = EcsU64 }); EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), .quantity = EcsDataRate, .base = EcsMegaBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBytesPerSecond, .kind = EcsU64 }); EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), .quantity = EcsDataRate, .base = EcsGigaBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBytesPerSecond, .kind = EcsU64 }); ecs_set_scope(world, prev_scope); /* Percentage */ EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Percentage" }); ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = EcsPercentage, .symbol = "%" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPercentage, .kind = EcsF32 }); /* Angles */ EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Angle" }); prev_scope = ecs_set_scope(world, EcsAngle); EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Radians" }), .quantity = EcsAngle, .symbol = "rad" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsRadians, .kind = EcsF32 }); EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Degrees" }), .quantity = EcsAngle, .symbol = "°" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDegrees, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* DeciBel */ EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bel" }), .symbol = "B" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBel, .kind = EcsF32 }); EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "DeciBel" }), .prefix = EcsDeci, .base = EcsBel }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDeciBel, .kind = EcsF32 }); EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Frequency" }); prev_scope = ecs_set_scope(world, EcsFrequency); EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hertz" }), .quantity = EcsFrequency, .symbol = "Hz" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsHertz, .kind = EcsF32 }); EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloHertz" }), .prefix = EcsKilo, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloHertz, .kind = EcsF32 }); EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaHertz" }), .prefix = EcsMega, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaHertz, .kind = EcsF32 }); EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaHertz" }), .prefix = EcsGiga, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaHertz, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Uri" }); prev_scope = ecs_set_scope(world, EcsUri); EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hyperlink" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriHyperlink, .kind = EcsString }); EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Image" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriImage, .kind = EcsString }); EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "File" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriFile, .kind = EcsString }); ecs_set_scope(world, prev_scope); /* Documentation */ #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, EcsDuration, "Time amount (e.g. \"20 seconds\", \"2 hours\")"); ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); ecs_doc_set_brief(world, EcsHours, "60 minutes"); ecs_doc_set_brief(world, EcsDays, "24 hours"); ecs_doc_set_brief(world, EcsTime, "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); ecs_doc_set_brief(world, EcsDate, "Seconds passed since January 1st 1970"); ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); ecs_doc_set_brief(world, EcsElectricCurrent, "Units of electrical current (e.g. \"2 ampere\")"); ecs_doc_set_brief(world, EcsAmount, "Units of amount of substance (e.g. \"2 mole\")"); ecs_doc_set_brief(world, EcsLuminousIntensity, "Units of luminous intensity (e.g. \"1 candela\")"); ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); ecs_doc_set_brief(world, EcsLength, "Units of length (e.g. \"5 meters\", \"20 miles\")"); ecs_doc_set_brief(world, EcsPressure, "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); ecs_doc_set_brief(world, EcsSpeed, "Units of movement (e.g. \"5 meters/second\")"); ecs_doc_set_brief(world, EcsAcceleration, "Unit of speed increase (e.g. \"5 meters/second/second\")"); ecs_doc_set_brief(world, EcsTemperature, "Units of temperature (e.g. \"5 degrees Celsius\")"); ecs_doc_set_brief(world, EcsData, "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); ecs_doc_set_brief(world, EcsDataRate, "Units of data transmission (e.g. \"100 megabits/second\")"); ecs_doc_set_brief(world, EcsAngle, "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); ecs_doc_set_brief(world, EcsFrequency, "The number of occurrences of a repeating event per unit of time."); ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); #endif } #endif /** * @file datastructures/allocator.c * @brief Allocator for any size. * * Allocators create a block allocator for each requested size. */ static ecs_size_t flecs_allocator_size( ecs_size_t size) { return ECS_ALIGN(size, 16); } static ecs_size_t flecs_allocator_size_hash( ecs_size_t size) { return size >> 4; } void flecs_allocator_init( ecs_allocator_t *a) { flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, FLECS_SPARSE_PAGE_SIZE); flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); } void flecs_allocator_fini( ecs_allocator_t *a) { int32_t i = 0, count = flecs_sparse_count(&a->sizes); for (i = 0; i < count; i ++) { ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( &a->sizes, ecs_block_allocator_t, i); flecs_ballocator_fini(ba); } flecs_sparse_fini(&a->sizes); flecs_ballocator_fini(&a->chunks); } ecs_block_allocator_t* flecs_allocator_get( ecs_allocator_t *a, ecs_size_t size) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); if (!size) { return NULL; } ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); size = flecs_allocator_size(size); ecs_size_t hash = flecs_allocator_size_hash(size); ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes, ecs_block_allocator_t, (uint32_t)hash); if (!result) { result = flecs_sparse_ensure_fast_t(&a->sizes, ecs_block_allocator_t, (uint32_t)hash); flecs_ballocator_init(result, size); } ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); return result; } char* flecs_strdup( ecs_allocator_t *a, const char* str) { ecs_size_t len = ecs_os_strlen(str); char *result = flecs_alloc_n(a, char, len + 1); ecs_os_memcpy(result, str, len + 1); return result; } void flecs_strfree( ecs_allocator_t *a, char* str) { ecs_size_t len = ecs_os_strlen(str); flecs_free_n(a, char, len + 1, str); } void* flecs_dup( ecs_allocator_t *a, ecs_size_t size, const void *src) { ecs_block_allocator_t *ba = flecs_allocator_get(a, size); if (ba) { void *dst = flecs_balloc(ba); ecs_os_memcpy(dst, src, size); return dst; } else { return NULL; } } /** * @file datastructures/bitset.c * @brief Bitset data structure. * * Simple bitset implementation. The bitset allows for storage of arbitrary * numbers of bits. */ static void ensure( ecs_bitset_t *bs, ecs_size_t size) { if (!bs->size) { int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->size = ((size - 1) / 64 + 1) * 64; bs->data = ecs_os_calloc(new_size); } else if (size > bs->size) { int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->size = ((size - 1) / 64 + 1) * 64; int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->data = ecs_os_realloc(bs->data, new_size); ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); } } void flecs_bitset_init( ecs_bitset_t* bs) { bs->size = 0; bs->count = 0; bs->data = NULL; } void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count) { if (count > bs->count) { bs->count = count; ensure(bs, count); } } void flecs_bitset_fini( ecs_bitset_t *bs) { ecs_os_free(bs->data); bs->data = NULL; bs->count = 0; } void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count) { int32_t elem = bs->count += count; ensure(bs, elem); } void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); uint32_t hi = ((uint32_t)elem) >> 6; uint32_t lo = ((uint32_t)elem) & 0x3F; uint64_t v = bs->data[hi]; bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); error: return; } bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); error: return false; } int32_t flecs_bitset_count( const ecs_bitset_t *bs) { return bs->count; } void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); int32_t last = bs->count - 1; bool last_value = flecs_bitset_get(bs, last); flecs_bitset_set(bs, elem, last_value); flecs_bitset_set(bs, last, 0); bs->count --; error: return; } void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b) { ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); bool a = flecs_bitset_get(bs, elem_a); bool b = flecs_bitset_get(bs, elem_b); flecs_bitset_set(bs, elem_a, b); flecs_bitset_set(bs, elem_b, a); error: return; } /** * @file datastructures/block_allocator.c * @brief Block allocator. * * A block allocator is an allocator for a fixed size that allocates blocks of * memory with N elements of the requested size. */ // #ifdef FLECS_SANITIZE // #define FLECS_MEMSET_UNINITIALIZED // #endif int64_t ecs_block_allocator_alloc_count = 0; int64_t ecs_block_allocator_free_count = 0; #ifndef FLECS_USE_OS_ALLOC static ecs_block_allocator_chunk_header_t* flecs_balloc_block( ecs_block_allocator_t *allocator) { if (!allocator->chunk_size) { return NULL; } ecs_block_allocator_block_t *block = ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + allocator->block_size); ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, ECS_SIZEOF(ecs_block_allocator_block_t)); block->memory = first_chunk; if (!allocator->block_tail) { ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); block->next = NULL; allocator->block_head = block; allocator->block_tail = block; } else { block->next = NULL; allocator->block_tail->next = block; allocator->block_tail = block; } ecs_block_allocator_chunk_header_t *chunk = first_chunk; int32_t i, end; for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); chunk = chunk->next; } ecs_os_linc(&ecs_block_allocator_alloc_count); chunk->next = NULL; return first_chunk; } #endif void flecs_ballocator_init( ecs_block_allocator_t *ba, ecs_size_t size) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ba->data_size = size; #ifdef FLECS_SANITIZE size += ECS_SIZEOF(int64_t); #endif ba->chunk_size = ECS_ALIGN(size, 16); ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); ba->block_size = ba->chunks_per_block * ba->chunk_size; ba->head = NULL; ba->block_head = NULL; ba->block_tail = NULL; } ecs_block_allocator_t* flecs_ballocator_new( ecs_size_t size) { ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); flecs_ballocator_init(result, size); return result; } void flecs_ballocator_fini( ecs_block_allocator_t *ba) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, "(size = %u)", (uint32_t)ba->data_size); #endif ecs_block_allocator_block_t *block; for (block = ba->block_head; block;) { ecs_block_allocator_block_t *next = block->next; ecs_os_free(block); ecs_os_linc(&ecs_block_allocator_free_count); block = next; } ba->block_head = NULL; } void flecs_ballocator_free( ecs_block_allocator_t *ba) { flecs_ballocator_fini(ba); ecs_os_free(ba); } void* flecs_balloc( ecs_block_allocator_t *ba) { void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_malloc(ba->data_size); #else if (!ba) return NULL; if (!ba->head) { ba->head = flecs_balloc_block(ba); } result = ba->head; ba->head = ba->head->next; #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); ba->alloc_count ++; *(int64_t*)result = ba->chunk_size; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #endif #endif #ifdef FLECS_MEMSET_UNINITIALIZED ecs_os_memset(result, 0xAA, ba->data_size); #endif return result; } void* flecs_bcalloc( ecs_block_allocator_t *ba) { #ifdef FLECS_USE_OS_ALLOC return ecs_os_calloc(ba->data_size); #else if (!ba) return NULL; void *result = flecs_balloc(ba); ecs_os_memset(result, 0, ba->data_size); return result; #endif } void flecs_bfree( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC (void)ba; ecs_os_free(memory); return; #else if (!ba) { ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); return; } if (memory == NULL) { return; } #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); if (*(int64_t*)memory != ba->chunk_size) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", memory, *(int64_t*)memory, ba->chunk_size); ecs_abort(ECS_INTERNAL_ERROR, NULL); } ba->alloc_count --; #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); #endif } void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) { void *result; #ifdef FLECS_USE_OS_ALLOC (void)src; result = ecs_os_realloc(memory, dst->data_size); #else if (dst == src) { return memory; } result = flecs_balloc(dst); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { size = dst->data_size; } ecs_os_memcpy(result, memory, size); } flecs_bfree(src, memory); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, dst->data_size - src->data_size); } else if (dst && !src) { ecs_os_memset(result, 0xAA, dst->data_size); } #endif return result; } void* flecs_bdup( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC if (memory && ba->chunk_size) { return ecs_os_memdup(memory, ba->data_size); } else { return NULL; } #else void *result = flecs_balloc(ba); if (result) { ecs_os_memcpy(result, memory, ba->data_size); } return result; #endif } // This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) // main repo: https://github.com/wangyi-fudan/wyhash // author: 王一 Wang Yi // contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, // Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, // Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, // hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric /* quick example: string s="fjsakfdsjkf"; uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); */ #ifndef WYHASH_CONDOM //protections that produce different results: //1: normal valid behavior //2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" #define WYHASH_CONDOM 1 #endif #ifndef WYHASH_32BIT_MUM //0: normal version, slow on 32 bit systems //1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function #define WYHASH_32BIT_MUM 0 #endif //includes #include #include #if defined(_MSC_VER) && defined(_M_X64) #include #pragma intrinsic(_umul128) #endif //likely and unlikely macros #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) #define likely_(x) __builtin_expect(x,1) #define unlikely_(x) __builtin_expect(x,0) #else #define likely_(x) (x) #define unlikely_(x) (x) #endif //128bit multiply function static inline void wymum_(uint64_t *A, uint64_t *B){ #if(WYHASH_32BIT_MUM) uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; #if(WYHASH_CONDOM>1) *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; #else *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; #endif #elif defined(__SIZEOF_INT128__) __uint128_t r=*A; r*=*B; #if(WYHASH_CONDOM>1) *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); #else *A=(uint64_t)r; *B=(uint64_t)(r>>64); #endif #elif defined(_MSC_VER) && defined(_M_X64) #if(WYHASH_CONDOM>1) uint64_t a, b; a=_umul128(*A,*B,&b); *A^=a; *B^=b; #else *A=_umul128(*A,*B,B); #endif #else uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; #if(WYHASH_CONDOM>1) *A^=lo; *B^=hi; #else *A=lo; *B=hi; #endif #endif } //multiply and xor mix function, aka MUM static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } //endian macros #ifndef WYHASH_LITTLE_ENDIAN #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 1 #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 0 #else #warning could not determine endianness! Falling back to little endian. #define WYHASH_LITTLE_ENDIAN 1 #endif #endif //read functions #if (WYHASH_LITTLE_ENDIAN) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} #elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} #elif defined(_MSC_VER) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} #else static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); } static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); } #endif static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} //wyhash main function static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; if(likely_(len<=16)){ if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} else a=b=0; } else{ size_t i=len; if(unlikely_(i>48)){ uint64_t see1=seed, see2=seed; do{ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); p+=48; i-=48; }while(likely_(i>48)); seed^=see1^see2; } while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } a=wyr8_(p+i-16); b=wyr8_(p+i-8); } a^=secret[1]; b^=seed; wymum_(&a,&b); return wymix_(a^secret[0]^len,b^secret[1]); } //the default secret parameters static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; uint64_t flecs_hash( const void *data, ecs_size_t length) { return wyhash(data, flecs_ito(size_t, length), 0, wyp_); } /** * @file datastructures/hashmap.c * @brief Hashmap data structure. * * The hashmap data structure is built on top of the map data structure. Where * the map data structure can only work with 64bit key values, the hashmap can * hash keys of any size, and handles collisions between hashes. */ static int32_t flecs_hashmap_find_key( const ecs_hashmap_t *map, ecs_vec_t *keys, ecs_size_t key_size, const void *key) { int32_t i, count = ecs_vec_count(keys); void *key_array = ecs_vec_first(keys); for (i = 0; i < count; i ++) { void *key_ptr = ECS_OFFSET(key_array, key_size * i); if (map->compare(key_ptr, key) == 0) { return i; } } return -1; } void flecs_hashmap_init_( ecs_hashmap_t *map, ecs_size_t key_size, ecs_size_t value_size, ecs_hash_value_action_t hash, ecs_compare_action_t compare, ecs_allocator_t *allocator) { map->key_size = key_size; map->value_size = value_size; map->hash = hash; map->compare = compare; flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); ecs_map_init(&map->impl, allocator); } void flecs_hashmap_fini( ecs_hashmap_t *map) { ecs_allocator_t *a = map->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&map->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); #ifdef FLECS_SANITIZE flecs_bfree(&map->bucket_allocator, bucket); #endif } flecs_ballocator_fini(&map->bucket_allocator); ecs_map_fini(&map->impl); } void flecs_hashmap_copy( ecs_hashmap_t *dst, const ecs_hashmap_t *src) { ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, src->compare, src->impl.allocator); ecs_map_copy(&dst->impl, &src->impl); ecs_allocator_t *a = dst->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&dst->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); bucket_ptr[0] = dst_bucket; dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); } } void* flecs_hashmap_get_( const ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return NULL; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return NULL; } return ecs_vec_get(&bucket->values, value_size, index); } flecs_hashmap_result_t flecs_hashmap_ensure_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); ecs_hm_bucket_t *bucket = r[0]; if (!bucket) { bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); } ecs_allocator_t *a = map->impl.allocator; void *value_ptr, *key_ptr; ecs_vec_t *keys = &bucket->keys; ecs_vec_t *values = &bucket->values; if (!keys->array) { keys = ecs_vec_init(a, &bucket->keys, key_size, 1); values = ecs_vec_init(a, &bucket->values, value_size, 1); key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); if (index == -1) { key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { key_ptr = ecs_vec_get(keys, key_size, index); value_ptr = ecs_vec_get(values, value_size, index); } } return (flecs_hashmap_result_t){ .key = key_ptr, .value = value_ptr, .hash = hash }; } void flecs_hashmap_set_( ecs_hashmap_t *map, ecs_size_t key_size, void *key, ecs_size_t value_size, const void *value) { void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy(value_ptr, value, value_size); } ecs_hm_bucket_t* flecs_hashmap_get_bucket( const ecs_hashmap_t *map, uint64_t hash) { ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); } void flecs_hm_bucket_remove( ecs_hashmap_t *map, ecs_hm_bucket_t *bucket, uint64_t hash, int32_t index) { ecs_vec_remove(&bucket->keys, map->key_size, index); ecs_vec_remove(&bucket->values, map->value_size, index); if (!ecs_vec_count(&bucket->keys)) { ecs_allocator_t *a = map->impl.allocator; ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; flecs_bfree(&map->bucket_allocator, bucket); } } void flecs_hashmap_remove_w_hash_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size, uint64_t hash) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); (void)value_size; ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return; } flecs_hm_bucket_remove(map, bucket, hash, index); } void flecs_hashmap_remove_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); } flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map) { return (flecs_hashmap_iter_t){ .it = ecs_map_iter(&map->impl) }; } void* flecs_hashmap_next_( flecs_hashmap_iter_t *it, ecs_size_t key_size, void *key_out, ecs_size_t value_size) { int32_t index = ++ it->index; ecs_hm_bucket_t *bucket = it->bucket; while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { ecs_map_next(&it->it); bucket = it->bucket = ecs_map_ptr(&it->it); if (!bucket) { return NULL; } index = it->index = 0; } if (key_out) { *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); } return ecs_vec_get(&bucket->values, value_size, index); } /** * @file datastructures/map.c * @brief Map data structure. * * Map data structure for 64bit keys and dynamic payload size. */ /* The ratio used to determine whether the map should flecs_map_rehash. If * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ #define ECS_LOAD_FACTOR (12) #define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) static uint8_t flecs_log2(uint32_t v) { static const uint8_t log2table[32] = {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } /* Get bucket count for number of elements */ static int32_t flecs_map_get_bucket_count( int32_t count) { return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); } /* Get bucket shift amount for a given bucket count */ static uint8_t flecs_map_get_bucket_shift ( int32_t bucket_count) { return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); } /* Get bucket index for provided map key */ static int32_t flecs_map_get_bucket_index( uint16_t bucket_shift, ecs_map_key_t key) { ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); return (int32_t)((11400714819323198485ull * key) >> bucket_shift); } /* Get bucket for key */ static ecs_bucket_t* flecs_map_get_bucket( const ecs_map_t *map, ecs_map_key_t key) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key); ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); return &map->buckets[bucket_id]; } /* Add element to bucket */ static ecs_map_val_t* flecs_map_bucket_add( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); new_entry->key = key; new_entry->next = bucket->first; bucket->first = new_entry; return &new_entry->value; } /* Remove element from bucket */ static ecs_map_val_t flecs_map_bucket_remove( ecs_map_t *map, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { ecs_map_val_t value = entry->value; ecs_bucket_entry_t **next_holder = &bucket->first; while(*next_holder != entry) { next_holder = &(*next_holder)->next; } *next_holder = entry->next; flecs_bfree(map->entry_allocator, entry); map->count --; return value; } } return 0; } /* Free contents of bucket */ static void flecs_map_bucket_clear( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket) { ecs_bucket_entry_t *entry = bucket->first; while(entry) { ecs_bucket_entry_t *next = entry->next; flecs_bfree(allocator, entry); entry = next; } } /* Get payload pointer for key from bucket */ static ecs_map_val_t* flecs_map_bucket_get( ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { return &entry->value; } } return NULL; } /* Grow number of buckets */ static void flecs_map_rehash( ecs_map_t *map, int32_t count) { count = flecs_next_pow_of_2(count); if (count < 2) { count = 2; } ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); int32_t old_count = map->bucket_count; ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); if (map->allocator) { map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); } else { map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); } map->bucket_count = count; map->bucket_shift = flecs_map_get_bucket_shift(count); /* Remap old bucket entries to new buckets */ for (b = buckets; b < end; b++) { ecs_bucket_entry_t* entry; for (entry = b->first; entry;) { ecs_bucket_entry_t* next = entry->next; int32_t bucket_index = flecs_map_get_bucket_index( map->bucket_shift, entry->key); ecs_bucket_t *bucket = &map->buckets[bucket_index]; entry->next = bucket->first; bucket->first = entry; entry = next; } } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); } else { ecs_os_free(buckets); } } void ecs_map_params_init( ecs_map_params_t *params, ecs_allocator_t *allocator) { params->allocator = allocator; flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); } void ecs_map_params_fini( ecs_map_params_t *params) { flecs_ballocator_fini(¶ms->entry_allocator); } void ecs_map_init_w_params( ecs_map_t *result, ecs_map_params_t *params) { ecs_os_zeromem(result); result->allocator = params->allocator; if (params->entry_allocator.chunk_size) { result->entry_allocator = ¶ms->entry_allocator; result->shared_allocator = true; } else { result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); } flecs_map_rehash(result, 0); } void ecs_map_init_w_params_if( ecs_map_t *result, ecs_map_params_t *params) { if (!ecs_map_is_init(result)) { ecs_map_init_w_params(result, params); } } void ecs_map_init( ecs_map_t *result, ecs_allocator_t *allocator) { ecs_map_init_w_params(result, &(ecs_map_params_t) { .allocator = allocator }); } void ecs_map_init_if( ecs_map_t *result, ecs_allocator_t *allocator) { if (!ecs_map_is_init(result)) { ecs_map_init(result, allocator); } } void ecs_map_fini( ecs_map_t *map) { if (!ecs_map_is_init(map)) { return; } bool sanitize = false; #ifdef FLECS_SANITIZE sanitize = true; #endif /* Free buckets in sanitized mode, so we can replace the allocator with * regular malloc/free and use asan/valgrind to find memory errors. */ ecs_allocator_t *a = map->allocator; ecs_block_allocator_t *ea = map->entry_allocator; if (map->shared_allocator || sanitize) { ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; while (bucket != end) { flecs_map_bucket_clear(ea, bucket); bucket ++; } } if (ea && !map->shared_allocator) { flecs_ballocator_free(ea); map->entry_allocator = NULL; } if (a) { flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); } else { ecs_os_free(map->buckets); } map->bucket_shift = 0; } ecs_map_val_t* ecs_map_get( const ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); } void* ecs_map_get_deref_( const ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t* ptr = flecs_map_bucket_get( flecs_map_get_bucket(map, key), key); if (ptr) { return (void*)(uintptr_t)ptr[0]; } return NULL; } void ecs_map_insert( ecs_map_t *map, ecs_map_key_t key, ecs_map_val_t value) { ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); } ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; } void* ecs_map_insert_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { void *elem = ecs_os_calloc(elem_size); ecs_map_insert_ptr(map, key, (uintptr_t)elem); return elem; } ecs_map_val_t* ecs_map_ensure( ecs_map_t *map, ecs_map_key_t key) { ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); if (result) { return result; } int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); bucket = flecs_map_get_bucket(map, key); } ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); *v = 0; return v; } void* ecs_map_ensure_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { ecs_map_val_t *val = ecs_map_ensure(map, key); if (!*val) { void *elem = ecs_os_calloc(elem_size); *val = (ecs_map_val_t)(uintptr_t)elem; return elem; } else { return (void*)(uintptr_t)*val; } } ecs_map_val_t ecs_map_remove( ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); } void ecs_map_remove_free( ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t val = ecs_map_remove(map, key); if (val) { ecs_os_free((void*)(uintptr_t)val); } } void ecs_map_clear( ecs_map_t *map) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = map->bucket_count; for (i = 0; i < count; i ++) { flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]); } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); } else { ecs_os_free(map->buckets); } map->buckets = NULL; map->bucket_count = 0; map->count = 0; flecs_map_rehash(map, 2); } ecs_map_iter_t ecs_map_iter( const ecs_map_t *map) { if (ecs_map_is_init(map)) { return (ecs_map_iter_t){ .map = map, .bucket = NULL, .entry = NULL }; } else { return (ecs_map_iter_t){ 0 }; } } bool ecs_map_next( ecs_map_iter_t *iter) { const ecs_map_t *map = iter->map; ecs_bucket_t *end; if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { return false; } ecs_bucket_entry_t *entry = NULL; if (!iter->bucket) { for (iter->bucket = map->buckets; iter->bucket != end; ++iter->bucket) { if (iter->bucket->first) { entry = iter->bucket->first; break; } } if (iter->bucket == end) { return false; } } else if ((entry = iter->entry) == NULL) { do { ++iter->bucket; if (iter->bucket == end) { return false; } } while(!iter->bucket->first); entry = iter->bucket->first; } ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); iter->entry = entry->next; iter->res = &entry->key; return true; } void ecs_map_copy( ecs_map_t *dst, const ecs_map_t *src) { if (ecs_map_is_init(dst)) { ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); ecs_map_fini(dst); } if (!ecs_map_is_init(src)) { return; } ecs_map_init(dst, src->allocator); ecs_map_iter_t it = ecs_map_iter(src); while (ecs_map_next(&it)) { ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); } } /** * @file datastructures/name_index.c * @brief Data structure for resolving 64bit keys by string (name). */ static uint64_t flecs_name_index_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } static int flecs_name_index_compare( const void *ptr1, const void *ptr2) { const ecs_hashed_string_t *str1 = ptr1; const ecs_hashed_string_t *str2 = ptr2; ecs_size_t len1 = str1->length; ecs_size_t len2 = str2->length; if (len1 != len2) { return (len1 > len2) - (len1 < len2); } return ecs_os_memcmp(str1->value, str2->value, len1); } void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { flecs_hashmap_init_(hm, ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), flecs_name_index_hash, flecs_name_index_compare, allocator); } void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { if (!hm->compare) { flecs_name_index_init(hm, allocator); } } bool flecs_name_index_is_init( const ecs_hashmap_t *hm) { return hm->compare != NULL; } ecs_hashmap_t* flecs_name_index_new( ecs_world_t *world, ecs_allocator_t *allocator) { ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); flecs_name_index_init(result, allocator); result->hashmap_allocator = &world->allocators.hashmap; return result; } void flecs_name_index_fini( ecs_hashmap_t *map) { flecs_hashmap_fini(map); } void flecs_name_index_free( ecs_hashmap_t *map) { if (map) { flecs_name_index_fini(map); flecs_bfree(map->hashmap_allocator, map); } } ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *map) { ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); result->hashmap_allocator = map->hashmap_allocator; flecs_hashmap_copy(result, map); return result; } ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash) { if (!length) { length = ecs_os_strlen(name); } else { ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); } if (!hash) { hash = flecs_hash(name, length); } else { ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); } return (ecs_hashed_string_t) { .value = ECS_CONST_CAST(char*, name), .length = length, .hash = hash }; } const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); if (!b) { return NULL; } ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); int32_t i, count = ecs_vec_count(&b->keys); for (i = 0; i < count; i ++) { ecs_hashed_string_t *key = &keys[i]; ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); if (hs.length != key->length) { continue; } if (!ecs_os_strcmp(name, key->value)) { uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); return e; } } return NULL; } uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); if (id) { return id[0]; } return 0; } void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t e, uint64_t hash) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { flecs_hm_bucket_remove(map, b, hash, i); break; } } } void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { ecs_hashed_string_t *key = ecs_vec_get_t( &b->keys, ecs_hashed_string_t, i); key->value = ECS_CONST_CAST(char*, name); ecs_assert(ecs_os_strlen(name) == key->length, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_hash(name, key->length) == key->hash, ECS_INTERNAL_ERROR, NULL); return; } } /* Record must already have been in the index */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash) { ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); uint64_t existing = flecs_name_index_find( map, name, key.length, key.hash); if (existing) { if (existing != id) { ecs_abort(ECS_ALREADY_DEFINED, "conflicting id registered with name '%s'", name); } } flecs_hashmap_result_t hmr = flecs_hashmap_ensure( map, &key, uint64_t); *((uint64_t*)hmr.value) = id; error: return; } /** * @file datastructures/sparse.c * @brief Sparse set data structure. */ /** Compute the page index from an id by stripping the first 12 bits */ #define PAGE(index) ((int32_t)((uint32_t)index >> FLECS_SPARSE_PAGE_BITS)) /** This computes the offset of an index inside a page */ #define OFFSET(index) ((int32_t)index & (FLECS_SPARSE_PAGE_SIZE - 1)) /* Utility to get a pointer to the payload */ #define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) typedef struct ecs_page_t { int32_t *sparse; /* Sparse array with indices to dense array */ void *data; /* Store data in sparse array to reduce * indirection and provide stable pointers. */ } ecs_page_t; static ecs_page_t* flecs_sparse_page_new( ecs_sparse_t *sparse, int32_t page_index) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; int32_t count = ecs_vec_count(&sparse->pages); ecs_page_t *pages; if (count <= page_index) { ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); } else { pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); } ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); ecs_page_t *result = &pages[page_index]; ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); /* Initialize sparse array with zero's, as zero is used to indicate that the * sparse element has not been paired with a dense element. Use zero * as this means we can take advantage of calloc having a possibly better * performance than malloc + memset. */ result->sparse = ca ? flecs_bcalloc(ca) : ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); /* Initialize the data array with zero's to guarantee that data is * always initialized. When an entry is removed, data is reset back to * zero. Initialize now, as this can take advantage of calloc. */ result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); return result; } static void flecs_sparse_page_free( ecs_sparse_t *sparse, ecs_page_t *page) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; if (ca) { flecs_bfree(ca, page->sparse); } else { ecs_os_free(page->sparse); } if (a) { flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); } else { ecs_os_free(page->data); } } static ecs_page_t* flecs_sparse_get_page( const ecs_sparse_t *sparse, int32_t page_index) { ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); if (page_index >= ecs_vec_count(&sparse->pages)) { return NULL; } return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index); } static ecs_page_t* flecs_sparse_get_or_create_page( ecs_sparse_t *sparse, int32_t page_index) { ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); if (page && page->sparse) { return page; } return flecs_sparse_page_new(sparse, page_index); } static void flecs_sparse_grow_dense( ecs_sparse_t *sparse) { ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } static uint64_t flecs_sparse_strip_generation( uint64_t *index_out) { uint64_t index = *index_out; uint64_t gen = index & ECS_GENERATION_MASK; /* Make sure there's no junk in the id */ ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), ECS_INVALID_PARAMETER, NULL); *index_out -= gen; return gen; } static void flecs_sparse_assign_index( ecs_page_t * page, uint64_t * dense_array, uint64_t index, int32_t dense) { /* Initialize sparse-dense pair. This assigns the dense index to the sparse * array, and the sparse index to the dense array .*/ page->sparse[OFFSET(index)] = dense; dense_array[dense] = index; } static uint64_t flecs_sparse_inc_gen( uint64_t index) { /* When an index is deleted, its generation is increased so that we can do * liveliness checking while recycling ids */ return ECS_GENERATION_INC(index); } static uint64_t flecs_sparse_inc_id( ecs_sparse_t *sparse) { /* Generate a new id. The last issued id could be stored in an external * variable, such as is the case with the last issued entity id, which is * stored on the world. */ return ++ sparse->max_id; } static uint64_t flecs_sparse_get_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); return sparse->max_id; } static void flecs_sparse_set_id( ecs_sparse_t *sparse, uint64_t value) { /* Sometimes the max id needs to be assigned directly, which typically * happens when the API calls get_or_create for an id that hasn't been * issued before. */ sparse->max_id = value; } /* Pair dense id with new sparse id */ static uint64_t flecs_sparse_create_id( ecs_sparse_t *sparse, int32_t dense) { uint64_t index = flecs_sparse_inc_id(sparse); flecs_sparse_grow_dense(sparse); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); ecs_assert(page->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, dense); return index; } /* Create new id */ static uint64_t flecs_sparse_new_index( ecs_sparse_t *sparse) { int32_t dense_count = ecs_vec_count(&sparse->dense); int32_t count = sparse->count ++; ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); if (count < dense_count) { /* If there are unused elements in the dense array, return first */ uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[count]; } else { return flecs_sparse_create_id(sparse, count); } } /* Get value from sparse set when it is guaranteed that the value exists. This * function is used when values are obtained using a dense index */ static void* flecs_sparse_get_sparse( const ecs_sparse_t *sparse, int32_t dense, uint64_t index) { flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = OFFSET(index); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); (void)dense; return DATA(page->data, sparse->size, offset); } /* Swap dense elements. A swap occurs when an element is removed, or when a * removed element is recycled. */ static void flecs_sparse_swap_dense( ecs_sparse_t * sparse, ecs_page_t * page_a, int32_t a, int32_t b) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t index_a = dense_array[a]; uint64_t index_b = dense_array[b]; ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, PAGE(index_b)); flecs_sparse_assign_index(page_a, dense_array, index_a, b); flecs_sparse_assign_index(page_b, dense_array, index_b, a); } void flecs_sparse_init( ecs_sparse_t *result, struct ecs_allocator_t *allocator, ecs_block_allocator_t *page_allocator, ecs_size_t size) { ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); result->size = size; result->max_id = UINT64_MAX; result->allocator = allocator; result->page_allocator = page_allocator; ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); result->dense.count = 1; /* Consume first value in dense array as 0 is used in the sparse array to * indicate that a sparse element hasn't been paired yet. */ ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; result->count = 1; } void flecs_sparse_clear( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { int32_t *indices = pages[i].sparse; if (indices) { ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); } } ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); sparse->count = 1; sparse->max_id = 0; } void flecs_sparse_fini( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { flecs_sparse_page_free(sparse, &pages[i]); } ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } uint64_t flecs_sparse_new_id( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_sparse_new_index(sparse); } void* flecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t size) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); uint64_t index = flecs_sparse_new_index(sparse); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, size, OFFSET(index)); } uint64_t flecs_sparse_last_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[sparse->count - 1]; } void* flecs_sparse_ensure( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint64_t gen = flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Check if element is alive. If element is not alive, update indices so * that the first unused dense element points to the sparse element. */ int32_t count = sparse->count; if (dense >= count) { /* If dense is not alive, swap it with the first unused element. */ flecs_sparse_swap_dense(sparse, page, dense, count); dense = count; /* First unused element is now last used element */ sparse->count ++; } else { /* Dense is already alive, nothing to be done */ } /* Ensure provided generation matches current. Only allow mismatching * generations if the provided generation count is 0. This allows for * using the ensure function in combination with ids that have their * generation stripped. */ #ifdef FLECS_DEBUG uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); #endif } else { /* Element is not paired yet. Must add a new element to dense array */ flecs_sparse_grow_dense(sparse); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); int32_t dense_count = ecs_vec_count(&sparse->dense) - 1; int32_t count = sparse->count ++; /* If index is larger than max id, update max id */ if (index >= flecs_sparse_get_id(sparse)) { flecs_sparse_set_id(sparse, index); } if (count < dense_count) { /* If there are unused elements in the list, move the first unused * element to the end of the list */ uint64_t unused = dense_array[count]; ecs_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, PAGE(unused)); flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count); } flecs_sparse_assign_index(page, dense_array, index, count); dense_array[count] |= gen; } return DATA(page->data, sparse->size, offset); } void* flecs_sparse_ensure_fast( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index_long) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint32_t index = (uint32_t)index_long; ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; int32_t count = sparse->count; if (!dense) { /* Element is not paired yet. Must add a new element to dense array */ sparse->count = count + 1; if (count == ecs_vec_count(&sparse->dense)) { flecs_sparse_grow_dense(sparse); } uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, count); } return DATA(page->data, sparse->size, offset); } void flecs_sparse_remove( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return; } uint64_t gen = flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (gen != cur_gen) { /* Generation doesn't match which means that the provided entity is * already not alive. */ return; } /* Increase generation */ dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen); int32_t count = sparse->count; if (dense == (count - 1)) { /* If dense is the last used element, simply decrease count */ sparse->count --; } else if (dense < count) { /* If element is alive, move it to unused elements */ flecs_sparse_swap_dense(sparse, page, dense, count - 1); sparse->count --; } else { /* Element is not alive, nothing to be done */ return; } /* Reset memory to zero on remove */ void *ptr = DATA(page->data, sparse->size, offset); ecs_os_memset(ptr, 0, size); } else { /* Element is not paired and thus not alive, nothing to be done */ return; } } void* flecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t size, int32_t dense_index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); (void)size; dense_index ++; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); } bool flecs_sparse_is_alive( const ecs_sparse_t *sparse, uint64_t index) { ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return false; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return false; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return false; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return true; } void* flecs_sparse_try( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return NULL; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } void* flecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, PAGE(index)); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; (void)cur_gen; (void)gen; ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } void* flecs_sparse_get_any( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; bool in_use = dense && (dense < sparse->count); if (!in_use) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } int32_t flecs_sparse_count( const ecs_sparse_t *sparse) { if (!sparse || !sparse->count) { return 0; } return sparse->count - 1; } const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); if (sparse->dense.array) { return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); } else { return NULL; } } void ecs_sparse_init( ecs_sparse_t *sparse, ecs_size_t elem_size) { flecs_sparse_init(sparse, NULL, NULL, elem_size); } void* ecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t elem_size) { return flecs_sparse_add(sparse, elem_size); } uint64_t ecs_sparse_last_id( const ecs_sparse_t *sparse) { return flecs_sparse_last_id(sparse); } int32_t ecs_sparse_count( const ecs_sparse_t *sparse) { return flecs_sparse_count(sparse); } void* ecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t elem_size, int32_t index) { return flecs_sparse_get_dense(sparse, elem_size, index); } void* ecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t elem_size, uint64_t id) { return flecs_sparse_get(sparse, elem_size, id); } /** * @file datastructures/stack_allocator.c * @brief Stack allocator. * * The stack allocator enables pushing and popping values to a stack, and has * a lower overhead when compared to block allocators. A stack allocator is a * good fit for small temporary allocations. * * The stack allocator allocates memory in pages. If the requested size of an * allocation exceeds the page size, a regular allocator is used instead. */ #define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) int64_t ecs_stack_allocator_alloc_count = 0; int64_t ecs_stack_allocator_free_count = 0; static ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { ecs_stack_page_t *result = ecs_os_malloc( FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); result->next = NULL; result->id = page_id + 1; ecs_os_linc(&ecs_stack_allocator_alloc_count); return result; } void* flecs_stack_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { ecs_stack_page_t *page = stack->tail_page; if (page == &stack->first && !page->data) { page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); ecs_os_linc(&ecs_stack_allocator_alloc_count); } int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); void *result = NULL; if (next_sp > ECS_STACK_PAGE_SIZE) { if (size > ECS_STACK_PAGE_SIZE) { result = ecs_os_malloc(size); /* Too large for page */ goto done; } if (page->next) { page = page->next; } else { page = page->next = flecs_stack_page_new(page->id); } sp = 0; next_sp = flecs_ito(int16_t, size); stack->tail_page = page; } page->sp = next_sp; result = ECS_OFFSET(page->data, sp); done: #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif return result; } void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { void *ptr = flecs_stack_alloc(stack, size, align); ecs_os_memset(ptr, 0, size); return ptr; } void flecs_stack_free( void *ptr, ecs_size_t size) { if (size > ECS_STACK_PAGE_SIZE) { ecs_os_free(ptr); } } ecs_stack_cursor_t* flecs_stack_get_cursor( ecs_stack_t *stack) { ecs_stack_page_t *page = stack->tail_page; int16_t sp = stack->tail_page->sp; ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); result->page = page; result->sp = sp; result->is_free = false; #ifdef FLECS_DEBUG ++ stack->cursor_count; result->owner = stack; #endif result->prev = stack->tail_cursor; stack->tail_cursor = result; return result; } void flecs_stack_restore_cursor( ecs_stack_t *stack, ecs_stack_cursor_t *cursor) { if (!cursor) { return; } ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, NULL); ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, NULL); ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, NULL); cursor->is_free = true; #ifdef FLECS_DEBUG -- stack->cursor_count; #endif /* If cursor is not the last on the stack no memory should be freed */ if (cursor != stack->tail_cursor) { return; } /* Iterate freed cursors to know how much memory we can free */ do { ecs_stack_cursor_t* prev = cursor->prev; if (!prev || !prev->is_free) { break; /* Found active cursor, free up until this point */ } cursor = prev; } while (cursor); stack->tail_cursor = cursor->prev; stack->tail_page = cursor->page; stack->tail_page->sp = cursor->sp; /* If the cursor count is zero, stack should be empty * if the cursor count is non-zero, stack should not be empty */ ecs_dbg_assert((stack->cursor_count == 0) == (stack->tail_page == &stack->first && stack->tail_page->sp == 0), ECS_LEAK_DETECTED, NULL); } void flecs_stack_reset( ecs_stack_t *stack) { ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); stack->tail_page = &stack->first; stack->first.sp = 0; stack->tail_cursor = NULL; } void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); stack->tail_page = &stack->first; stack->first.data = NULL; } void flecs_stack_fini( ecs_stack_t *stack) { ecs_stack_page_t *next, *cur = &stack->first; ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, NULL); ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, NULL); ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, NULL); do { next = cur->next; if (cur == &stack->first) { if (cur->data) { ecs_os_linc(&ecs_stack_allocator_free_count); } ecs_os_free(cur->data); } else { ecs_os_linc(&ecs_stack_allocator_free_count); ecs_os_free(cur); } } while ((cur = next)); } /** * @file datastructures/strbuf.c * @brief Utility for constructing strings. * * A buffer builds up a list of elements which individually can be up to N bytes * large. While appending, data is added to these elements. More elements are * added on the fly when needed. When an application calls ecs_strbuf_get, all * elements are combined in one string and the element administration is freed. * * This approach prevents reallocs of large blocks of memory, and therefore * copying large blocks of memory when appending to a large buffer. A buffer * preallocates some memory for the element overhead so that for small strings * there is hardly any overhead, while for large strings the overhead is offset * by the reduced time spent on copying memory. * * The functionality provided by strbuf is similar to std::stringstream. */ #include /** * stm32tpl -- STM32 C++ Template Peripheral Library * Visit https://github.com/antongus/stm32tpl for new versions * * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA */ #define MAX_PRECISION (10) #define EXP_THRESHOLD (3) #define INT64_MAX_F ((double)INT64_MAX) static const double rounders[MAX_PRECISION + 1] = { 0.5, // 0 0.05, // 1 0.005, // 2 0.0005, // 3 0.00005, // 4 0.000005, // 5 0.0000005, // 6 0.00000005, // 7 0.000000005, // 8 0.0000000005, // 9 0.00000000005 // 10 }; static char* flecs_strbuf_itoa( char *buf, int64_t v) { char *ptr = buf; char * p1; char c; if (!v) { *ptr++ = '0'; } else { if (v < 0) { ptr[0] = '-'; ptr ++; v *= -1; } char *p = ptr; while (v) { int64_t vdiv = v / 10; int64_t vmod = v - (vdiv * 10); p[0] = (char)('0' + vmod); p ++; v = vdiv; } p1 = p; while (p > ptr) { c = *--p; *p = *ptr; *ptr++ = c; } ptr = p1; } return ptr; } static void flecs_strbuf_ftoa( ecs_strbuf_t *out, double f, int precision, char nan_delim) { char buf[64]; char * ptr = buf; char c; int64_t intPart; int64_t exp = 0; if (ecs_os_isnan(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "NaN"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "NaN"); return; } } if (ecs_os_isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "Inf"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "Inf"); return; } } if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } if (f < 0) { f = -f; *ptr++ = '-'; } if (precision < 0) { if (f < 1.0) precision = 6; else if (f < 10.0) precision = 5; else if (f < 100.0) precision = 4; else if (f < 1000.0) precision = 3; else if (f < 10000.0) precision = 2; else if (f < 100000.0) precision = 1; else precision = 0; } if (precision) { f += rounders[precision]; } /* Make sure that number can be represented as 64bit int, increase exp */ while (f > INT64_MAX_F) { f /= 1000 * 1000 * 1000; exp += 9; } intPart = (int64_t)f; f -= (double)intPart; ptr = flecs_strbuf_itoa(ptr, intPart); if (precision) { *ptr++ = '.'; while (precision--) { f *= 10.0; c = (char)f; *ptr++ = (char)('0' + c); f -= c; } } *ptr = 0; /* Remove trailing 0s */ while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { ptr[-1] = '\0'; ptr --; } if (ptr != buf && ptr[-1] == '.') { ptr[-1] = '\0'; ptr --; } /* If 0s before . exceed threshold, convert to exponent to save space * without losing precision. */ char *cur = ptr; while ((&cur[-1] != buf) && (cur[-1] == '0')) { cur --; } if (exp || ((ptr - cur) > EXP_THRESHOLD)) { cur[0] = '\0'; exp += (ptr - cur); ptr = cur; } if (exp) { char *p1 = &buf[1]; if (nan_delim) { ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); buf[0] = nan_delim; p1 ++; } /* Make sure that exp starts after first character */ c = p1[0]; if (c) { p1[0] = '.'; do { char t = (++p1)[0]; if (t == '.') { exp ++; p1 --; break; } p1[0] = c; c = t; exp ++; } while (c); ptr = p1 + 1; } else { ptr = p1; } ptr[0] = 'e'; ptr = flecs_strbuf_itoa(ptr + 1, exp); if (nan_delim) { ptr[0] = nan_delim; ptr ++; } ptr[0] = '\0'; } ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } /* Add an extra element to the buffer */ static void flecs_strbuf_grow( ecs_strbuf_t *b) { if (!b->content) { b->content = b->small_string; b->size = ECS_STRBUF_SMALL_STRING_SIZE; } else if (b->content == b->small_string) { b->size *= 2; b->content = ecs_os_malloc_n(char, b->size); ecs_os_memcpy(b->content, b->small_string, b->length); } else { b->size *= 2; if (b->size < 16) b->size = 16; b->content = ecs_os_realloc_n(b->content, char, b->size); } } static char* flecs_strbuf_ptr( ecs_strbuf_t *b) { ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); return &b->content[b->length]; } /* Append a format string to a buffer */ static void flecs_strbuf_vappend( ecs_strbuf_t *b, const char* str, va_list args) { va_list arg_cpy; if (!str) { return; } /* Compute the memory required to add the string to the buffer. If user * provided buffer, use space left in buffer, otherwise use space left in * current element. */ int32_t mem_left = b->size - b->length; int32_t mem_required; va_copy(arg_cpy, args); if (b->content) { mem_required = vsnprintf( flecs_strbuf_ptr(b), flecs_itosize(mem_left), str, args); } else { mem_required = vsnprintf(NULL, 0, str, args); mem_left = 0; } ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); if ((mem_required + 1) >= mem_left) { while ((mem_required + 1) >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } vsnprintf(flecs_strbuf_ptr(b), flecs_itosize(mem_required + 1), str, arg_cpy); } b->length += mem_required; va_end(arg_cpy); } static void flecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str, int n) { int32_t mem_left = b->size - b->length; while (n >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); b->length += n; } static void flecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { if (b->size == b->length) { flecs_strbuf_grow(b); } flecs_strbuf_ptr(b)[0] = ch; b->length ++; } void ecs_strbuf_vappend( ecs_strbuf_t *b, const char* fmt, va_list args) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_vappend(b, fmt, args); } void ecs_strbuf_append( ecs_strbuf_t *b, const char* fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_appendstrn( ecs_strbuf_t *b, const char* str, int32_t len) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, len); } void ecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_appendint( ecs_strbuf_t *b, int64_t v) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char numbuf[32]; char *ptr = flecs_strbuf_itoa(numbuf, v); ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } void ecs_strbuf_appendflt( ecs_strbuf_t *b, double flt, char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_ftoa(b, flt, 10, nan_delim); } void ecs_strbuf_appendbool( ecs_strbuf_t *buffer, bool v) { ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); if (v) { ecs_strbuf_appendlit(buffer, "true"); } else { ecs_strbuf_appendlit(buffer, "false"); } } void ecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } void ecs_strbuf_mergebuff( ecs_strbuf_t *b, ecs_strbuf_t *src) { if (src->content) { ecs_strbuf_appendstr(b, src->content); } ecs_strbuf_reset(src); } char* ecs_strbuf_get( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; if (!result) { return NULL; } ecs_strbuf_appendch(b, '\0'); result = b->content; if (result == b->small_string) { result = ecs_os_memdup_n(result, char, b->length + 1); } b->length = 0; b->content = NULL; b->size = 0; b->list_sp = 0; return result; } char* ecs_strbuf_get_small( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; result[b->length] = '\0'; b->length = 0; b->content = NULL; b->size = 0; return result; } void ecs_strbuf_reset( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->content && b->content != b->small_string) { ecs_os_free(b->content); } *b = ECS_STRBUF_INIT; } void ecs_strbuf_list_push( ecs_strbuf_t *b, const char *list_open, const char *separator) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, NULL); b->list_sp ++; ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, ECS_INVALID_OPERATION, NULL); b->list_stack[b->list_sp].count = 0; b->list_stack[b->list_sp].separator = separator; if (list_open) { char ch = list_open[0]; if (ch && !list_open[1]) { ecs_strbuf_appendch(b, ch); } else { ecs_strbuf_appendstr(b, list_open); } } } void ecs_strbuf_list_pop( ecs_strbuf_t *b, const char *list_close) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, NULL); b->list_sp --; if (list_close) { char ch = list_close[0]; if (ch && !list_close[1]) { ecs_strbuf_appendch(b, list_close[0]); } else { ecs_strbuf_appendstr(b, list_close); } } } void ecs_strbuf_list_next( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t list_sp = b->list_sp; if (b->list_stack[list_sp].count != 0) { const char *sep = b->list_stack[list_sp].separator; if (sep && !sep[1]) { ecs_strbuf_appendch(b, sep[0]); } else { ecs_strbuf_appendstr(b, sep); } } b->list_stack[list_sp].count ++; } void ecs_strbuf_list_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_list_append( ecs_strbuf_t *b, const char *fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_list_appendstr( ecs_strbuf_t *b, const char *str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstr(b, str); } void ecs_strbuf_list_appendstrn( ecs_strbuf_t *b, const char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstrn(b, str, n); } int32_t ecs_strbuf_written( const ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return b->length; } /** * @file datastructures/switch_list.c * @brief Interleaved linked list for storing mutually exclusive values. * * Data structure that stores N interleaved linked lists in an array. * This allows for efficient storage of elements with mutually exclusive values. * Each linked list has a header element which points to the index in the array * that stores the first node of the list. Each list node points to the next * array element. * * The data structure allows for efficient storage and retrieval for values with * mutually exclusive values, such as enumeration values. The linked list allows * an application to obtain all elements for a given (enumeration) value without * having to search. * * While the list accepts 64 bit values, it only uses the lower 32bits of the * value for selecting the correct linked list. * * The switch list is used to store union relationships. */ #ifdef FLECS_SANITIZE static void flecs_switch_verify_nodes( ecs_switch_header_t *hdr, ecs_switch_node_t *nodes) { if (!hdr) { return; } int32_t prev = -1, elem = hdr->element, count = 0; while (elem != -1) { ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); prev = elem; elem = nodes[elem].next; count ++; } ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); } #else #define flecs_switch_verify_nodes(hdr, nodes) #endif static ecs_switch_header_t* flecs_switch_get_header( const ecs_switch_t *sw, uint64_t value) { if (value == 0) { return NULL; } return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value); } static ecs_switch_header_t *flecs_switch_ensure_header( ecs_switch_t *sw, uint64_t value) { ecs_switch_header_t *node = flecs_switch_get_header(sw, value); if (!node && (value != 0)) { node = (ecs_switch_header_t*)ecs_map_ensure(&sw->hdrs, value); node->count = 0; node->element = -1; } return node; } static void flecs_switch_remove_node( ecs_switch_header_t *hdr, ecs_switch_node_t *nodes, ecs_switch_node_t *node, int32_t element) { ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); /* Update previous node/header */ if (hdr->element == element) { ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); /* If this is the first node, update the header */ hdr->element = node->next; } else { /* If this is not the first node, update the previous node to the * removed node's next ptr */ ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); ecs_switch_node_t *prev_node = &nodes[node->prev]; prev_node->next = node->next; } /* Update next node */ int32_t next = node->next; if (next != -1) { ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); /* If this is not the last node, update the next node to point to the * removed node's prev ptr */ ecs_switch_node_t *next_node = &nodes[next]; next_node->prev = node->prev; } /* Decrease count of current header */ hdr->count --; ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); } void flecs_switch_init( ecs_switch_t *sw, ecs_allocator_t *allocator, int32_t elements) { ecs_map_init(&sw->hdrs, allocator); ecs_vec_init_t(allocator, &sw->nodes, ecs_switch_node_t, elements); ecs_vec_init_t(allocator, &sw->values, uint64_t, elements); ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); uint64_t *values = ecs_vec_first(&sw->values); int i; for (i = 0; i < elements; i ++) { nodes[i].prev = -1; nodes[i].next = -1; values[i] = 0; } } void flecs_switch_clear( ecs_switch_t *sw) { ecs_map_clear(&sw->hdrs); ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); } void flecs_switch_fini( ecs_switch_t *sw) { ecs_map_fini(&sw->hdrs); ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); } void flecs_switch_add( ecs_switch_t *sw) { ecs_switch_node_t *node = ecs_vec_append_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); uint64_t *value = ecs_vec_append_t(sw->hdrs.allocator, &sw->values, uint64_t); node->prev = -1; node->next = -1; *value = 0; } void flecs_switch_set_count( ecs_switch_t *sw, int32_t count) { int32_t old_count = ecs_vec_count(&sw->nodes); if (old_count == count) { return; } ecs_vec_set_count_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t, count); ecs_vec_set_count_t(sw->hdrs.allocator, &sw->values, uint64_t, count); ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); uint64_t *values = ecs_vec_first(&sw->values); int32_t i; for (i = old_count; i < count; i ++) { ecs_switch_node_t *node = &nodes[i]; node->prev = -1; node->next = -1; values[i] = 0; } } int32_t flecs_switch_count( ecs_switch_t *sw) { ecs_assert(ecs_vec_count(&sw->values) == ecs_vec_count(&sw->nodes), ECS_INTERNAL_ERROR, NULL); return ecs_vec_count(&sw->values); } void flecs_switch_ensure( ecs_switch_t *sw, int32_t count) { int32_t old_count = ecs_vec_count(&sw->nodes); if (old_count >= count) { return; } flecs_switch_set_count(sw, count); } void flecs_switch_addn( ecs_switch_t *sw, int32_t count) { int32_t old_count = ecs_vec_count(&sw->nodes); flecs_switch_set_count(sw, old_count + count); } void flecs_switch_set( ecs_switch_t *sw, int32_t element, uint64_t value) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); uint64_t *values = ecs_vec_first(&sw->values); uint64_t cur_value = values[element]; /* If the node is already assigned to the value, nothing to be done */ if (cur_value == value) { return; } ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); ecs_switch_node_t *node = &nodes[element]; ecs_switch_header_t *dst_hdr = flecs_switch_ensure_header(sw, value); ecs_switch_header_t *cur_hdr = flecs_switch_get_header(sw, cur_value); flecs_switch_verify_nodes(cur_hdr, nodes); flecs_switch_verify_nodes(dst_hdr, nodes); /* If value is not 0, and dst_hdr is NULL, then this is not a valid value * for this switch */ ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); if (cur_hdr) { flecs_switch_remove_node(cur_hdr, nodes, node, element); } /* Now update the node itself by adding it as the first node of dst */ node->prev = -1; values[element] = value; if (dst_hdr) { node->next = dst_hdr->element; /* Also update the dst header */ int32_t first = dst_hdr->element; if (first != -1) { ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); ecs_switch_node_t *first_node = &nodes[first]; first_node->prev = element; } dst_hdr->element = element; dst_hdr->count ++; } } void flecs_switch_remove( ecs_switch_t *sw, int32_t elem) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(elem >= 0, ECS_INVALID_PARAMETER, NULL); uint64_t *values = ecs_vec_first(&sw->values); uint64_t value = values[elem]; ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); ecs_switch_node_t *node = &nodes[elem]; /* If node is currently assigned to a case, remove it from the list */ if (value != 0) { ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_switch_verify_nodes(hdr, nodes); flecs_switch_remove_node(hdr, nodes, node, elem); } int32_t last_elem = ecs_vec_count(&sw->nodes) - 1; if (last_elem != elem) { ecs_switch_node_t *last = ecs_vec_last_t(&sw->nodes, ecs_switch_node_t); int32_t next = last->next, prev = last->prev; if (next != -1) { ecs_switch_node_t *n = &nodes[next]; n->prev = elem; } if (prev != -1) { ecs_switch_node_t *n = &nodes[prev]; n->next = elem; } else { ecs_switch_header_t *hdr = flecs_switch_get_header(sw, values[last_elem]); if (hdr && hdr->element != -1) { ecs_assert(hdr->element == last_elem, ECS_INTERNAL_ERROR, NULL); hdr->element = elem; } } } /* Remove element from arrays */ ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem); ecs_vec_remove_t(&sw->values, uint64_t, elem); } uint64_t flecs_switch_get( const ecs_switch_t *sw, int32_t element) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); uint64_t *values = ecs_vec_first(&sw->values); return values[element]; } ecs_vec_t* flecs_switch_values( const ecs_switch_t *sw) { return ECS_CONST_CAST(ecs_vec_t*, &sw->values); } int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value) { ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); if (!hdr) { return 0; } return hdr->count; } void flecs_switch_swap( ecs_switch_t *sw, int32_t elem_1, int32_t elem_2) { uint64_t v1 = flecs_switch_get(sw, elem_1); uint64_t v2 = flecs_switch_get(sw, elem_2); flecs_switch_set(sw, elem_2, v1); flecs_switch_set(sw, elem_1, v2); } int32_t flecs_switch_first( const ecs_switch_t *sw, uint64_t value) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); if (!hdr) { return -1; } return hdr->element; } int32_t flecs_switch_next( const ecs_switch_t *sw, int32_t element) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); return nodes[element].next; } /** * @file datastructures/vec.c * @brief Vector with allocator support. */ ecs_vec_t* ecs_vec_init( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { v->array = flecs_alloc(allocator, size * elem_count); } else { v->array = ecs_os_malloc(size * elem_count); } } v->size = elem_count; #ifdef FLECS_SANITIZE v->elem_size = size; #endif return v; } void ecs_vec_init_if( ecs_vec_t *vec, ecs_size_t size) { ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); (void)vec; (void)size; #ifdef FLECS_SANITIZE if (!vec->elem_size) { ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); vec->elem_size = size; } #endif } void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (v->array) { ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (allocator) { flecs_free(allocator, size * v->size, v->array); } else { ecs_os_free(v->array); } v->array = NULL; v->count = 0; v->size = 0; } } ecs_vec_t* ecs_vec_reset( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (!v->size) { ecs_vec_init(allocator, v, size, 0); } else { ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); ecs_vec_clear(v); } return v; } void ecs_vec_clear( ecs_vec_t *vec) { vec->count = 0; } ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); void *array; if (allocator) { array = flecs_dup(allocator, size * v->size, v->array); } else { array = ecs_os_memdup(v->array, size * v->size); } return (ecs_vec_t) { .count = v->count, .size = v->size, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } ecs_vec_t ecs_vec_copy_shrink( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; void *array = NULL; if (count) { if (allocator) { array = flecs_dup(allocator, size * count, v->array); } else { array = ecs_os_memdup(v->array, size * count); } } return (ecs_vec_t) { .count = count, .size = count, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (count < v->size) { if (count) { if (allocator) { v->array = flecs_realloc( allocator, size * count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * count); } v->size = count; } else { ecs_vec_fini(allocator, v, size); } } } void ecs_vec_set_size( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->size != elem_count) { if (elem_count < v->count) { elem_count = v->count; } elem_count = flecs_next_pow_of_2(elem_count); if (elem_count < 2) { elem_count = 2; } if (elem_count != v->size) { if (allocator) { v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * elem_count); } v->size = elem_count; } } } void ecs_vec_set_min_size( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { if (elem_count > vec->size) { ecs_vec_set_size(allocator, vec, size, elem_count); } } void ecs_vec_set_min_count( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { ecs_vec_set_min_size(allocator, vec, size, elem_count); if (vec->count < elem_count) { vec->count = elem_count; } } void ecs_vec_set_min_count_zeromem( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { int32_t count = vec->count; if (count < elem_count) { ecs_vec_set_min_count(allocator, vec, size, elem_count); ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, size * (elem_count - count)); } } void ecs_vec_set_count( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->count != elem_count) { if (v->size < elem_count) { ecs_vec_set_size(allocator, v, size, elem_count); } v->count = elem_count; } } void* ecs_vec_grow( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); int32_t count = v->count; ecs_vec_set_count(allocator, v, size, count + elem_count); return ECS_ELEM(v->array, size, count); } void* ecs_vec_append( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (v->size == count) { ecs_vec_set_size(allocator, v, size, count + 1); } v->count = count + 1; return ECS_ELEM(v->array, size, count); } void ecs_vec_remove( ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); if (index == --v->count) { return; } ecs_os_memcpy( ECS_ELEM(v->array, size, index), ECS_ELEM(v->array, size, v->count), size); } void ecs_vec_remove_last( ecs_vec_t *v) { v->count --; } int32_t ecs_vec_count( const ecs_vec_t *v) { return v->count; } int32_t ecs_vec_size( const ecs_vec_t *v) { return v->size; } void* ecs_vec_get( const ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); return ECS_ELEM(v->array, size, index); } void* ecs_vec_last( const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(!v->elem_size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(v->array, size, v->count - 1); } void* ecs_vec_first( const ecs_vec_t *v) { return v->array; } static ecs_entity_index_page_t* flecs_entity_index_ensure_page( ecs_entity_index_t *index, uint32_t id) { int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); if (page_index >= ecs_vec_count(&index->pages)) { ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages, ecs_entity_index_page_t*, page_index + 1); } ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index); ecs_entity_index_page_t *page = *page_ptr; if (!page) { page = *page_ptr = flecs_bcalloc(&index->page_allocator); ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL); } return page; } void flecs_entity_index_init( ecs_allocator_t *allocator, ecs_entity_index_t *index) { index->allocator = allocator; index->alive_count = 1; ecs_vec_init_t(allocator, &index->dense, uint64_t, 1); ecs_vec_set_count_t(allocator, &index->dense, uint64_t, 1); ecs_vec_init_t(allocator, &index->pages, ecs_entity_index_page_t*, 0); flecs_ballocator_init(&index->page_allocator, ECS_SIZEOF(ecs_entity_index_page_t)); } void flecs_entity_index_fini( ecs_entity_index_t *index) { ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); #if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) int32_t i, count = ecs_vec_count(&index->pages); ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); for (i = 0; i < count; i ++) { flecs_bfree(&index->page_allocator, pages[i]); } #endif ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); flecs_ballocator_fini(&index->page_allocator); } ecs_record_t* flecs_entity_index_get_any( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index)[0]; ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; ecs_assert(r->dense != 0, ECS_INVALID_PARAMETER, NULL); return r; } ecs_record_t* flecs_entity_index_get( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_get_any(index, entity); ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, ECS_INVALID_PARAMETER, NULL); return r; } ecs_record_t* flecs_entity_index_try_get_any( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); if (page_index >= ecs_vec_count(&index->pages)) { return NULL; } ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index)[0]; if (!page) { return NULL; } ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; if (!r->dense) { return NULL; } return r; } ecs_record_t* flecs_entity_index_try_get( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { if (r->dense >= index->alive_count) { return NULL; } if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { return NULL; } } return r; } ecs_record_t* flecs_entity_index_ensure( ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; int32_t dense = r->dense; if (dense) { /* Entity is already alive, nothing to be done */ if (dense < index->alive_count) { ecs_assert( ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, ECS_INTERNAL_ERROR, NULL); return r; } } else { /* Entity doesn't have a dense index yet */ ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; r->dense = dense = ecs_vec_count(&index->dense) - 1; index->max_id = id > index->max_id ? id : index->max_id; } ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); /* Entity is not alive, swap with first not alive element */ uint64_t *ids = ecs_vec_first(&index->dense); uint64_t e_swap = ids[index->alive_count]; ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); ecs_assert(r_swap->dense == index->alive_count, ECS_INTERNAL_ERROR, NULL); r_swap->dense = dense; r->dense = index->alive_count; ids[dense] = e_swap; ids[index->alive_count ++] = entity; ecs_assert(flecs_entity_index_is_alive(index, entity), ECS_INTERNAL_ERROR, NULL); return r; } void flecs_entity_index_remove( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get(index, entity); if (!r) { /* Entity is not alive or doesn't exist, nothing to be done */ return; } int32_t dense = r->dense; int32_t i_swap = -- index->alive_count; uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); uint64_t e_swap = e_swap_ptr[0]; ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); r_swap->dense = dense; r->table = NULL; r->idr = NULL; r->row = 0; r->dense = i_swap; ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap; e_swap_ptr[0] = ECS_GENERATION_INC(entity); ecs_assert(!flecs_entity_index_is_alive(index, entity), ECS_INTERNAL_ERROR, NULL); } void flecs_entity_index_make_alive( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity; } } uint64_t flecs_entity_index_get_alive( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; } else { return 0; } } bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity) { return flecs_entity_index_try_get(index, entity) != NULL; } bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; ecs_record_t *r = flecs_entity_index_try_get_any(index, id); if (!r || !r->dense) { /* Doesn't exist yet, so is valid */ return true; } /* If the id exists, it must be alive */ return r->dense < index->alive_count; } bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity) { return flecs_entity_index_try_get_any(index, entity) != NULL; } uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index) { if (index->alive_count != ecs_vec_count(&index->dense)) { /* Recycle id */ return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; } /* Create new id */ uint32_t id = (uint32_t)++ index->max_id; ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = index->alive_count ++; ecs_assert(index->alive_count == ecs_vec_count(&index->dense), ECS_INTERNAL_ERROR, NULL); return id; } uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count) { int32_t alive_count = index->alive_count; int32_t new_count = alive_count + count; int32_t dense_count = ecs_vec_count(&index->dense); if (new_count < dense_count) { /* Recycle ids */ index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } /* Allocate new ids */ ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); int32_t i, to_add = new_count - dense_count; for (i = 0; i < to_add; i ++) { uint32_t id = (uint32_t)++ index->max_id; int32_t dense = dense_count + i; ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = dense; } index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size) { ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); } int32_t flecs_entity_index_count( const ecs_entity_index_t *index) { return index->alive_count - 1; } int32_t flecs_entity_index_size( const ecs_entity_index_t *index) { return ecs_vec_count(&index->dense) - 1; } int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index) { return ecs_vec_count(&index->dense) - index->alive_count; } void flecs_entity_index_clear( ecs_entity_index_t *index) { int32_t i, count = ecs_vec_count(&index->pages); ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, ecs_entity_index_page_t*); for (i = 0; i < count; i ++) { ecs_entity_index_page_t *page = pages[i]; if (page) { ecs_os_zeromem(page); } } ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); index->alive_count = 1; index->max_id = 0; } const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index) { return ecs_vec_get_t(&index->dense, uint64_t, 1); } static void flecs_entity_index_copy_intern( ecs_entity_index_t * dst, const ecs_entity_index_t * src) { flecs_entity_index_set_size(dst, flecs_entity_index_size(src)); const uint64_t *ids = flecs_entity_index_ids(src); int32_t i, count = src->alive_count; for (i = 0; i < count - 1; i ++) { uint64_t id = ids[i]; ecs_record_t *src_ptr = flecs_entity_index_get(src, id); ecs_record_t *dst_ptr = flecs_entity_index_ensure(dst, id); flecs_entity_index_make_alive(dst, id); ecs_os_memcpy_t(dst_ptr, src_ptr, ecs_record_t); } dst->max_id = src->max_id; ecs_assert(src->alive_count == dst->alive_count, ECS_INTERNAL_ERROR, NULL); } void flecs_entity_index_copy( ecs_entity_index_t *dst, const ecs_entity_index_t *src) { if (!src) { return; } flecs_entity_index_init(src->allocator, dst); flecs_entity_index_copy_intern(dst, src); } void flecs_entity_index_restore( ecs_entity_index_t *dst, const ecs_entity_index_t *src) { if (!src) { return; } flecs_entity_index_clear(dst); flecs_entity_index_copy_intern(dst, src); } /** * @file id_index.c * @brief Index for looking up tables by (component) id. * * An id record stores the administration for an in use (component) id, that is * an id that has been used in tables. * * An id record contains a table cache, which stores the list of tables that * have the id. Each entry in the cache (a table record) stores the first * occurrence of the id in the table and the number of occurrences of the id in * the table (in the case of wildcard ids). * * Id records are used in lots of scenarios, like uncached queries, or for * getting a component array/component for an entity. */ static ecs_id_record_elem_t* flecs_id_record_elem( ecs_id_record_t *head, ecs_id_record_elem_t *list, ecs_id_record_t *idr) { return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); } static void flecs_id_record_elem_insert( ecs_id_record_t *head, ecs_id_record_t *idr, ecs_id_record_elem_t *elem) { ecs_id_record_elem_t *head_elem = flecs_id_record_elem(idr, elem, head); ecs_id_record_t *cur = head_elem->next; elem->next = cur; elem->prev = head; if (cur) { ecs_id_record_elem_t *cur_elem = flecs_id_record_elem(idr, elem, cur); cur_elem->prev = idr; } head_elem->next = idr; } static void flecs_id_record_elem_remove( ecs_id_record_t *idr, ecs_id_record_elem_t *elem) { ecs_id_record_t *prev = elem->prev; ecs_id_record_t *next = elem->next; ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_elem_t *prev_elem = flecs_id_record_elem(idr, elem, prev); prev_elem->next = next; if (next) { ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next); next_elem->prev = prev; } } static void flecs_insert_id_elem( ecs_world_t *world, ecs_id_record_t *idr, ecs_id_t wildcard, ecs_id_record_t *widr) { ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); if (!widr) { widr = flecs_id_record_ensure(world, wildcard); } ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_insert(widr, idr, &idr->first); } else { ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_insert(widr, idr, &idr->second); if (idr->flags & EcsIdTraversable) { flecs_id_record_elem_insert(widr, idr, &idr->trav); } } } static void flecs_remove_id_elem( ecs_id_record_t *idr, ecs_id_t wildcard) { ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_remove(idr, &idr->first); } else { ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_remove(idr, &idr->second); if (idr->flags & EcsIdTraversable) { flecs_id_record_elem_remove(idr, &idr->trav); } } } static ecs_id_t flecs_id_record_hash( ecs_id_t id) { id = ecs_strip_generation(id); if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); if (r == EcsAny) { r = EcsWildcard; } if (o == EcsAny) { o = EcsWildcard; } id = ecs_pair(r, o); } return id; } static ecs_id_record_t* flecs_id_record_new( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr, *idr_t = NULL; ecs_id_t hash = flecs_id_record_hash(id); if (hash >= FLECS_HI_ID_RECORD_ID) { idr = flecs_bcalloc(&world->allocators.id_record); ecs_map_insert_ptr(&world->id_index_hi, hash, idr); } else { idr = &world->id_index_lo[hash]; ecs_os_zeromem(idr); } ecs_table_cache_init(world, &idr->cache); idr->id = id; idr->refcount = 1; idr->reachable.current = -1; bool is_wildcard = ecs_id_is_wildcard(id); bool is_pair = ECS_IS_PAIR(id); ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; if (is_pair) { // rel = ecs_pair_first(world, id); rel = ECS_PAIR_FIRST(id); rel = flecs_entities_get_alive(world, rel); ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); /* Relationship object can be 0, as tables without a ChildOf * relationship are added to the (ChildOf, 0) id record */ tgt = ECS_PAIR_SECOND(id); #ifdef FLECS_DEBUG /* Check constraints */ if (tgt) { tgt = flecs_entities_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); /* Can't use relationship as target */ if (ecs_has_id(world, tgt, EcsRelationship)) { if (!ecs_id_is_wildcard(rel) && !ecs_has_id(world, rel, EcsTrait)) { char *idstr = ecs_id_str(world, id); char *tgtstr = ecs_id_str(world, tgt); ecs_err("constraint violated: relationship '%s' cannot be used" " as target in pair '%s'", tgtstr, idstr); ecs_os_free(tgtstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } } if (ecs_has_id(world, rel, EcsTarget)) { char *idstr = ecs_id_str(world, id); char *relstr = ecs_id_str(world, rel); ecs_err("constraint violated: " "%s: target '%s' cannot be used as relationship", idstr, relstr); ecs_os_free(relstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } if (tgt && !ecs_id_is_wildcard(tgt)) { /* Check if target of relationship satisfies OneOf property */ ecs_entity_t oneof = flecs_get_oneof(world, rel); if (oneof) { if (!ecs_has_pair(world, tgt, EcsChildOf, oneof)) { char *idstr = ecs_id_str(world, id); char *tgtstr = ecs_get_fullpath(world, tgt); char *oneofstr = ecs_get_fullpath(world, oneof); ecs_err("OneOf constraint violated: " "%s: '%s' is not a child of '%s'", idstr, tgtstr, oneofstr); ecs_os_free(oneofstr); ecs_os_free(tgtstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } /* Check if we're not trying to inherit from a final target */ if (rel == EcsIsA) { if (ecs_has_id(world, tgt, EcsFinal)) { char *idstr = ecs_id_str(world, id); char *tgtstr = ecs_get_fullpath(world, tgt); ecs_err("Final constraint violated: " "%s: cannot inherit from final entity '%s'", idstr, tgtstr); ecs_os_free(tgtstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } } #endif if (!is_wildcard && (rel != EcsFlag)) { /* Inherit flags from (relationship, *) record */ ecs_id_record_t *idr_r = flecs_id_record_ensure( world, ecs_pair(rel, EcsWildcard)); idr->parent = idr_r; idr->flags = idr_r->flags; /* If pair is not a wildcard, append it to wildcard lists. These * allow for quickly enumerating all relationships for an object, * or all objects for a relationship. */ flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); if (rel == EcsUnion) { idr->flags |= EcsIdUnion; } } } else { rel = id & ECS_COMPONENT_MASK; ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); /* Can't use relationship outside of a pair */ #ifdef FLECS_DEBUG rel = flecs_entities_get_alive(world, rel); if (ecs_has_id(world, rel, EcsRelationship) || ecs_has_id(world, rel, EcsTarget)) { char *idstr = ecs_id_str(world, id); char *relstr = ecs_id_str(world, rel); ecs_err("constraint violated: " "%s: relationship '%s' cannot be used as component", idstr, relstr); ecs_os_free(relstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } if (ecs_has_id(world, rel, EcsTarget)) { char *idstr = ecs_id_str(world, id); char *relstr = ecs_id_str(world, rel); ecs_err("constraint violated: " "%s: target '%s' cannot be used as component", idstr, relstr); ecs_os_free(relstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } #endif } /* Initialize type info if id is not a tag */ if (!is_wildcard && (!role || is_pair)) { if (!(idr->flags & EcsIdTag)) { const ecs_type_info_t *ti = flecs_type_info_get(world, rel); if (!ti && tgt) { ti = flecs_type_info_get(world, tgt); } idr->type_info = ti; } } /* Mark entities that are used as component/pair ids. When a tracked * entity is deleted, cleanup policies are applied so that the store * won't contain any tables with deleted ids. */ /* Flag for OnDelete policies */ flecs_add_flag(world, rel, EcsEntityIsId); if (tgt) { /* Flag for OnDeleteTarget policies */ ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(tgt_r, EcsEntityIsTarget); if (idr->flags & EcsIdTraversable) { /* Flag used to determine if object should be traversed when * propagating events or with super/subset queries */ flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); /* Add reference to (*, tgt) id record to entity record */ tgt_r->idr = idr_t; } } ecs_observable_t *o = &world->observable; idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; idr->flags |= flecs_observers_exist(o, id, EcsUnSet) * EcsIdHasUnSet; idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); ecs_os_free(id_str); } /* Update counters */ world->info.id_create_total ++; world->info.component_id_count += idr->type_info != NULL; world->info.tag_id_count += idr->type_info == NULL; world->info.pair_id_count += is_pair; return idr; } static void flecs_id_record_assert_empty( ecs_id_record_t *idr) { (void)idr; ecs_assert(flecs_table_cache_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); } static void flecs_id_record_free( ecs_world_t *world, ecs_id_record_t *idr) { ecs_poly_assert(world, ecs_world_t); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = idr->id; flecs_id_record_assert_empty(idr); /* Id is still in use by a filter, query, rule or observer */ ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), ECS_ID_IN_USE, "cannot delete id that is queried for"); if (ECS_IS_PAIR(id)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t tgt = ECS_PAIR_SECOND(id); if (!ecs_id_is_wildcard(id)) { if (ECS_PAIR_FIRST(id) != EcsFlag) { /* If id is not a wildcard, remove it from the wildcard lists */ flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt)); } } else { ecs_log_push_2(); /* If id is a wildcard, it means that all id records that match the * wildcard are also empty, so release them */ if (ECS_PAIR_FIRST(id) == EcsWildcard) { /* Iterate (*, Target) list */ ecs_id_record_t *cur, *next = idr->second.next; while ((cur = next)) { flecs_id_record_assert_empty(cur); next = cur->second.next; flecs_id_record_release(world, cur); } } else { /* Iterate (Relationship, *) list */ ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *cur, *next = idr->first.next; while ((cur = next)) { flecs_id_record_assert_empty(cur); next = cur->first.next; flecs_id_record_release(world, cur); } } ecs_log_pop_2(); } } /* Update counters */ world->info.id_delete_total ++; world->info.pair_id_count -= ECS_IS_PAIR(id); world->info.component_id_count -= idr->type_info != NULL; world->info.tag_id_count -= idr->type_info == NULL; /* Unregister the id record from the world & free resources */ ecs_table_cache_fini(&idr->cache); flecs_name_index_free(idr->name_index); ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); ecs_id_t hash = flecs_id_record_hash(id); if (hash >= FLECS_HI_ID_RECORD_ID) { ecs_map_remove(&world->id_index_hi, hash); flecs_bfree(&world->allocators.id_record, idr); } else { idr->id = 0; /* Tombstone */ } if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); ecs_os_free(id_str); } } ecs_id_record_t* flecs_id_record_ensure( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { idr = flecs_id_record_new(world, id); } return idr; } ecs_id_record_t* flecs_id_record_get( const ecs_world_t *world, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); if (id == ecs_pair(EcsIsA, EcsWildcard)) { return world->idr_isa_wildcard; } else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { return world->idr_childof_wildcard; } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { return world->idr_identifier_name; } ecs_id_t hash = flecs_id_record_hash(id); ecs_id_record_t *idr = NULL; if (hash >= FLECS_HI_ID_RECORD_ID) { idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash); } else { idr = &world->id_index_lo[hash]; if (!idr->id) { idr = NULL; } } return idr; } ecs_id_record_t* flecs_query_id_record_get( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { ecs_entity_t first = ECS_PAIR_FIRST(id); if (ECS_IS_PAIR(id) && (first != EcsWildcard)) { idr = flecs_id_record_get(world, ecs_pair(EcsUnion, first)); } return idr; } if (ECS_IS_PAIR(id) && ECS_PAIR_SECOND(id) == EcsWildcard && (idr->flags & EcsIdUnion)) { idr = flecs_id_record_get(world, ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); } return idr; } void flecs_id_record_claim( ecs_world_t *world, ecs_id_record_t *idr) { (void)world; idr->refcount ++; } int32_t flecs_id_record_release( ecs_world_t *world, ecs_id_record_t *idr) { int32_t rc = -- idr->refcount; ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); if (!rc) { flecs_id_record_free(world, idr); } return rc; } void flecs_id_record_release_tables( ecs_world_t *world, ecs_id_record_t *idr) { /* Cache should not contain tables that aren't empty */ ecs_assert(flecs_table_cache_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_iter_t it; if (flecs_table_cache_empty_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { /* Release current table */ flecs_table_free(world, tr->hdr.table); } } } bool flecs_id_record_set_type_info( ecs_world_t *world, ecs_id_record_t *idr, const ecs_type_info_t *ti) { bool is_wildcard = ecs_id_is_wildcard(idr->id); if (!is_wildcard) { if (ti) { if (!idr->type_info) { world->info.tag_id_count --; world->info.component_id_count ++; } } else { if (idr->type_info) { world->info.tag_id_count ++; world->info.component_id_count --; } } } bool changed = idr->type_info != ti; idr->type_info = ti; return changed; } ecs_hashmap_t* flecs_id_record_name_index_ensure( ecs_world_t *world, ecs_id_record_t *idr) { ecs_hashmap_t *map = idr->name_index; if (!map) { map = idr->name_index = flecs_name_index_new(world, &world->allocator); } return map; } ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_id_record_name_index_ensure(world, idr); } ecs_hashmap_t* flecs_id_name_index_get( const ecs_world_t *world, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return NULL; } return idr->name_index; } ecs_table_record_t* flecs_table_record_get( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); ecs_id_record_t* idr = flecs_id_record_get(world, id); if (!idr) { return NULL; } return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table) { ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } void flecs_init_id_records( ecs_world_t *world) { /* Cache often used id records on world */ world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); world->idr_wildcard_wildcard = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, EcsWildcard)); world->idr_any = flecs_id_record_ensure(world, EcsAny); world->idr_isa_wildcard = flecs_id_record_ensure(world, ecs_pair(EcsIsA, EcsWildcard)); } void flecs_fini_id_records( ecs_world_t *world) { /* Loop & delete first element until there are no elements left. Id records * can recursively delete each other, this ensures we always have a * valid iterator. */ while (ecs_map_count(&world->id_index_hi) > 0) { ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); ecs_map_next(&it); flecs_id_record_release(world, ecs_map_ptr(&it)); } int32_t i; for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { ecs_id_record_t *idr = &world->id_index_lo[i]; if (idr->id) { flecs_id_record_release(world, idr); } } ecs_assert(ecs_map_count(&world->id_index_hi) == 0, ECS_INTERNAL_ERROR, NULL); ecs_map_fini(&world->id_index_hi); ecs_os_free(world->id_index_lo); } /** * @file table.c * @brief Table storage implementation. * * Tables are the data structure that store the component data. Tables have * columns for each component in the table, and rows for each entity stored in * the table. Once created, the component list for a table doesn't change, but * entities can move from one table to another. * * Each table has a type, which is a vector with the (component) ids in the * table. The vector is sorted by id, which ensures that there can be only one * table for each unique combination of components. * * Not all ids in a table have to be components. Tags are ids that have no * data type associated with them, and as a result don't need to be explicitly * stored beyond an element in the table type. To save space and speed up table * creation, each table has a reference to a "storage table", which is a table * that only includes component ids (so excluding tags). * * Note that the actual data is not stored on the storage table. The storage * table is only used for sharing administration. A column_map member maps * between column indices of the table and its storage table. Tables are * refcounted, which ensures that storage tables won't be deleted if other * tables have references to it. */ /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severely slow down many ECS operations. */ #ifdef FLECS_SANITIZE static void flecs_table_check_sanity(ecs_table_t *table) { int32_t size = ecs_vec_size(&table->data.entities); int32_t count = ecs_vec_count(&table->data.entities); int32_t i; int32_t sw_offset = table->_ ? table->_->sw_offset : 0; int32_t sw_count = table->_ ? table->_->sw_count : 0; int32_t bs_offset = table->_ ? table->_->bs_offset : 0; int32_t bs_count = table->_ ? table->_->bs_count : 0; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); if (table->column_count) { int32_t column_count = table->column_count; ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); int32_t *column_map = table->column_map; ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &table->data.columns[i].data; ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); int32_t column_map_id = column_map[i + type_count]; ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); } } else { ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); } if (sw_count) { ecs_assert(table->_->sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < sw_count; i ++) { ecs_switch_t *sw = &table->_->sw_columns[i]; ecs_assert(ecs_vec_count(&sw->values) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, ECS_INTERNAL_ERROR, NULL); } } if (bs_count) { ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &table->_->bs_columns[i]; ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), ECS_INTERNAL_ERROR, NULL); } } ecs_assert((table->_->traversable_count == 0) || (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif /* Set flags for type hooks so table operations can quickly check whether a * fast or complex operation that invokes hooks is required. */ static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) { ecs_flags32_t flags = 0; if (ti->hooks.ctor) { flags |= EcsTableHasCtors; } if (ti->hooks.on_add) { flags |= EcsTableHasCtors; } if (ti->hooks.dtor) { flags |= EcsTableHasDtors; } if (ti->hooks.on_remove) { flags |= EcsTableHasDtors; } if (ti->hooks.copy) { flags |= EcsTableHasCopy; } if (ti->hooks.move) { flags |= EcsTableHasMove; } return flags; } static void flecs_table_init_columns( ecs_world_t *world, ecs_table_t *table, int32_t column_count) { if (!column_count) { return; } int32_t i, cur = 0, ids_count = table->type.count; ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); table->data.columns = columns; ecs_id_t *ids = table->type.array; ecs_table_record_t *records = table->_->records; int32_t *t2s = table->column_map; int32_t *s2t = &table->column_map[ids_count]; for (i = 0; i < ids_count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; const ecs_type_info_t *ti = idr->type_info; if (!ti) { t2s[i] = -1; continue; } t2s[i] = cur; s2t[cur] = i; tr->column = flecs_ito(int16_t, cur); columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); columns[cur].id = ids[i]; columns[cur].size = ti->size; if (ECS_IS_PAIR(ids[i])) { ecs_table_record_t *wc_tr = flecs_id_record_get_table( idr->parent, table); if (wc_tr->index == tr->index) { wc_tr->column = tr->column; } } #ifdef FLECS_DEBUG ecs_vec_init(NULL, &columns[cur].data, ti->size, 0); #endif table->flags |= flecs_type_info_flags(ti); cur ++; } } /* Initialize table storage */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { ecs_data_t *storage = &table->data; ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); flecs_table_init_columns(world, table, table->column_count); ecs_table__t *meta = table->_; int32_t i, sw_count = meta->sw_count; int32_t bs_count = meta->bs_count; if (sw_count) { meta->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); for (i = 0; i < sw_count; i ++) { flecs_switch_init(&meta->sw_columns[i], &world->allocator, 0); } } if (bs_count) { meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); for (i = 0; i < bs_count; i ++) { flecs_bitset_init(&meta->bs_columns[i]); } } } /* Initialize table flags. Table flags are used in lots of scenarios to quickly * check the features of a table without having to inspect the table type. Table * flags are typically used to early-out of potentially expensive operations. */ static void flecs_table_init_flags( ecs_world_t *world, ecs_table_t *table) { ecs_id_t *ids = table->type.array; int32_t count = table->type.count; int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (id <= EcsLastInternalComponentId) { table->flags |= EcsTableHasBuiltins; } if (id == EcsModule) { table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } else if (id == EcsPrefab) { table->flags |= EcsTableIsPrefab; } else if (id == EcsDisabled) { table->flags |= EcsTableIsDisabled; } else { if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); table->flags |= EcsTableHasPairs; if (r == EcsIsA) { table->flags |= EcsTableHasIsA; } else if (r == EcsChildOf) { table->flags |= EcsTableHasChildOf; ecs_entity_t obj = ecs_pair_second(world, id); ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); if (obj == EcsFlecs || obj == EcsFlecsCore || ecs_has_id(world, obj, EcsModule)) { /* If table contains entities that are inside one of the * builtin modules, it contains builtin entities */ table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { table->flags |= EcsTableHasName; } else if (r == EcsUnion) { ecs_table__t *meta = table->_; table->flags |= EcsTableHasUnion; if (!meta->sw_count) { meta->sw_offset = flecs_ito(int16_t, i); } meta->sw_count ++; } else if (r == ecs_id(EcsFlattenTarget)) { ecs_table__t *meta = table->_; table->flags |= EcsTableHasTarget; meta->ft_offset = flecs_ito(int16_t, i); } else if (r == ecs_id(EcsPoly)) { table->flags |= EcsTableHasBuiltins; } } else { if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_table__t *meta = table->_; table->flags |= EcsTableHasToggle; if (!meta->bs_count) { meta->bs_offset = flecs_ito(int16_t, i); } meta->bs_count ++; } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { table->flags |= EcsTableHasOverrides; } } } } } /* Utility function that appends an element to the table record array */ static void flecs_table_append_to_records( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, ecs_id_t id, int32_t column) { /* To avoid a quadratic search, use the O(1) lookup that the index * already provides. */ ecs_id_record_t *idr = flecs_id_record_ensure(world, id); ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( idr, table); if (!tr) { tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); tr->index = flecs_ito(int16_t, column); tr->count = 1; ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } else { tr->count ++; } ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { /* Make sure table->flags is initialized */ flecs_table_init_flags(world, table); /* The following code walks the table type to discover which id records the * table needs to register table records with. * * In addition to registering itself with id records for each id in the * table type, a table also registers itself with wildcard id records. For * example, if a table contains (Eats, Apples), it will register itself with * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it * easier for wildcard queries to find the relevant tables. */ int32_t dst_i = 0, dst_count = table->type.count; int32_t src_i = 0, src_count = 0; ecs_id_t *dst_ids = table->type.array; ecs_id_t *src_ids = NULL; ecs_table_record_t *tr = NULL, *src_tr = NULL; if (from) { src_count = from->type.count; src_ids = from->type.array; src_tr = from->_->records; } /* We don't know in advance how large the records array will be, so use * cached vector. This eliminates unnecessary allocations, and/or expensive * iterations to determine how many records we need. */ ecs_allocator_t *a = &world->allocator; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(a, records, ecs_table_record_t); ecs_id_record_t *idr, *childof_idr = NULL; int32_t last_id = -1; /* Track last regular (non-pair) id */ int32_t first_pair = -1; /* Track the first pair in the table */ int32_t first_role = -1; /* Track first id with role */ /* Scan to find boundaries of regular ids, pairs and roles */ for (dst_i = 0; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { first_pair = dst_i; } if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { last_id = dst_i; } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { first_role = dst_i; } } /* The easy part: initialize a record for every id in the type */ for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t src_id = src_ids[src_i]; idr = NULL; if (dst_id == src_id) { ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; } else if (dst_id < src_id) { idr = flecs_id_record_ensure(world, dst_id); } if (idr) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } dst_i += dst_id <= src_id; src_i += dst_id >= src_id; } /* Add remaining ids that the "from" table didn't have */ for (; (dst_i < dst_count); dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; tr = ecs_vec_append_t(a, records, ecs_table_record_t); idr = flecs_id_record_ensure(world, dst_id); tr->hdr.cache = (ecs_table_cache_t*)idr; ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } /* We're going to insert records from the vector into the index that * will get patched up later. To ensure the record pointers don't get * invalidated we need to grow the vector so that it won't realloc as * we're adding the next set of records */ if (first_role != -1 || first_pair != -1) { int32_t start = first_role; if (first_pair != -1 && (start != -1 || first_pair < start)) { start = first_pair; } /* Total number of records can never be higher than * - number of regular (non-pair) ids + * - three records for pairs: (R,T), (R,*), (*,T) * - one wildcard (*), one any (_) and one pair wildcard (*,*) record * - one record for (ChildOf, 0) */ int32_t flag_id_count = dst_count - start; int32_t record_count = start + 3 * flag_id_count + 3 + 1; ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); } /* Add records for ids with roles (used by cleanup logic) */ if (first_role != -1) { for (dst_i = first_role; dst_i < dst_count; dst_i ++) { ecs_id_t id = dst_ids[dst_i]; if (!ECS_IS_PAIR(id)) { ecs_entity_t first = 0; ecs_entity_t second = 0; if (ECS_HAS_ID_FLAG(id, PAIR)) { first = ECS_PAIR_FIRST(id); second = ECS_PAIR_SECOND(id); } else { first = id & ECS_COMPONENT_MASK; } if (first) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, first), dst_i); } if (second) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, second), dst_i); } } } } int32_t last_pair = -1; bool has_childof = table->flags & EcsTableHasChildOf; if (first_pair != -1) { /* Add a (Relationship, *) record for each relationship. */ ecs_entity_t r = 0; for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (!ECS_IS_PAIR(dst_id)) { break; /* no more pairs */ } if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); ecs_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; r = ECS_PAIR_FIRST(dst_id); if (r == EcsChildOf) { childof_idr = p_idr; } idr = p_idr->parent; /* (R, *) */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 0; } ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); tr->count ++; } last_pair = dst_i; /* Add a (*, Target) record for each relationship target. Type * ids are sorted relationship-first, so we can't simply do a single linear * scan to find all occurrences for a target. */ for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); flecs_table_append_to_records( world, table, records, tgt_id, dst_i); } } /* Lastly, add records for all-wildcard ids */ if (last_id >= 0) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; tr->index = 0; tr->count = flecs_ito(int16_t, last_id + 1); } if (last_pair - first_pair) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; tr->index = flecs_ito(int16_t, first_pair); tr->count = flecs_ito(int16_t, last_pair - first_pair); } if (dst_count) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; tr->index = 0; tr->count = 1; } if (dst_count && !has_childof) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); childof_idr = world->idr_childof_0; tr->hdr.cache = (ecs_table_cache_t*)childof_idr; tr->index = 0; tr->count = 1; } /* Now that all records have been added, copy them to array */ int32_t i, dst_record_count = ecs_vec_count(records); ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); table->_->record_count = flecs_ito(int16_t, dst_record_count); table->_->records = dst_tr; int32_t column_count = 0; /* Register & patch up records */ for (i = 0; i < dst_record_count; i ++) { tr = &dst_tr[i]; idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_cache_get(&idr->cache, table)) { /* If this is a target wildcard record it has already been * registered, but the record is now at a different location in * memory. Patch up the linked list with the new address */ ecs_table_cache_replace(&idr->cache, table, &tr->hdr); } else { /* Other records are not registered yet */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } /* Claim id record so it stays alive as long as the table exists */ flecs_id_record_claim(world, idr); /* Initialize event flags */ table->flags |= idr->flags & EcsIdEventMask; /* Initialize column index (will be overwritten by init_columns) */ tr->column = -1; if (idr->flags & EcsIdAlwaysOverride) { table->flags |= EcsTableHasOverrides; } if ((i < table->type.count) && (idr->type_info != NULL)) { column_count ++; } } if (column_count) { table->column_map = flecs_walloc_n(world, int32_t, dst_count + column_count); } table->column_count = flecs_ito(int16_t, column_count); flecs_table_init_data(world, table); if (table->flags & EcsTableHasName) { ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); table->_->name_index = flecs_id_record_name_index_ensure(world, childof_idr); ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); } if (table->flags & EcsTableHasOnTableCreate) { flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &table->type, .event = EcsOnTableCreate, .table = table, .flags = EcsEventTableOnly, .observable = world }); } } /* Unregister table from id records */ static void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) { uint64_t table_id = table->id; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; ecs_id_t id = ((ecs_id_record_t*)cache)->id; ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, ECS_INTERNAL_ERROR, NULL); (void)id; ecs_table_cache_remove(cache, table_id, &tr->hdr); flecs_id_record_release(world, (ecs_id_record_t*)cache); } flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); } /* Keep track for what kind of builtin events observers are registered that can * potentially match the table. This allows code to early out of calling the * emit function that notifies observers. */ static void flecs_table_add_trigger_flags( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { (void)world; if (event == EcsOnAdd) { table->flags |= EcsTableHasOnAdd; } else if (event == EcsOnRemove) { table->flags |= EcsTableHasOnRemove; } else if (event == EcsOnSet) { table->flags |= EcsTableHasOnSet; } else if (event == EcsUnSet) { table->flags |= EcsTableHasUnSet; } else if (event == EcsOnTableFill) { table->flags |= EcsTableHasOnTableFill; } else if (event == EcsOnTableEmpty) { table->flags |= EcsTableHasOnTableEmpty; } } /* Invoke OnRemove observers for all entities in table. Useful during table * deletion or when clearing entities from a table. */ static void flecs_table_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { int32_t count = data->entities.count; if (count) { flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); } } /* Invoke type hook for entities in table */ static void flecs_table_invoke_hook( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, ecs_entity_t event, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count) { void *ptr = ecs_vec_get(&column->data, column->size, row); flecs_invoke_hook(world, table, count, row, entities, ptr, column->id, column->ti, event, callback); } /* Construct components */ static void flecs_table_invoke_ctor( ecs_column_t *column, int32_t row, int32_t count) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { void *ptr = ecs_vec_get(&column->data, column->size, row); ctor(ptr, count, ti); } } /* Destruct components */ static void flecs_table_invoke_dtor( ecs_column_t *column, int32_t row, int32_t count) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { void *ptr = ecs_vec_get(&column->data, column->size, row); dtor(ptr, count, ti); } } /* Run hooks that get invoked when component is added to entity */ static void flecs_table_invoke_add_hooks( ecs_world_t *world, ecs_table_t *table, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count, bool construct) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { flecs_table_invoke_ctor(column, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, entities, row, count); } } /* Run hooks that get invoked when component is removed from entity */ static void flecs_table_invoke_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count, bool dtor) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, entities, row, count); } if (dtor) { flecs_table_invoke_dtor(column, row, count); } } /* Destruct all components and/or delete all entities in table in range */ static void flecs_table_dtor_all( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t row, int32_t count, bool update_entity_index, bool is_delete) { /* Can't delete and not update the entity index */ ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); int32_t ids_count = table->column_count; ecs_entity_t *entities = data->entities.array; int32_t i, c, end = row + count; if (is_delete && table->_->traversable_count) { /* If table contains monitored entities with traversable relationships, * make sure to invalidate observer cache */ flecs_emit_propagate_invalidate(world, table, row, count); } /* If table has components with destructors, iterate component columns */ if (table->flags & EcsTableHasDtors) { /* Throw up a lock just to be sure */ table->_->lock = true; /* Run on_remove callbacks first before destructing components */ for (c = 0; c < ids_count; c++) { ecs_column_t *column = &data->columns[c]; ecs_iter_action_t on_remove = column->ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entities[row], row, count); } } /* Destruct components */ for (c = 0; c < ids_count; c++) { flecs_table_invoke_dtor(&data->columns[c], row, count); } /* Iterate entities first, then components. This ensures that only one * entity is invalidated at a time, which ensures that destructors can * safely access other entities. */ for (i = row; i < end; i ++) { /* Update entity index after invoking destructors so that entity can * be safely used in destructor callbacks. */ if (update_entity_index) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); if (is_delete) { flecs_entities_remove(world, e); ecs_assert(ecs_is_valid(world, e) == false, ECS_INTERNAL_ERROR, NULL); } else { // If this is not a delete, clear the entity index record ecs_record_t *record = flecs_entities_get(world, e); record->table = NULL; record->row = 0; } } else { /* This should only happen in rare cases, such as when the data * cleaned up is not part of the world (like with snapshots) */ } } table->_->lock = false; /* If table does not have destructors, just update entity index */ } else if (update_entity_index) { if (is_delete) { for (i = row; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); flecs_entities_remove(world, e); ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); } } else { for (i = row; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_get(world, e); record->table = NULL; record->row = record->row & ECS_ROW_FLAGS_MASK; (void)e; } } } } /* Cleanup table storage */ static void flecs_table_fini_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, bool do_on_remove, bool update_entity_index, bool is_delete, bool deactivate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); if (!data) { return; } if (do_on_remove) { flecs_table_notify_on_remove(world, table, data); } int32_t count = flecs_table_data_count(data); if (count) { flecs_table_dtor_all(world, table, data, 0, count, update_entity_index, is_delete); } ecs_column_t *columns = data->columns; if (columns) { int32_t c, column_count = table->column_count; for (c = 0; c < column_count; c ++) { /* Sanity check */ ecs_assert(columns[c].data.count == data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_vec_fini(&world->allocator, &columns[c].data, columns[c].size); } flecs_wfree_n(world, ecs_column_t, column_count, columns); data->columns = NULL; } ecs_table__t *meta = table->_; ecs_switch_t *sw_columns = meta->sw_columns; if (sw_columns) { int32_t c, column_count = meta->sw_count; for (c = 0; c < column_count; c ++) { flecs_switch_fini(&sw_columns[c]); } flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns); meta->sw_columns = NULL; } ecs_bitset_t *bs_columns = meta->bs_columns; if (bs_columns) { int32_t c, column_count = meta->bs_count; for (c = 0; c < column_count; c ++) { flecs_bitset_fini(&bs_columns[c]); } flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); meta->bs_columns = NULL; } ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); if (deactivate && count) { flecs_table_set_empty(world, table); } table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; } ecs_vec_t* flecs_table_entities( ecs_table_t *table) { return &table->data.entities; } ecs_entity_t* flecs_table_entities_array( ecs_table_t *table) { return ecs_vec_first(flecs_table_entities(table)); } /* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ void flecs_table_clear_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { flecs_table_fini_data(world, table, data, false, false, false, false); } /* Cleanup, no OnRemove, clear entity index, deactivate table */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, &table->data, false, true, false, true); } /* Cleanup, run OnRemove, clear entity index, deactivate table */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, &table->data, true, true, false, true); } /* Cleanup, run OnRemove, delete from entity index, deactivate table */ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, &table->data, true, true, true, true); } /* Unset all components in table. This function is called before a table is * deleted, and invokes all UnSet handlers, if any */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table) { (void)world; flecs_table_notify_on_remove(world, table, &table->data); } /* Free table resources. */ void flecs_table_free( ecs_world_t *world, ecs_table_t *table) { bool is_root = table == &world->store.root; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), ECS_INTERNAL_ERROR, NULL); (void)world; if (!is_root && !(world->flags & EcsWorldQuit)) { if (table->flags & EcsTableHasOnTableDelete) { flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &table->type, .event = EcsOnTableDelete, .table = table, .flags = EcsEventTableOnly, .observable = world }); } } if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &table->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", expr, table->id); ecs_os_free(expr); ecs_log_push_2(); } world->info.empty_table_count -= (ecs_table_count(table) == 0); /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ flecs_table_fini_data(world, table, &table->data, false, true, true, false); flecs_table_clear_edges(world, table); if (!is_root) { ecs_type_t ids = { .array = table->type.array, .count = table->type.count }; flecs_hashmap_remove_w_hash( &world->store.table_map, &ids, ecs_table_t*, table->_->hash); } flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); flecs_wfree_n(world, int32_t, table->column_count + table->type.count, table->column_map); flecs_table_records_unregister(world, table); /* Update counters */ world->info.table_count --; world->info.table_delete_total ++; flecs_free_t(&world->allocator, ecs_table__t, table->_); if (!(world->flags & EcsWorldFini)) { ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); flecs_table_free_type(world, table); flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); } ecs_log_pop_2(); } /* Free table type. Do this separately from freeing the table as types can be * in use by application destructors. */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table) { flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } /* Reset a table to its initial state. */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_clear_edges(world, table); } /* Keep track of number of traversable entities in table. A traversable entity * is an entity used as target in a pair with a traversable relationship. The * traversable count and flag are used by code to early out of mechanisms like * event propagation and recursive cleanup. */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value) { int32_t result = table->_->traversable_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { table->flags &= ~EcsTableHasTraversable; } else if (result == value) { table->flags |= EcsTableHasTraversable; } } /* Mark table column dirty. This usually happens as the result of a set * operation, or iteration of a query with [out] fields. */ static void flecs_table_mark_table_dirty( ecs_world_t *world, ecs_table_t *table, int32_t index) { (void)world; if (table->dirty_state) { table->dirty_state[index] ++; } } /* Mark table component dirty */ void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table->dirty_state) { ecs_id_record_t *idr = flecs_id_record_get(world, component); if (!idr) { return; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr || tr->column == -1) { return; } table->dirty_state[tr->column + 1] ++; } } /* Get (or create) dirty state of table. Used by queries for change tracking */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->dirty_state) { int32_t column_count = table->column_count; table->dirty_state = flecs_alloc_n(&world->allocator, int32_t, column_count + 1); ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < column_count + 1; i ++) { table->dirty_state[i] = 1; } } return table->dirty_state; } /* Table move logic for switch (union relationship) column */ static void flecs_table_move_switch_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { ecs_table__t *dst_meta = dst_table->_; ecs_table__t *src_meta = src_table->_; if (!dst_meta && !src_meta) { return; } int32_t i_old = 0, src_column_count = src_meta ? src_meta->sw_count : 0; int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->sw_count : 0; if (!src_column_count && !dst_column_count) { return; } ecs_switch_t *src_columns = src_meta ? src_meta->sw_columns : NULL; ecs_switch_t *dst_columns = dst_meta ? dst_meta->sw_columns : NULL; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_meta ? dst_meta->sw_offset : 0; int32_t offset_old = src_meta ? src_meta->sw_offset : 0; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_entity_t dst_id = dst_ids[i_new + offset_new]; ecs_entity_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { ecs_switch_t *src_switch = &src_columns[i_old]; ecs_switch_t *dst_switch = &dst_columns[i_new]; flecs_switch_ensure(dst_switch, dst_index + count); int i; for (i = 0; i < count; i ++) { uint64_t value = flecs_switch_get(src_switch, src_index + i); flecs_switch_set(dst_switch, dst_index + i, value); } if (clear) { ecs_assert(count == flecs_switch_count(src_switch), ECS_INTERNAL_ERROR, NULL); flecs_switch_clear(src_switch); } } else if (dst_id > src_id) { ecs_switch_t *src_switch = &src_columns[i_old]; flecs_switch_clear(src_switch); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_switch_t *src_switch = &src_columns[i_old]; ecs_assert(count == flecs_switch_count(src_switch), ECS_INTERNAL_ERROR, NULL); flecs_switch_clear(src_switch); } } } /* Table move logic for bitset (toggle component) column */ static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { ecs_table__t *dst_meta = dst_table->_; ecs_table__t *src_meta = src_table->_; if (!dst_meta && !src_meta) { return; } int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; if (!src_column_count && !dst_column_count) { return; } ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; int32_t offset_old = src_meta ? src_meta->bs_offset : 0; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new + offset_new]; ecs_id_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_bitset_t *dst_bs = &dst_columns[i_new]; flecs_bitset_ensure(dst_bs, dst_index + count); int i; for (i = 0; i < count; i ++) { uint64_t value = flecs_bitset_get(src_bs, src_index + i); flecs_bitset_set(dst_bs, dst_index + i, value); } if (clear) { ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } else if (dst_id > src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; flecs_bitset_fini(src_bs); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } } /* Grow table column. When a column needs to be reallocated this function takes * care of correctly invoking ctor/move/dtor hooks. */ static void flecs_table_grow_column( ecs_world_t *world, ecs_column_t *column, int32_t to_add, int32_t dst_size, bool construct) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; int32_t size = column->size; int32_t count = column->data.count; int32_t src_size = column->data.size; int32_t dst_count = count + to_add; bool can_realloc = dst_size != src_size; void *result = NULL; ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); /* If the array could possibly realloc and the component has a move action * defined, move old elements manually */ ecs_move_t move_ctor; if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { ecs_xtor_t ctor = ti->hooks.ctor; ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); /* Create vector */ ecs_vec_t dst; ecs_vec_init(&world->allocator, &dst, size, dst_size); dst.count = dst_count; void *src_buffer = column->data.array; void *dst_buffer = dst.array; /* Move (and construct) existing elements to new vector */ move_ctor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ result = ECS_ELEM(dst_buffer, size, count); ctor(result, to_add, ti); } /* Free old vector */ ecs_vec_fini(&world->allocator, &column->data, size); column->data = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { ecs_vec_set_size(&world->allocator, &column->data, size, dst_size); } result = ecs_vec_grow(&world->allocator, &column->data, size, to_add); ecs_xtor_t ctor; if (construct && (ctor = ti->hooks.ctor)) { /* If new elements need to be constructed and component has a * constructor, construct */ ctor(result, to_add, ti); } } ecs_assert(column->data.size == dst_size, ECS_INTERNAL_ERROR, NULL); } /* Grow all data structures in a table */ static int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t to_add, int32_t size, const ecs_entity_t *ids) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur_count = flecs_table_data_count(data); int32_t column_count = table->column_count; /* Add entity to column with entity ids */ ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size); ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1; data->entities.count += to_add; if (data->entities.size > size) { size = data->entities.size; } /* Initialize entity ids and record ptrs */ int32_t i; if (ids) { ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); } else { ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); } /* Add elements to each column array */ ecs_column_t *columns = data->columns; for (i = 0; i < column_count; i ++) { flecs_table_grow_column(world, &columns[i], to_add, size, true); ecs_assert(columns[i].data.size == size, ECS_INTERNAL_ERROR, NULL); flecs_table_invoke_add_hooks(world, table, &columns[i], e, cur_count, to_add, false); } ecs_table__t *meta = table->_; int32_t sw_count = meta->sw_count; int32_t bs_count = meta->bs_count; ecs_switch_t *sw_columns = meta->sw_columns; ecs_bitset_t *bs_columns = meta->bs_columns; /* Add elements to each switch column */ for (i = 0; i < sw_count; i ++) { ecs_switch_t *sw = &sw_columns[i]; flecs_switch_addn(sw, to_add); } /* Add elements to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, to_add); } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); if (!(world->flags & EcsWorldReadonly) && !cur_count) { flecs_table_set_empty(world, table); } /* Return index of first added entity */ return cur_count; } /* Append operation for tables that don't have any complex logic */ static void flecs_table_fast_append( ecs_world_t *world, ecs_column_t *columns, int32_t count) { /* Add elements to each column array */ int32_t i; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; ecs_vec_append(&world->allocator, &column->data, column->size); } } /* Append entity to table */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, bool construct, bool on_add) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(!(table->flags & EcsTableHasTarget), ECS_INVALID_OPERATION, NULL); flecs_table_check_sanity(table); /* Get count & size before growing entities array. This tells us whether the * arrays will realloc */ ecs_data_t *data = &table->data; int32_t count = data->entities.count; int32_t column_count = table->column_count; ecs_column_t *columns = table->data.columns; /* Grow buffer with entity ids, set new element to new entity */ ecs_entity_t *e = ecs_vec_append_t(&world->allocator, &data->entities, ecs_entity_t); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); *e = entity; /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); /* Fast path: no switch columns, no lifecycle actions */ if (!(table->flags & EcsTableIsComplex)) { flecs_table_fast_append(world, columns, column_count); if (!count) { flecs_table_set_empty(world, table); /* See below */ } return count; } ecs_entity_t *entities = data->entities.array; /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ int32_t size = data->entities.size; /* Grow component arrays with 1 element */ int32_t i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; flecs_table_grow_column(world, column, 1, size, construct); ecs_iter_action_t on_add_hook; if (on_add && (on_add_hook = column->ti->hooks.on_add)) { flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, &entities[count], count, 1); } ecs_assert(columns[i].data.size == data->entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns[i].data.count == data->entities.count, ECS_INTERNAL_ERROR, NULL); } ecs_table__t *meta = table->_; int32_t sw_count = meta->sw_count; int32_t bs_count = meta->bs_count; ecs_switch_t *sw_columns = meta->sw_columns; ecs_bitset_t *bs_columns = meta->bs_columns; /* Add element to each switch column */ for (i = 0; i < sw_count; i ++) { ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &sw_columns[i]; flecs_switch_add(sw); } /* Add element to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, 1); } /* If this is the first entity in this table, signal queries so that the * table moves from an inactive table to an active table. */ if (!count) { flecs_table_set_empty(world, table); } flecs_table_check_sanity(table); return count; } /* Delete last operation for tables that don't have any complex logic */ static void flecs_table_fast_delete_last( ecs_column_t *columns, int32_t column_count) { int i; for (i = 0; i < column_count; i ++) { ecs_vec_remove_last(&columns[i].data); } } /* Delete operation for tables that don't have any complex logic */ static void flecs_table_fast_delete( ecs_column_t *columns, int32_t column_count, int32_t index) { int i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_vec_remove(&column->data, column->size, index); } } /* Delete entity from table */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(!(table->flags & EcsTableHasTarget), ECS_INVALID_OPERATION, NULL); flecs_table_check_sanity(table); ecs_data_t *data = &table->data; int32_t count = data->entities.count; ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); count --; ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); /* Move last entity id to index */ ecs_entity_t *entities = data->entities.array; ecs_entity_t entity_to_move = entities[count]; ecs_entity_t entity_to_delete = entities[index]; entities[index] = entity_to_move; ecs_vec_remove_last(&data->entities); /* Update record of moved entity in entity index */ if (index != count) { ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); if (record_to_move) { uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); /* If table is empty, deactivate it */ if (!count) { flecs_table_set_empty(world, table); } /* Destruct component data */ ecs_column_t *columns = data->columns; int32_t column_count = table->column_count; int32_t i; /* If this is a table without lifecycle callbacks or special columns, take * fast path that just remove an element from the array(s) */ if (!(table->flags & EcsTableIsComplex)) { if (index == count) { flecs_table_fast_delete_last(columns, column_count); } else { flecs_table_fast_delete(columns, column_count, index); } flecs_table_check_sanity(table); return; } /* Last element, destruct & remove */ if (index == count) { /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { flecs_table_invoke_remove_hooks(world, table, &columns[i], &entity_to_delete, index, 1, true); } } flecs_table_fast_delete_last(columns, column_count); /* Not last element, move last element to deleted element & destruct */ } else { /* If table has component destructors, invoke */ if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_type_info_t *ti = column->ti; ecs_size_t size = column->size; void *dst = ecs_vec_get(&column->data, size, index); void *src = ecs_vec_last(&column->data, size); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (destruct && on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, index, 1); } ecs_move_t move_dtor = ti->hooks.move_dtor; // If move_ctor is not set but ctor_move_dtor is set, assign move_dtor to ctor_move_dtor. // This adjustment is crucial for compatibility across different language bindings where // the standard memory model of C may not apply, potentially altering how operations // within the table are handled. if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { move_dtor = ti->hooks.ctor_move_dtor; } if (move_dtor) { move_dtor(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } ecs_vec_remove_last(&column->data); } } else { flecs_table_fast_delete(columns, column_count, index); } } /* Remove elements from switch columns */ ecs_table__t *meta = table->_; ecs_switch_t *sw_columns = meta->sw_columns; int32_t sw_count = meta->sw_count; for (i = 0; i < sw_count; i ++) { flecs_switch_remove(&sw_columns[i], index); } /* Remove elements from bitset columns */ ecs_bitset_t *bs_columns = meta->bs_columns; int32_t bs_count = meta->bs_count; for (i = 0; i < bs_count; i ++) { flecs_bitset_remove(&bs_columns[i], index); } flecs_table_check_sanity(table); } /* Move operation for tables that don't have any complex logic */ static void flecs_table_fast_move( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = dst_column->id; ecs_id_t src_id = src_column->id; if (dst_id == src_id) { int32_t size = dst_column->size; void *dst = ecs_vec_get(&dst_column->data, size, dst_index); void *src = ecs_vec_get(&src_column->data, size, src_index); ecs_os_memcpy(dst, src, size); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } } /* Move entity from src to dst table */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, bool construct) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { flecs_table_fast_move(dst_table, dst_index, src_table, src_index); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); return; } flecs_table_move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); flecs_table_move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); /* If the source and destination entities are the same, move component * between tables. If the entities are not the same (like when cloning) use * a copy. */ bool same_entity = dst_entity == src_entity; /* Call move_dtor for moved away from storage only if the entity is at the * last index in the source table. If it isn't the last entity, the last * entity in the table will be moved to the src storage, which will take * care of cleaning up resources. */ bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = dst_column->id; ecs_id_t src_id = src_column->id; if (dst_id == src_id) { int32_t size = dst_column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *dst = ecs_vec_get(&dst_column->data, size, dst_index); void *src = ecs_vec_get(&src_column->data, size, src_index); ecs_type_info_t *ti = dst_column->ti; if (same_entity) { ecs_move_t move = ti->hooks.move_ctor; if (use_move_dtor || !move) { /* Also use move_dtor if component doesn't have a move_ctor * registered, to ensure that the dtor gets called to * cleanup resources. */ move = ti->hooks.ctor_move_dtor; } if (move) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } } else { if (dst_id < src_id) { flecs_table_invoke_add_hooks(world, dst_table, dst_column, &dst_entity, dst_index, 1, construct); } else { flecs_table_invoke_remove_hooks(world, src_table, src_column, &src_entity, src_index, 1, use_move_dtor); } } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } for (; (i_new < dst_column_count); i_new ++) { flecs_table_invoke_add_hooks(world, dst_table, &dst_columns[i_new], &dst_entity, dst_index, 1, construct); } for (; (i_old < src_column_count); i_old ++) { flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], &src_entity, src_index, 1, use_move_dtor); } flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); } /* Append n entities to table */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t to_add, const ecs_entity_t *ids) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); int32_t result = flecs_table_grow_data( world, table, data, to_add, cur_count + to_add, ids); flecs_table_check_sanity(table); return result; } /* Set allocated table size */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t size) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); if (cur_count < size) { flecs_table_grow_data(world, table, data, 0, size, NULL); flecs_table_check_sanity(table); } } /* Shrink table storage to fit number of entities */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); (void)world; flecs_table_check_sanity(table); ecs_data_t *data = &table->data; bool has_payload = data->entities.array != NULL; ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t); int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &data->columns[i]; ecs_vec_reclaim(&world->allocator, &column->data, column->size); } return has_payload; } /* Return number of entities in table */ int32_t flecs_table_data_count( const ecs_data_t *data) { return data ? data->entities.count : 0; } /* Swap operation for switch (union relationship) columns */ static void flecs_table_swap_switch_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->_->sw_count; if (!column_count) { return; } ecs_switch_t *columns = table->_->sw_columns; for (i = 0; i < column_count; i ++) { ecs_switch_t *sw = &columns[i]; flecs_switch_swap(sw, row_1, row_2); } } /* Swap operation for bitset (toggle component) columns */ static void flecs_table_swap_bitset_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->_->bs_count; if (!column_count) { return; } ecs_bitset_t *columns = table->_->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } /* Swap two rows in a table. Used for table sorting. */ void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2) { (void)world; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(table); if (row_1 == row_2) { return; } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_entity_t *entities = table->data.entities.array; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of whether entity is watched */ uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); /* Swap entities & records */ entities[row_1] = e2; entities[row_2] = e1; record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); flecs_table_swap_switch_columns(table, row_1, row_2); flecs_table_swap_bitset_columns(table, row_1, row_2); ecs_column_t *columns = table->data.columns; if (!columns) { flecs_table_check_sanity(table); return; } /* Find the maximum size of column elements * and allocate a temporary buffer for swapping */ int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; for (i = 0; i < column_count; i++) { temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].size); } void* tmp = ecs_os_alloca(temp_buffer_size); /* Swap columns */ for (i = 0; i < column_count; i ++) { int32_t size = columns[i].size; void *ptr = columns[i].data.array; void *el_1 = ECS_ELEM(ptr, size, row_1); void *el_2 = ECS_ELEM(ptr, size, row_2); ecs_os_memcpy(tmp, el_1, size); ecs_os_memcpy(el_1, el_2, size); ecs_os_memcpy(el_2, tmp, size); } flecs_table_check_sanity(table); } static void flecs_table_merge_vec( ecs_world_t *world, ecs_vec_t *dst, ecs_vec_t *src, int32_t size, int32_t elem_size) { int32_t dst_count = dst->count; if (!dst_count) { ecs_vec_fini(&world->allocator, dst, size); *dst = *src; src->array = NULL; src->count = 0; src->size = 0; } else { int32_t src_count = src->count; if (elem_size) { ecs_vec_set_size(&world->allocator, dst, size, elem_size); } ecs_vec_set_count(&world->allocator, dst, size, dst_count + src_count); void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); void *src_ptr = src->array; ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); ecs_vec_fini(&world->allocator, src, size); } } /* Merge data from one table column into other table column */ static void flecs_table_merge_column( ecs_world_t *world, ecs_column_t *dst, ecs_column_t *src, int32_t column_size) { ecs_size_t size = dst->size; int32_t dst_count = dst->data.count; if (!dst_count) { ecs_vec_fini(&world->allocator, &dst->data, size); *dst = *src; src->data.array = NULL; src->data.count = 0; src->data.size = 0; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { int32_t src_count = src->data.count; flecs_table_grow_column(world, dst, src_count, column_size, false); void *dst_ptr = ECS_ELEM(dst->data.array, size, dst_count); void *src_ptr = src->data.array; /* Move values into column */ ecs_type_info_t *ti = dst->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move = ti->hooks.ctor_move_dtor; if (move) { move(dst_ptr, src_ptr, src_count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); } ecs_vec_fini(&world->allocator, &src->data, size); } } /* Merge storage of two tables. */ static void flecs_table_merge_data( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, int32_t src_count, int32_t dst_count, ecs_data_t *src_data, ecs_data_t *dst_data) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_data->columns; ecs_column_t *dst_columns = dst_data->columns; ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ flecs_table_merge_vec(world, &dst_data->entities, &src_data->entities, ECS_SIZEOF(ecs_entity_t), 0); ecs_assert(dst_data->entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); int32_t column_size = dst_data->entities.size; ecs_allocator_t *a = &world->allocator; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = dst_column->id; ecs_id_t src_id = src_column->id; if (dst_id == src_id) { flecs_table_merge_column(world, dst_column, src_column, column_size); flecs_table_mark_table_dirty(world, dst_table, i_new + 1); i_new ++; i_old ++; } else if (dst_id < src_id) { /* New column, make sure vector is large enough. */ ecs_size_t size = dst_column->size; ecs_vec_set_size(a, &dst_column->data, size, column_size); ecs_vec_set_count(a, &dst_column->data, size, src_count + dst_count); flecs_table_invoke_ctor(dst_column, dst_count, src_count); i_new ++; } else if (dst_id > src_id) { /* Old column does not occur in new table, destruct */ flecs_table_invoke_dtor(src_column, 0, src_count); ecs_vec_fini(a, &src_column->data, src_column->size); i_old ++; } } flecs_table_move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); flecs_table_move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); /* Initialize remaining columns */ for (; i_new < dst_column_count; i_new ++) { ecs_column_t *column = &dst_columns[i_new]; int32_t size = column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_set_size(a, &column->data, size, column_size); ecs_vec_set_count(a, &column->data, size, src_count + dst_count); flecs_table_invoke_ctor(column, dst_count, src_count); } /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { ecs_column_t *column = &src_columns[i_old]; flecs_table_invoke_dtor(column, 0, src_count); ecs_vec_fini(a, &column->data, column->size); } /* Mark entity column as dirty */ flecs_table_mark_table_dirty(world, dst_table, 0); } /* Merge source table into destination table. This typically happens as result * of a bulk operation, like when a component is removed from all entities in * the source table (like for the Remove OnDelete policy). */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, ecs_data_t *dst_data, ecs_data_t *src_data) { ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); bool move_data = false; /* If there is nothing to merge to, just clear the old table */ if (!dst_table) { flecs_table_clear_data(world, src_table, src_data); flecs_table_check_sanity(src_table); return; } else { ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, NULL); } /* If there is no data to merge, drop out */ if (!src_data) { return; } if (!dst_data) { dst_data = &dst_table->data; if (dst_table == src_table) { move_data = true; } } ecs_entity_t *src_entities = src_data->entities.array; int32_t src_count = src_data->entities.count; int32_t dst_count = dst_data->entities.count; /* First, update entity index so old entities point to new type */ int32_t i; for(i = 0; i < src_count; i ++) { ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); record->table = dst_table; } /* Merge table columns */ if (move_data) { *dst_data = *src_data; } else { flecs_table_merge_data(world, dst_table, src_table, src_count, dst_count, src_data, dst_data); } if (src_count) { if (!dst_count) { flecs_table_set_empty(world, dst_table); } flecs_table_set_empty(world, src_table); flecs_table_traversable_add(dst_table, src_table->_->traversable_count); flecs_table_traversable_add(src_table, -src_table->_->traversable_count); ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } /* Replace data with other data. Used by snapshots to restore previous state. */ void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { int32_t prev_count = 0; ecs_data_t *table_data = &table->data; ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); prev_count = table_data->entities.count; flecs_table_notify_on_remove(world, table, table_data); flecs_table_clear_data(world, table, table_data); if (data) { table->data = *data; } else { flecs_table_init_data(world, table); } int32_t count = ecs_table_count(table); if (!prev_count && count) { flecs_table_set_empty(world, table); } else if (prev_count && !count) { flecs_table_set_empty(world, table); } flecs_table_check_sanity(table); } /* Internal mechanism for propagating information to tables */ void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_table_event_t *event) { if (world->flags & EcsWorldFini) { return; } switch(event->kind) { case EcsTableTriggersForId: flecs_table_add_trigger_flags(world, table, event->event); break; case EcsTableNoTriggersForId: break; } } /* -- Public API -- */ void ecs_table_lock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock ++; } } } void ecs_table_unlock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock --; ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, NULL); } } } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } int32_t ecs_table_get_type_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return -1; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return -1; } return tr->index; error: return -1; } int32_t ecs_table_get_column_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return -1; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return -1; } return tr->column; error: return -1; } int32_t ecs_table_column_count( const ecs_table_t *table) { return table->column_count; } int32_t ecs_table_type_to_column_index( const ecs_table_t *table, int32_t index) { ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int32_t *column_map = table->column_map; if (column_map) { return column_map[index]; } error: return -1; } int32_t ecs_table_column_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->column_map[offset + index]; error: return -1; } void* ecs_table_get_column( const ecs_table_t *table, int32_t index, int32_t offset) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_column_t *column = &table->data.columns[index]; void *result = column->data.array; if (offset) { result = ECS_ELEM(result, column->size, offset); } return result; error: return NULL; } void* ecs_table_get_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, int32_t offset) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t index = ecs_table_get_column_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } size_t ecs_table_get_column_size( const ecs_table_t *table, int32_t column) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_ito(size_t, table->data.columns[column].size); error: return 0; } int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_table_data_count(&table->data); } bool ecs_table_has_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { return ecs_table_get_type_index(world, table, id) != -1; } int32_t ecs_table_get_depth( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t rel) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_relation_depth(world, rel, table); error: return -1; } bool ecs_table_has_flags( ecs_table_t *table, ecs_flags32_t flags) { return (table->flags & flags) == flags; } int32_t flecs_table_column_to_union_index( const ecs_table_t *table, int32_t column) { int32_t sw_count = table->_->sw_count; if (sw_count) { int32_t sw_offset = table->_->sw_offset; if (column >= sw_offset && column < (sw_offset + sw_count)){ return column - sw_offset; } } return -1; } void ecs_table_swap_rows( ecs_world_t* world, ecs_table_t* table, int32_t row_1, int32_t row_2) { flecs_table_swap(world, table, row_1, row_2); } int32_t flecs_table_observed_count( const ecs_table_t *table) { return table->_->traversable_count; } void* ecs_record_get_column( const ecs_record_t *r, int32_t index, size_t c_size) { (void)c_size; ecs_table_t *table = r->table; ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_column_t *column = &table->data.columns[index]; ecs_size_t size = column->size; ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, ECS_INVALID_PARAMETER, NULL); return ecs_vec_get(&column->data, size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } ecs_record_t* ecs_record_find( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); if (r) { return r; } error: return NULL; } /** * @file table_cache.c * @brief Data structure for fast table iteration/lookups. * * A table cache is a data structure that provides constant time operations for * insertion and removal of tables, and to testing whether a table is registered * with the cache. A table cache also provides functions to iterate the tables * in a cache. * * The world stores a table cache per (component) id inside the id record * administration. Cached queries store a table cache with matched tables. * * A table cache has separate lists for non-empty tables and empty tables. This * improves performance as applications don't waste time iterating empty tables. */ static void flecs_table_cache_list_remove( ecs_table_cache_t *cache, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t *next = elem->next; ecs_table_cache_hdr_t *prev = elem->prev; if (next) { next->prev = prev; } if (prev) { prev->next = next; } cache->empty_tables.count -= !!elem->empty; cache->tables.count -= !elem->empty; if (cache->empty_tables.first == elem) { cache->empty_tables.first = next; } else if (cache->tables.first == elem) { cache->tables.first = next; } if (cache->empty_tables.last == elem) { cache->empty_tables.last = prev; } if (cache->tables.last == elem) { cache->tables.last = prev; } } static void flecs_table_cache_list_insert( ecs_table_cache_t *cache, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t *last; if (elem->empty) { last = cache->empty_tables.last; cache->empty_tables.last = elem; if ((++ cache->empty_tables.count) == 1) { cache->empty_tables.first = elem; } } else { last = cache->tables.last; cache->tables.last = elem; if ((++ cache->tables.count) == 1) { cache->tables.first = elem; } } elem->next = NULL; elem->prev = last; if (last) { last->next = elem; } } void ecs_table_cache_init( ecs_world_t *world, ecs_table_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init_w_params(&cache->index, &world->allocators.ptr); } void ecs_table_cache_fini( ecs_table_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_fini(&cache->index); } bool ecs_table_cache_is_empty( const ecs_table_cache_t *cache) { return ecs_map_count(&cache->index) == 0; } void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_table_cache_get(cache, table) == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); bool empty; if (!table) { empty = false; } else { empty = ecs_table_count(table) == 0; } result->cache = cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); result->empty = empty; flecs_table_cache_list_insert(cache, result); if (table) { ecs_map_insert_ptr(&cache->index, table->id, result); } ecs_assert(empty || cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!empty || cache->empty_tables.first != NULL, ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t **r = ecs_map_get_ref( &cache->index, ecs_table_cache_hdr_t, table->id); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *old = *r; ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; if (prev) { ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); prev->next = elem; } if (next) { ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); next->prev = elem; } if (cache->empty_tables.first == old) { cache->empty_tables.first = elem; } if (cache->empty_tables.last == old) { cache->empty_tables.last = elem; } if (cache->tables.first == old) { cache->tables.first = elem; } if (cache->tables.last == old) { cache->tables.last = elem; } *r = elem; elem->prev = prev; elem->next = next; } void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); if (table) { if (ecs_map_is_init(&cache->index)) { return ecs_map_get_deref(&cache->index, void**, table->id); } return NULL; } else { ecs_table_cache_hdr_t *elem = cache->tables.first; ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); return elem; } } void* ecs_table_cache_remove( ecs_table_cache_t *cache, uint64_t table_id, ecs_table_cache_hdr_t *elem) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table_id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); flecs_table_cache_list_remove(cache, elem); ecs_map_remove(&cache->index, table_id); return elem; } bool ecs_table_cache_set_empty( ecs_table_cache_t *cache, const ecs_table_t *table, bool empty) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, ecs_table_cache_hdr_t, table->id); if (!elem) { return false; } if (elem->empty == empty) { return false; } flecs_table_cache_list_remove(cache, elem); elem->empty = empty; flecs_table_cache_list_insert(cache, elem); return true; } bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->tables.first; out->next_list = NULL; out->cur = NULL; return out->next != NULL; } bool flecs_table_cache_empty_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->empty_tables.first; out->next_list = NULL; out->cur = NULL; return out->next != NULL; } bool flecs_table_cache_all_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->empty_tables.first; out->next_list = cache->tables.first; out->cur = NULL; return out->next != NULL || out->next_list != NULL; } ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it) { ecs_table_cache_hdr_t *next = it->next; if (!next) { next = it->next_list; it->next_list = NULL; if (!next) { return NULL; } } it->cur = next; it->next = next->next; return next; } /** * @file table_graph.c * @brief Data structure to speed up table transitions. * * The table graph is used to speed up finding tables in add/remove operations. * For example, if component C is added to an entity in table [A, B], the entity * must be moved to table [A, B, C]. The graph speeds this process up with an * edge for component C that connects [A, B] to [A, B, C]. */ /* Id sequence (type) utilities */ static uint64_t flecs_type_hash(const void *ptr) { const ecs_type_t *type = ptr; ecs_id_t *ids = type->array; int32_t count = type->count; return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); } static int flecs_type_compare(const void *ptr_1, const void *ptr_2) { const ecs_type_t *type_1 = ptr_1; const ecs_type_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } const ecs_id_t *ids_1 = type_1->array; const ecs_id_t *ids_2 = type_2->array; int result = 0; int32_t i; for (i = 0; !result && (i < count_1); i ++) { ecs_id_t id_1 = ids_1[i]; ecs_id_t id_2 = ids_2[i]; result = (id_1 > id_2) - (id_1 < id_2); } return result; } void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm) { flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, flecs_type_hash, flecs_type_compare, &world->allocator); } /* Find location where to insert id into type */ static int flecs_type_find_insert( const ecs_type_t *type, int32_t offset, ecs_id_t to_add) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = offset; i < count; i ++) { ecs_id_t id = array[i]; if (id == to_add) { return -1; } if (id > to_add) { return i; } } return i; } /* Find location of id in type */ static int flecs_type_find( const ecs_type_t *type, ecs_id_t id) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t cur = array[i]; if (ecs_id_match(cur, id)) { return i; } if (cur > id) { return -1; } } return -1; } /* Count number of matching ids */ static int flecs_type_count_matches( const ecs_type_t *type, ecs_id_t wildcard, int32_t offset) { ecs_id_t *array = type->array; int32_t i = offset, count = type->count; for (; i < count; i ++) { ecs_id_t cur = array[i]; if (!ecs_id_match(cur, wildcard)) { break; } } return i - offset; } /* Create type from source type with id */ static int flecs_type_new_with( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t with) { ecs_id_t *src_array = src->array; int32_t at = flecs_type_find_insert(src, 0, with); if (at == -1) { return -1; } int32_t dst_count = src->count + 1; ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->count = dst_count; dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = src->count - at; if (remain) { ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } dst_array[at] = with; return 0; } /* Create type from source type without ids matching wildcard */ static int flecs_type_new_filtered( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t wildcard, int32_t at) { *dst = flecs_type_copy(world, src); ecs_id_t *dst_array = dst->array; ecs_id_t *src_array = src->array; if (at) { ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t i = at + 1, w = at, count = src->count; for (; i < count; i ++) { ecs_id_t id = src_array[i]; if (!ecs_id_match(id, wildcard)) { dst_array[w] = id; w ++; } } dst->count = w; if (w != count) { dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); } return 0; } /* Create type from source type without id */ static int flecs_type_new_without( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t without) { ecs_id_t *src_array = src->array; int32_t count = 1, at = flecs_type_find(src, without); if (at == -1) { return -1; } int32_t src_count = src->count; if (src_count == 1) { dst->array = NULL; dst->count = 0; return 0; } if (ecs_id_is_wildcard(without)) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = ECS_PAIR_FIRST(without); ecs_entity_t o = ECS_PAIR_SECOND(without); if (r == EcsWildcard && o != EcsWildcard) { return flecs_type_new_filtered(world, dst, src, without, at); } } count += flecs_type_count_matches(src, without, at + 1); } int32_t dst_count = src_count - count; dst->count = dst_count; if (!dst_count) { dst->array = NULL; return 0; } ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = dst_count - at; if (remain) { ecs_os_memcpy_n( &dst_array[at], &src_array[at + count], ecs_id_t, remain); } return 0; } /* Copy type */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src) { int32_t src_count = src->count; if (!src_count) { return (ecs_type_t){ 0 }; } ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); return (ecs_type_t) { .array = ids, .count = src_count }; } /* Free type */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type) { int32_t count = type->count; if (count) { flecs_wfree_n(world, ecs_id_t, type->count, type->array); } } /* Add to type */ static void flecs_type_add( ecs_world_t *world, ecs_type_t *type, ecs_id_t add) { ecs_type_t new_type; int res = flecs_type_new_with(world, &new_type, type, add); if (res != -1) { flecs_type_free(world, type); type->array = new_type.array; type->count = new_type.count; } } /* Graph edge utilities */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &builder->added, ecs_id_t, 256); ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); } void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &builder->added, ecs_id_t); ecs_vec_fini_t(a, &builder->removed, ecs_id_t); } void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder) { ecs_vec_clear(&builder->added); ecs_vec_clear(&builder->removed); } static void flecs_table_diff_build_type( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *type, int32_t offset) { int32_t count = vec->count - offset; ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); if (count) { type->array = flecs_wdup_n(world, ecs_id_t, count, ECS_ELEM_T(vec->array, ecs_id_t, offset)); type->count = count; ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); } } void flecs_table_diff_build( ecs_world_t *world, ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, int32_t removed_offset) { flecs_table_diff_build_type(world, &builder->added, &diff->added, added_offset); flecs_table_diff_build_type(world, &builder->removed, &diff->removed, removed_offset); } void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff) { diff->added = (ecs_type_t){ .array = builder->added.array, .count = builder->added.count }; diff->removed = (ecs_type_t){ .array = builder->removed.array, .count = builder->removed.count }; } static void flecs_table_diff_build_add_type_to_vec( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *add) { if (!add || !add->count) { return; } int32_t offset = vec->count; ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), add->array, ecs_id_t, add->count); } void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src) { flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); } static void flecs_table_diff_free( ecs_world_t *world, ecs_table_diff_t *diff) { flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); flecs_bfree(&world->allocators.table_diff, diff); } static ecs_graph_edge_t* flecs_table_ensure_hi_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { if (!edges->hi) { edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); ecs_map_init_w_params(edges->hi, &world->allocators.ptr); } ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); ecs_graph_edge_t *edge = r[0]; if (edge) { return edge; } if (id < FLECS_HI_COMPONENT_ID) { edge = &edges->lo[id]; } else { edge = flecs_bcalloc(&world->allocators.graph_edge); } r[0] = edge; return edge; } static ecs_graph_edge_t* flecs_table_ensure_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { ecs_graph_edge_t *edge; if (id < FLECS_HI_COMPONENT_ID) { if (!edges->lo) { edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); } edge = &edges->lo[id]; } else { edge = flecs_table_ensure_hi_edge(world, edges, id); } return edge; } static void flecs_table_disconnect_edge( ecs_world_t *world, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); (void)id; /* Remove backref from destination table */ ecs_graph_edge_hdr_t *next = edge->hdr.next; ecs_graph_edge_hdr_t *prev = edge->hdr.prev; if (next) { next->prev = prev; } if (prev) { prev->next = next; } /* Remove data associated with edge */ ecs_table_diff_t *diff = edge->diff; if (diff) { flecs_table_diff_free(world, diff); } /* If edge id is low, clear it from fast lookup array */ if (id < FLECS_HI_COMPONENT_ID) { ecs_os_memset_t(edge, 0, ecs_graph_edge_t); } else { flecs_bfree(&world->allocators.graph_edge, edge); } } static void flecs_table_remove_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_disconnect_edge(world, id, edge); ecs_map_remove(edges->hi, id); } static void flecs_table_init_edges( ecs_graph_edges_t *edges) { edges->lo = NULL; edges->hi = NULL; } static void flecs_table_init_node( ecs_graph_node_t *node) { flecs_table_init_edges(&node->add); flecs_table_init_edges(&node->remove); } bool flecs_table_records_update_empty( ecs_table_t *table) { bool result = false; bool is_empty = ecs_table_count(table) == 0; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; result |= ecs_table_cache_set_empty(cache, table, is_empty); } return result; } static void flecs_init_table( ecs_world_t *world, ecs_table_t *table, ecs_table_t *prev) { table->flags = 0; table->dirty_state = NULL; table->_->lock = 0; table->_->generation = 0; flecs_table_init_node(&table->node); flecs_table_init(world, table, prev); } static ecs_table_t *flecs_create_table( ecs_world_t *world, ecs_type_t *type, flecs_hashmap_result_t table_elem, ecs_table_t *prev) { ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE int32_t i, j, count = type->count; for (i = 0; i < count - 1; i ++) { if (type->array[i] >= type->array[i + 1]) { for (j = 0; j < count; j ++) { char *str = ecs_id_str(world, type->array[j]); if (i == j) { ecs_err(" > %d: %s", j, str); } else { ecs_err(" %d: %s", j, str); } ecs_os_free(str); } ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); } } #endif result->id = flecs_sparse_last_id(&world->store.tables); result->type = *type; if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &result->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", expr, result->id); ecs_os_free(expr); } ecs_log_push_2(); /* Store table in table hashmap */ *(ecs_table_t**)table_elem.value = result; /* Set keyvalue to one that has the same lifecycle as the table */ *(ecs_type_t*)table_elem.key = result->type; result->_->hash = table_elem.hash; flecs_init_table(world, result, prev); /* Update counters */ world->info.table_count ++; world->info.empty_table_count ++; world->info.table_create_total ++; ecs_log_pop_2(); return result; } static ecs_table_t* flecs_table_ensure( ecs_world_t *world, ecs_type_t *type, bool own_type, ecs_table_t *prev) { ecs_poly_assert(world, ecs_world_t); int32_t id_count = type->count; if (!id_count) { return &world->store.root; } ecs_table_t *table; flecs_hashmap_result_t elem = flecs_hashmap_ensure( &world->store.table_map, type, ecs_table_t*); if ((table = *(ecs_table_t**)elem.value)) { if (own_type) { flecs_type_free(world, type); } return table; } /* If we get here, table needs to be created which is only allowed when the * application is not currently in progress */ ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); /* If we get here, the table has not been found, so create it. */ if (own_type) { return flecs_create_table(world, type, elem, prev); } ecs_type_t copy = flecs_type_copy(world, type); return flecs_create_table(world, ©, elem, prev); } static void flecs_diff_insert_added( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; } static void flecs_diff_insert_removed( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_allocator_t *a = &world->allocator; ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; } static void flecs_compute_table_diff( ecs_world_t *world, ecs_table_t *node, ecs_table_t *next, ecs_graph_edge_t *edge, ecs_id_t id) { if (ECS_IS_PAIR(id)) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair( ECS_PAIR_FIRST(id), EcsWildcard)); if (idr->flags & EcsIdUnion) { if (node != next) { id = ecs_pair(EcsUnion, ECS_PAIR_FIRST(id)); } else { ecs_table_diff_t *diff = flecs_bcalloc( &world->allocators.table_diff); diff->added.count = 1; diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); edge->diff = diff; return; } } } ecs_type_t node_type = node->type; ecs_type_t next_type = next->type; ecs_id_t *ids_node = node_type.array; ecs_id_t *ids_next = next_type.array; int32_t i_node = 0, node_count = node_type.count; int32_t i_next = 0, next_count = next_type.count; int32_t added_count = 0; int32_t removed_count = 0; bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); /* First do a scan to see how big the diff is, so we don't have to realloc * or alloc more memory than required. */ for (; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; bool added = id_next < id_node; bool removed = id_node < id_next; trivial_edge &= !added || id_next == id; trivial_edge &= !removed || id_node == id; added_count += added; removed_count += removed; i_node += id_node <= id_next; i_next += id_next <= id_node; } added_count += next_count - i_next; removed_count += node_count - i_node; trivial_edge &= (added_count + removed_count) <= 1 && !ecs_id_is_wildcard(id); if (trivial_edge) { /* If edge is trivial there's no need to create a diff element for it */ return; } ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; int32_t added_offset = builder->added.count; int32_t removed_offset = builder->removed.count; for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; if (id_next < id_node) { flecs_diff_insert_added(world, builder, id_next); } else if (id_node < id_next) { flecs_diff_insert_removed(world, builder, id_node); } i_node += id_node <= id_next; i_next += id_next <= id_node; } for (; i_next < next_count; i_next ++) { flecs_diff_insert_added(world, builder, ids_next[i_next]); } for (; i_node < node_count; i_node ++) { flecs_diff_insert_removed(world, builder, ids_node[i_node]); } ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff = diff; flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } static void flecs_add_overrides_for_base( ecs_world_t *world, ecs_type_t *dst_type, ecs_id_t pair) { ecs_entity_t base = ecs_pair_second(world, pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_t *base_table = ecs_get_table(world, base); if (!base_table) { return; } ecs_id_t *ids = base_table->type.array; ecs_flags32_t flags = base_table->flags; if (flags & EcsTableHasOverrides) { int32_t i, count = base_table->type.count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; ecs_id_t to_add = 0; if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { to_add = id & ~ECS_OVERRIDE; } else { ecs_table_record_t *tr = &base_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdAlwaysOverride) { to_add = id; } } if (to_add) { ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); bool exclusive = false; if (ECS_IS_PAIR(to_add)) { ecs_id_record_t *idr = flecs_id_record_get(world, wc); if (idr) { exclusive = (idr->flags & EcsIdExclusive) != 0; } } if (!exclusive) { flecs_type_add(world, dst_type, to_add); } else { int32_t column = flecs_type_find(dst_type, wc); if (column == -1) { flecs_type_add(world, dst_type, to_add); } else { dst_type->array[column] = to_add; } } } } } if (flags & EcsTableHasIsA) { ecs_table_record_t *tr = flecs_id_record_get_table( world->idr_isa_wildcard, base_table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = tr->index, end = i + tr->count; for (; i != end; i ++) { flecs_add_overrides_for_base(world, dst_type, ids[i]); } } } static void flecs_add_with_property( ecs_world_t *world, ecs_id_record_t *idr_with_wildcard, ecs_type_t *dst_type, ecs_entity_t r, ecs_entity_t o) { r = ecs_get_alive(world, r); /* Check if component/relationship has With pairs, which contain ids * that need to be added to the table. */ ecs_table_t *table = ecs_get_table(world, r); if (!table) { return; } ecs_table_record_t *tr = flecs_id_record_get_table( idr_with_wildcard, table); if (tr) { int32_t i = tr->index, end = i + tr->count; ecs_id_t *ids = table->type.array; for (; i < end; i ++) { ecs_id_t id = ids[i]; ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); ecs_id_t ra = ECS_PAIR_SECOND(id); ecs_id_t a = ra; if (o) { a = ecs_pair(ra, o); } flecs_type_add(world, dst_type, a); flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); } } } static ecs_table_t* flecs_find_table_with( ecs_world_t *world, ecs_table_t *node, ecs_id_t with) { ecs_make_alive_id(world, with); ecs_id_record_t *idr = NULL; ecs_entity_t r = 0, o = 0; if (ECS_IS_PAIR(with)) { r = ECS_PAIR_FIRST(with); o = ECS_PAIR_SECOND(with); idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); if (idr->flags & EcsIdUnion) { ecs_type_t dst_type; ecs_id_t union_id = ecs_pair(EcsUnion, r); int res = flecs_type_new_with( world, &dst_type, &node->type, union_id); if (res == -1) { return node; } return flecs_table_ensure(world, &dst_type, true, node); } else if (idr->flags & EcsIdExclusive) { /* Relationship is exclusive, check if table already has it */ ecs_table_record_t *tr = flecs_id_record_get_table(idr, node); if (tr) { /* Table already has an instance of the relationship, create * a new id sequence with the existing id replaced */ ecs_type_t dst_type = flecs_type_copy(world, &node->type); ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); dst_type.array[tr->index] = with; return flecs_table_ensure(world, &dst_type, true, node); } } } else { idr = flecs_id_record_ensure(world, with); r = with; } /* Create sequence with new id */ ecs_type_t dst_type; int res = flecs_type_new_with(world, &dst_type, &node->type, with); if (res == -1) { return node; /* Current table already has id */ } if (r == EcsIsA) { /* If adding a prefab, check if prefab has overrides */ flecs_add_overrides_for_base(world, &dst_type, with); } else if (r == EcsChildOf) { o = ecs_get_alive(world, o); if (ecs_has_id(world, o, EcsPrefab)) { flecs_type_add(world, &dst_type, EcsPrefab); } } if (idr->flags & EcsIdWith) { ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, ecs_pair(EcsWith, EcsWildcard)); /* If id has With property, add targets to type */ flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); } return flecs_table_ensure(world, &dst_type, true, node); } static ecs_table_t* flecs_find_table_without( ecs_world_t *world, ecs_table_t *node, ecs_id_t without) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = 0; ecs_id_record_t *idr = NULL; r = ECS_PAIR_FIRST(without); idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); if (idr && idr->flags & EcsIdUnion) { without = ecs_pair(EcsUnion, r); } } /* Create sequence with new id */ ecs_type_t dst_type; int res = flecs_type_new_without(world, &dst_type, &node->type, without); if (res == -1) { return node; /* Current table does not have id */ } return flecs_table_ensure(world, &dst_type, true, node); } static void flecs_table_init_edge( ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->hdr.next == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->hdr.prev == NULL, ECS_INTERNAL_ERROR, NULL); edge->from = table; edge->to = to; edge->id = id; } static void flecs_init_edge_for_add( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { flecs_table_init_edge(table, edge, id, to); flecs_table_ensure_hi_edge(world, &table->node.add, id); if (table != to || table->flags & EcsTableHasUnion) { /* Add edges are appended to refs.next */ ecs_graph_edge_hdr_t *to_refs = &to->node.refs; ecs_graph_edge_hdr_t *next = to_refs->next; to_refs->next = &edge->hdr; edge->hdr.prev = to_refs; edge->hdr.next = next; if (next) { next->prev = &edge->hdr; } flecs_compute_table_diff(world, table, to, edge, id); } } static void flecs_init_edge_for_remove( ecs_world_t *world, ecs_table_t *table, ecs_graph_edge_t *edge, ecs_id_t id, ecs_table_t *to) { flecs_table_init_edge(table, edge, id, to); flecs_table_ensure_hi_edge(world, &table->node.remove, id); if (table != to) { /* Remove edges are appended to refs.prev */ ecs_graph_edge_hdr_t *to_refs = &to->node.refs; ecs_graph_edge_hdr_t *prev = to_refs->prev; to_refs->prev = &edge->hdr; edge->hdr.next = to_refs; edge->hdr.prev = prev; if (prev) { prev->next = &edge->hdr; } flecs_compute_table_diff(world, table, to, edge, id); } } static ecs_table_t* flecs_create_edge_for_remove( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { ecs_table_t *to = flecs_find_table_without(world, node, id); flecs_init_edge_for_remove(world, node, edge, id, to); return to; } static ecs_table_t* flecs_create_edge_for_add( ecs_world_t *world, ecs_table_t *node, ecs_graph_edge_t *edge, ecs_id_t id) { ecs_table_t *to = flecs_find_table_with(world, node, id); flecs_init_edge_for_add(world, node, edge, id, to); return to; } ecs_table_t* flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *node, ecs_id_t *id_ptr, ecs_table_diff_t *diff) { ecs_poly_assert(world, ecs_world_t); node = node ? node : &world->store.root; /* Removing 0 from an entity is not valid */ ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t id = id_ptr[0]; ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.remove, id); ecs_table_t *to = edge->to; if (!to) { to = flecs_create_edge_for_remove(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } if (node != to) { if (edge->diff) { *diff = *edge->diff; } else { diff->added.count = 0; diff->removed.array = id_ptr; diff->removed.count = 1; } } return to; error: return NULL; } ecs_table_t* flecs_table_traverse_add( ecs_world_t *world, ecs_table_t *node, ecs_id_t *id_ptr, ecs_table_diff_t *diff) { ecs_poly_assert(world, ecs_world_t); ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); node = node ? node : &world->store.root; /* Adding 0 to an entity is not valid */ ecs_check(id_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id_ptr[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_t id = id_ptr[0]; ecs_graph_edge_t *edge = flecs_table_ensure_edge(world, &node->node.add, id); ecs_table_t *to = edge->to; if (!to) { to = flecs_create_edge_for_add(world, node, edge, id); ecs_assert(to != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->to != NULL, ECS_INTERNAL_ERROR, NULL); } if (node != to || edge->diff) { if (edge->diff) { *diff = *edge->diff; } else { diff->added.array = id_ptr; diff->added.count = 1; diff->removed.count = 0; } } return to; error: return NULL; } ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type) { ecs_poly_assert(world, ecs_world_t); return flecs_table_ensure(world, type, false, NULL); } void flecs_init_root_table( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); world->store.root.type = (ecs_type_t){0}; world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); flecs_init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ uint64_t new_id = flecs_sparse_new_id(&world->store.tables); ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); (void)new_id; } void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table) { (void)world; ecs_poly_assert(world, ecs_world_t); ecs_log_push_1(); ecs_map_iter_t it; ecs_graph_node_t *table_node = &table->node; ecs_graph_edges_t *node_add = &table_node->add; ecs_graph_edges_t *node_remove = &table_node->remove; ecs_map_t *add_hi = node_add->hi; ecs_map_t *remove_hi = node_remove->hi; ecs_graph_edge_hdr_t *node_refs = &table_node->refs; /* Cleanup outgoing edges */ it = ecs_map_iter(add_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } it = ecs_map_iter(remove_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } /* Cleanup incoming add edges */ ecs_graph_edge_hdr_t *next, *cur = node_refs->next; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->next; flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); } while ((cur = next)); } /* Cleanup incoming remove edges */ cur = node_refs->prev; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->prev; flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); } while ((cur = next)); } if (node_add->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); } if (node_remove->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); } ecs_map_fini(add_hi); ecs_map_fini(remove_hi); flecs_free_t(&world->allocator, ecs_map_t, add_hi); flecs_free_t(&world->allocator, ecs_map_t, remove_hi); table_node->add.lo = NULL; table_node->remove.lo = NULL; table_node->add.hi = NULL; table_node->remove.hi = NULL; ecs_log_pop_1(); } /* Public convenience functions for traversing table graph */ ecs_table_t* ecs_table_add_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; return flecs_table_traverse_add(world, table, &id, &diff); } ecs_table_t* ecs_table_remove_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; return flecs_table_traverse_remove(world, table, &id, &diff); } ecs_table_t* ecs_table_find( ecs_world_t *world, const ecs_id_t *ids, int32_t id_count) { ecs_type_t type = { .array = ECS_CONST_CAST(ecs_id_t*, ids), .count = id_count }; return flecs_table_ensure(world, &type, false, NULL); } /** * @file expr/deserialize.c * @brief Deserialize flecs string format into (component) values. */ #include #ifdef FLECS_EXPR /* String deserializer for values & simple expressions */ /* Order in enumeration is important, as it is used for precedence */ typedef enum ecs_expr_oper_t { EcsExprOperUnknown, EcsLeftParen, EcsCondAnd, EcsCondOr, EcsCondEq, EcsCondNeq, EcsCondGt, EcsCondGtEq, EcsCondLt, EcsCondLtEq, EcsShiftLeft, EcsShiftRight, EcsAdd, EcsSub, EcsMul, EcsDiv, EcsMin } ecs_expr_oper_t; /* Used to track temporary values */ #define EXPR_MAX_STACK_SIZE (256) typedef struct ecs_expr_value_t { const ecs_type_info_t *ti; void *ptr; } ecs_expr_value_t; typedef struct ecs_value_stack_t { ecs_expr_value_t values[EXPR_MAX_STACK_SIZE]; ecs_stack_cursor_t *cursor; ecs_stack_t *stack; ecs_stage_t *stage; int32_t count; } ecs_value_stack_t; static const char* flecs_parse_expr( ecs_world_t *world, ecs_value_stack_t *stack, const char *ptr, ecs_value_t *value, ecs_expr_oper_t op, const ecs_parse_expr_desc_t *desc); static void* flecs_expr_value_new( ecs_value_stack_t *stack, ecs_entity_t type) { ecs_stage_t *stage = stack->stage; ecs_world_t *world = stage->world; ecs_id_record_t *idr = flecs_id_record_get(world, type); if (!idr) { return NULL; } const ecs_type_info_t *ti = idr->type_info; if (!ti) { return NULL; } ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); if (ti->hooks.ctor) { ti->hooks.ctor(result, 1, ti); } else { ecs_os_memset(result, 0, ti->size); } if (ti->hooks.dtor) { /* Track values that have destructors */ stack->values[stack->count].ti = ti; stack->values[stack->count].ptr = result; stack->count ++; } return result; } static const char* flecs_str_to_expr_oper( const char *str, ecs_expr_oper_t *op) { if (!ecs_os_strncmp(str, "+", 1)) { *op = EcsAdd; return str + 1; } else if (!ecs_os_strncmp(str, "-", 1)) { *op = EcsSub; return str + 1; } else if (!ecs_os_strncmp(str, "*", 1)) { *op = EcsMul; return str + 1; } else if (!ecs_os_strncmp(str, "/", 1)) { *op = EcsDiv; return str + 1; } else if (!ecs_os_strncmp(str, "&&", 2)) { *op = EcsCondAnd; return str + 2; } else if (!ecs_os_strncmp(str, "||", 2)) { *op = EcsCondOr; return str + 2; } else if (!ecs_os_strncmp(str, "==", 2)) { *op = EcsCondEq; return str + 2; } else if (!ecs_os_strncmp(str, "!=", 2)) { *op = EcsCondNeq; return str + 2; } else if (!ecs_os_strncmp(str, ">=", 2)) { *op = EcsCondGtEq; return str + 2; } else if (!ecs_os_strncmp(str, "<=", 2)) { *op = EcsCondLtEq; return str + 2; } else if (!ecs_os_strncmp(str, ">>", 2)) { *op = EcsShiftRight; return str + 2; } else if (!ecs_os_strncmp(str, "<<", 2)) { *op = EcsShiftLeft; return str + 2; } else if (!ecs_os_strncmp(str, ">", 1)) { *op = EcsCondGt; return str + 1; } else if (!ecs_os_strncmp(str, "<", 1)) { *op = EcsCondLt; return str + 1; } *op = EcsExprOperUnknown; return NULL; } const char *ecs_parse_expr_token( const char *name, const char *expr, const char *ptr, char *token) { char *token_ptr = token; if (ptr[0] == '/') { char ch; if (ptr[1] == '/') { // Single line comment for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} token[0] = 0; return ecs_parse_ws_eol(ptr); } else if (ptr[1] == '*') { // Multi line comment for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { if (ch == '*' && ptr[1] == '/') { token[0] = 0; return ecs_parse_ws_eol(ptr + 2); } } ecs_parser_error(name, expr, ptr - expr, "missing */ for multiline comment"); return NULL; } } ecs_expr_oper_t op; if (ptr[0] == '(') { token[0] = '('; token[1] = 0; return ptr + 1; } else if (ptr[0] != '-') { const char *tptr = flecs_str_to_expr_oper(ptr, &op); if (tptr) { ecs_os_strncpy(token, ptr, tptr - ptr); return tptr; } } while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr, 0))) { if (ptr[0] == '|' && ptr[1] != '|') { token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; token_ptr[0] = '|'; token_ptr[1] = '\0'; token_ptr ++; ptr ++; } else { break; } } return ptr; } static const char* flecs_parse_multiline_string( ecs_meta_cursor_t *cur, const char *name, const char *expr, const char *ptr) { /* Multiline string */ ecs_strbuf_t str = ECS_STRBUF_INIT; char ch; while ((ch = ptr[0]) && (ch != '`')) { if (ch == '\\' && ptr[1] == '`') { ch = '`'; ptr ++; } ecs_strbuf_appendch(&str, ch); ptr ++; } if (ch != '`') { ecs_parser_error(name, expr, ptr - expr, "missing '`' to close multiline string"); goto error; } char *strval = ecs_strbuf_get(&str); if (ecs_meta_set_string(cur, strval) != 0) { goto error; } ecs_os_free(strval); return ptr + 1; error: return NULL; } static bool flecs_parse_is_float( const char *ptr) { ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); char ch; while ((ch = (++ptr)[0])) { if (ch == '.' || ch == 'e') { return true; } if (!isdigit(ch)) { return false; } } return false; } /* Attempt to resolve variable dotexpression to value (foo.bar) */ static ecs_value_t flecs_dotresolve_var( ecs_world_t *world, ecs_vars_t *vars, char *token) { char *dot = strchr(token, '.'); if (!dot) { return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; } dot[0] = '\0'; const ecs_expr_var_t *var = ecs_vars_lookup(vars, token); if (!var) { return (ecs_value_t){0}; } ecs_meta_cursor_t cur = ecs_meta_cursor( world, var->value.type, var->value.ptr); ecs_meta_push(&cur); if (ecs_meta_dotmember(&cur, dot + 1) != 0) { return (ecs_value_t){0}; } return (ecs_value_t){ .ptr = ecs_meta_get_ptr(&cur), .type = ecs_meta_get_type(&cur) }; } static int flecs_meta_call( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_meta_cursor_t *cur, const char *function) { ecs_entity_t type = ecs_meta_get_type(cur); void *value_ptr = ecs_meta_get_ptr(cur); if (!ecs_os_strcmp(function, "parent")) { if (type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, ptr - expr, "parent() can only be called on entity"); return -1; } *(ecs_entity_t*)value_ptr = ecs_get_parent( world, *(ecs_entity_t*)value_ptr); } else if (!ecs_os_strcmp(function, "name")) { if (type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, ptr - expr, "name() can only be called on entity"); return -1; } char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); } else if (!ecs_os_strcmp(function, "doc_name")) { #ifdef FLECS_DOC if (type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, ptr - expr, "name() can only be called on entity"); return -1; } char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); #else ecs_parser_error(name, expr, ptr - expr, "doc_name() is not available without FLECS_DOC addon"); return -1; #endif } else { ecs_parser_error(name, expr, ptr - expr, "unknown function '%s'", function); return -1; } return 0; } /* Determine the type of an expression from the first character(s). This allows * us to initialize a storage for a type if none was provided. */ static ecs_entity_t flecs_parse_discover_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_entity_t input_type, const ecs_parse_expr_desc_t *desc) { /* String literal */ if (ptr[0] == '"' || ptr[0] == '`') { if (input_type == ecs_id(ecs_char_t)) { return input_type; } return ecs_id(ecs_string_t); } /* Negative number literal */ if (ptr[0] == '-') { if (!isdigit(ptr[1])) { ecs_parser_error(name, expr, ptr - expr, "invalid literal"); return 0; } if (flecs_parse_is_float(ptr + 1)) { return ecs_id(ecs_f64_t); } else { return ecs_id(ecs_i64_t); } } /* Positive number literal */ if (isdigit(ptr[0])) { if (flecs_parse_is_float(ptr)) { return ecs_id(ecs_f64_t); } else { return ecs_id(ecs_u64_t); } } /* Variable */ if (ptr[0] == '$') { if (!desc || !desc->vars) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable (no variable scope)"); return 0; } char token[ECS_MAX_TOKEN_SIZE]; if (ecs_parse_expr_token(name, expr, &ptr[1], token) == NULL) { return 0; } const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); if (!var) { ecs_size_t len = ecs_os_strlen(token); if (ptr[len + 1] == '(') { while ((len > 0) && (token[len] != '.')) { len --; } if (token[len] == '.') { token[len] = '\0'; } } ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); if (v.type) { return v.type; } ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return 0; } return var->value.type; } /* Boolean */ if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { if (!isalpha(ptr[4]) && ptr[4] != '_') { return ecs_id(ecs_bool_t); } } if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { if (!isalpha(ptr[5]) && ptr[5] != '_') { return ecs_id(ecs_bool_t); } } /* Entity identifier */ if (isalpha(ptr[0])) { if (!input_type) { /* Identifier could also be enum/bitmask constant */ return ecs_id(ecs_entity_t); } } /* If no default type was provided we can't automatically deduce the type of * composite/collection expressions. */ if (!input_type) { if (ptr[0] == '{') { ecs_parser_error(name, expr, ptr - expr, "unknown type for composite literal"); return 0; } if (ptr[0] == '[') { ecs_parser_error(name, expr, ptr - expr, "unknown type for collection literal"); return 0; } ecs_parser_error(name, expr, ptr - expr, "invalid expression"); } return input_type; } /* Normalize types to their largest representation. * Rather than taking the original type of a value, use the largest * representation of the type so we don't have to worry about overflowing the * original type in the operation. */ static ecs_entity_t flecs_largest_type( const EcsPrimitive *type) { switch(type->kind) { case EcsBool: return ecs_id(ecs_bool_t); case EcsChar: return ecs_id(ecs_char_t); case EcsByte: return ecs_id(ecs_u8_t); case EcsU8: return ecs_id(ecs_u64_t); case EcsU16: return ecs_id(ecs_u64_t); case EcsU32: return ecs_id(ecs_u64_t); case EcsU64: return ecs_id(ecs_u64_t); case EcsI8: return ecs_id(ecs_i64_t); case EcsI16: return ecs_id(ecs_i64_t); case EcsI32: return ecs_id(ecs_i64_t); case EcsI64: return ecs_id(ecs_i64_t); case EcsF32: return ecs_id(ecs_f64_t); case EcsF64: return ecs_id(ecs_f64_t); case EcsUPtr: return ecs_id(ecs_u64_t); case EcsIPtr: return ecs_id(ecs_i64_t); case EcsString: return ecs_id(ecs_string_t); case EcsEntity: return ecs_id(ecs_entity_t); case EcsId: return ecs_id(ecs_id_t); default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } /** Test if a normalized type can promote to another type in an expression */ static bool flecs_is_type_number( ecs_entity_t type) { if (type == ecs_id(ecs_bool_t)) return false; else if (type == ecs_id(ecs_char_t)) return false; else if (type == ecs_id(ecs_u8_t)) return false; else if (type == ecs_id(ecs_u64_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; } static bool flecs_oper_valid_for_type( ecs_entity_t type, ecs_expr_oper_t op) { switch(op) { case EcsAdd: case EcsSub: case EcsMul: case EcsDiv: return flecs_is_type_number(type); case EcsCondEq: case EcsCondNeq: case EcsCondAnd: case EcsCondOr: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: return flecs_is_type_number(type) || (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); case EcsShiftLeft: case EcsShiftRight: return (type == ecs_id(ecs_u64_t)); case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: return false; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } /** Promote type to most expressive (f64 > i64 > u64) */ static ecs_entity_t flecs_promote_type( ecs_entity_t type, ecs_entity_t promote_to) { if (type == ecs_id(ecs_u64_t)) { return promote_to; } if (promote_to == ecs_id(ecs_u64_t)) { return type; } if (type == ecs_id(ecs_f64_t)) { return type; } if (promote_to == ecs_id(ecs_f64_t)) { return promote_to; } return ecs_id(ecs_i64_t); } static int flecs_oper_precedence( ecs_expr_oper_t left, ecs_expr_oper_t right) { return (left > right) - (left < right); } static void flecs_value_cast( ecs_world_t *world, ecs_value_stack_t *stack, ecs_value_t *value, ecs_entity_t type) { if (value->type == type) { return; } ecs_value_t result; result.type = type; result.ptr = flecs_expr_value_new(stack, type); if (value->ptr) { ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); ecs_meta_set_value(&cur, value); } *value = result; } static bool flecs_expr_op_is_equality( ecs_expr_oper_t op) { switch(op) { case EcsCondEq: case EcsCondNeq: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: return true; case EcsCondAnd: case EcsCondOr: case EcsShiftLeft: case EcsShiftRight: case EcsAdd: case EcsSub: case EcsMul: case EcsDiv: case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: return false; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } error: return false; } static ecs_entity_t flecs_binary_expr_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *rvalue, ecs_expr_oper_t op, ecs_entity_t *operand_type_out) { ecs_entity_t result_type = 0, operand_type = 0; switch(op) { case EcsDiv: /* Result type of a division is always a float */ *operand_type_out = ecs_id(ecs_f64_t); return ecs_id(ecs_f64_t); case EcsCondAnd: case EcsCondOr: /* Result type of a condition operator is always a bool */ *operand_type_out = ecs_id(ecs_bool_t); return ecs_id(ecs_bool_t); case EcsCondEq: case EcsCondNeq: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: /* Result type of equality operator is always bool, but operand types * should not be casted to bool */ result_type = ecs_id(ecs_bool_t); break; case EcsShiftLeft: case EcsShiftRight: case EcsAdd: case EcsSub: case EcsMul: case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: break; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } /* Result type for arithmetic operators is determined by operands */ const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { char *lname = ecs_get_fullpath(world, lvalue->type); char *rname = ecs_get_fullpath(world, rvalue->type); ecs_parser_error(name, expr, ptr - expr, "invalid non-primitive type in binary expression (%s, %s)", lname, rname); ecs_os_free(lname); ecs_os_free(rname); return 0; } ecs_entity_t ltype = flecs_largest_type(ltype_ptr); ecs_entity_t rtype = flecs_largest_type(rtype_ptr); if (ltype == rtype) { operand_type = ltype; goto done; } if (flecs_expr_op_is_equality(op)) { ecs_parser_error(name, expr, ptr - expr, "mismatching types in equality expression"); return 0; } if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { ecs_parser_error(name, expr, ptr - expr, "incompatible types in binary expression"); return 0; } operand_type = flecs_promote_type(ltype, rtype); done: if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ operand_type = ecs_id(ecs_i64_t); } if (!result_type) { result_type = operand_type; } *operand_type_out = operand_type; return result_type; error: return 0; } /* Macro's to let the compiler do the operations & conversion work for us */ #define ECS_VALUE_GET(value, T) (*(T*)value->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) #define ECS_BINARY_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ return -1;\ } else if (left->type == ecs_id(ecs_u8_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if (left->type == ecs_id(ecs_char_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ } else if (left->type == ecs_id(ecs_u8_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if (left->type == ecs_id(ecs_char_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_BOOL_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_UINT_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } static int flecs_binary_expr_do( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *rvalue, ecs_value_t *result, ecs_expr_oper_t op) { /* Find expression type */ ecs_entity_t operand_type, type = flecs_binary_expr_type( world, name, expr, ptr, lvalue, rvalue, op, &operand_type); if (!type) { goto error; } if (!flecs_oper_valid_for_type(type, op)) { ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); goto error; } flecs_value_cast(world, stack, lvalue, operand_type); flecs_value_cast(world, stack, rvalue, operand_type); ecs_value_t *storage = result; ecs_value_t tmp_storage = {0}; if (result->type != type) { storage = &tmp_storage; storage->type = type; storage->ptr = flecs_expr_value_new(stack, type); } switch(op) { case EcsAdd: ECS_BINARY_OP(lvalue, rvalue, storage, +); break; case EcsSub: ECS_BINARY_OP(lvalue, rvalue, storage, -); break; case EcsMul: ECS_BINARY_OP(lvalue, rvalue, storage, *); break; case EcsDiv: ECS_BINARY_OP(lvalue, rvalue, storage, /); break; case EcsCondEq: ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); break; case EcsCondNeq: ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); break; case EcsCondGt: ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); break; case EcsCondGtEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); break; case EcsCondLt: ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); break; case EcsCondLtEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); break; case EcsCondAnd: ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); break; case EcsCondOr: ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); break; case EcsShiftLeft: ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); break; case EcsShiftRight: ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); break; case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); goto error; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } if (storage->ptr != result->ptr) { if (!result->ptr) { *result = *storage; } else { ecs_meta_cursor_t cur = ecs_meta_cursor(world, result->type, result->ptr); ecs_meta_set_value(&cur, storage); } } return 0; error: return -1; } static const char* flecs_binary_expr_parse( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *result, ecs_expr_oper_t left_op, const ecs_parse_expr_desc_t *desc) { ecs_entity_t result_type = result->type; do { ecs_expr_oper_t op; ptr = flecs_str_to_expr_oper(ptr, &op); if (!ptr) { ecs_parser_error(name, expr, ptr - expr, "invalid operator"); return NULL; } ptr = ecs_parse_ws_eol(ptr); ecs_value_t rvalue = {0}; const char *rptr = flecs_parse_expr(world, stack, ptr, &rvalue, op, desc); if (!rptr) { return NULL; } if (flecs_binary_expr_do(world, stack, name, expr, ptr, lvalue, &rvalue, result, op)) { return NULL; } ptr = rptr; ecs_expr_oper_t right_op; flecs_str_to_expr_oper(rptr, &right_op); if (right_op > left_op) { if (result_type) { /* If result was initialized, preserve its value */ lvalue->type = result->type; lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); continue; } else { /* Otherwise move result to lvalue */ *lvalue = *result; ecs_os_zeromem(result); continue; } } break; } while (true); return ptr; } static const char* flecs_funccall_parse( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, char *token, ecs_meta_cursor_t *cur, ecs_value_t *value, bool isvar, const ecs_parse_expr_desc_t *desc) { char *sep = strrchr(token, '.'); if (!sep) { ecs_parser_error(name, expr, ptr - expr, "missing object for function call '%s'", token); return NULL; } sep[0] = '\0'; const char *function = sep + 1; if (!isvar) { if (ecs_meta_set_string(cur, token) != 0) { goto error; } } else { const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); ecs_meta_set_value(cur, &var->value); } do { if (!function[0]) { ecs_parser_error(name, expr, ptr - expr, "missing function name for function call '%s'", token); return NULL; } if (flecs_meta_call(world, stack, name, expr, ptr, cur, function) != 0) { goto error; } ecs_entity_t type = ecs_meta_get_type(cur); value->ptr = ecs_meta_get_ptr(cur); value->type = type; ptr += 2; if (ptr[0] != '.') { break; } ptr ++; char *paren = strchr(ptr, '('); if (!paren) { break; } ecs_size_t len = flecs_ito(int32_t, paren - ptr); if (len >= ECS_MAX_TOKEN_SIZE) { ecs_parser_error(name, expr, ptr - expr, "token exceeds maximum token size"); goto error; } ecs_os_strncpy(token, ptr, len); token[len] = '\0'; function = token; ptr = paren; } while (true); return ptr; error: return NULL; } static const char* flecs_parse_expr( ecs_world_t *world, ecs_value_stack_t *stack, const char *ptr, ecs_value_t *value, ecs_expr_oper_t left_op, const ecs_parse_expr_desc_t *desc) { ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); char token[ECS_MAX_TOKEN_SIZE]; int depth = 0; ecs_value_t result = {0}; ecs_meta_cursor_t cur = {0}; const char *name = desc ? desc->name : NULL; const char *expr = desc ? desc->expr : NULL; token[0] = '\0'; expr = expr ? expr : ptr; ptr = ecs_parse_ws_eol(ptr); /* Check for postfix operators */ ecs_expr_oper_t unary_op = EcsExprOperUnknown; if (ptr[0] == '-' && !isdigit(ptr[1])) { unary_op = EcsMin; ptr = ecs_parse_ws_eol(ptr + 1); } /* Initialize storage and cursor. If expression starts with a '(' storage * will be initialized by a nested expression */ if (ptr[0] != '(') { ecs_entity_t type = flecs_parse_discover_type( world, name, expr, ptr, value->type, desc); if (!type) { return NULL; } result.type = type; if (type != value->type) { result.ptr = flecs_expr_value_new(stack, type); } else { result.ptr = value->ptr; } cur = ecs_meta_cursor(world, result.type, result.ptr); if (!cur.valid) { return NULL; } cur.lookup_action = desc ? desc->lookup_action : NULL; cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; } /* Loop that parses all values in a value scope */ while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { /* Used to track of the result of the parsed token can be used as the * lvalue for a binary expression */ bool is_lvalue = false; bool newline = false; if (!token[0]) { /* Comment */ continue; } if (!ecs_os_strcmp(token, "(")) { ecs_value_t temp_result, *out; if (!depth) { out = &result; } else { temp_result.type = ecs_meta_get_type(&cur); temp_result.ptr = ecs_meta_get_ptr(&cur); out = &temp_result; } /* Parenthesis, parse nested expression */ ptr = flecs_parse_expr(world, stack, ptr, out, EcsLeftParen, desc); if (ptr[0] != ')') { ecs_parser_error(name, expr, ptr - expr, "missing closing parenthesis"); return NULL; } ptr = ecs_parse_ws(ptr + 1); is_lvalue = true; } else if (!ecs_os_strcmp(token, "{")) { /* Parse nested value scope */ ecs_entity_t scope_type = ecs_meta_get_type(&cur); depth ++; /* Keep track of depth so we know when parsing is done */ if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { char *path = ecs_get_fullpath(world, scope_type); ecs_parser_error(name, expr, ptr - expr, "expected '[' for collection type '%s'", path); ecs_os_free(path); return NULL; } } else if (!ecs_os_strcmp(token, "}")) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "[")) { /* Open collection value scope */ depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); return NULL; } } else if (!ecs_os_strcmp(token, "]")) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "-")) { if (unary_op != EcsExprOperUnknown) { ecs_parser_error(name, expr, ptr - expr, "unexpected unary operator"); return NULL; } unary_op = EcsMin; } else if (!ecs_os_strcmp(token, ",")) { /* Move to next field */ if (ecs_meta_next(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "null")) { if (ecs_meta_set_null(&cur) != 0) { goto error; } is_lvalue = true; } else if (token[0] == '\"') { /* Regular string */ if (ecs_meta_set_string_literal(&cur, token) != 0) { goto error; } is_lvalue = true; } else if (!ecs_os_strcmp(token, "`")) { /* Multiline string */ if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { goto error; } is_lvalue = true; } else if (token[0] == '$') { /* Variable */ if (!desc || !desc->vars) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s' (no variable scope)", token); return NULL; } if (!token[1]) { /* Empty name means default to assigned member */ const char *member = ecs_meta_get_member(&cur); if (!member) { ecs_parser_error(name, expr, ptr - expr, "invalid default variable outside member assignment"); return NULL; } ecs_os_strcpy(&token[1], member); } const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]); if (!var) { if (ptr[0] == '(') { /* Function */ ptr = flecs_funccall_parse(world, stack, name, expr, ptr, &token[1], &cur, &result, true, desc); if (!ptr) { goto error; } } else { ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); if (!v.ptr) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return NULL; } else { ecs_meta_set_value(&cur, &v); } } } else { ecs_meta_set_value(&cur, &var->value); } is_lvalue = true; } else { const char *tptr = ecs_parse_ws(ptr); for (; ptr != tptr; ptr ++) { if (ptr[0] == '\n') { newline = true; } } if (ptr[0] == ':') { /* Member assignment */ ptr ++; if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else if (ptr[0] == '(') { /* Function */ ptr = flecs_funccall_parse(world, stack, name, expr, ptr, token, &cur, &result, false, desc); if (!ptr) { goto error; } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } is_lvalue = true; } /* If lvalue was parsed, apply operators. Expressions cannot start * directly after a newline character. */ if (is_lvalue && !newline) { if (unary_op != EcsExprOperUnknown) { if (unary_op == EcsMin) { int64_t v = -1; ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; ecs_value_t *out, rvalue, temp_out = {0}; if (!depth) { rvalue = result; ecs_os_zeromem(&result); out = &result; } else { ecs_entity_t cur_type = ecs_meta_get_type(&cur); void *cur_ptr = ecs_meta_get_ptr(&cur); rvalue.type = cur_type; rvalue.ptr = cur_ptr; temp_out.type = cur_type; temp_out.ptr = cur_ptr; out = &temp_out; } flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, &rvalue, out, EcsMul); } unary_op = 0; } ecs_expr_oper_t right_op; flecs_str_to_expr_oper(ptr, &right_op); if (right_op) { /* This is a binary expression, test precedence to determine if * it should be evaluated here */ if (flecs_oper_precedence(left_op, right_op) < 0) { ecs_value_t lvalue; ecs_value_t *op_result = &result; ecs_value_t temp_storage; if (!depth) { /* Root level value, move result to lvalue storage */ lvalue = result; ecs_os_zeromem(&result); } else { /* Not a root level value. Move the parsed lvalue to a * temporary storage, and initialize the result value * for the binary operation with the current cursor */ ecs_entity_t cur_type = ecs_meta_get_type(&cur); void *cur_ptr = ecs_meta_get_ptr(&cur); lvalue.type = cur_type; lvalue.ptr = flecs_expr_value_new(stack, cur_type); ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); temp_storage.type = cur_type; temp_storage.ptr = cur_ptr; op_result = &temp_storage; } /* Do the binary expression */ ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, &lvalue, op_result, left_op, desc); if (!ptr) { return NULL; } } } } if (!depth) { /* Reached the end of the root scope */ break; } ptr = ecs_parse_ws_eol(ptr); } if (!value->ptr) { value->type = result.type; value->ptr = flecs_expr_value_new(stack, result.type); } if (value->ptr != result.ptr) { cur = ecs_meta_cursor(world, value->type, value->ptr); ecs_meta_set_value(&cur, &result); } ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return ptr; error: return NULL; } const char* ecs_parse_expr( ecs_world_t *world, const char *ptr, ecs_value_t *value, const ecs_parse_expr_desc_t *desc) { /* Prepare storage for temporary values */ ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_value_stack_t stack; stack.count = 0; stack.stage = stage; stack.stack = &stage->allocators.deser_stack; stack.cursor = flecs_stack_get_cursor(stack.stack); /* Parse expression */ bool storage_provided = value->ptr != NULL; ptr = flecs_parse_expr(world, &stack, ptr, value, EcsExprOperUnknown, desc); /* If no result value was provided, allocate one as we can't return a * pointer to a temporary storage */ if (!storage_provided && value->ptr) { ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); void *temp_storage = value->ptr; value->ptr = ecs_value_new(world, value->type); ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); } /* Cleanup temporary values */ int i; for (i = 0; i < stack.count; i ++) { const ecs_type_info_t *ti = stack.values[i].ti; ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); ti->hooks.dtor(stack.values[i].ptr, 1, ti); } flecs_stack_restore_cursor(stack.stack, stack.cursor); return ptr; } #endif /** * @file expr/serialize.c * @brief Serialize (component) values to flecs string format. */ #ifdef FLECS_EXPR static int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str, bool is_expr); static int flecs_expr_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array, bool is_expr); static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str, bool is_expr); static ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { return kind - EcsOpPrimitive; } /* Serialize a primitive value */ static int flecs_expr_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str, bool is_expr) { switch(kind) { case EcsBool: if (*(const bool*)base) { ecs_strbuf_appendlit(str, "true"); } else { ecs_strbuf_appendlit(str, "false"); } break; case EcsChar: { char chbuf[3]; char ch = *(const char*)base; if (ch) { ecs_chresc(chbuf, *(const char*)base, '"'); if (is_expr) ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, chbuf); if (is_expr) ecs_strbuf_appendch(str, '"'); } else { ecs_strbuf_appendch(str, '0'); } break; } case EcsByte: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); break; case EcsU8: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); break; case EcsU16: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)base)); break; case EcsU32: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)base)); break; case EcsU64: ecs_strbuf_append(str, "%llu", *(const uint64_t*)base); break; case EcsI8: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)base)); break; case EcsI16: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)base)); break; case EcsI32: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)base)); break; case EcsI64: ecs_strbuf_appendint(str, *(const int64_t*)base); break; case EcsF32: ecs_strbuf_appendflt(str, (double)*(const float*)base, 0); break; case EcsF64: ecs_strbuf_appendflt(str, *(const double*)base, 0); break; case EcsIPtr: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)base)); break; case EcsUPtr: ecs_strbuf_append(str, "%u", *(const uintptr_t*)base); break; case EcsString: { const char *value = *ECS_CONST_CAST(const char**, base); if (value) { if (!is_expr) { ecs_strbuf_appendstr(str, value); } else { ecs_size_t length = ecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstrn(str, value, length); ecs_strbuf_appendch(str, '"'); } else { char *out = ecs_os_malloc(length + 3); ecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr(str, out); ecs_os_free(out); } } } else { ecs_strbuf_appendlit(str, "null"); } break; } case EcsEntity: { ecs_entity_t e = *(const ecs_entity_t*)base; if (!e) { ecs_strbuf_appendch(str, '0'); } else { ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); } break; } case EcsId: { ecs_id_t id = *(const ecs_id_t*)base; if (!id) { ecs_strbuf_appendch(str, '0'); } else { ecs_id_str_buf(world, id, str); } break; } default: ecs_err("invalid primitive kind"); return -1; } return 0; } /* Serialize enumeration */ static int flecs_expr_ser_enum( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); int32_t val = *(const int32_t*)base; /* Enumeration constants are stored in a map that is keyed on the * enumeration value. */ ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants, ecs_enum_constant_t, (ecs_map_key_t)val); if (!c) { char *path = ecs_get_fullpath(world, op->type); ecs_err("value %d is not valid for enum type '%s'", val, path); ecs_os_free(path); goto error; } ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); return 0; error: return -1; } /* Serialize bitmask */ static int flecs_expr_ser_bitmask( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); uint32_t value = *(const uint32_t*)ptr; ecs_strbuf_list_push(str, "", "|"); /* Multiple flags can be set at a given time. Iterate through all the flags * and append the ones that are set. */ ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); int count = 0; while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); count ++; value -= (uint32_t)key; } } if (value != 0) { /* All bits must have been matched by a constant */ char *path = ecs_get_fullpath(world, op->type); ecs_err( "value for bitmask %s contains bits (%u) that cannot be mapped to constant", path, value); ecs_os_free(path); goto error; } if (!count) { ecs_strbuf_list_appendstr(str, "0"); } ecs_strbuf_list_pop(str, ""); return 0; error: return -1; } /* Serialize elements of a contiguous array */ static int expr_ser_elements( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, int32_t elem_count, int32_t elem_size, ecs_strbuf_t *str, bool is_array) { ecs_strbuf_list_push(str, "[", ", "); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (flecs_expr_ser_type_ops( world, ops, op_count, ptr, str, is_array, true)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } ecs_strbuf_list_pop(str, "]"); return 0; } static int expr_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str, bool is_array) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vec_count(&ser->ops); return expr_ser_elements( world, ops, op_count, base, elem_count, comp->size, str, is_array); } /* Serialize array */ static int expr_ser_array( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsArray *a = ecs_get(world, op->type, EcsArray); ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); return expr_ser_type_elements( world, a->type, ptr, a->count, str, true); } /* Serialize vector */ static int expr_ser_vector( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const ecs_vec_t *value = base; const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(value); void *array = ecs_vec_first(value); /* Serialize contiguous buffer of vector */ return expr_ser_type_elements(world, v->type, array, count, str, false); } /* Forward serialization to the different type kinds */ static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str, bool is_expr) { switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpEnum: if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpBitmask: if (flecs_expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpArray: if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpVector: if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpScope: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str, is_expr)) { /* Unknown operation */ ecs_err("unknown serializer operation kind (%d)", op->kind); goto error; } break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int flecs_expr_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array, bool is_expr) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (in_array <= 0) { if (op->name) { ecs_strbuf_list_next(str); ecs_strbuf_append(str, "%s: ", op->name); } int32_t elem_count = op->count; if (elem_count > 1) { /* Serialize inline array */ if (expr_ser_elements(world, op, op->op_count, base, elem_count, op->size, str, true)) { return -1; } i += op->op_count - 1; continue; } } switch(op->kind) { case EcsOpPush: ecs_strbuf_list_push(str, "{", ", "); in_array --; break; case EcsOpPop: ecs_strbuf_list_pop(str, "}"); in_array ++; break; case EcsOpArray: case EcsOpVector: case EcsOpEnum: case EcsOpBitmask: case EcsOpScope: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { goto error; } break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } } return 0; error: return -1; } /* Iterate over the type ops of a type */ static int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str, bool is_expr) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, is_expr); } int ecs_ptr_to_expr_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (ser == NULL) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize value for type '%s'", path); ecs_os_free(path); goto error; } if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { goto error; } return 0; error: return -1; } char* ecs_ptr_to_expr( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_str_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (ser == NULL) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize value for type '%s'", path); ecs_os_free(path); goto error; } if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { goto error; } return 0; error: return -1; } char* ecs_ptr_to_str( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_primitive_to_expr_buf( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str) { return flecs_expr_ser_primitive(world, kind, base, str, true); } #endif /** * @file expr/utils.c * @brief String parsing utilities. */ #ifdef FLECS_EXPR #include char* ecs_chresc( char *out, char in, char delimiter) { char *bptr = out; switch(in) { case '\a': *bptr++ = '\\'; *bptr = 'a'; break; case '\b': *bptr++ = '\\'; *bptr = 'b'; break; case '\f': *bptr++ = '\\'; *bptr = 'f'; break; case '\n': *bptr++ = '\\'; *bptr = 'n'; break; case '\r': *bptr++ = '\\'; *bptr = 'r'; break; case '\t': *bptr++ = '\\'; *bptr = 't'; break; case '\v': *bptr++ = '\\'; *bptr = 'v'; break; case '\\': *bptr++ = '\\'; *bptr = '\\'; break; case '\033': *bptr = '['; /* Used for terminal colors */ break; default: if (in == delimiter) { *bptr++ = '\\'; *bptr = delimiter; } else { *bptr = in; } break; } *(++bptr) = '\0'; return bptr; } const char* ecs_chrparse( const char *in, char *out) { const char *result = in + 1; char ch; if (in[0] == '\\') { result ++; switch(in[1]) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '\\': ch = '\\'; break; case '"': ch = '"'; break; case '0': ch = '\0'; break; case ' ': ch = ' '; break; case '$': ch = '$'; break; default: goto error; } } else { ch = in[0]; } if (out) { *out = ch; } return result; error: return NULL; } ecs_size_t ecs_stresc( char *out, ecs_size_t n, char delimiter, const char *in) { const char *ptr = in; char ch, *bptr = out, buff[3]; ecs_size_t written = 0; while ((ch = *ptr++)) { if ((written += (ecs_size_t)(ecs_chresc( buff, ch, delimiter) - buff)) <= n) { /* If size != 0, an out buffer must be provided. */ ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); *bptr++ = buff[0]; if ((ch = buff[1])) { *bptr = ch; bptr++; } } } if (bptr) { while (written < n) { *bptr = '\0'; bptr++; written++; } } return written; error: return 0; } char* ecs_astresc( char delimiter, const char *in) { if (!in) { return NULL; } ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); char *out = ecs_os_malloc_n(char, len + 1); ecs_stresc(out, len, delimiter, in); out[len] = '\0'; return out; } static const char* flecs_parse_var_name( const char *ptr, char *token_out) { char ch, *bptr = token_out; while ((ch = *ptr)) { if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { goto error; } if (isalpha(ch) || isdigit(ch) || ch == '_') { *bptr = ch; bptr ++; ptr ++; } else { break; } } if (bptr == token_out) { goto error; } *bptr = '\0'; return ptr; error: return NULL; } static const char* flecs_parse_interpolated_str( const char *ptr, char *token_out) { char ch, *bptr = token_out; while ((ch = *ptr)) { if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { goto error; } if (ch == '\\') { if (ptr[1] == '}') { *bptr = '}'; bptr ++; ptr += 2; continue; } } if (ch != '}') { *bptr = ch; bptr ++; ptr ++; } else { ptr ++; break; } } if (bptr == token_out) { goto error; } *bptr = '\0'; return ptr; error: return NULL; } char* ecs_interpolate_string( ecs_world_t *world, const char *str, const ecs_vars_t *vars) { char token[ECS_MAX_TOKEN_SIZE]; ecs_strbuf_t result = ECS_STRBUF_INIT; const char *ptr; char ch; for(ptr = str; (ch = *ptr); ptr++) { if (ch == '\\') { ptr ++; if (ptr[0] == '$') { ecs_strbuf_appendch(&result, '$'); continue; } if (ptr[0] == '\\') { ecs_strbuf_appendch(&result, '\\'); continue; } if (ptr[0] == '{') { ecs_strbuf_appendch(&result, '{'); continue; } if (ptr[0] == '}') { ecs_strbuf_appendch(&result, '}'); continue; } ptr --; } if (ch == '$') { ptr = flecs_parse_var_name(ptr + 1, token); if (!ptr) { ecs_parser_error(NULL, str, ptr - str, "invalid variable name '%s'", ptr); goto error; } ecs_expr_var_t *var = ecs_vars_lookup(vars, token); if (!var) { ecs_parser_error(NULL, str, ptr - str, "unresolved variable '%s'", token); goto error; } if (ecs_ptr_to_str_buf( world, var->value.type, var->value.ptr, &result)) { goto error; } ptr --; } else if (ch == '{') { ptr = flecs_parse_interpolated_str(ptr + 1, token); if (!ptr) { ecs_parser_error(NULL, str, ptr - str, "invalid interpolated expression"); goto error; } ecs_parse_expr_desc_t expr_desc = { .vars = ECS_CONST_CAST(ecs_vars_t*, vars) }; ecs_value_t expr_result = {0}; if (!ecs_parse_expr(world, token, &expr_result, &expr_desc)) { goto error; } if (ecs_ptr_to_str_buf( world, expr_result.type, expr_result.ptr, &result)) { goto error; } ecs_value_free(world, expr_result.type, expr_result.ptr); ptr --; } else { ecs_strbuf_appendch(&result, ch); } } return ecs_strbuf_get(&result); error: return NULL; } void ecs_iter_to_vars( const ecs_iter_t *it, ecs_vars_t *vars, int offset) { ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); /* Set variable for $this */ if (it->count) { ecs_expr_var_t *var = ecs_vars_lookup(vars, "this"); if (!var) { ecs_value_t v = { .ptr = &it->entities[offset], .type = ecs_id(ecs_entity_t) }; var = ecs_vars_declare_w_value(vars, "this", &v); var->owned = false; } else { var->value.ptr = &it->entities[offset]; } } /* Set variables for fields */ { int32_t i, field_count = it->field_count; for (i = 0; i < field_count; i ++) { ecs_size_t size = it->sizes[i]; if (!size) { continue; } void *ptr = it->ptrs[i]; if (!ptr) { continue; } ptr = ECS_OFFSET(ptr, offset * size); char name[16]; ecs_os_sprintf(name, "%d", i + 1); ecs_expr_var_t *var = ecs_vars_lookup(vars, name); if (!var) { ecs_value_t v = { .ptr = ptr, .type = it->ids[i] }; var = ecs_vars_declare_w_value(vars, name, &v); var->owned = false; } else { ecs_check(var->value.type == it->ids[i], ECS_INVALID_PARAMETER, NULL); var->value.ptr = ptr; } } } /* Set variables for query variables */ { int32_t i, var_count = it->variable_count; for (i = 1 /* skip this variable */ ; i < var_count; i ++) { ecs_entity_t *e_ptr = NULL; ecs_var_t *query_var = &it->variables[i]; if (query_var->entity) { e_ptr = &query_var->entity; } else { ecs_table_range_t *range = &query_var->range; if (range->count == 1) { ecs_entity_t *entities = range->table->data.entities.array; e_ptr = &entities[range->offset]; } } if (!e_ptr) { continue; } ecs_expr_var_t *var = ecs_vars_lookup(vars, it->variable_names[i]); if (!var) { ecs_value_t v = { .ptr = e_ptr, .type = ecs_id(ecs_entity_t) }; var = ecs_vars_declare_w_value(vars, it->variable_names[i], &v); var->owned = false; } else { ecs_check(var->value.type == ecs_id(ecs_entity_t), ECS_INVALID_PARAMETER, NULL); var->value.ptr = e_ptr; } } } error: return; } #endif /** * @file expr/vars.c * @brief Utilities for variable substitution in flecs string expressions. */ #ifdef FLECS_EXPR static void flecs_expr_var_scope_init( ecs_world_t *world, ecs_expr_var_scope_t *scope, ecs_expr_var_scope_t *parent) { flecs_name_index_init(&scope->var_index, &world->allocator); ecs_vec_init_t(&world->allocator, &scope->vars, ecs_expr_var_t, 0); scope->parent = parent; } static void flecs_expr_var_scope_fini( ecs_world_t *world, ecs_expr_var_scope_t *scope) { ecs_vec_t *vars = &scope->vars; int32_t i, count = vars->count; for (i = 0; i < count; i++) { ecs_expr_var_t *var = ecs_vec_get_t(vars, ecs_expr_var_t, i); if (var->owned) { ecs_value_free(world, var->value.type, var->value.ptr); } flecs_strfree(&world->allocator, var->name); } ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t); flecs_name_index_fini(&scope->var_index); } void ecs_vars_init( ecs_world_t *world, ecs_vars_t *vars) { flecs_expr_var_scope_init(world, &vars->root, NULL); vars->world = world; vars->cur = &vars->root; } void ecs_vars_fini( ecs_vars_t *vars) { ecs_expr_var_scope_t *cur = vars->cur, *next; do { next = cur->parent; flecs_expr_var_scope_fini(vars->world, cur); if (cur != &vars->root) { flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, cur); } else { break; } } while ((cur = next)); } void ecs_vars_push( ecs_vars_t *vars) { ecs_expr_var_scope_t *scope = flecs_calloc_t(&vars->world->allocator, ecs_expr_var_scope_t); flecs_expr_var_scope_init(vars->world, scope, vars->cur); vars->cur = scope; } int ecs_vars_pop( ecs_vars_t *vars) { ecs_expr_var_scope_t *scope = vars->cur; ecs_check(scope != &vars->root, ECS_INVALID_OPERATION, NULL); vars->cur = scope->parent; flecs_expr_var_scope_fini(vars->world, scope); flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, scope); return 0; error: return 1; } ecs_expr_var_t* ecs_vars_declare( ecs_vars_t *vars, const char *name, ecs_entity_t type) { ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_expr_var_scope_t *scope = vars->cur; ecs_hashmap_t *var_index = &scope->var_index; if (flecs_name_index_find(var_index, name, 0, 0) != 0) { ecs_err("variable %s redeclared", name); goto error; } ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, &scope->vars, ecs_expr_var_t); var->value.ptr = ecs_value_new(vars->world, type); if (!var->value.ptr) { goto error; } var->value.type = type; var->name = flecs_strdup(&vars->world->allocator, name); var->owned = true; flecs_name_index_ensure(var_index, flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); return var; error: return NULL; } ecs_expr_var_t* ecs_vars_declare_w_value( ecs_vars_t *vars, const char *name, ecs_value_t *value) { ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_var_scope_t *scope = vars->cur; ecs_hashmap_t *var_index = &scope->var_index; if (flecs_name_index_find(var_index, name, 0, 0) != 0) { ecs_err("variable %s redeclared", name); ecs_value_free(vars->world, value->type, value->ptr); goto error; } ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, &scope->vars, ecs_expr_var_t); var->value = *value; var->name = flecs_strdup(&vars->world->allocator, name); var->owned = true; value->ptr = NULL; /* Take ownership, prevent double free */ flecs_name_index_ensure(var_index, flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); return var; error: return NULL; } static ecs_expr_var_t* flecs_vars_scope_lookup( ecs_expr_var_scope_t *scope, const char *name) { uint64_t var_id = flecs_name_index_find(&scope->var_index, name, 0, 0); if (var_id == 0) { if (scope->parent) { return flecs_vars_scope_lookup(scope->parent, name); } return NULL; } return ecs_vec_get_t(&scope->vars, ecs_expr_var_t, flecs_uto(int32_t, var_id - 1)); } ecs_expr_var_t* ecs_vars_lookup( const ecs_vars_t *vars, const char *name) { return flecs_vars_scope_lookup(vars->cur, name); } #endif /** * @file json/deserialize.c * @brief Deserialize JSON strings into (component) values. */ /** * @file json/json.h * @brief Internal functions for JSON addon. */ #ifdef FLECS_JSON /* Deserialize from JSON */ typedef enum ecs_json_token_t { JsonObjectOpen, JsonObjectClose, JsonArrayOpen, JsonArrayClose, JsonColon, JsonComma, JsonNumber, JsonString, JsonBoolean, JsonTrue, JsonFalse, JsonNull, JsonLargeInt, JsonLargeString, JsonInvalid } ecs_json_token_t; typedef struct ecs_json_value_ser_ctx_t { ecs_entity_t type; const EcsMetaTypeSerialized *ser; char *id_label; bool initialized; } ecs_json_value_ser_ctx_t; /* Cached data for serializer */ typedef struct ecs_json_ser_ctx_t { ecs_id_record_t *idr_doc_name; ecs_id_record_t *idr_doc_color; ecs_json_value_ser_ctx_t value_ctx[64]; } ecs_json_ser_ctx_t; const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token); const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf); const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_string( const char *json, char *token, char **out, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_array( const char *json, char *token, const ecs_from_json_desc_t *desc); /* Serialize to JSON */ void flecs_json_next( ecs_strbuf_t *buf); void flecs_json_number( ecs_strbuf_t *buf, double value); void flecs_json_u32( ecs_strbuf_t *buf, uint32_t value); void flecs_json_true( ecs_strbuf_t *buf); void flecs_json_false( ecs_strbuf_t *buf); void flecs_json_bool( ecs_strbuf_t *buf, bool value); void flecs_json_array_push( ecs_strbuf_t *buf); void flecs_json_array_pop( ecs_strbuf_t *buf); void flecs_json_object_push( ecs_strbuf_t *buf); void flecs_json_object_pop( ecs_strbuf_t *buf); void flecs_json_string( ecs_strbuf_t *buf, const char *value); void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value); void flecs_json_member( ecs_strbuf_t *buf, const char *name); void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len); #define flecs_json_memberl(buf, name)\ flecs_json_membern(buf, name, sizeof(name) - 1) void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_color( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id); void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id); ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind); bool flecs_json_serialize_get_field_ctx( const ecs_world_t *world, const ecs_iter_t *it, int32_t f, ecs_json_ser_ctx_t *ser_ctx); int flecs_json_serialize_iter_result_rows( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx); bool flecs_json_serialize_iter_result_is_set( const ecs_iter_t *it, ecs_strbuf_t *buf); bool flecs_json_skip_variable( const char *name); void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, const ecs_filter_t *q, int field, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx); void flecs_json_serialize_query( const ecs_world_t *world, const ecs_filter_t *q, ecs_strbuf_t *buf); int flecs_json_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str); #endif #include #ifdef FLECS_JSON typedef struct { ecs_allocator_t *a; ecs_vec_t records; ecs_vec_t result_ids; ecs_vec_t columns_set; ecs_map_t anonymous_ids; ecs_map_t missing_reflection; } ecs_from_json_ctx_t; static void flecs_from_json_ctx_init( ecs_allocator_t *a, ecs_from_json_ctx_t *ctx) { ctx->a = a; ecs_vec_init_t(a, &ctx->records, ecs_record_t*, 0); ecs_vec_init_t(a, &ctx->result_ids, ecs_id_t, 0); ecs_vec_init_t(a, &ctx->columns_set, ecs_id_t, 0); ecs_map_init(&ctx->anonymous_ids, a); ecs_map_init(&ctx->missing_reflection, a); } static void flecs_from_json_ctx_fini( ecs_from_json_ctx_t *ctx) { ecs_vec_fini_t(ctx->a, &ctx->records, ecs_record_t*); ecs_vec_fini_t(ctx->a, &ctx->result_ids, ecs_record_t*); ecs_vec_fini_t(ctx->a, &ctx->columns_set, ecs_id_t); ecs_map_fini(&ctx->anonymous_ids); ecs_map_fini(&ctx->missing_reflection); } static const char* flecs_json_parse_path( const ecs_world_t *world, const char *json, char *token, ecs_entity_t *out, const ecs_from_json_desc_t *desc) { char *path = NULL; json = flecs_json_expect_string(json, token, &path, desc); if (!json) { goto error; } ecs_entity_t result = ecs_lookup(world, path); if (!result) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "unresolved identifier '%s'", path); goto error; } *out = result; if (path != token) { ecs_os_free(path); } return json; error: if (path != token) { ecs_os_free(path); } return NULL; } const char* ecs_ptr_from_json( const ecs_world_t *world, ecs_entity_t type, void *ptr, const char *json, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; char *token = token_buffer; int depth = 0; const char *name = NULL; const char *expr = NULL; ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); if (cur.valid == false) { return NULL; } if (desc) { name = desc->name; expr = desc->expr; cur.lookup_action = desc->lookup_action; cur.lookup_ctx = desc->lookup_ctx; } while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } token = ecs_strbuf_get(&large_token); token_kind = JsonString; } if (token_kind == JsonObjectOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '['"); return NULL; } } else if (token_kind == JsonObjectClose) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonArrayOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '{'"); return NULL; } } else if (token_kind == JsonArrayClose) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonComma) { if (ecs_meta_next(&cur) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonString) { const char *lah = flecs_json_parse( json, &token_kind, t_lah); if (token_kind == JsonColon) { /* Member assignment */ json = lah; if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } } else if (token_kind == JsonNumber) { double number = atof(token); if (ecs_meta_set_float(&cur, number) != 0) { goto error; } } else if (token_kind == JsonLargeInt) { int64_t number = flecs_ito(int64_t, atoll(token)); if (ecs_meta_set_int(&cur, number) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonTrue) { if (ecs_meta_set_bool(&cur, true) != 0) { goto error; } } else if (token_kind == JsonFalse) { if (ecs_meta_set_bool(&cur, false) != 0) { goto error; } } else { goto error; } if (token != token_buffer) { ecs_os_free(token); token = token_buffer; } if (!depth) { break; } } return json; error: return NULL; } const char* ecs_entity_from_json( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc_param) { ecs_json_token_t token_kind = 0; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; if (desc_param) { desc = *desc_param; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonObjectClose) { return lah; } json = flecs_json_expect_member(json, token, &desc); if (!json) { return NULL; } if (!ecs_os_strcmp(token, "path")) { char *path = NULL; json = flecs_json_expect_string(json, token, &path, &desc); if (!json) { goto error; } ecs_add_fullpath(world, e, path); if (path != token) { ecs_os_free(path); } json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } if (token_kind == JsonObjectClose) { return json; } else if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "unexpected character"); goto error; } json = flecs_json_expect_member_name(json, token, "ids", &desc); if (!json) { goto error; } } else if (ecs_os_strcmp(token, "ids")) { ecs_parser_error(name, expr, json - expr, "expected member 'ids'"); goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } ids = json; json = flecs_json_skip_array(json, token, &desc); if (!json) { return NULL; } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonObjectClose) { if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "expected ','"); goto error; } json = flecs_json_expect_member_name(json, token, "values", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } values = json; } do { ecs_entity_t first = 0, second = 0, type_id = 0; ecs_id_t id; ids = flecs_json_parse(ids, &token_kind, token); if (!ids) { goto error; } if (token_kind == JsonArrayClose) { if (values) { if (values[0] != ']') { ecs_parser_error(name, expr, values - expr, "expected ']'"); goto error; } json = ecs_parse_ws_eol(values + 1); } else { json = ids; } break; } else if (token_kind == JsonArrayOpen) { ids = flecs_json_parse_path(world, ids, token, &first, &desc); if (!ids) { goto error; } ids = flecs_json_parse(ids, &token_kind, token); if (!ids) { goto error; } if (token_kind == JsonComma) { /* Id is a pair*/ ids = flecs_json_parse_path(world, ids, token, &second, &desc); if (!ids) { goto error; } ids = flecs_json_expect(ids, JsonArrayClose, token, &desc); if (!ids) { goto error; } } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); goto error; } lah = flecs_json_parse(ids, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonComma) { ids = lah; } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, lah - expr, "expected ',' or ']'"); goto error; } } else { ecs_parser_error(name, expr, lah - expr, "expected '[' or ']'"); goto error; } if (second) { id = ecs_pair(first, second); type_id = ecs_get_typeid(world, id); if (!type_id) { ecs_parser_error(name, expr, ids - expr, "id is not a type"); goto error; } } else { id = first; type_id = first; } /* Get mutable pointer */ void *comp_ptr = ecs_ensure_id(world, e, id); if (!comp_ptr) { char *idstr = ecs_id_str(world, id); ecs_parser_error(name, expr, json - expr, "id '%s' is not a valid component", idstr); ecs_os_free(idstr); goto error; } if (values) { ecs_from_json_desc_t parse_desc = { .name = name, .expr = expr, }; values = ecs_ptr_from_json( world, type_id, comp_ptr, values, &parse_desc); if (!values) { goto error; } lah = flecs_json_parse(values, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonComma) { values = lah; } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, json - expr, "expected ',' or ']'"); goto error; } else { values = ecs_parse_ws_eol(values); } ecs_modified_id(world, e, id); } } while(ids[0]); return flecs_json_expect(json, JsonObjectClose, token, &desc); error: return NULL; } static ecs_entity_t flecs_json_new_id( ecs_world_t *world, ecs_entity_t ser_id) { /* Try to honor low id requirements */ if (ser_id < FLECS_HI_COMPONENT_ID) { return ecs_new_low_id(world); } else { return ecs_new_id(world); } } static ecs_entity_t flecs_json_lookup( ecs_world_t *world, ecs_entity_t parent, const char *name, const ecs_from_json_desc_t *desc) { ecs_entity_t scope = 0; if (parent) { scope = ecs_set_scope(world, parent); } ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); if (parent) { ecs_set_scope(world, scope); } return result; } static void flecs_json_mark_reserved( ecs_map_t *anonymous_ids, ecs_entity_t e) { ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); reserved[0] = 0; } static bool flecs_json_name_is_anonymous( const char *name) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (isdigit(name[0])) { const char *ptr; for (ptr = name + 1; *ptr; ptr ++) { if (!isdigit(*ptr)) { break; } } if (!(*ptr)) { return true; } } return false; } static ecs_entity_t flecs_json_ensure_entity( ecs_world_t *world, const char *name, ecs_map_t *anonymous_ids) { ecs_entity_t e = 0; if (flecs_json_name_is_anonymous(name)) { /* Anonymous entity, find or create mapping to new id */ ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(name)); ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); if (deser_id) { if (!deser_id[0]) { /* Id is already issued by deserializer, create new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } else { /* Id mapping exists */ } } else { /* Id has not yet been issued by deserializer, which means it's safe * to use. This allows the deserializer to bind to existing * anonymous ids, as they will never be reissued. */ deser_id = ecs_map_ensure(anonymous_ids, ser_id); if (!ecs_exists(world, ser_id) || (ecs_is_alive(world, ser_id) && !ecs_get_name(world, ser_id))) { /* Only use existing id if it's alive or doesn't exist yet. The * id could have been recycled for another entity * Also don't use existing id if the existing entity is not * anonymous. */ deser_id[0] = ser_id; ecs_make_alive(world, ser_id); } else { /* If id exists and is not alive, create a new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } } e = deser_id[0]; } else { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); if (!e) { e = ecs_entity(world, { .name = name }); flecs_json_mark_reserved(anonymous_ids, e); } } return e; } static ecs_table_t* flecs_json_parse_table( ecs_world_t *world, const char *json, char *token, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; ecs_vec_clear(&ctx->result_ids); do { ecs_id_t id = 0; json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } char *first_name = NULL; json = flecs_json_expect_string(json, token, &first_name, desc); if (!json) { goto error; } ecs_entity_t first = flecs_json_lookup(world, 0, first_name, desc); if (first_name != token) { ecs_os_free(first_name); } if (!first) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { char *second_name = NULL; json = flecs_json_expect_string(json, token, &second_name, desc); if (!json) { goto error; } ecs_entity_t second = flecs_json_lookup(world, 0, second_name, desc); if (second_name != token) { ecs_os_free(second_name); } if (!second) { goto error; } id = ecs_pair(first, second); json = flecs_json_expect(json, JsonArrayClose, token, desc); if (!json) { goto error; } } else if (token_kind == JsonArrayClose) { id = first; } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']"); goto error; } ecs_vec_append_t(ctx->a, &ctx->result_ids, ecs_id_t)[0] = id; const char *lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = lah; } else if (token_kind == JsonArrayClose) { break; } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while (json[0]); /* Make a copy of the ids array because we need the original order for * deserializing the component values, and the sorted order for finding or * creating the table. */ ecs_vec_t id_copy = ecs_vec_copy_t(ctx->a, &ctx->result_ids, ecs_id_t); ecs_type_t type = { .array = ecs_vec_first(&id_copy), .count = ecs_vec_count(&id_copy) }; qsort(type.array, flecs_itosize(type.count), sizeof(ecs_id_t), flecs_id_qsort_cmp); ecs_table_t *table = flecs_table_find_or_create(world, &type); if (!table) { goto error; } ecs_vec_fini_t(ctx->a, &id_copy, ecs_id_t); return table; error: return NULL; } static void flecs_json_zeromem_table( ecs_table_t *table) { int32_t count = ecs_vec_count(&table->data.entities); int32_t i, column_count = table->column_count; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &table->data.columns[i]; ecs_type_info_t *ti = column->ti; if (!ti->hooks.ctor) { void *ptr = ecs_vec_first(&column->data); ecs_os_memset(ptr, 0, ti->size * count); } } } static int flecs_json_parse_entities( ecs_world_t *world, ecs_table_t *table, ecs_entity_t parent, const char *json, char *token, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { char name_token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t token_kind = 0; ecs_vec_clear(&ctx->records); do { char *name = NULL; json = flecs_json_parse(json, &token_kind, name_token); if (!json) { goto error; } if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } name = ecs_strbuf_get(&large_token); token_kind = JsonString; } else { name = name_token; } if ((token_kind != JsonNumber) && (token_kind != JsonString)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected number or string"); goto error; } ecs_entity_t e = flecs_json_lookup(world, parent, name, desc); ecs_record_t *r = flecs_entities_try(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (r->table != table) { bool cleared = false; if (r->table) { ecs_commit(world, e, r, r->table, NULL, &r->table->type); cleared = true; } ecs_commit(world, e, r, table, &table->type, NULL); if (cleared) { char *entity_name = strrchr(name, '.'); if (entity_name) { entity_name ++; } else { entity_name = name; } if (!flecs_json_name_is_anonymous(entity_name)) { ecs_set_name(world, e, entity_name); } } } if (name != name_token) { ecs_os_free(name); } if (table != r->table) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid entity identifier"); goto error; } else { ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); ecs_record_t** elem = ecs_vec_append_t( ctx->a, &ctx->records, ecs_record_t*); *elem = r; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while(json[0]); return 0; error: return -1; } static const char* flecs_json_missing_reflection( ecs_world_t *world, ecs_id_t id, const char *json, char *token, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { if (!desc->strict && ecs_map_get(&ctx->missing_reflection, id)) { json = flecs_json_skip_array(json, token, desc); goto done; } /* Don't spam log when multiple values of a type can't be deserialized */ ecs_map_ensure(&ctx->missing_reflection, id); char *id_str = ecs_id_str(world, id); ecs_parser_error(desc->name, desc->expr, json - desc->expr, "missing reflection for '%s'", id_str); ecs_os_free(id_str); done: if (desc->strict) { return NULL; } else { return flecs_json_skip_array(json, token, desc); } } static const char* flecs_json_parse_column( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, const char *json, char *token, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { /* If deserializing id caused trouble before, don't bother trying again */ if (!desc->strict && ecs_map_get(&ctx->missing_reflection, id)) { return flecs_json_skip_array(json, token, desc); } ecs_table_record_t *tr = flecs_table_record_get(world, table, id); /* Table was created with this id, it should exist */ ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* If id is not a component, reflection data is missing */ int32_t column_index = tr->column; if (column_index == -1) { return flecs_json_missing_reflection(world, id, json, token, ctx, desc); } ecs_json_token_t token_kind = 0; ecs_column_t *column = &table->data.columns[column_index]; ecs_type_info_t *ti = column->ti; ecs_size_t size = ti->size; ecs_entity_t type = ti->component; ecs_record_t **record_array = ecs_vec_first_t(&ctx->records, ecs_record_t*); int32_t entity = 0; bool values_set = false; const char *values_start = json; do { ecs_record_t *r = record_array[entity]; int32_t row = ECS_RECORD_TO_ROW(r->row); void *ptr = ecs_vec_get(&column->data, size, row); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); json = ecs_ptr_from_json(world, type, ptr, json, desc); if (!json) { if (desc->strict) { break; } else { return flecs_json_missing_reflection( world, id, values_start, token, ctx, desc); } } else { values_set = true; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); } entity ++; } while (json[0]); if (values_set) { ecs_id_t *id_set = ecs_vec_append_t( ctx->a, &ctx->columns_set, ecs_id_t); *id_set = id; } return json; } static const char* flecs_json_parse_values( ecs_world_t *world, ecs_table_t *table, const char *json, char *token, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; int32_t value = 0; ecs_vec_clear(&ctx->columns_set); do { if (value >= table->type.count) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "more elements in values array than expected for table"); goto error; } json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } if (token_kind == JsonArrayClose) { break; } else if (token_kind == JsonArrayOpen) { ecs_id_t *idptr = ecs_vec_get_t(&ctx->result_ids, ecs_id_t, value); ecs_assert(idptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = idptr[0]; json = flecs_json_parse_column(world, table, id, json, token, ctx, desc); if (!json) { goto error; } } else if (token_kind == JsonNumber) { if (!ecs_os_strcmp(token, "0")) { /* no data */ } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "unexpected number"); goto error; } } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } value ++; } while (json[0]); /* Send OnSet notifications */ ecs_defer_begin(world); ecs_type_t type = { .array = ctx->columns_set.array, .count = ctx->columns_set.count }; int32_t table_count = ecs_table_count(table); int32_t i, record_count = ecs_vec_count(&ctx->records); /* If the entire table was inserted, send bulk notification */ if (table_count == ecs_vec_count(&ctx->records)) { flecs_notify_on_set( world, table, 0, ecs_table_count(table), &type, true); } else { ecs_record_t **rvec = ecs_vec_first_t(&ctx->records, ecs_record_t*); for (i = 0; i < record_count; i ++) { ecs_record_t *r = rvec[i]; int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_notify_on_set(world, table, row, 1, &type, true); } } ecs_defer_end(world); return json; error: return NULL; } static const char* flecs_json_parse_result( ecs_world_t *world, const char *json, char *token, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; const char *ids = NULL, *values = NULL, *entities = NULL; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "ids", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } ids = json; /* store start of ids array */ json = flecs_json_skip_array(json, token, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } ecs_entity_t parent = 0; if (!ecs_os_strcmp(token, "parent")) { char *parent_name = NULL; json = flecs_json_expect_string(json, token, &parent_name, desc); if (!json) { goto error; } parent = flecs_json_lookup(world, 0, parent_name, desc); if (parent_name != token) { ecs_os_free(parent_name); } json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } } if (ecs_os_strcmp(token, "entities")) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected 'entities'"); goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } entities = json; /* store start of entity id array */ json = flecs_json_skip_array(json, token, desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = flecs_json_expect_member_name(json, token, "values", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } values = json; /* store start of entities array */ } else if (token_kind != JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or '}'"); goto error; } /* Find table from ids */ ecs_table_t *table = flecs_json_parse_table(world, ids, token, ctx, desc); if (!table) { goto error; } /* Add entities to table */ if (flecs_json_parse_entities(world, table, parent, entities, token, ctx, desc)) { goto error; } /* If not parsing in strict mode, initialize component arrays to 0. This * ensures that even if components can't be deserialized (because of * incomplete reflection data) component values aren't left uninitialized */ if (!desc->strict) { flecs_json_zeromem_table(table); } /* Parse values */ if (values) { json = flecs_json_parse_values(world, table, values, token, ctx, desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = flecs_json_expect_member_name(json, token, "alerts", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonBoolean, token, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonObjectClose, token, desc); if (!json) { goto error; } } else if (token_kind != JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected '}'"); goto error; } } return json; error: return NULL; } const char* ecs_world_from_json( ecs_world_t *world, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; ecs_allocator_t *a = &world->allocator; ecs_from_json_ctx_t ctx; flecs_from_json_ctx_init(a, &ctx); const char *name = NULL, *expr = json, *lah; if (desc_arg) { desc = *desc_arg; } if (!desc.lookup_action) { desc.lookup_action = (ecs_entity_t(*)( const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; desc.lookup_ctx = &ctx.anonymous_ids; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "results", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { json = lah; goto end; } do { json = flecs_json_parse_result(world, json, token, &ctx, &desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "expected ',' or ']'"); goto error; } } while(json && json[0]); end: flecs_from_json_ctx_fini(&ctx); json = flecs_json_expect(json, JsonObjectClose, token, &desc); if (!json) { goto error; } return json; error: flecs_from_json_ctx_fini(&ctx); return NULL; } const char* ecs_world_from_json_file( ecs_world_t *world, const char *filename, const ecs_from_json_desc_t *desc) { char *json = flecs_load_from_file(filename); if (!json) { ecs_err("file not found: %s", filename); return NULL; } const char *result = ecs_world_from_json(world, json, desc); ecs_os_free(json); return result; } #endif /** * @file json/json.c * @brief JSON serializer utilities. */ #include #ifdef FLECS_JSON static const char* flecs_json_token_str( ecs_json_token_t token_kind) { switch(token_kind) { case JsonObjectOpen: return "{"; case JsonObjectClose: return "}"; case JsonArrayOpen: return "["; case JsonArrayClose: return "]"; case JsonColon: return ":"; case JsonComma: return ","; case JsonNumber: return "number"; case JsonLargeInt: return "large integer"; case JsonLargeString: case JsonString: return "string"; case JsonBoolean: return "bool"; case JsonTrue: return "true"; case JsonFalse: return "false"; case JsonNull: return "null"; case JsonInvalid: return "invalid"; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return "<>"; } const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); json = ecs_parse_ws_eol(json); char ch = json[0]; if (ch == '{') { token_kind[0] = JsonObjectOpen; return json + 1; } else if (ch == '}') { token_kind[0] = JsonObjectClose; return json + 1; } else if (ch == '[') { token_kind[0] = JsonArrayOpen; return json + 1; } else if (ch == ']') { token_kind[0] = JsonArrayClose; return json + 1; } else if (ch == ':') { token_kind[0] = JsonColon; return json + 1; } else if (ch == ',') { token_kind[0] = JsonComma; return json + 1; } else if (ch == '"') { const char *start = json; char *token_ptr = token; json ++; for (; (ch = json[0]); ) { if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { /* Token doesn't fit in buffer, signal to app to try again with * dynamic buffer. */ token_kind[0] = JsonLargeString; return start; } if (ch == '"') { json ++; token_ptr[0] = '\0'; break; } json = ecs_chrparse(json, token_ptr ++); } if (!ch) { token_kind[0] = JsonInvalid; return NULL; } else { token_kind[0] = JsonString; return json; } } else if (isdigit(ch) || (ch == '-')) { token_kind[0] = JsonNumber; const char *result = ecs_parse_digit(json, token); /* Cheap initial check if parsed token could represent large int */ if (result - json > 15) { /* Less cheap secondary check to see if number is integer */ if (!strchr(token, '.')) { token_kind[0] = JsonLargeInt; } } return result; } else if (isalpha(ch)) { if (!ecs_os_strncmp(json, "null", 4)) { token_kind[0] = JsonNull; json += 4; } else if (!ecs_os_strncmp(json, "true", 4)) { token_kind[0] = JsonTrue; json += 4; } else if (!ecs_os_strncmp(json, "false", 5)) { token_kind[0] = JsonFalse; json += 5; } if (isalpha(json[0]) || isdigit(json[0])) { token_kind[0] = JsonInvalid; return NULL; } return json; } else { token_kind[0] = JsonInvalid; return NULL; } } const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf) { if (json[0] != '"') { return NULL; /* can only parse strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = ecs_chrparse(json, &ch_out); ecs_strbuf_appendch(buf, ch_out); } if (!ch) { return NULL; } else { return json; } } const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc) { /* Strings must be handled by flecs_json_expect_string for LargeString */ ecs_assert(token_kind != JsonString, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t kind = 0; json = flecs_json_parse(json, &kind, token); if (kind == JsonInvalid) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (kind != token_kind) { if (token_kind == JsonBoolean && (kind == JsonTrue || kind == JsonFalse)) { /* ok */ } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s, got %s", flecs_json_token_str(token_kind), flecs_json_token_str(kind)); return NULL; } } return json; } const char* flecs_json_expect_string( const char *json, char *token, char **out, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonInvalid) { ecs_parser_error( desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (token_kind != JsonString && token_kind != JsonLargeString) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected string"); return NULL; } if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { return NULL; } if (out) { *out = ecs_strbuf_get(&large_token); } else { ecs_strbuf_reset(&large_token); } } else if (out) { *out = token; } return json; } const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc) { char *out = NULL; json = flecs_json_expect_string(json, token, &out, desc); if (!json) { return NULL; } if (out != token) { ecs_os_free(out); } json = flecs_json_expect(json, JsonColon, token, desc); if (!json) { return NULL; } return json; } const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc) { json = flecs_json_expect_member(json, token, desc); if (!json) { return NULL; } if (ecs_os_strcmp(token, member_name)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected member '%s'", member_name); return NULL; } return json; } static const char* flecs_json_skip_string( const char *json) { if (json[0] != '"') { return NULL; /* can only skip strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = ecs_chrparse(json, &ch_out); } if (!ch) { return NULL; } else { return json; } } const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_object(json, token, desc); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_array(json, token, desc); } else if (token_kind == JsonLargeString) { json = flecs_json_skip_string(json); } else if (token_kind == JsonObjectClose) { return json; } else if (token_kind == JsonArrayClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }, got ]"); return NULL; } ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }, got end of string"); return NULL; } const char* flecs_json_skip_array( const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_object(json, token, desc); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_array(json, token, desc); } else if (token_kind == JsonLargeString) { json = flecs_json_skip_string(json); } else if (token_kind == JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); return NULL; } else if (token_kind == JsonArrayClose) { return json; } ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); return NULL; } void flecs_json_next( ecs_strbuf_t *buf) { ecs_strbuf_list_next(buf); } void flecs_json_number( ecs_strbuf_t *buf, double value) { ecs_strbuf_appendflt(buf, value, '"'); } void flecs_json_u32( ecs_strbuf_t *buf, uint32_t value) { ecs_strbuf_appendint(buf, flecs_uto(int64_t, value)); } void flecs_json_true( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "true"); } void flecs_json_false( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "false"); } void flecs_json_bool( ecs_strbuf_t *buf, bool value) { if (value) { flecs_json_true(buf); } else { flecs_json_false(buf); } } void flecs_json_array_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "[", ", "); } void flecs_json_array_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "]"); } void flecs_json_object_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "{", ", "); } void flecs_json_object_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "}"); } void flecs_json_string( ecs_strbuf_t *buf, const char *value) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, value); ecs_strbuf_appendch(buf, '"'); } void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value) { ecs_size_t length = ecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, value, length); ecs_strbuf_appendch(buf, '"'); } else { char *out = ecs_os_malloc(length + 3); ecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr(buf, out); ecs_os_free(out); } } void flecs_json_member( ecs_strbuf_t *buf, const char *name) { flecs_json_membern(buf, name, ecs_os_strlen(name)); } void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len) { ecs_strbuf_list_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, name, name_len); ecs_strbuf_appendlit(buf, "\":"); } void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } static const char* flecs_json_entity_label( const ecs_world_t *world, ecs_entity_t e) { const char *lbl = NULL; if (!e) { return "0"; } #ifdef FLECS_DOC lbl = ecs_doc_get_name(world, e); #else lbl = ecs_get_name(world, e); #endif return lbl; } void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { const char *lbl = flecs_json_entity_label(world, e); if (lbl) { flecs_json_string_escape(buf, lbl); } else { ecs_strbuf_appendch(buf, '0'); } } void flecs_json_color( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { (void)world; (void)e; const char *color = NULL; #ifdef FLECS_DOC color = ecs_doc_get_color(world, e); #endif if (color) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, color); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '0'); } } void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_appendch(buf, '['); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } ecs_strbuf_appendch(buf, ']'); } void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); { const char *lbl = flecs_json_entity_label(world, first); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ','); { const char *lbl = flecs_json_entity_label(world, second); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ')'); } else { const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } } ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind) { return kind - EcsOpPrimitive; } #endif /** * @file json/serialize.c * @brief Serialize (component) values to JSON strings. */ #ifdef FLECS_JSON static int flecs_json_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array); static int flecs_json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str); /* Serialize enumeration */ static int json_ser_enum( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); int32_t value = *(const int32_t*)base; /* Enumeration constants are stored in a map that is keyed on the * enumeration value. */ ecs_enum_constant_t *constant = ecs_map_get_deref(&enum_type->constants, ecs_enum_constant_t, (ecs_map_key_t)value); if (!constant) { /* If the value is not found, it is not a valid enumeration constant */ char *name = ecs_get_fullpath(world, op->type); ecs_err("enumeration value '%d' of type '%s' is not a valid constant", value, name); ecs_os_free(name); goto error; } ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); ecs_strbuf_appendch(str, '"'); return 0; error: return -1; } /* Serialize bitmask */ static int json_ser_bitmask( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); uint32_t value = *(const uint32_t*)ptr; if (!value) { ecs_strbuf_appendch(str, '0'); return 0; } ecs_strbuf_list_push(str, "\"", "|"); /* Multiple flags can be set at a given time. Iterate through all the flags * and append the ones that are set. */ ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, constant->constant)); value -= (uint32_t)key; } } if (value != 0) { /* All bits must have been matched by a constant */ char *name = ecs_get_fullpath(world, op->type); ecs_err("bitmask value '%u' of type '%s' contains invalid/unknown bits", value, name); ecs_os_free(name); goto error; } ecs_strbuf_list_pop(str, "\""); return 0; error: return -1; } /* Serialize elements of a contiguous array */ static int json_ser_elements( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, int32_t elem_count, int32_t elem_size, ecs_strbuf_t *str, bool is_array) { flecs_json_array_push(str); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (flecs_json_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } flecs_json_array_pop(str); return 0; } static int flecs_json_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str, bool is_array) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vec_count(&ser->ops); return json_ser_elements( world, ops, op_count, base, elem_count, comp->size, str, is_array); } /* Serialize array */ static int json_ser_array( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsArray *a = ecs_get(world, op->type, EcsArray); ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_json_ser_type_elements( world, a->type, ptr, a->count, str, true); } /* Serialize vector */ static int json_ser_vector( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const ecs_vec_t *value = base; const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(value); void *array = ecs_vec_first(value); /* Serialize contiguous buffer of vector */ return flecs_json_ser_type_elements(world, v->type, array, count, str, false); } typedef struct json_serializer_ctx_t { ecs_strbuf_t *str; bool is_collection; bool is_struct; } json_serializer_ctx_t; static int json_ser_custom_value( const ecs_serializer_t *ser, ecs_entity_t type, const void *value) { json_serializer_ctx_t *json_ser = ser->ctx; if (json_ser->is_collection) { ecs_strbuf_list_next(json_ser->str); } return ecs_ptr_to_json_buf(ser->world, type, value, json_ser->str); } static int json_ser_custom_member( const ecs_serializer_t *ser, const char *name) { json_serializer_ctx_t *json_ser = ser->ctx; if (!json_ser->is_struct) { ecs_err("serializer::member can only be called for structs"); return -1; } flecs_json_member(json_ser->str, name); return 0; } static int json_ser_custom_type( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); ecs_assert(ct != NULL, ECS_INVALID_OPERATION, NULL); ecs_assert(ct->as_type != 0, ECS_INVALID_OPERATION, NULL); ecs_assert(ct->serialize != NULL, ECS_INVALID_OPERATION, ecs_get_name(world, op->type)); const EcsMetaType *pt = ecs_get(world, ct->as_type, EcsMetaType); ecs_assert(pt != NULL, ECS_INVALID_OPERATION, NULL); ecs_type_kind_t kind = pt->kind; bool is_collection = false; bool is_struct = false; if (kind == EcsStructType) { flecs_json_object_push(str); is_struct = true; } else if (kind == EcsArrayType || kind == EcsVectorType) { flecs_json_array_push(str); is_collection = true; } json_serializer_ctx_t json_ser = { .str = str, .is_struct = is_struct, .is_collection = is_collection }; ecs_serializer_t ser = { .world = world, .value = json_ser_custom_value, .member = json_ser_custom_member, .ctx = &json_ser }; if (ct->serialize(&ser, base)) { return -1; } if (kind == EcsStructType) { flecs_json_object_pop(str); } else if (kind == EcsArrayType || kind == EcsVectorType) { flecs_json_array_pop(str); } return 0; } /* Forward serialization to the different type kinds */ static int flecs_json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { void *vptr = ECS_OFFSET(ptr, op->offset); bool large_int = false; if (op->kind == EcsOpI64) { if (*(int64_t*)vptr >= 2147483648) { large_int = true; } } else if (op->kind == EcsOpU64) { if (*(uint64_t*)vptr >= 2147483648) { large_int = true; } } if (large_int) { ecs_strbuf_appendch(str, '"'); } switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpF32: ecs_strbuf_appendflt(str, (ecs_f64_t)*(const ecs_f32_t*)vptr, '"'); break; case EcsOpF64: ecs_strbuf_appendflt(str, *(ecs_f64_t*)vptr, '"'); break; case EcsOpEnum: if (json_ser_enum(world, op, vptr, str)) { goto error; } break; case EcsOpBitmask: if (json_ser_bitmask(world, op, vptr, str)) { goto error; } break; case EcsOpArray: if (json_ser_array(world, op, vptr, str)) { goto error; } break; case EcsOpVector: if (json_ser_vector(world, op, vptr, str)) { goto error; } break; case EcsOpOpaque: if (json_ser_custom_type(world, op, vptr, str)) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = *(const ecs_entity_t*)vptr; if (!e) { ecs_strbuf_appendch(str, '0'); } else { flecs_json_path(str, world, e); } break; } case EcsOpId: { ecs_id_t id = *(const ecs_id_t*)vptr; if (!id) { ecs_strbuf_appendch(str, '0'); } else { flecs_json_id(str, world, id); } break; } case EcsOpU64: case EcsOpI64: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: if (ecs_primitive_to_expr_buf(world, flecs_json_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str)) { ecs_throw(ECS_INTERNAL_ERROR, NULL); } break; case EcsOpPrimitive: case EcsOpScope: default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } if (large_int) { ecs_strbuf_appendch(str, '"'); } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int flecs_json_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (in_array <= 0) { if (op->name) { flecs_json_member(str, op->name); } int32_t elem_count = op->count; if (elem_count > 1) { /* Serialize inline array */ if (json_ser_elements(world, op, op->op_count, base, elem_count, op->size, str, true)) { return -1; } i += op->op_count - 1; continue; } } switch(op->kind) { case EcsOpPush: flecs_json_object_push(str); in_array --; break; case EcsOpPop: flecs_json_object_pop(str); in_array ++; break; case EcsOpArray: case EcsOpVector: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (flecs_json_ser_type_op(world, op, base, str)) { goto error; } break; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } } return 0; error: return -1; } /* Iterate over the type ops of a type */ int flecs_json_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); return flecs_json_ser_type_ops(world, ops, count, base, str, 0); } static int array_to_json_buf_w_type_data( const ecs_world_t *world, const void *ptr, int32_t count, ecs_strbuf_t *buf, const EcsComponent *comp, const EcsMetaTypeSerialized *ser) { if (count) { ecs_size_t size = comp->size; flecs_json_array_push(buf); do { ecs_strbuf_list_next(buf); if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } ptr = ECS_OFFSET(ptr, size); } while (-- count); flecs_json_array_pop(buf); } else { if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } } return 0; } int ecs_array_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, int32_t count, ecs_strbuf_t *buf) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize to JSON, '%s' is not a component", path); ecs_os_free(path); return -1; } const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (!ser) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); ecs_os_free(path); return -1; } return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } char* ecs_array_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr, int32_t count) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf) { return ecs_array_to_json_buf(world, type, ptr, 0, buf); } char* ecs_ptr_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { return ecs_array_to_json(world, type, ptr, 0); } static bool flecs_json_skip_id( const ecs_world_t *world, ecs_id_t id, const ecs_entity_to_json_desc_t *desc, ecs_entity_t ent, ecs_entity_t inst, ecs_entity_t *pred_out, ecs_entity_t *obj_out, ecs_entity_t *role_out, bool *hidden_out) { bool is_base = ent != inst; ecs_entity_t pred = 0, obj = 0, role = 0; bool hidden = false; if (ECS_HAS_ID_FLAG(id, PAIR)) { pred = ecs_pair_first(world, id); obj = ecs_pair_second(world, id); } else { pred = id & ECS_COMPONENT_MASK; if (id & ECS_ID_FLAGS_MASK) { role = id & ECS_ID_FLAGS_MASK; } } if (is_base) { if (ecs_has_id(world, pred, EcsDontInherit)) { return true; } } if (!desc || !desc->serialize_private) { if (ecs_has_id(world, pred, EcsPrivate)) { return true; } } if (is_base) { if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { hidden = true; } } if (hidden && (!desc || !desc->serialize_hidden)) { return true; } *pred_out = pred; *obj_out = obj; *role_out = role; if (hidden_out) *hidden_out = hidden; return false; } static int flecs_json_append_type_labels( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { (void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; (void)desc; #ifdef FLECS_DOC if (!desc || !desc->serialize_id_labels) { return 0; } flecs_json_memberl(buf, "id_labels"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { ecs_entity_t pred = 0, obj = 0, role = 0; if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { continue; } if (obj && (pred == EcsUnion)) { pred = obj; obj = ecs_get_target(world, ent, pred, 0); if (!ecs_is_alive(world, obj)) { /* Union relationships aren't automatically cleaned up, so they * can contain invalid entity ids. Don't serialize value until * relationship is valid again. */ continue; } } if (desc && desc->serialize_id_labels) { flecs_json_next(buf); flecs_json_array_push(buf); flecs_json_next(buf); flecs_json_label(buf, world, pred); if (obj) { flecs_json_next(buf); flecs_json_label(buf, world, obj); } flecs_json_array_pop(buf); } } flecs_json_array_pop(buf); #endif return 0; } static int flecs_json_append_type_values( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { if (!desc || !desc->serialize_values) { return 0; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { bool hidden; ecs_entity_t pred = 0, obj = 0, role = 0; ecs_id_t id = ids[i]; if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, &hidden)) { continue; } if (!hidden) { bool serialized = false; ecs_entity_t typeid = ecs_get_typeid(world, id); if (typeid) { const EcsMetaTypeSerialized *ser = ecs_get( world, typeid, EcsMetaTypeSerialized); if (ser) { const void *ptr = ecs_get_id(world, ent, id); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_next(buf); if (flecs_json_ser_type(world, &ser->ops, ptr, buf) != 0) { /* Entity contains invalid value */ return -1; } serialized = true; } } if (!serialized) { flecs_json_next(buf); flecs_json_number(buf, 0); } } else { if (!desc || desc->serialize_hidden) { flecs_json_next(buf); flecs_json_number(buf, 0); } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_append_type_info( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { if (!desc || !desc->serialize_type_info) { return 0; } flecs_json_memberl(buf, "type_info"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { bool hidden; ecs_entity_t pred = 0, obj = 0, role = 0; ecs_id_t id = ids[i]; if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, &hidden)) { continue; } if (!hidden) { ecs_entity_t typeid = ecs_get_typeid(world, id); if (typeid) { flecs_json_next(buf); if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { return -1; } } else { flecs_json_next(buf); flecs_json_number(buf, 0); } } else { if (!desc || desc->serialize_hidden) { flecs_json_next(buf); flecs_json_number(buf, 0); } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_append_type_hidden( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { if (!desc || !desc->serialize_hidden) { return 0; } if (ent == inst) { return 0; /* if this is not a base, components are never hidden */ } flecs_json_memberl(buf, "hidden"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { bool hidden; ecs_entity_t pred = 0, obj = 0, role = 0; ecs_id_t id = ids[i]; if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, &hidden)) { continue; } flecs_json_next(buf); flecs_json_bool(buf, hidden); } flecs_json_array_pop(buf); return 0; } static int flecs_json_append_type( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { const ecs_id_t *ids = NULL; int32_t i, count = 0; const ecs_type_t *type = ecs_get_type(world, ent); if (type) { ids = type->array; count = type->count; } if (!desc || desc->serialize_ids) { flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (i = 0; i < count; i ++) { ecs_entity_t pred = 0, obj = 0, role = 0; if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { continue; } if (obj && (pred == EcsUnion)) { pred = obj; obj = ecs_get_target(world, ent, pred, 0); if (!ecs_is_alive(world, obj)) { /* Union relationships aren't automatically cleaned up, so they * can contain invalid entity ids. Don't serialize value until * relationship is valid again. */ continue; } } flecs_json_next(buf); flecs_json_array_push(buf); flecs_json_next(buf); flecs_json_path(buf, world, pred); if (obj || role) { flecs_json_next(buf); if (obj) { flecs_json_path(buf, world, obj); } else { flecs_json_number(buf, 0); } if (role) { flecs_json_next(buf); flecs_json_string(buf, ecs_id_flag_str(role)); } } flecs_json_array_pop(buf); } flecs_json_array_pop(buf); } if (flecs_json_append_type_labels(world, buf, ids, count, ent, inst, desc)) { return -1; } if (flecs_json_append_type_values(world, buf, ids, count, ent, inst, desc)) { return -1; } if (flecs_json_append_type_info(world, buf, ids, count, ent, inst, desc)) { return -1; } if (flecs_json_append_type_hidden(world, buf, ids, count, ent, inst, desc)) { return -1; } return 0; } static int flecs_json_append_base( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { const ecs_type_t *type = ecs_get_type(world, ent); ecs_id_t *ids = NULL; int32_t i, count = 0; if (type) { ids = type->array; count = type->count; } for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ECS_HAS_RELATION(id, EcsIsA)) { if (flecs_json_append_base(world, buf, ecs_pair_second(world, id), inst, desc)) { return -1; } } } ecs_strbuf_list_next(buf); flecs_json_object_push(buf); flecs_json_memberl(buf, "path"); flecs_json_path(buf, world, ent); if (flecs_json_append_type(world, buf, ent, inst, desc)) { return -1; } flecs_json_object_pop(buf); return 0; } #ifdef FLECS_ALERTS static int flecs_json_serialize_entity_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, const EcsAlertsActive *alerts, bool self) { ecs_assert(alerts != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); while (ecs_map_next(&it)) { flecs_json_next(buf); flecs_json_object_push(buf); ecs_entity_t ai = ecs_map_value(&it); char *alert_name = ecs_get_fullpath(world, ai); flecs_json_memberl(buf, "alert"); flecs_json_string(buf, alert_name); ecs_os_free(alert_name); ecs_entity_t severity_id = ecs_get_target( world, ai, ecs_id(EcsAlert), 0); const char *severity = ecs_get_name(world, severity_id); const EcsAlertInstance *alert = ecs_get( world, ai, EcsAlertInstance); if (alert) { if (alert->message) { flecs_json_memberl(buf, "message"); flecs_json_string(buf, alert->message); } flecs_json_memberl(buf, "severity"); flecs_json_string(buf, severity); if (!self) { char *path = ecs_get_fullpath(world, entity); flecs_json_memberl(buf, "path"); flecs_json_string(buf, path); ecs_os_free(path); } } flecs_json_object_pop(buf); } return 0; } static int flecs_json_serialize_children_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { ecs_filter_t f = ECS_FILTER_INIT; ecs_filter(ECS_CONST_CAST(ecs_world_t*, world), { .storage = &f, .terms = {{ .id = ecs_pair(EcsChildOf, entity) }} }); ecs_iter_t it = ecs_filter_iter(world, &f); while (ecs_filter_next(&it)) { EcsAlertsActive *alerts = ecs_table_get_id( world, it.table, ecs_id(EcsAlertsActive), it.offset); int32_t i; for (i = 0; i < it.count; i ++) { ecs_entity_t child = it.entities[i]; if (alerts) { if (flecs_json_serialize_entity_alerts( world, buf, child, &alerts[i], false)) { goto error; } } ecs_record_t *r = flecs_entities_get(world, it.entities[i]); if (r->row & EcsEntityIsTraversable) { if (flecs_json_serialize_children_alerts( world, buf, child)) { goto error; } } } } ecs_filter_fini(&f); return 0; error: return -1; } #endif static int flecs_json_serialize_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { (void)world; (void)buf; (void)entity; #ifdef FLECS_ALERTS if (!ecs_id(EcsAlertsActive)) { return 0; /* Alert module not imported */ } flecs_json_memberl(buf, "alerts"); flecs_json_array_push(buf); const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); if (alerts) { flecs_json_serialize_entity_alerts(world, buf, entity, alerts, true); } flecs_json_serialize_children_alerts(world, buf, entity); flecs_json_array_pop(buf); #endif return 0; } static int flecs_json_serialize_refs_idr( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_id_record_t *idr) { char *id_str = ecs_id_str(world, ecs_pair_first(world, idr->id)); flecs_json_member(buf, id_str); ecs_os_free(id_str); flecs_json_array_push(buf); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; flecs_json_next(buf); flecs_json_path(buf, world, e); } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_serialize_refs( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, ecs_entity_t relationship) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(relationship, entity)); if (idr) { if (relationship == EcsWildcard) { ecs_id_record_t *cur = idr; while ((cur = cur->second.next)) { flecs_json_serialize_refs_idr(world, buf, cur); } } else { flecs_json_serialize_refs_idr(world, buf, idr); } } return 0; } static int flecs_json_serialize_matches( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair_t(EcsPoly, EcsQuery)); if (idr) { ecs_table_cache_iter_t it; if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (i = 0; i < count; i ++) { ecs_poly_t *q = queries[i].poly; if (!q) { continue; } ecs_iter_t qit; ecs_iter_poly(world, q, &qit, NULL); if (!qit.variables) { ecs_iter_fini(&qit); continue; } ecs_iter_set_var(&qit, 0, entity); if (ecs_iter_is_true(&qit)) { flecs_json_next(buf); flecs_json_path(buf, world, entities[i]); } } } } } return 0; } int ecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_entity_to_json_desc_t *desc) { if (!entity || !ecs_is_valid(world, entity)) { return -1; } flecs_json_object_push(buf); if (!desc || desc->serialize_path) { flecs_json_memberl(buf, "path"); flecs_json_path(buf, world, entity); } #ifdef FLECS_DOC if (desc && desc->serialize_label) { flecs_json_memberl(buf, "label"); const char *doc_name = ecs_doc_get_name(world, entity); if (doc_name) { flecs_json_string_escape(buf, doc_name); } else { char num_buf[20]; ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); flecs_json_string(buf, num_buf); } } if (desc && desc->serialize_brief) { const char *doc_brief = ecs_doc_get_brief(world, entity); if (doc_brief) { flecs_json_memberl(buf, "brief"); flecs_json_string_escape(buf, doc_brief); } } if (desc && desc->serialize_link) { const char *doc_link = ecs_doc_get_link(world, entity); if (doc_link) { flecs_json_memberl(buf, "link"); flecs_json_string_escape(buf, doc_link); } } if (desc && desc->serialize_color) { const char *doc_color = ecs_doc_get_color(world, entity); if (doc_color) { flecs_json_memberl(buf, "color"); flecs_json_string_escape(buf, doc_color); } } #endif const ecs_type_t *type = ecs_get_type(world, entity); ecs_id_t *ids = NULL; int32_t i, count = 0; if (type) { ids = type->array; count = type->count; } if (!desc || desc->serialize_base) { if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { flecs_json_memberl(buf, "is_a"); flecs_json_array_push(buf); for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ECS_HAS_RELATION(id, EcsIsA)) { if (flecs_json_append_base( world, buf, ecs_pair_second(world, id), entity, desc)) { return -1; } } } flecs_json_array_pop(buf); } } if (flecs_json_append_type(world, buf, entity, entity, desc)) { goto error; } if (desc && desc->serialize_alerts) { if (flecs_json_serialize_alerts(world, buf, entity)) { goto error; } } if (desc && desc->serialize_refs) { flecs_json_memberl(buf, "refs"); flecs_json_object_push(buf); if (flecs_json_serialize_refs(world, buf, entity, desc->serialize_refs)) { goto error; } flecs_json_object_pop(buf); } if (desc && desc->serialize_matches) { flecs_json_memberl(buf, "matches"); flecs_json_array_push(buf); if (flecs_json_serialize_matches(world, buf, entity)) { goto error; } flecs_json_array_pop(buf); } flecs_json_object_pop(buf); return 0; error: return -1; } char* ecs_entity_to_json( const ecs_world_t *world, ecs_entity_t entity, const ecs_entity_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } bool flecs_json_skip_variable( const char *name) { if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { return true; } else { return false; } } static void flecs_json_serialize_id( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { flecs_json_id(buf, world, id); } static void flecs_json_serialize_id_label( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_entity_t pred = id, obj = 0; if (ECS_IS_PAIR(id)) { pred = ecs_pair_first(world, id); obj = ecs_pair_second(world, id); } flecs_json_array_push(buf); flecs_json_next(buf); flecs_json_label(buf, world, pred); if (obj) { flecs_json_next(buf); flecs_json_label(buf, world, obj); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_ids( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t field_count = it->field_count; if (!field_count) { return; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (int i = 0; i < field_count; i ++) { flecs_json_next(buf); flecs_json_serialize_id(world, it->terms[i].id, buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_id_labels( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t field_count = it->field_count; if (!field_count) { return; } flecs_json_memberl(buf, "id_labels"); flecs_json_array_push(buf); for (int i = 0; i < field_count; i ++) { flecs_json_next(buf); flecs_json_serialize_id_label(world, it->terms[i].id, buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_id_str( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_strbuf_appendch(buf, '"'); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_first(world, id); ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf( world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); } ecs_strbuf_appendch(buf, '"'); } static void flecs_json_serialize_type_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t field_count = it->field_count; if (!field_count) { return; } if (it->flags & EcsIterNoData) { return; } flecs_json_memberl(buf, "type_info"); flecs_json_object_push(buf); for (int i = 0; i < field_count; i ++) { flecs_json_next(buf); ecs_entity_t typeid = 0; if (it->terms[i].inout != EcsInOutNone) { typeid = ecs_get_typeid(world, it->terms[i].id); } if (typeid) { flecs_json_serialize_id_str(world, typeid, buf); ecs_strbuf_appendch(buf, ':'); ecs_type_info_to_json_buf(world, typeid, buf); } else { flecs_json_serialize_id_str(world, it->terms[i].id, buf); ecs_strbuf_appendlit(buf, ":0"); } } flecs_json_object_pop(buf); } static void flecs_json_serialize_field_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx) { int32_t field_count = it->field_count; if (!field_count || !it->query) { return; } const ecs_filter_t *q = it->query; flecs_json_memberl(buf, "field_info"); flecs_json_array_push(buf); int f; for (f = 0; f < field_count; f ++) { flecs_json_next(buf); flecs_json_serialize_field(world, it, q, f, buf, ctx); } flecs_json_array_pop(buf); } static void flecs_json_serialize_query_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->query) { return; } const ecs_filter_t *q = it->query; flecs_json_memberl(buf, "query_info"); flecs_json_serialize_query(world, q, buf); } static void flecs_json_serialize_query_plan( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { (void)world; (void)buf; (void)desc; #ifdef FLECS_RULES if (!desc->query) { return; } if (!ecs_poly_is(desc->query, ecs_rule_t)) { return; } const ecs_rule_t *q = desc->query; ecs_poly_assert(q, ecs_rule_t); flecs_json_memberl(buf, "query_plan"); bool prev_color = ecs_log_enable_colors(true); char *plan = ecs_rule_str(q); flecs_json_string_escape(buf, plan); ecs_os_free(plan); ecs_log_enable_colors(prev_color); #endif } static void flecs_json_serialize_query_profile( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { if (!desc->query) { return; } ecs_time_t t = {0}; int32_t result_count = 0, entity_count = 0, i, sample_count = 100; ecs_size_t component_bytes = 0, shared_component_bytes = 0; double eval_time = 0, eval_min = 0, eval_max = 0; ecs_time_measure(&t); for (i = 0; i < sample_count; i ++) { result_count = 0; entity_count = 0; component_bytes = 0; shared_component_bytes = 0; ecs_iter_t pit; ecs_iter_poly(world, desc->query, &pit, NULL); pit.flags |= EcsIterIsInstanced; while (ecs_iter_next(&pit)) { result_count ++; entity_count += pit.count; int32_t f, field_count = pit.field_count; for (f = 0; f < field_count; f ++) { size_t size = ecs_field_size(&pit, f + 1); if (ecs_field_is_set(&pit, f + 1) && size) { if (ecs_field_is_self(&pit, f + 1)) { component_bytes += flecs_uto(ecs_size_t, size) * pit.count; } else { shared_component_bytes += flecs_uto(ecs_size_t, size); } } } } double time_measure = ecs_time_measure(&t); if (!i) { eval_min = time_measure; } else if (time_measure < eval_min) { eval_min = time_measure; } if (time_measure > eval_max) { eval_max = time_measure; } eval_time += time_measure; /* Don't profile for too long */ if (eval_time > 0.001) { i ++; break; } } eval_time /= i; flecs_json_memberl(buf, "query_profile"); flecs_json_object_push(buf); if (it->query) { /* Correct for profiler */ ECS_CONST_CAST(ecs_filter_t*, it->query)->eval_count -= i; flecs_json_memberl(buf, "eval_count"); flecs_json_number(buf, it->query->eval_count); } flecs_json_memberl(buf, "result_count"); flecs_json_number(buf, result_count); flecs_json_memberl(buf, "entity_count"); flecs_json_number(buf, entity_count); flecs_json_memberl(buf, "eval_time_avg_us"); flecs_json_number(buf, eval_time * 1000.0 * 1000.0); flecs_json_memberl(buf, "eval_time_min_us"); flecs_json_number(buf, eval_min * 1000.0 * 1000.0); flecs_json_memberl(buf, "eval_time_max_us"); flecs_json_number(buf, eval_max * 1000.0 * 1000.0); flecs_json_memberl(buf, "component_bytes"); flecs_json_number(buf, component_bytes); flecs_json_memberl(buf, "shared_component_bytes"); flecs_json_number(buf, shared_component_bytes); flecs_json_object_pop(buf); } static void flecs_json_serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_string(buf, var_name); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_ids( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { flecs_json_next(buf); flecs_json_serialize_id(world, ecs_field_id(it, i + 1), buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_id_labels( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "id_labels"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { flecs_json_next(buf); flecs_json_serialize_id_label(world, ecs_field_id(it, i + 1), buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_table_type( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { if (!it->table) { return; } if (desc->serialize_ids) { flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); ecs_type_t *type = &it->table->type; for (int i = 0; i < type->count; i ++) { ecs_id_t id = type->array[i]; if (!desc->serialize_private) { ecs_entity_t e = id; if (ECS_IS_PAIR(id)) { e = ecs_pair_first(world, id); } if (ecs_owns_id(world, e, EcsPrivate)) { continue; } } flecs_json_next(buf); flecs_json_serialize_id(world, id, buf); } flecs_json_array_pop(buf); } if (desc->serialize_id_labels) { flecs_json_memberl(buf, "id_labels"); flecs_json_array_push(buf); ecs_type_t *type = &it->table->type; for (int i = 0; i < type->count; i ++) { ecs_id_t id = type->array[i]; if (!desc->serialize_private) { ecs_entity_t e = id; if (ECS_IS_PAIR(id)) { e = ecs_pair_first(world, id); } if (ecs_owns_id(world, e, EcsPrivate)) { continue; } } flecs_json_next(buf); flecs_json_serialize_id_label(world, id, buf); } flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_sources( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "sources"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { flecs_json_next(buf); ecs_entity_t subj = it->sources[i]; if (subj) { flecs_json_path(buf, world, subj); } else { ecs_strbuf_appendch(buf, '0'); } } flecs_json_array_pop(buf); } bool flecs_json_serialize_iter_result_is_set( const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!(it->flags & EcsIterHasCondSet)) { return false; } flecs_json_memberl(buf, "is_set"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { ecs_strbuf_list_next(buf); if (ecs_field_is_set(it, i + 1)) { flecs_json_true(buf); } else { flecs_json_false(buf); } } flecs_json_array_pop(buf); return true; } static void flecs_json_serialize_iter_result_variables( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; ecs_var_t *variables = it->variables; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_path(buf, world, variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_variable_labels( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; ecs_var_t *variables = it->variables; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "var_labels"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_label(buf, world, variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_variable_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; ecs_var_t *variables = it->variables; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "var_ids"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_u32(buf, (uint32_t)variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static bool flecs_json_serialize_iter_result_entity_names( const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); EcsIdentifier *names = ecs_table_get_id(it->world, it->table, ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); if (!names) { return false; } int i; for (i = 0; i < it->count; i ++) { flecs_json_next(buf); flecs_json_string(buf, names[i].value); } return true; } static void flecs_json_serialize_iter_result_entity_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->count) { return; } flecs_json_memberl(buf, "entity_ids"); flecs_json_array_push(buf); ecs_entity_t *entities = it->entities; int i, count = it->count; for (i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_u32(buf, (uint32_t)entities[i]); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_parent( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_table_t *table = it->table; if (!(table->flags & EcsTableHasChildOf)) { return; } ecs_table_record_t *tr = flecs_id_record_get_table( world->idr_childof_wildcard, it->table); if (tr == NULL) { return; } ecs_id_t id = table->type.array[tr->index]; ecs_entity_t parent = ecs_pair_second(world, id); char *path = ecs_get_path_w_sep(world, 0, parent, ".", ""); flecs_json_memberl(buf, "parent"); flecs_json_string(buf, path); ecs_os_free(path); } static void flecs_json_serialize_iter_result_entities( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->count) { return; } flecs_json_serialize_iter_result_parent(world, it, buf); flecs_json_memberl(buf, "entities"); flecs_json_array_push(buf); if (!flecs_json_serialize_iter_result_entity_names(it, buf)) { ecs_entity_t *entities = it->entities; int i, count = it->count; for (i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_u32(buf, (uint32_t)entities[i]); } } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_entity_labels( const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_json_ser_ctx_t *ser_ctx) { (void)buf; (void)ser_ctx; if (!it->count) { return; } if (!ser_ctx->idr_doc_name) { return; } #ifdef FLECS_DOC ecs_table_t *table = it->table; ecs_table_record_t *tr = flecs_id_record_get_table( ser_ctx->idr_doc_name, table); if (tr == NULL) { return; } EcsDocDescription *labels = ecs_table_get_column( table, tr->column, it->offset); ecs_assert(labels != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_memberl(buf, "entity_labels"); flecs_json_array_push(buf); int i; for (i = 0; i < it->count; i ++) { flecs_json_next(buf); flecs_json_string_escape(buf, labels[i].value); } flecs_json_array_pop(buf); #endif } static void flecs_json_serialize_iter_result_colors( const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_json_ser_ctx_t *ser_ctx) { (void)buf; (void)ser_ctx; if (!it->count) { return; } #ifdef FLECS_DOC if (!ser_ctx->idr_doc_color) { return; } ecs_table_record_t *tr = flecs_id_record_get_table( ser_ctx->idr_doc_color, it->table); if (tr == NULL) { return; } EcsDocDescription *colors = ecs_table_get_column( it->table, tr->column, it->offset); ecs_assert(colors != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_memberl(buf, "colors"); flecs_json_array_push(buf); int i; for (i = 0; i < it->count; i ++) { flecs_json_next(buf); flecs_json_string(buf, colors[i].value); } flecs_json_array_pop(buf); #endif } static int flecs_json_serialize_iter_result_values( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->ptrs || (it->flags & EcsIterNoData)) { return 0; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); int32_t i, term_count = it->field_count; for (i = 0; i < term_count; i ++) { ecs_strbuf_list_next(buf); const void *ptr = NULL; if (it->ptrs) { ptr = it->ptrs[i]; } if (!ptr) { /* No data in column. Append 0 if this is not an optional term */ if (ecs_field_is_set(it, i + 1)) { ecs_strbuf_appendch(buf, '0'); continue; } } if (ecs_field_is_writeonly(it, i + 1)) { ecs_strbuf_appendch(buf, '0'); continue; } /* Get component id (can be different in case of pairs) */ ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); if (!type) { /* Odd, we have a ptr but no Component? Not the place of the * serializer to complain about that. */ ecs_strbuf_appendch(buf, '0'); continue; } const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { /* Also odd, typeid but not a component? */ ecs_strbuf_appendch(buf, '0'); continue; } const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (!ser) { /* Not odd, component just has no reflection data */ ecs_strbuf_appendch(buf, '0'); continue; } /* If term is not set, append empty array. This indicates that the term * could have had data but doesn't */ if (!ecs_field_is_set(it, i + 1)) { flecs_json_array_push(buf); flecs_json_array_pop(buf); continue; } if (ecs_field_is_self(it, i + 1)) { int32_t count = it->count; if (array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser)) { return -1; } } else { if (array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser)) { return -1; } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_serialize_iter_result_columns( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_table_t *table = it->table; if (!table || !table->column_count) { return 0; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); ecs_type_t *type = &table->type; int32_t *column_map = table->column_map; ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < type->count; i ++) { int32_t storage_column = -1; if (column_map) { storage_column = column_map[i]; } if (!desc->serialize_private) { ecs_id_t id = type->array[i]; ecs_entity_t e = id; if (ECS_IS_PAIR(id)) { e = ecs_pair_first(world, id); } if (ecs_owns_id(world, e, EcsPrivate)) { continue; } } ecs_strbuf_list_next(buf); if (storage_column == -1) { ecs_strbuf_appendch(buf, '0'); continue; } ecs_entity_t typeid = table->data.columns[storage_column].ti->component; if (!typeid) { ecs_strbuf_appendch(buf, '0'); continue; } const EcsComponent *comp = ecs_get(world, typeid, EcsComponent); if (!comp) { ecs_strbuf_appendch(buf, '0'); continue; } const EcsMetaTypeSerialized *ser = ecs_get( world, typeid, EcsMetaTypeSerialized); if (!ser) { ecs_strbuf_appendch(buf, '0'); continue; } void *ptr = ecs_vec_first(&table->data.columns[storage_column].data); if (array_to_json_buf_w_type_data(world, ptr, it->count, buf, comp, ser)) { return -1; } } flecs_json_array_pop(buf); return 0; } static int flecs_json_serialize_iter_result( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { if (desc && desc->serialize_rows) { if (flecs_json_serialize_iter_result_rows(world, it, buf, desc, ser_ctx)) { return -1; } } else { flecs_json_next(buf); flecs_json_object_push(buf); /* Each result can be matched with different component ids. Add them to * the result so clients know with which component an entity was matched */ if (desc && desc->serialize_table) { flecs_json_serialize_iter_result_table_type(world, it, buf, desc); } else { if (!desc || desc->serialize_ids) { flecs_json_serialize_iter_result_ids(world, it, buf); } if (desc && desc->serialize_id_labels) { flecs_json_serialize_iter_result_id_labels(world, it, buf); } } /* Include information on which entity the term is matched with */ if (!desc || (desc->serialize_sources && !desc->serialize_table)) { flecs_json_serialize_iter_result_sources(world, it, buf); } /* Write variable values for current result */ if (!desc || desc->serialize_variables) { flecs_json_serialize_iter_result_variables(world, it, buf); } /* Write labels for variables */ if (desc && desc->serialize_variable_labels) { flecs_json_serialize_iter_result_variable_labels(world, it, buf); } /* Write ids for variables */ if (desc && desc->serialize_variable_ids) { flecs_json_serialize_iter_result_variable_ids(it, buf); } /* Include information on which terms are set, to support optional terms */ if (!desc || (desc->serialize_is_set && !desc->serialize_table)) { flecs_json_serialize_iter_result_is_set(it, buf); } /* Write entity ids for current result (for queries with This terms) */ if (!desc || desc->serialize_entities) { flecs_json_serialize_iter_result_entities(world, it, buf); } /* Write ids for entities */ if (desc && desc->serialize_entity_ids) { flecs_json_serialize_iter_result_entity_ids(it, buf); } /* Write labels for entities */ if (desc && desc->serialize_entity_labels) { flecs_json_serialize_iter_result_entity_labels(it, buf, ser_ctx); } /* Write colors for entities */ if (desc && desc->serialize_colors) { flecs_json_serialize_iter_result_colors(it, buf, ser_ctx); } /* Serialize component values */ if (desc && desc->serialize_table) { if (flecs_json_serialize_iter_result_columns(world, it, buf, desc)) { return -1; } } else { if (!desc || desc->serialize_values) { if (flecs_json_serialize_iter_result_values(world, it, buf)) { return -1; } } } /* Add "alerts": true member if table has entities with active alerts */ #ifdef FLECS_ALERTS if (it->table && (ecs_id(EcsAlertsActive) != 0)) { /* Only add field if alerts addon is imported */ if (ecs_table_has_id(world, it->table, ecs_id(EcsAlertsActive))) { flecs_json_memberl(buf, "alerts"); flecs_json_true(buf); } } #endif flecs_json_object_pop(buf); } return 0; } int ecs_iter_to_json_buf( const ecs_world_t *world, ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_time_t duration = {0}; if (desc && desc->measure_eval_duration) { ecs_time_measure(&duration); } /* Cache id record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC ser_ctx.idr_doc_name = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsName)); ser_ctx.idr_doc_color = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif flecs_json_object_push(buf); /* Serialize component ids of the terms (usually provided by query) */ if (!desc || !desc->serialize_rows) { if (!desc || desc->serialize_term_ids) { flecs_json_serialize_iter_ids(world, it, buf); } if (desc && desc->serialize_term_labels) { flecs_json_serialize_iter_id_labels(world, it, buf); } /* Serialize variable names, if iterator has any */ flecs_json_serialize_iter_variables(it, buf); } /* Serialize type info if enabled */ if (desc && desc->serialize_type_info) { flecs_json_serialize_type_info(world, it, buf); } /* Serialize field info if enabled */ if (desc && desc->serialize_field_info) { flecs_json_serialize_field_info(world, it, buf, &ser_ctx); } /* Serialize query info if enabled */ if (desc && desc->serialize_query_info) { flecs_json_serialize_query_info(world, it, buf); } /* Serialize query plan if enabled */ if (desc && desc->serialize_query_plan) { flecs_json_serialize_query_plan(world, buf, desc); } /* Profile query */ if (desc && desc->serialize_query_profile) { flecs_json_serialize_query_profile(world, buf, it, desc); } /* Serialize results */ if (!desc || !desc->dont_serialize_results) { flecs_json_memberl(buf, "results"); flecs_json_array_push(buf); /* Use instancing for improved performance */ ECS_BIT_SET(it->flags, EcsIterIsInstanced); /* If serializing entire table, don't bother letting the iterator populate * data fields as we'll be iterating all columns. */ if (desc && desc->serialize_table) { ECS_BIT_SET(it->flags, EcsIterNoData); } ecs_iter_next_action_t next = it->next; while (next(it)) { if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_ctx)) { ecs_strbuf_reset(buf); ecs_iter_fini(it); return -1; } } flecs_json_array_pop(buf); } else { ecs_iter_fini(it); } int32_t f, field_count = it->field_count; if (desc && (desc->serialize_rows || desc->serialize_values)) { for (f = 0; f < field_count; f ++) { ecs_os_free(ser_ctx.value_ctx[f].id_label); } } if (desc && desc->measure_eval_duration) { double dt = ecs_time_measure(&duration); flecs_json_memberl(buf, "eval_duration"); flecs_json_number(buf, dt); } flecs_json_object_pop(buf); return 0; } char* ecs_iter_to_json( const ecs_world_t *world, ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_iter_to_json_buf(world, it, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } int ecs_world_to_json_buf( ecs_world_t *world, ecs_strbuf_t *buf_out, const ecs_world_to_json_desc_t *desc) { ecs_filter_t f = ECS_FILTER_INIT; ecs_filter_desc_t filter_desc = {0}; filter_desc.storage = &f; if (desc && desc->serialize_builtin && desc->serialize_modules) { filter_desc.terms[0].id = EcsAny; } else { bool serialize_builtin = desc && desc->serialize_builtin; bool serialize_modules = desc && desc->serialize_modules; int32_t term_id = 0; if (!serialize_builtin) { filter_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); filter_desc.terms[term_id].oper = EcsNot; filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; term_id ++; } if (!serialize_modules) { filter_desc.terms[term_id].id = EcsModule; filter_desc.terms[term_id].oper = EcsNot; filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; } } filter_desc.flags = EcsFilterMatchDisabled|EcsFilterMatchPrefab; if (ecs_filter_init(world, &filter_desc) == NULL) { return -1; } ecs_iter_t it = ecs_filter_iter(world, &f); ecs_iter_to_json_desc_t json_desc = { .serialize_table = true, .serialize_ids = true, .serialize_entities = true, .serialize_private = true }; int ret = ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); ecs_filter_fini(&f); return ret; } char* ecs_world_to_json( ecs_world_t *world, const ecs_world_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_world_to_json_buf(world, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif /** * @file json/serialize_type_info.c * @brief Serialize type (reflection) information to JSON. */ #ifdef FLECS_JSON void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, const ecs_filter_t *q, int field, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx) { flecs_json_object_push(buf); flecs_json_memberl(buf, "id"); flecs_json_serialize_get_field_ctx(world, it, field, ctx); ecs_json_value_ser_ctx_t *value_ctx = &ctx->value_ctx[field]; if (value_ctx->id_label) { flecs_json_string(buf, value_ctx->id_label); const ecs_term_t *term = &q->terms[0]; int t; for (t = 0; t < q->term_count; t ++) { if (q->terms[t].field_index == field) { term = &q->terms[t]; break; } } if (term->oper != EcsNot) { if (term->oper == EcsOptional) { flecs_json_memberl(buf, "optional"); flecs_json_bool(buf, true); } if (ECS_IS_PAIR(term->id)) { if (term->first.flags & EcsIsEntity && term->first.id) { if (ecs_has_id(world, term->first.id, EcsExclusive)) { flecs_json_memberl(buf, "exclusive"); flecs_json_bool(buf, true); } } } if (value_ctx->type) { flecs_json_memberl(buf, "type"); flecs_json_label(buf, world, value_ctx->type); const char *symbol = ecs_get_symbol(world, value_ctx->type); if (symbol) { flecs_json_memberl(buf, "symbol"); flecs_json_string(buf, symbol); } } if (value_ctx->ser) { flecs_json_memberl(buf, "schema"); ecs_type_info_to_json_buf(world, value_ctx->type, buf); } } else { flecs_json_memberl(buf, "not"); flecs_json_bool(buf, true); } } else { ecs_strbuf_appendlit(buf, "0"); } flecs_json_object_pop(buf); } #endif /** * @file json/serialize_iter_rows.c * @brief Serialize (component) values to JSON strings. */ #ifdef FLECS_JSON static bool flecs_json_serialize_row_tags( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t f, field_count = it->field_count; ecs_id_t *ids = it->ids; void **ptrs = it->ptrs; int32_t tag_count = 0; for (f = 0; f < field_count; f ++) { if (!ecs_field_is_set(it, f + 1)) { continue; } ecs_id_t id = ids[f]; if (ECS_IS_PAIR(id)) { continue; } if (ptrs[f]) { continue; /* Ignore components */ } if (!tag_count) { flecs_json_memberl(buf, "tags"); flecs_json_array_push(buf); } flecs_json_next(buf); flecs_json_label(buf, world, id); tag_count ++; } if (tag_count) { flecs_json_array_pop(buf); } return tag_count != 0; } static bool flecs_json_serialize_row_pairs( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t f, field_count = it->field_count; ecs_id_t *ids = it->ids; int32_t pair_count = 0; for (f = 0; f < field_count; f ++) { if (!ecs_field_is_set(it, f + 1)) { continue; } ecs_id_t id = ids[f]; if (!ECS_IS_PAIR(id)) { continue; } if (!pair_count) { flecs_json_memberl(buf, "pairs"); flecs_json_object_push(buf); } flecs_json_next(buf); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); flecs_json_label(buf, world, first); ecs_strbuf_appendlit(buf, ":"); flecs_json_label(buf, world, second); pair_count ++; } if (pair_count) { flecs_json_object_pop(buf); } return pair_count != 0; } static bool flecs_json_serialize_row_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; ecs_entity_t var = it->variables[i].entity; if (!var) { /* Can't happen, but not the place of the serializer to complain */ continue; } if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_object_push(buf); actual_count ++; } flecs_json_member(buf, var_name); flecs_json_label(buf, world, var); } if (actual_count) { flecs_json_object_pop(buf); } return actual_count != 0; } static bool flecs_json_serialize_tags_pairs_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { bool result = false; ecs_strbuf_list_push(buf, "", ","); result |= flecs_json_serialize_row_tags(world, it, buf); result |= flecs_json_serialize_row_pairs(world, it, buf); result |= flecs_json_serialize_row_vars(world, it, buf); result |= flecs_json_serialize_iter_result_is_set(it, buf); ecs_strbuf_list_pop(buf, ""); if (!result) { ecs_strbuf_reset(buf); } return result; } static bool flecs_json_serialize_table_row_tags( const ecs_world_t *world, const ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { int32_t f, type_count = table->type.count; ecs_id_t *ids = table->type.array; int32_t *column_map = table->column_map; int32_t tag_count = 0; for (f = 0; f < type_count; f ++) { ecs_id_t id = ids[f]; if (ECS_IS_PAIR(id)) { continue; } if (column_map[f] != -1) { continue; /* Ignore components */ } if (!desc || !desc->serialize_private) { if (ecs_has_id(world, id & ECS_COMPONENT_MASK, EcsPrivate)) { continue; } } if (!tag_count) { flecs_json_memberl(buf, "tags"); flecs_json_array_push(buf); } flecs_json_next(buf); flecs_json_label(buf, world, id); tag_count ++; } if (tag_count) { flecs_json_array_pop(buf); } return tag_count != 0; } static bool flecs_json_serialize_table_row_pairs( const ecs_world_t *world, const ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { int32_t f, type_count = table->type.count; ecs_id_t *ids = table->type.array; int32_t pair_count = 0; bool same_first = false; for (f = 0; f < type_count; f ++) { ecs_id_t id = ids[f]; if (!ECS_IS_PAIR(id)) { continue; } ecs_entity_t first = flecs_entities_get_alive( world, ECS_PAIR_FIRST(id)); if (!desc || !desc->serialize_private) { if (ecs_has_id(world, first, EcsPrivate)) { continue; } } if (!pair_count) { flecs_json_memberl(buf, "pairs"); flecs_json_object_push(buf); } ecs_entity_t second = flecs_entities_get_alive( world, ECS_PAIR_SECOND(id)); bool is_last = f == (type_count - 1); bool is_same = !is_last && (ECS_PAIR_FIRST(ids[f + 1]) == ECS_PAIR_FIRST(id)); if (same_first && f && ECS_PAIR_FIRST(ids[f - 1]) != ECS_PAIR_FIRST(id)) { /* New pair has different first elem, so close array */ flecs_json_array_pop(buf); same_first = false; } if (!same_first) { /* Only append pair label if we're not appending to array */ flecs_json_next(buf); flecs_json_label(buf, world, first); ecs_strbuf_appendlit(buf, ":"); /* Open array scope if this is a pair with multiple targets */ if (is_same) { flecs_json_array_push(buf); same_first = true; } } if (same_first) { flecs_json_next(buf); } flecs_json_label(buf, world, second); pair_count ++; } if (same_first) { flecs_json_array_pop(buf); } if (pair_count) { flecs_json_object_pop(buf); } return pair_count != 0; } static bool flecs_json_serialize_table_tags_pairs_vars( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { bool result = false; ecs_strbuf_list_push(buf, "", ","); result |= flecs_json_serialize_table_row_tags(world, table, buf, desc); result |= flecs_json_serialize_table_row_pairs(world, table, buf, desc); ecs_strbuf_list_pop(buf, ""); if (!result) { ecs_strbuf_reset(buf); } return result; } static bool flecs_json_serialize_get_value_ctx( const ecs_world_t *world, ecs_id_t id, ecs_json_value_ser_ctx_t *ctx) { if (!id) { return false; } if (!ctx->initialized) { ctx->initialized = true; ecs_strbuf_t idlbl = ECS_STRBUF_INIT; flecs_json_id_member(&idlbl, world, id); ctx->id_label = ecs_strbuf_get(&idlbl); ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { return false; } ctx->type = type; ctx->ser = ecs_get(world, type, EcsMetaTypeSerialized); if (!ctx->ser) { return false; } return true; } else { return ctx->ser != NULL; } } bool flecs_json_serialize_get_field_ctx( const ecs_world_t *world, const ecs_iter_t *it, int32_t f, ecs_json_ser_ctx_t *ser_ctx) { ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; if (it->query) { return flecs_json_serialize_get_value_ctx( world, it->query->ids[f], value_ctx); } else if (it->ids[f]) { return flecs_json_serialize_get_value_ctx(world, it->ids[f], value_ctx); } else { return false; } } static int flecs_json_serialize_row_components( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, int32_t row) { void **ptrs = it->ptrs; if (!ptrs) { return 0; } int32_t f, field_count = it->field_count, component_count = 0; for (f = 0; f < field_count; f ++) { if (!ecs_field_is_set(it, f + 1)) { continue; } void *ptr = ptrs[f]; if (!ptr) { continue; } if (ecs_field_is_self(it, f + 1)) { ptr = ECS_ELEM(ptr, it->sizes[f], row); } ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; if (!flecs_json_serialize_get_value_ctx(world, it->ids[f], value_ctx)) { continue; } if (!component_count) { flecs_json_memberl(buf, "components"); flecs_json_object_push(buf); } flecs_json_member(buf, value_ctx->id_label); if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { return -1; } component_count ++; } if (component_count) { flecs_json_object_pop(buf); } return 0; } static int flecs_json_serialize_row_table_components( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, ecs_json_value_ser_ctx_t *values_ctx, int32_t row, int32_t column_count) { int32_t f, component_count = 0; for (f = 0; f < column_count; f ++) { ecs_column_t *column = &table->data.columns[f]; ecs_json_value_ser_ctx_t *value_ctx = &values_ctx[f]; if (!flecs_json_serialize_get_value_ctx(world, column->id, value_ctx)) { continue; } if (!component_count) { flecs_json_memberl(buf, "components"); flecs_json_object_push(buf); } void *ptr = ecs_vec_get(&column->data, column->size, row); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(value_ctx->id_label != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_member(buf, value_ctx->id_label); if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { return -1; } component_count ++; } if (component_count) { flecs_json_object_pop(buf); } return 0; } static void flecs_json_serialize_iter_this_row( const ecs_iter_t *it, const char *parent_path, const EcsIdentifier *names, int32_t row, ecs_strbuf_t *buf) { if (parent_path) { flecs_json_memberl(buf, "parent"); flecs_json_string(buf, parent_path); } flecs_json_memberl(buf, "name"); if (names) { flecs_json_string(buf, names[row].value); } else { ecs_strbuf_appendlit(buf, "\""); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)it->entities[row])); ecs_strbuf_appendlit(buf, "\""); } } static int flecs_json_serialize_iter_result_query_rows( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, int32_t count, bool has_this, const char *parent_path, const EcsIdentifier *names) { /* Serialize tags, pairs, vars once, since they're the same for each row */ ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; int32_t tags_pairs_vars_len = 0; char *tags_pairs_vars = NULL; if (flecs_json_serialize_tags_pairs_vars(world, it, &tags_pairs_vars_buf)) { tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); } int32_t i; for (i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_object_push(buf); if (has_this) { flecs_json_serialize_iter_this_row( it, parent_path, names, i, buf); } if (tags_pairs_vars) { ecs_strbuf_list_appendstrn(buf, tags_pairs_vars, tags_pairs_vars_len); } if (flecs_json_serialize_row_components(world, it, buf, ser_ctx, i)) { return -1; } flecs_json_object_pop(buf); } ecs_os_free(tags_pairs_vars); return 0; } static int flecs_json_serialize_iter_result_table_rows( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const EcsIdentifier *names) { ecs_table_t *table = it->table; if (!table || !count) { return 0; } /* Serialize tags, pairs, vars once, since they're the same for each row */ ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; int32_t tags_pairs_vars_len = 0; char *tags_pairs_vars = NULL; if (flecs_json_serialize_table_tags_pairs_vars( world, table, &tags_pairs_vars_buf, desc)) { tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); } /* If one entity has more than 256 components (oof), bad luck */ ecs_json_value_ser_ctx_t values_ctx[256] = {{0}}; int32_t column_count = table->column_count; if (column_count > 256) { column_count = 256; } int32_t i, end = it->offset + count; int result = 0; for (i = it->offset; i < end; i ++) { flecs_json_next(buf); flecs_json_object_push(buf); if (has_this) { const EcsIdentifier *name_ptr = NULL; if (names) { /* Correct for offset applied by iterator */ name_ptr = &names[-it->offset]; } flecs_json_serialize_iter_this_row( it, parent_path, name_ptr, i, buf); } if (tags_pairs_vars) { ecs_strbuf_list_appendstrn(buf, tags_pairs_vars, tags_pairs_vars_len); } if (flecs_json_serialize_row_table_components( world, table, buf, values_ctx, i, column_count)) { result = -1; break; } flecs_json_object_pop(buf); } for (i = 0; i < column_count; i ++) { ecs_os_free(values_ctx[i].id_label); } ecs_os_free(tags_pairs_vars); return result; } int flecs_json_serialize_iter_result_rows( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { char *parent_path = NULL; EcsIdentifier *names = NULL; int32_t count = it->count; bool has_this = true; if (!count) { count = 1; /* Query without this variable */ has_this = false; } else { ecs_table_t *table = it->table; if (table) { /* Get path to parent once for entire table */ if (table->flags & EcsTableHasChildOf) { const ecs_table_record_t *tr = flecs_table_record_get( world, table, ecs_pair(EcsChildOf, EcsWildcard)); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t parent = ecs_pair_second( world, table->type.array[tr->index]); parent_path = ecs_get_path_w_sep(world, 0, parent, ".", ""); } /* Fetch name column once vs. calling ecs_get_name for each row */ if (table->flags & EcsTableHasName) { names = ecs_table_get_id(it->world, it->table, ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); } } else { /* Very rare case, but could happen if someone's using an iterator * to return empty entities. */ } } if (!desc->serialize_table) { flecs_json_serialize_iter_result_query_rows( world, it, buf, ser_ctx, count, has_this, parent_path, names); } else { flecs_json_serialize_iter_result_table_rows( world, it, buf, desc, count, has_this, parent_path, names); } ecs_os_free(parent_path); return 0; } #endif /** * @file json/serialize_query_info.c * @brief Serialize (component) values to JSON strings. */ #ifdef FLECS_JSON static const char* flecs_json_inout_str( ecs_inout_kind_t kind) { switch(kind) { case EcsIn: return "in"; case EcsOut: return "out"; case EcsInOut: return "inout"; case EcsInOutNone: return "none"; case EcsInOutDefault: return "default"; default: return "unknown"; } } static const char* flecs_json_oper_str( ecs_oper_kind_t kind) { switch(kind) { case EcsAnd: return "and"; case EcsNot: return "not"; case EcsOr: return "or"; case EcsOptional: return "optional"; case EcsAndFrom: return "andfrom"; case EcsNotFrom: return "notfrom"; case EcsOrFrom: return "orfrom"; default: return "unknown"; } } static void flecs_json_serialize_term_entity( const ecs_world_t *world, ecs_entity_t e, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "entity"); flecs_json_path(buf, world, e); if (e) { const char *symbol = ecs_get_symbol(world, e); if (symbol) { flecs_json_memberl(buf, "symbol"); flecs_json_string(buf, symbol); } if (ecs_has(world, e, EcsComponent)) { flecs_json_memberl(buf, "type"); flecs_json_true(buf); } } } static void flecs_json_serialize_term_ref( const ecs_world_t *world, const ecs_term_id_t *ref, ecs_strbuf_t *buf) { flecs_json_object_push(buf); if (ref->flags & EcsIsEntity) { flecs_json_serialize_term_entity(world, ref->id, buf); } else if (ref->flags & EcsIsVariable) { flecs_json_memberl(buf, "var"); if (ref->name) { flecs_json_string(buf, ref->name); } else if (ref->id) { if (ref->id == EcsThis) { flecs_json_string(buf, "this"); } else { flecs_json_path(buf, world, ref->id); } } } else if (ref->flags & EcsIsName) { flecs_json_memberl(buf, "name"); flecs_json_string(buf, ref->name); } flecs_json_object_pop(buf); } static void flecs_json_serialize_term_trav( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf) { if (term->src.trav) { flecs_json_memberl(buf, "trav"); flecs_json_object_push(buf); flecs_json_serialize_term_entity(world, term->src.trav, buf); flecs_json_object_pop(buf); } flecs_json_memberl(buf, "flags"); flecs_json_array_push(buf); if (term->src.flags & EcsSelf) { flecs_json_next(buf); flecs_json_string(buf, "self"); } if (term->src.flags & EcsCascade) { flecs_json_next(buf); flecs_json_string(buf, "cascade"); } else if (term->src.flags & EcsUp) { flecs_json_next(buf); flecs_json_string(buf, "up"); } flecs_json_array_pop(buf); } static void flecs_json_serialize_term( const ecs_world_t *world, const ecs_filter_t *q, int t, ecs_strbuf_t *buf) { ecs_term_t *term = &q->terms[t]; flecs_json_object_push(buf); flecs_json_memberl(buf, "inout"); flecs_json_string(buf, flecs_json_inout_str(term->inout)); flecs_json_memberl(buf, "has_data"); flecs_json_bool(buf, 0 == (term->flags & EcsTermNoData)); if (term->first.flags & EcsIsEntity && term->first.id) { if (ecs_has_id(world, term->first.id, EcsDontInherit)) { flecs_json_memberl(buf, "dont_inherit"); flecs_json_true(buf); } } flecs_json_memberl(buf, "oper"); flecs_json_string(buf, flecs_json_oper_str(term->oper)); flecs_json_memberl(buf, "src"); flecs_json_serialize_term_ref(world, &term->src, buf); flecs_json_memberl(buf, "first"); flecs_json_serialize_term_ref(world, &term->first, buf); if (term->second.id || term->second.name || term->second.flags & EcsIsEntity) { flecs_json_memberl(buf, "second"); flecs_json_serialize_term_ref(world, &term->second, buf); } flecs_json_serialize_term_trav(world, term, buf); flecs_json_object_pop(buf); } void flecs_json_serialize_query( const ecs_world_t *world, const ecs_filter_t *q, ecs_strbuf_t *buf) { flecs_json_object_push(buf); flecs_json_memberl(buf, "terms"); flecs_json_array_push(buf); int t; for (t = 0; t < q->term_count; t ++) { flecs_json_next(buf); flecs_json_serialize_term(world, q, t, buf); } flecs_json_array_pop(buf); flecs_json_object_pop(buf); } #endif /** * @file json/serialize_type_info.c * @brief Serialize type (reflection) information to JSON. */ #ifdef FLECS_JSON static int json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf); static int json_typeinfo_ser_primitive( ecs_primitive_kind_t kind, ecs_strbuf_t *str) { switch(kind) { case EcsBool: flecs_json_string(str, "bool"); break; case EcsChar: case EcsString: flecs_json_string(str, "text"); break; case EcsByte: flecs_json_string(str, "byte"); break; case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsIPtr: case EcsUPtr: flecs_json_string(str, "int"); break; case EcsF32: case EcsF64: flecs_json_string(str, "float"); break; case EcsEntity: flecs_json_string(str, "entity"); break; case EcsId: flecs_json_string(str, "id"); break; default: return -1; } return 0; } static void json_typeinfo_ser_constants( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = ecs_pair(EcsChildOf, type) }); while (ecs_term_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { flecs_json_next(str); flecs_json_string(str, ecs_get_name(world, it.entities[i])); } } } static void json_typeinfo_ser_enum( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"enum\""); json_typeinfo_ser_constants(world, type, str); } static void json_typeinfo_ser_bitmask( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"bitmask\""); json_typeinfo_ser_constants(world, type, str); } static int json_typeinfo_ser_array( const ecs_world_t *world, ecs_entity_t elem_type, int32_t count, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"array\""); flecs_json_next(str); if (json_typeinfo_ser_type(world, elem_type, str)) { goto error; } ecs_strbuf_list_append(str, "%u", count); return 0; error: return -1; } static int json_typeinfo_ser_array_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { const EcsArray *arr = ecs_get(world, type, EcsArray); ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { goto error; } return 0; error: return -1; } static int json_typeinfo_ser_vector( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { const EcsVector *arr = ecs_get(world, type, EcsVector); ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_strbuf_list_appendstr(str, "\"vector\""); flecs_json_next(str); if (json_typeinfo_ser_type(world, arr->type, str)) { goto error; } return 0; error: return -1; } /* Serialize unit information */ static int json_typeinfo_ser_unit( const ecs_world_t *world, ecs_strbuf_t *str, ecs_entity_t unit) { flecs_json_memberl(str, "unit"); flecs_json_path(str, world, unit); const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); if (uptr) { if (uptr->symbol) { flecs_json_memberl(str, "symbol"); flecs_json_string(str, uptr->symbol); } ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); if (quantity) { flecs_json_memberl(str, "quantity"); flecs_json_path(str, world, quantity); } } return 0; } static void json_typeinfo_ser_range( ecs_strbuf_t *str, const char *kind, ecs_member_value_range_t *range) { flecs_json_member(str, kind); flecs_json_array_push(str); flecs_json_next(str); flecs_json_number(str, range->min); flecs_json_next(str); flecs_json_number(str, range->max); flecs_json_array_pop(str); } /* Forward serialization to the different type kinds */ static int json_typeinfo_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, ecs_strbuf_t *str, const EcsStruct *st) { if (op->kind == EcsOpOpaque) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); return json_typeinfo_ser_type(world, ct->as_type, str); } flecs_json_array_push(str); switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpEnum: json_typeinfo_ser_enum(world, op->type, str); break; case EcsOpBitmask: json_typeinfo_ser_bitmask(world, op->type, str); break; case EcsOpArray: json_typeinfo_ser_array_type(world, op->type, str); break; case EcsOpVector: json_typeinfo_ser_vector(world, op->type, str); break; case EcsOpOpaque: /* Can't happen, already handled above */ ecs_throw(ECS_INTERNAL_ERROR, NULL); break; case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: if (json_typeinfo_ser_primitive( flecs_json_op_to_primitive_kind(op->kind), str)) { ecs_throw(ECS_INTERNAL_ERROR, NULL); } break; case EcsOpScope: case EcsOpPrimitive: default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } if (st) { ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); bool value_range = ECS_NEQ(m->range.min, m->range.max); bool error_range = ECS_NEQ(m->error_range.min, m->error_range.max); bool warning_range = ECS_NEQ(m->warning_range.min, m->warning_range.max); ecs_entity_t unit = m->unit; if (unit || error_range || warning_range || value_range) { flecs_json_next(str); flecs_json_next(str); flecs_json_object_push(str); if (unit) { json_typeinfo_ser_unit(world, str, unit); } if (value_range) { json_typeinfo_ser_range(str, "range", &m->range); } if (error_range) { json_typeinfo_ser_range(str, "error_range", &m->error_range); } if (warning_range) { json_typeinfo_ser_range(str, "warning_range", &m->warning_range); } flecs_json_object_pop(str); } } flecs_json_array_pop(str); return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int json_typeinfo_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, ecs_strbuf_t *str, const EcsStruct *st) { const EcsStruct *stack[64] = {st}; int32_t sp = 1; for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op != ops) { if (op->name) { flecs_json_member(str, op->name); } } int32_t elem_count = op->count; if (elem_count > 1) { flecs_json_array_push(str); json_typeinfo_ser_array(world, op->type, op->count, str); flecs_json_array_pop(str); i += op->op_count - 1; continue; } switch(op->kind) { case EcsOpPush: flecs_json_object_push(str); ecs_assert(sp < 63, ECS_INVALID_OPERATION, "type nesting too deep"); stack[sp ++] = ecs_get(world, op->type, EcsStruct); break; case EcsOpPop: flecs_json_object_pop(str); sp --; break; case EcsOpArray: case EcsOpVector: case EcsOpEnum: case EcsOpBitmask: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (json_typeinfo_ser_type_op(world, op, str, stack[sp - 1])) { goto error; } break; case EcsOpPrimitive: case EcsOpScope: default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } } return 0; error: return -1; } static int json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { ecs_strbuf_appendch(buf, '0'); return 0; } const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (!ser) { ecs_strbuf_appendch(buf, '0'); return 0; } const EcsStruct *st = ecs_get(world, type, EcsStruct); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(&ser->ops); return json_typeinfo_ser_type_ops(world, ops, count, buf, st); } int ecs_type_info_to_json_buf( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { return json_typeinfo_ser_type(world, type, buf); } char* ecs_type_info_to_json( const ecs_world_t *world, ecs_entity_t type) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_type_info_to_json_buf(world, type, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } #endif /** * @file meta/api.c * @brief API for creating entities with reflection data. */ /** * @file meta/meta.h * @brief Private functions for meta addon. */ #ifndef FLECS_META_PRIVATE_H #define FLECS_META_PRIVATE_H #ifdef FLECS_META void ecs_meta_type_serialized_init( ecs_iter_t *it); void ecs_meta_dtor_serialized( EcsMetaTypeSerialized *ptr); ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind( ecs_primitive_kind_t kind); bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data); void flecs_meta_import_definitions( ecs_world_t *world); #endif #endif #ifdef FLECS_META static bool flecs_type_is_number( ecs_world_t *world, ecs_entity_t type) { const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); if (!p) { return false; } switch(p->kind) { case EcsChar: case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsF32: case EcsF64: return true; case EcsBool: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: return false; default: ecs_abort(ECS_INVALID_PARAMETER, NULL); } } ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsPrimitive, { desc->kind }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_enum_init( ecs_world_t *world, const ecs_enum_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsEnum); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_enum_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, {m_desc->value}); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_bitmask_init( ecs_world_t *world, const ecs_bitmask_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsBitmask); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, {m_desc->value}); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_array_init( ecs_world_t *world, const ecs_array_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsArray, { .type = desc->type, .count = desc->count }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_vector_init( ecs_world_t *world, const ecs_vector_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsVector, { .type = desc->type }); flecs_resume_readonly(world, &rs); return t; } static bool flecs_member_range_overlaps( const ecs_member_value_range_t *range, const ecs_member_value_range_t *with) { if (ECS_EQ(with->min, with->max)) { return false; } if (ECS_EQ(range->min, range->max)) { return false; } if (range->min < with->min || range->max > with->max) { return true; } return false; } ecs_entity_t ecs_struct_init( ecs_world_t *world, const ecs_struct_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_member_t *m_desc = &desc->members[i]; if (!m_desc->type) { break; } if (!m_desc->name) { ecs_err("member %d of struct '%s' does not have a name", i, ecs_get_name(world, t)); goto error; } ecs_entity_t m = ecs_entity(world, { .name = m_desc->name }); ecs_set(world, m, EcsMember, { .type = m_desc->type, .count = m_desc->count, .offset = m_desc->offset, .unit = m_desc->unit }); EcsMemberRanges *ranges = NULL; const ecs_member_value_range_t *range = &m_desc->range; const ecs_member_value_range_t *error = &m_desc->error_range; const ecs_member_value_range_t *warning = &m_desc->warning_range; if (ECS_NEQ(range->min, range->max)) { ranges = ecs_ensure(world, m, EcsMemberRanges); if (range->min > range->max) { char *member_name = ecs_get_fullpath(world, m); ecs_err("member '%s' has an invalid value range [%f..%f]", member_name, range->min, range->max); ecs_os_free(member_name); goto error; } ranges->value.min = range->min; ranges->value.max = range->max; } if (ECS_NEQ(error->min, error->max)) { if (error->min > error->max) { char *member_name = ecs_get_fullpath(world, m); ecs_err("member '%s' has an invalid error range [%f..%f]", member_name, error->min, error->max); ecs_os_free(member_name); goto error; } if (flecs_member_range_overlaps(error, range)) { char *member_name = ecs_get_fullpath(world, m); ecs_err("error range of member '%s' overlaps with value range", member_name); ecs_os_free(member_name); goto error; } if (!ranges) { ranges = ecs_ensure(world, m, EcsMemberRanges); } ranges->error.min = error->min; ranges->error.max = error->max; } if (ECS_NEQ(warning->min, warning->max)) { if (warning->min > warning->max) { char *member_name = ecs_get_fullpath(world, m); ecs_err("member '%s' has an invalid warning range [%f..%f]", member_name, warning->min, warning->max); ecs_os_free(member_name); goto error; } if (flecs_member_range_overlaps(warning, range)) { char *member_name = ecs_get_fullpath(world, m); ecs_err("warning range of member '%s' overlaps with value " "range", member_name); ecs_os_free(member_name); goto error; } if (flecs_member_range_overlaps(warning, error)) { char *member_name = ecs_get_fullpath(world, m); ecs_err("warning range of member '%s' overlaps with error " "range", member_name); ecs_os_free(member_name); goto error; } if (!ranges) { ranges = ecs_ensure(world, m, EcsMemberRanges); } ranges->warning.min = warning->min; ranges->warning.max = warning->max; } if (ranges && !flecs_type_is_number(world, m_desc->type)) { char *member_name = ecs_get_fullpath(world, m); ecs_err("member '%s' has an value/error/warning range, but is not a " "number", member_name); ecs_os_free(member_name); goto error; } if (ranges) { ecs_modified(world, m, EcsMemberRanges); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("struct '%s' has no members", ecs_get_name(world, t)); goto error; } if (!ecs_has(world, t, EcsStruct)) { goto error; } return t; error: flecs_resume_readonly(world, &rs); if (t) { ecs_delete(world, t); } return 0; } ecs_entity_t ecs_opaque_init( ecs_world_t *world, const ecs_opaque_desc_t *desc) { ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set_ptr(world, t, EcsOpaque, &desc->type); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_unit_init( ecs_world_t *world, const ecs_unit_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t quantity = desc->quantity; if (quantity) { if (!ecs_has_id(world, quantity, EcsQuantity)) { ecs_err("entity '%s' for unit '%s' is not a quantity", ecs_get_name(world, quantity), ecs_get_name(world, t)); goto error; } ecs_add_pair(world, t, EcsQuantity, desc->quantity); } else { ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); } EcsUnit *value = ecs_ensure(world, t, EcsUnit); value->base = desc->base; value->over = desc->over; value->translation = desc->translation; value->prefix = desc->prefix; ecs_os_strset(&value->symbol, desc->symbol); if (!flecs_unit_validate(world, t, value)) { goto error; } ecs_modified(world, t, EcsUnit); flecs_resume_readonly(world, &rs); return t; error: if (t) { ecs_delete(world, t); } flecs_resume_readonly(world, &rs); return 0; } ecs_entity_t ecs_unit_prefix_init( ecs_world_t *world, const ecs_unit_prefix_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsUnitPrefix, { .symbol = ECS_CONST_CAST(char*, desc->symbol), .translation = desc->translation }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_quantity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = ecs_entity_init(world, desc); if (!t) { return 0; } ecs_add_id(world, t, EcsQuantity); flecs_resume_readonly(world, &rs); return t; } #endif /** * @file meta/cursor.c * @brief API for assigning values of runtime types with reflection. */ #include #ifdef FLECS_META #ifdef FLECS_PARSER #endif static const char* flecs_meta_op_kind_str( ecs_meta_type_op_kind_t kind) { switch(kind) { case EcsOpEnum: return "Enum"; case EcsOpBitmask: return "Bitmask"; case EcsOpArray: return "Array"; case EcsOpVector: return "Vector"; case EcsOpOpaque: return "Opaque"; case EcsOpPush: return "Push"; case EcsOpPop: return "Pop"; case EcsOpPrimitive: return "Primitive"; case EcsOpBool: return "Bool"; case EcsOpChar: return "Char"; case EcsOpByte: return "Byte"; case EcsOpU8: return "U8"; case EcsOpU16: return "U16"; case EcsOpU32: return "U32"; case EcsOpU64: return "U64"; case EcsOpI8: return "I8"; case EcsOpI16: return "I16"; case EcsOpI32: return "I32"; case EcsOpI64: return "I64"; case EcsOpF32: return "F32"; case EcsOpF64: return "F64"; case EcsOpUPtr: return "UPtr"; case EcsOpIPtr: return "IPtr"; case EcsOpString: return "String"; case EcsOpEntity: return "Entity"; case EcsOpId: return "Id"; case EcsOpScope: return "Scope"; default: return "<< invalid kind >>"; } } /* Get current scope */ static ecs_meta_scope_t* flecs_meta_cursor_get_scope( const ecs_meta_cursor_t *cursor) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); error: return NULL; } /* Restore scope, if dotmember was used */ static ecs_meta_scope_t* flecs_meta_cursor_restore_scope( ecs_meta_cursor_t *cursor, const ecs_meta_scope_t* scope) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); if (scope->prev_depth) { cursor->depth = scope->prev_depth; } error: return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; } /* Get current operation for scope */ static ecs_meta_type_op_t* flecs_meta_cursor_get_op( ecs_meta_scope_t *scope) { ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, NULL); return &scope->ops[scope->op_cur]; } /* Get component for type in current scope */ static const EcsComponent* get_ecs_component( const ecs_world_t *world, ecs_meta_scope_t *scope) { const EcsComponent *comp = scope->comp; if (!comp) { comp = scope->comp = ecs_get(world, scope->type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); } return comp; } /* Get size for type in current scope */ static ecs_size_t get_size( const ecs_world_t *world, ecs_meta_scope_t *scope) { return get_ecs_component(world, scope)->size; } static int32_t get_elem_count( ecs_meta_scope_t *scope) { const EcsOpaque *opaque = scope->opaque; if (scope->vector) { return ecs_vec_count(scope->vector); } else if (opaque && opaque->count) { return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->count; } /* Get pointer to current field/element */ static ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( const ecs_world_t *world, ecs_meta_scope_t *scope) { ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_size_t size = get_size(world, scope); const EcsOpaque *opaque = scope->opaque; if (scope->vector) { ecs_vec_set_min_count(NULL, scope->vector, size, scope->elem_cur + 1); scope->ptr = ecs_vec_first(scope->vector); } else if (opaque) { if (scope->is_collection) { if (!opaque->ensure_element) { char *str = ecs_get_fullpath(world, scope->type); ecs_err("missing ensure_element for opaque type %s", str); ecs_os_free(str); return NULL; } scope->is_empty_scope = false; void *opaque_ptr = opaque->ensure_element( scope->ptr, flecs_ito(size_t, scope->elem_cur)); ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, "ensure_element returned NULL"); return opaque_ptr; } else if (op->name) { if (!opaque->ensure_member) { char *str = ecs_get_fullpath(world, scope->type); ecs_err("missing ensure_member for opaque type %s", str); ecs_os_free(str); return NULL; } ecs_assert(scope->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return opaque->ensure_member(scope->ptr, op->name); } else { ecs_err("invalid operation for opaque type"); return NULL; } } return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); } static int flecs_meta_cursor_push_type( const ecs_world_t *world, ecs_meta_scope_t *scope, ecs_entity_t type, void *ptr) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); ecs_os_free(str); return -1; } scope[0] = (ecs_meta_scope_t) { .type = type, .ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t), .op_count = ecs_vec_count(&ser->ops), .ptr = ptr }; return 0; } ecs_meta_cursor_t ecs_meta_cursor( const ecs_world_t *world, ecs_entity_t type, void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_meta_cursor_t result = { .world = world, .valid = true }; if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { result.valid = false; } return result; error: return (ecs_meta_cursor_t){ 0 }; } void* ecs_meta_get_ptr( ecs_meta_cursor_t *cursor) { return flecs_meta_cursor_get_ptr(cursor->world, flecs_meta_cursor_get_scope(cursor)); } int ecs_meta_next( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); if (scope->is_collection) { scope->elem_cur ++; scope->op_cur = 0; if (scope->opaque) { return 0; } if (scope->elem_cur >= get_elem_count(scope)) { ecs_err("out of collection bounds (%d)", scope->elem_cur); return -1; } return 0; } scope->op_cur += op->op_count; if (scope->op_cur >= scope->op_count) { ecs_err("out of bounds"); return -1; } return 0; } int ecs_meta_elem( ecs_meta_cursor_t *cursor, int32_t elem) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); if (!scope->is_collection) { ecs_err("ecs_meta_elem can be used for collections only"); return -1; } scope->elem_cur = elem; scope->op_cur = 0; if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { ecs_err("out of collection bounds (%d)", scope->elem_cur); return -1; } return 0; } int ecs_meta_member( ecs_meta_cursor_t *cursor, const char *name) { if (cursor->depth == 0) { ecs_err("cannot move to member in root scope"); return -1; } ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); ecs_hashmap_t *members = scope->members; const ecs_world_t *world = cursor->world; if (!members) { ecs_err("cannot move to member '%s' for non-struct type", name); return -1; } const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); if (!cur_ptr) { char *path = ecs_get_fullpath(world, scope->type); ecs_err("unknown member '%s' for type '%s'", name, path); ecs_os_free(path); return -1; } scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); const EcsOpaque *opaque = scope->opaque; if (opaque) { if (!opaque->ensure_member) { char *str = ecs_get_fullpath(world, scope->type); ecs_err("missing ensure_member for opaque type %s", str); ecs_os_free(str); } } return 0; } int ecs_meta_dotmember( ecs_meta_cursor_t *cursor, const char *name) { #ifdef FLECS_PARSER ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); flecs_meta_cursor_restore_scope(cursor, cur_scope); int32_t prev_depth = cursor->depth; int dotcount = 0; char token[ECS_MAX_TOKEN_SIZE]; const char *ptr = name; while ((ptr = ecs_parse_token(NULL, NULL, ptr, token, '.'))) { if (ptr[0] != '.' && ptr[0]) { ecs_parser_error(NULL, name, ptr - name, "expected '.' or end of string"); goto error; } if (dotcount) { ecs_meta_push(cursor); } if (ecs_meta_member(cursor, token)) { goto error; } if (!ptr[0]) { break; } ptr ++; /* Skip . */ dotcount ++; } cur_scope = flecs_meta_cursor_get_scope(cursor); if (dotcount) { cur_scope->prev_depth = prev_depth; } return 0; error: return -1; #else (void)cursor; (void)name; ecs_err("the FLECS_PARSER addon is required for ecs_meta_dotmember"); return -1; #endif } int ecs_meta_push( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); const ecs_world_t *world = cursor->world; if (cursor->depth == 0) { if (!cursor->is_primitive_scope) { if ((op->kind > EcsOpScope) && (op->count <= 1)) { cursor->is_primitive_scope = true; return 0; } } } void *ptr = flecs_meta_cursor_get_ptr(world, scope); cursor->depth ++; ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, ECS_INVALID_PARAMETER, NULL); ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); /* If we're not already in an inline array and this operation is an inline * array, push a frame for the array. * Doing this first ensures that inline arrays take precedence over other * kinds of push operations, such as for a struct element type. */ if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { /* Push a frame just for the element type, with inline_array = true */ next_scope[0] = (ecs_meta_scope_t){ .ops = op, .op_count = op->op_count, .ptr = scope->ptr, .type = op->type, .is_collection = true, .is_inline_array = true }; /* With 'is_inline_array' set to true we ensure that we can never push * the same inline array twice */ return 0; } /* Operation-specific switch behavior */ switch(op->kind) { /* Struct push: this happens when pushing a struct member. */ case EcsOpPush: { const EcsOpaque *opaque = scope->opaque; if (opaque) { /* If this is a nested push for an opaque type, push the type of the * element instead of the next operation. This ensures that we won't * use flattened offsets for nested members. */ if (flecs_meta_cursor_push_type( world, next_scope, op->type, ptr) != 0) { goto error; } /* Strip the Push operation since we already pushed */ next_scope->members = next_scope->ops[0].members; next_scope->ops = &next_scope->ops[1]; next_scope->op_count --; break; } /* The ops array contains a flattened list for all members and nested * members of a struct, so we can use (ops + 1) to initialize the ops * array of the next scope. */ next_scope[0] = (ecs_meta_scope_t) { .ops = &op[1], /* op after push */ .op_count = op->op_count - 1, /* don't include pop */ .ptr = scope->ptr, .type = op->type, .members = op->members }; break; } /* Array push for an array type. Arrays can be encoded in 2 ways: either by * setting the EcsMember::count member to a value >1, or by specifying an * array type as member type. This is the latter case. */ case EcsOpArray: { if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { goto error; } const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); next_scope->type = type_ptr->type; next_scope->is_collection = true; break; } /* Vector push */ case EcsOpVector: { next_scope->vector = ptr; if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { goto error; } const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); next_scope->type = type_ptr->type; next_scope->is_collection = true; break; } /* Opaque type push. Depending on the type the opaque type represents the * scope will be pushed as a struct or collection type. The type information * of the as_type is retained, as this is important for type checking and * for nested opaque type support. */ case EcsOpOpaque: { const EcsOpaque *type_ptr = ecs_get(world, op->type, EcsOpaque); ecs_entity_t as_type = type_ptr->as_type; const EcsMetaType *mtype_ptr = ecs_get(world, as_type, EcsMetaType); /* Check what kind of type the opaque type represents */ switch(mtype_ptr->kind) { /* Opaque vector support */ case EcsVectorType: { const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); next_scope->type = vt->type; /* Push the element type of the vector type */ if (flecs_meta_cursor_push_type( world, next_scope, vt->type, NULL) != 0) { goto error; } /* This tracks whether any data was assigned inside the scope. When * the scope is popped, and is_empty_scope is still true, the vector * will be resized to 0. */ next_scope->is_empty_scope = true; next_scope->is_collection = true; break; } /* Opaque array support */ case EcsArrayType: { const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); next_scope->type = at->type; /* Push the element type of the array type */ if (flecs_meta_cursor_push_type( world, next_scope, at->type, NULL) != 0) { goto error; } /* Arrays are always a fixed size */ next_scope->is_empty_scope = false; next_scope->is_collection = true; break; } /* Opaque struct support */ case EcsStructType: /* Push struct type that represents the opaque type. This ensures * that the deserializer retains information about members and * member types, which is necessary for nested opaque types, and * allows for error checking. */ if (flecs_meta_cursor_push_type( world, next_scope, as_type, NULL) != 0) { goto error; } /* Strip push op, since we already pushed */ next_scope->members = next_scope->ops[0].members; next_scope->ops = &next_scope->ops[1]; next_scope->op_count --; break; case EcsPrimitiveType: case EcsEnumType: case EcsBitmaskType: case EcsOpaqueType: default: break; } next_scope->ptr = ptr; next_scope->opaque = type_ptr; break; } case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: { char *path = ecs_get_fullpath(world, scope->type); ecs_err("invalid push for type '%s'", path); ecs_os_free(path); goto error; } default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } if (scope->is_collection && !scope->opaque) { next_scope->ptr = ECS_OFFSET(next_scope->ptr, scope->elem_cur * get_size(world, scope)); } return 0; error: return -1; } int ecs_meta_pop( ecs_meta_cursor_t *cursor) { if (cursor->is_primitive_scope) { cursor->is_primitive_scope = false; return 0; } ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); cursor->depth --; if (cursor->depth < 0) { ecs_err("unexpected end of scope"); return -1; } ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(next_scope); if (!scope->is_inline_array) { if (op->kind == EcsOpPush) { next_scope->op_cur += op->op_count - 1; /* push + op_count should point to the operation after pop */ op = flecs_meta_cursor_get_op(next_scope); ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { /* Collection type, nothing else to do */ } else if (op->kind == EcsOpOpaque) { const EcsOpaque *opaque = scope->opaque; if (scope->is_collection) { const EcsMetaType *mtype = ecs_get(cursor->world, opaque->as_type, EcsMetaType); ecs_assert(mtype != NULL, ECS_INTERNAL_ERROR, NULL); /* When popping a opaque collection type, call resize to make * sure the vector isn't larger than the number of elements we * deserialized. * If the opaque type represents an array, don't call resize. */ if (mtype->kind != EcsArrayType) { ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); if (!opaque->resize) { char *str = ecs_get_fullpath(cursor->world, scope->type); ecs_err("missing resize for opaque type %s", str); ecs_os_free(str); return -1; } if (scope->is_empty_scope) { /* If no values were serialized for scope, resize * collection to 0 elements. */ ecs_assert(!scope->elem_cur, ECS_INTERNAL_ERROR, NULL); opaque->resize(scope->ptr, 0); } else { /* Otherwise resize collection to the index of the last * deserialized element + 1 */ opaque->resize(scope->ptr, flecs_ito(size_t, scope->elem_cur + 1)); } } } else { /* Opaque struct type, nothing to be done */ } } else { /* should not have been able to push if the previous scope was not * a complex or collection type */ ecs_assert(false, ECS_INTERNAL_ERROR, NULL); } } return 0; } bool ecs_meta_is_collection( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); return scope->is_collection; } ecs_entity_t ecs_meta_get_type( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->type; } ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_entity_t type = scope->type; const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); if (!st) { return 0; } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); return m->unit; } const char* ecs_meta_get_member( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->name; } ecs_entity_t ecs_meta_get_member_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_entity_t type = scope->type; const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); if (!st) { return 0; } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); return m->member; } /* Utilities for type conversions and bounds checking */ static struct { int64_t min, max; } ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, INT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) }, [EcsOpEntity] = {0, INT64_MAX}, [EcsOpId] = {0, INT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, INT32_MAX} }; static struct { uint64_t min, max; } ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {0, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, UINT64_MAX}, [EcsOpI8] = {0, INT8_MAX}, [EcsOpI16] = {0, INT16_MAX}, [EcsOpI32] = {0, INT32_MAX}, [EcsOpI64] = {0, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, [EcsOpEntity] = {0, UINT64_MAX}, [EcsOpId] = {0, UINT64_MAX}, [EcsOpEnum] = {0, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; static struct { double min, max; } ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, (double)UINT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) }, [EcsOpEntity] = {0, (double)UINT64_MAX}, [EcsOpId] = {0, (double)UINT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; #define set_T(T, ptr, value)\ ((T*)ptr)[0] = ((T)value) #define case_T(kind, T, dst, src)\ case kind:\ set_T(T, dst, src);\ break #define case_T_checked(kind, T, dst, src, bounds)\ case kind:\ if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ ecs_err("value %.0f is out of bounds for type %s", (double)src,\ flecs_meta_op_kind_str(kind));\ return -1;\ }\ set_T(T, dst, src);\ break #define cases_T_float(dst, src)\ case_T(EcsOpF32, ecs_f32_t, dst, src);\ case_T(EcsOpF64, ecs_f64_t, dst, src) #define cases_T_signed(dst, src, bounds)\ case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) #define cases_T_unsigned(dst, src, bounds)\ case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpId, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) #define cases_T_bool(dst, src)\ case EcsOpBool:\ set_T(ecs_bool_t, dst, value != 0);\ break static void flecs_meta_conversion_error( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, const char *from) { if (op->kind == EcsOpPop) { ecs_err("cursor: out of bounds"); } else { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("unsupported conversion from %s to '%s'", from, path); ecs_os_free(path); } } int ecs_meta_set_bool( ecs_meta_cursor_t *cursor, bool value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_bool) { ot->assign_bool(ptr, value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpF32: case EcsOpF64: case EcsOpString: flecs_meta_conversion_error(cursor, op, "bool"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_char( ecs_meta_cursor_t *cursor, char value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_char) { /* preferred operation */ opaque->assign_char(ptr, value); break; } else if (opaque->assign_uint) { opaque->assign_uint(ptr, (uint64_t)value); break; } else if (opaque->assign_int) { opaque->assign_int(ptr, value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: flecs_meta_conversion_error(cursor, op, "char"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_int( ecs_meta_cursor_t *cursor, int64_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_int) { /* preferred operation */ opaque->assign_int(ptr, value); break; } else if (opaque->assign_float) { /* most expressive */ opaque->assign_float(ptr, (double)value); break; } else if (opaque->assign_uint && (value > 0)) { opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); break; } else if (opaque->assign_char && (value > 0) && (value < 256)) { opaque->assign_char(ptr, flecs_ito(char, value)); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpString: { if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "int"); return -1; } default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_uint( ecs_meta_cursor_t *cursor, uint64_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_uint) { /* preferred operation */ opaque->assign_uint(ptr, value); break; } else if (opaque->assign_float) { /* most expressive */ opaque->assign_float(ptr, (double)value); break; } else if (opaque->assign_int && (value < INT64_MAX)) { opaque->assign_int(ptr, flecs_uto(int64_t, value)); break; } else if (opaque->assign_char && (value < 256)) { opaque->assign_char(ptr, flecs_uto(char, value)); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpString: if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "uint"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_float( ecs_meta_cursor_t *cursor, double value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: if (ECS_EQZERO(value)) { set_T(bool, ptr, false); } else { set_T(bool, ptr, true); } break; cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_float) { /* preferred operation */ opaque->assign_float(ptr, value); break; } else if (opaque->assign_int && /* most expressive */ (value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) { opaque->assign_int(ptr, (int64_t)value); break; } else if (opaque->assign_uint && (value >= 0)) { opaque->assign_uint(ptr, (uint64_t)value); break; } else if (opaque->assign_entity && (value >= 0)) { opaque->assign_entity( ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), (ecs_entity_t)value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpString: flecs_meta_conversion_error(cursor, op, "float"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_value( ecs_meta_cursor_t *cursor, const ecs_value_t *value) { ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t type = value->type; ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); const EcsMetaType *mt = ecs_get(cursor->world, type, EcsMetaType); if (!mt) { ecs_err("type of value does not have reflection data"); return -1; } if (mt->kind == EcsPrimitiveType) { const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); switch(prim->kind) { case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); case EcsString: return ecs_meta_set_string(cursor, *(char**)value->ptr); case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); case EcsId: return ecs_meta_set_id(cursor, *(ecs_id_t*)value->ptr); default: ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); goto error; } } else if (mt->kind == EcsEnumType) { return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); } else if (mt->kind == EcsBitmaskType) { return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); } else { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); if (op->type != value->type) { char *type_str = ecs_get_fullpath(cursor->world, value->type); flecs_meta_conversion_error(cursor, op, type_str); ecs_os_free(type_str); goto error; } return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); } error: return -1; } static int flecs_meta_add_bitmask_constant( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, void *out, const char *value) { ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); if (!ecs_os_strcmp(value, "0")) { return 0; } ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); if (!c) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); ecs_os_free(path); return -1; } const ecs_u32_t *v = ecs_get_pair_object( cursor->world, c, EcsConstant, ecs_u32_t); if (v == NULL) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); ecs_os_free(path); return -1; } *(ecs_u32_t*)out |= v[0]; return 0; } static int flecs_meta_parse_bitmask( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, void *out, const char *value) { char token[ECS_MAX_TOKEN_SIZE]; const char *prev = value, *ptr = value; *(ecs_u32_t*)out = 0; while ((ptr = strchr(ptr, '|'))) { ecs_os_memcpy(token, prev, ptr - prev); token[ptr - prev] = '\0'; if (flecs_meta_add_bitmask_constant(cursor, op, out, token) != 0) { return -1; } ptr ++; prev = ptr; } if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { return -1; } return 0; } static int flecs_meta_cursor_lookup( ecs_meta_cursor_t *cursor, const char *value, ecs_entity_t *out) { if (ecs_os_strcmp(value, "0")) { if (cursor->lookup_action) { *out = cursor->lookup_action( cursor->world, value, cursor->lookup_ctx); } else { *out = ecs_lookup_path(cursor->world, 0, value); } if (!*out) { ecs_err("unresolved entity identifier '%s'", value); return -1; } } return 0; } int ecs_meta_set_string( ecs_meta_cursor_t *cursor, const char *value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: if (!ecs_os_strcmp(value, "true")) { set_T(ecs_bool_t, ptr, true); } else if (!ecs_os_strcmp(value, "false")) { set_T(ecs_bool_t, ptr, false); } else if (isdigit(value[0])) { if (!ecs_os_strcmp(value, "0")) { set_T(ecs_bool_t, ptr, false); } else { set_T(ecs_bool_t, ptr, true); } } else { ecs_err("invalid value for boolean '%s'", value); goto error; } break; case EcsOpI8: case EcsOpU8: case EcsOpByte: set_T(ecs_i8_t, ptr, atol(value)); break; case EcsOpChar: set_T(char, ptr, value[0]); break; case EcsOpI16: case EcsOpU16: set_T(ecs_i16_t, ptr, atol(value)); break; case EcsOpI32: case EcsOpU32: set_T(ecs_i32_t, ptr, atol(value)); break; case EcsOpI64: case EcsOpU64: set_T(ecs_i64_t, ptr, atoll(value)); break; case EcsOpIPtr: case EcsOpUPtr: set_T(ecs_iptr_t, ptr, atoll(value)); break; case EcsOpF32: set_T(ecs_f32_t, ptr, atof(value)); break; case EcsOpF64: set_T(ecs_f64_t, ptr, atof(value)); break; case EcsOpString: { ecs_assert(*(ecs_string_t*)ptr != value, ECS_INVALID_PARAMETER, NULL); ecs_os_free(*(ecs_string_t*)ptr); char *result = ecs_os_strdup(value); set_T(ecs_string_t, ptr, result); break; } case EcsOpEnum: { ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); if (!c) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("unresolved enum constant '%s' for type '%s'", value, path); ecs_os_free(path); goto error; } const ecs_i32_t *v = ecs_get_pair_object( cursor->world, c, EcsConstant, ecs_i32_t); if (v == NULL) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("'%s' is not an enum constant for type '%s'", value, path); ecs_os_free(path); goto error; } set_T(ecs_i32_t, ptr, v[0]); break; } case EcsOpBitmask: if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { goto error; } set_T(ecs_entity_t, ptr, e); break; } case EcsOpId: { ecs_id_t id = 0; #ifdef FLECS_PARSER ecs_term_t term = {0}; if (ecs_parse_term( cursor->world, NULL, value, value, &term, NULL, NULL, false)) { if (ecs_term_finalize(cursor->world, &term)) { ecs_term_fini(&term); goto error; } id = term.id; ecs_term_fini(&term); } else { ecs_term_fini(&term); goto error; } #endif set_T(ecs_id_t, ptr, id); break; } case EcsOpPop: ecs_err("excess element '%s' in scope", value); goto error; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_string) { /* preferred */ opaque->assign_string(ptr, value); break; } else if (opaque->assign_char && value[0] && !value[1]) { opaque->assign_char(ptr, value[0]); break; } else if (opaque->assign_entity) { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { goto error; } opaque->assign_entity(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), e); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpScope: case EcsOpPrimitive: ecs_err("unsupported conversion from string '%s' to '%s'", value, flecs_meta_op_kind_str(op->kind)); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_string_literal( ecs_meta_cursor_t *cursor, const char *value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); ecs_size_t len = ecs_os_strlen(value); if (value[0] != '\"' || value[len - 1] != '\"') { ecs_err("invalid string literal '%s'", value); goto error; } switch(op->kind) { case EcsOpChar: set_T(ecs_char_t, ptr, value[1]); break; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: len -= 2; char *result = ecs_os_malloc(len + 1); ecs_os_memcpy(result, value + 1, len); result[len] = '\0'; if (ecs_meta_set_string(cursor, result)) { ecs_os_free(result); return -1; } ecs_os_free(result); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_entity( ecs_meta_cursor_t *cursor, ecs_entity_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: set_T(ecs_entity_t, ptr, value); break; case EcsOpId: set_T(ecs_id_t, ptr, value); /* entities are valid ids */ break; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_entity) { opaque->assign_entity(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: flecs_meta_conversion_error(cursor, op, "entity"); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_id( ecs_meta_cursor_t *cursor, ecs_entity_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpId: set_T(ecs_id_t, ptr, value); break; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_id) { opaque->assign_id(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: flecs_meta_conversion_error(cursor, op, "id"); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_null( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch (op->kind) { case EcsOpString: ecs_os_free(*(char**)ptr); set_T(ecs_string_t, ptr, NULL); break; case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_null) { ot->assign_null(ptr); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: flecs_meta_conversion_error(cursor, op, "null"); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } bool ecs_meta_get_bool( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr != 0; case EcsOpU8: return *(ecs_u8_t*)ptr != 0; case EcsOpChar: return *(ecs_char_t*)ptr != 0; case EcsOpByte: return *(ecs_u8_t*)ptr != 0; case EcsOpI16: return *(ecs_i16_t*)ptr != 0; case EcsOpU16: return *(ecs_u16_t*)ptr != 0; case EcsOpI32: return *(ecs_i32_t*)ptr != 0; case EcsOpU32: return *(ecs_u32_t*)ptr != 0; case EcsOpI64: return *(ecs_i64_t*)ptr != 0; case EcsOpU64: return *(ecs_u64_t*)ptr != 0; case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; case EcsOpF32: return ECS_NEQZERO(*(ecs_f32_t*)ptr); case EcsOpF64: return ECS_NEQZERO(*(ecs_f64_t*)ptr); case EcsOpString: return *(const char**)ptr != NULL; case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; case EcsOpId: return *(ecs_id_t*)ptr != 0; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } char ecs_meta_get_char( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpChar: return *(ecs_char_t*)ptr != 0; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); break; } error: return 0; } int64_t ecs_meta_get_int( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(const ecs_bool_t*)ptr; case EcsOpI8: return *(const ecs_i8_t*)ptr; case EcsOpU8: return *(const ecs_u8_t*)ptr; case EcsOpChar: return *(const ecs_char_t*)ptr; case EcsOpByte: return *(const ecs_u8_t*)ptr; case EcsOpI16: return *(const ecs_i16_t*)ptr; case EcsOpU16: return *(const ecs_u16_t*)ptr; case EcsOpI32: return *(const ecs_i32_t*)ptr; case EcsOpU32: return *(const ecs_u32_t*)ptr; case EcsOpI64: return *(const ecs_i64_t*)ptr; case EcsOpU64: return flecs_uto(int64_t, *(const ecs_u64_t*)ptr); case EcsOpIPtr: return *(const ecs_iptr_t*)ptr; case EcsOpUPtr: return flecs_uto(int64_t, *(const ecs_uptr_t*)ptr); case EcsOpF32: return (int64_t)*(const ecs_f32_t*)ptr; case EcsOpF64: return (int64_t)*(const ecs_f64_t*)ptr; case EcsOpString: return atoi(*(const char**)ptr); case EcsOpEnum: return *(const ecs_i32_t*)ptr; case EcsOpBitmask: return *(const ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to int"); break; case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from id to int"); break; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } uint64_t ecs_meta_get_uint( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return flecs_ito(uint64_t, *(const ecs_i8_t*)ptr); case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return flecs_ito(uint64_t, *(const ecs_char_t*)ptr); case EcsOpByte: return flecs_ito(uint64_t, *(const ecs_u8_t*)ptr); case EcsOpI16: return flecs_ito(uint64_t, *(const ecs_i16_t*)ptr); case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); case EcsOpU64: return *(ecs_u64_t*)ptr; case EcsOpIPtr: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); case EcsOpUPtr: return *(ecs_uptr_t*)ptr; case EcsOpF32: return flecs_ito(uint64_t, *(const ecs_f32_t*)ptr); case EcsOpF64: return flecs_ito(uint64_t, *(const ecs_f64_t*)ptr); case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); case EcsOpEnum: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); case EcsOpBitmask: return *(const ecs_u32_t*)ptr; case EcsOpEntity: return *(const ecs_entity_t*)ptr; case EcsOpId: return *(const ecs_id_t*)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } static double flecs_meta_to_float( ecs_meta_type_op_kind_t kind, const void *ptr) { switch(kind) { case EcsOpBool: return *(const ecs_bool_t*)ptr; case EcsOpI8: return *(const ecs_i8_t*)ptr; case EcsOpU8: return *(const ecs_u8_t*)ptr; case EcsOpChar: return *(const ecs_char_t*)ptr; case EcsOpByte: return *(const ecs_u8_t*)ptr; case EcsOpI16: return *(const ecs_i16_t*)ptr; case EcsOpU16: return *(const ecs_u16_t*)ptr; case EcsOpI32: return *(const ecs_i32_t*)ptr; case EcsOpU32: return *(const ecs_u32_t*)ptr; case EcsOpI64: return (double)*(const ecs_i64_t*)ptr; case EcsOpU64: return (double)*(const ecs_u64_t*)ptr; case EcsOpIPtr: return (double)*(const ecs_iptr_t*)ptr; case EcsOpUPtr: return (double)*(const ecs_uptr_t*)ptr; case EcsOpF32: return (double)*(const ecs_f32_t*)ptr; case EcsOpF64: return *(const ecs_f64_t*)ptr; case EcsOpString: return atof(*ECS_CONST_CAST(const char**, ptr)); case EcsOpEnum: return *(const ecs_i32_t*)ptr; case EcsOpBitmask: return *(const ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to float"); break; case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from id to float"); break; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } double ecs_meta_get_float( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); return flecs_meta_to_float(op->kind, ptr); } const char* ecs_meta_get_string( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpString: return *(const char**)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpChar: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); break; } error: return 0; } ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: return *(ecs_entity_t*)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpChar: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); break; } error: return 0; } ecs_entity_t ecs_meta_get_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: return *(ecs_id_t*)ptr; /* Entities are valid ids */ case EcsOpId: return *(ecs_id_t*)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpChar: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); break; } error: return 0; } double ecs_meta_ptr_to_float( ecs_primitive_kind_t type_kind, const void *ptr) { ecs_meta_type_op_kind_t kind = flecs_meta_primitive_to_op_kind(type_kind); return flecs_meta_to_float(kind, ptr); } #endif /** * @file meta/definitions.c * @brief Reflection definitions for builtin types. */ #ifdef FLECS_META /* Opaque type serializatior addon vector */ static int flecs_addon_vec_serialize(const ecs_serializer_t *ser, const void *ptr) { char ***data = ECS_CONST_CAST(char***, ptr); char **addons = data[0]; do { ser->value(ser, ecs_id(ecs_string_t), addons); } while((++ addons)[0]); return 0; } static size_t flecs_addon_vec_count(const void *ptr) { int32_t count = 0; char ***data = ECS_CONST_CAST(char***, ptr); char **addons = data[0]; do { ++ count; } while(addons[count]); return flecs_ito(size_t, count); } /* Initialize reflection data for core components */ static void flecs_meta_import_core_definitions( ecs_world_t *world) { ecs_struct(world, { .entity = ecs_id(EcsComponent), .members = { { .name = "size", .type = ecs_id(ecs_i32_t) }, { .name = "alignment", .type = ecs_id(ecs_i32_t) } } }); ecs_entity_t string_vec = ecs_vector(world, { .entity = ecs_entity(world, { .name = "flecs.core.string_vec_t "}), .type = ecs_id(ecs_string_t) }); ecs_entity_t addon_vec = ecs_opaque(world, { .entity = ecs_component(world, { .type = { .name = "flecs.core.addon_vec_t", .size = ECS_SIZEOF(char**), .alignment = ECS_ALIGNOF(char**) } }), .type = { .as_type = string_vec, .serialize = flecs_addon_vec_serialize, .count = flecs_addon_vec_count, } }); ecs_struct(world, { .entity = ecs_entity(world, { .name = "flecs.core.build_info_t", .root_sep = "" }), .members = { { .name = "compiler", .type = ecs_id(ecs_string_t) }, { .name = "addons", .type = addon_vec }, { .name = "version", .type = ecs_id(ecs_string_t) }, { .name = "version_major", .type = ecs_id(ecs_i16_t) }, { .name = "version_minor", .type = ecs_id(ecs_i16_t) }, { .name = "version_patch", .type = ecs_id(ecs_i16_t) }, { .name = "debug", .type = ecs_id(ecs_bool_t) }, { .name = "sanitize", .type = ecs_id(ecs_bool_t) }, { .name = "perf_trace", .type = ecs_id(ecs_bool_t) } } }); } /* Initialize reflection data for doc components */ static void flecs_meta_import_doc_definitions( ecs_world_t *world) { (void)world; #ifdef FLECS_DOC ecs_struct(world, { .entity = ecs_id(EcsDocDescription), .members = { { .name = "value", .type = ecs_id(ecs_string_t) } } }); #endif } /* Initialize reflection data for meta components */ static void flecs_meta_import_meta_definitions( ecs_world_t *world) { ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "TypeKind" }), .constants = { { .name = "PrimitiveType" }, { .name = "BitmaskType" }, { .name = "EnumType" }, { .name = "StructType" }, { .name = "ArrayType" }, { .name = "VectorType" }, { .name = "OpaqueType" } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMetaType), .members = { { .name = "kind", .type = type_kind } } }); ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "PrimitiveKind" }), .constants = { { .name = "Bool", 1 }, { .name = "Char" }, { .name = "Byte" }, { .name = "U8" }, { .name = "U16" }, { .name = "U32" }, { .name = "U64 "}, { .name = "I8" }, { .name = "I16" }, { .name = "I32" }, { .name = "I64" }, { .name = "F32" }, { .name = "F64" }, { .name = "UPtr "}, { .name = "IPtr" }, { .name = "String" }, { .name = "Entity" }, { .name = "Id" } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsPrimitive), .members = { { .name = "kind", .type = primitive_kind } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMember), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "unit", .type = ecs_id(ecs_entity_t) }, { .name = "offset", .type = ecs_id(ecs_i32_t) } } }); ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "value_range" }), .members = { { .name = "min", .type = ecs_id(ecs_f64_t) }, { .name = "max", .type = ecs_id(ecs_f64_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMemberRanges), .members = { { .name = "value", .type = vr }, { .name = "warning", .type = vr }, { .name = "error", .type = vr } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsVector), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsOpaque), .members = { { .name = "as_type", .type = ecs_id(ecs_entity_t) } } }); ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "unit_translation" }), .members = { { .name = "factor", .type = ecs_id(ecs_i32_t) }, { .name = "power", .type = ecs_id(ecs_i32_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnit), .members = { { .name = "symbol", .type = ecs_id(ecs_string_t) }, { .name = "prefix", .type = ecs_id(ecs_entity_t) }, { .name = "base", .type = ecs_id(ecs_entity_t) }, { .name = "over", .type = ecs_id(ecs_entity_t) }, { .name = "translation", .type = ut } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnitPrefix), .members = { { .name = "symbol", .type = ecs_id(ecs_string_t) }, { .name = "translation", .type = ut } } }); /* Meta doc definitions */ #ifdef FLECS_DOC ecs_entity_t meta = ecs_lookup(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"); #endif } void flecs_meta_import_definitions( ecs_world_t *world) { flecs_meta_import_core_definitions(world); flecs_meta_import_doc_definitions(world); flecs_meta_import_meta_definitions(world); } #endif /** * @file meta/meta.c * @brief Meta addon. */ #ifdef FLECS_META /* ecs_string_t lifecycle */ static ECS_COPY(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = ecs_os_strdup(*(const ecs_string_t*)src); }) static ECS_MOVE(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = *(ecs_string_t*)src; *(ecs_string_t*)src = NULL; }) static ECS_DTOR(ecs_string_t, ptr, { ecs_os_free(*(ecs_string_t*)ptr); *(ecs_string_t*)ptr = NULL; }) /* EcsMetaTypeSerialized lifecycle */ void ecs_meta_dtor_serialized( EcsMetaTypeSerialized *ptr) { int32_t i, count = ecs_vec_count(&ptr->ops); ecs_meta_type_op_t *ops = ecs_vec_first(&ptr->ops); for (i = 0; i < count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op->members) { flecs_name_index_free(op->members); } } ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); } static ECS_COPY(EcsMetaTypeSerialized, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); int32_t o, count = ecs_vec_count(&dst->ops); ecs_meta_type_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_type_op_t); for (o = 0; o < count; o ++) { ecs_meta_type_op_t *op = &ops[o]; if (op->members) { op->members = flecs_name_index_copy(op->members); } } }) static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = src->ops; src->ops = (ecs_vec_t){0}; }) static ECS_DTOR(EcsMetaTypeSerialized, ptr, { ecs_meta_dtor_serialized(ptr); }) /* EcsStruct lifecycle */ static void flecs_struct_dtor( EcsStruct *ptr) { ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); int32_t i, count = ecs_vec_count(&ptr->members); for (i = 0; i < count; i ++) { ecs_os_free(ECS_CONST_CAST(char*, members[i].name)); } ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); } static ECS_COPY(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); int32_t m, count = ecs_vec_count(&dst->members); for (m = 0; m < count; m ++) { members[m].name = ecs_os_strdup(members[m].name); } }) static ECS_MOVE(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = src->members; src->members = (ecs_vec_t){0}; }) static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) /* EcsEnum lifecycle */ static void flecs_constants_dtor( ecs_map_t *constants) { ecs_map_iter_t it = ecs_map_iter(constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_os_free(c); } ecs_map_fini(constants); } static void flecs_constants_copy( ecs_map_t *dst, const ecs_map_t *src) { ecs_map_copy(dst, src); ecs_map_iter_t it = ecs_map_iter(dst); while (ecs_map_next(&it)) { ecs_enum_constant_t **r = ecs_map_ref(&it, ecs_enum_constant_t); ecs_enum_constant_t *src_c = r[0]; ecs_enum_constant_t *dst_c = ecs_os_calloc_t(ecs_enum_constant_t); *dst_c = *src_c; dst_c->name = ecs_os_strdup(dst_c->name); r[0] = dst_c; } } static ECS_COPY(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); flecs_constants_copy(&dst->constants, &src->constants); }) static ECS_MOVE(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; ecs_os_zeromem(&src->constants); }) static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) /* EcsBitmask lifecycle */ static ECS_COPY(EcsBitmask, dst, src, { /* bitmask constant & enum constant have the same layout */ flecs_constants_dtor(&dst->constants); flecs_constants_copy(&dst->constants, &src->constants); }) static ECS_MOVE(EcsBitmask, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; ecs_os_zeromem(&src->constants); }) static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) /* EcsUnit lifecycle */ static void dtor_unit( EcsUnit *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnit, dst, src, { dtor_unit(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; }) static ECS_MOVE(EcsUnit, dst, src, { dtor_unit(dst); dst->symbol = src->symbol; dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; src->symbol = NULL; src->base = 0; src->over = 0; src->prefix = 0; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) /* EcsUnitPrefix lifecycle */ static void dtor_unit_prefix( EcsUnitPrefix *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnitPrefix, dst, src, { dtor_unit_prefix(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->translation = src->translation; }) static ECS_MOVE(EcsUnitPrefix, dst, src, { dtor_unit_prefix(dst); dst->symbol = src->symbol; dst->translation = src->translation; src->symbol = NULL; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) /* Type initialization */ static const char* flecs_type_kind_str( ecs_type_kind_t kind) { switch(kind) { case EcsPrimitiveType: return "Primitive"; case EcsBitmaskType: return "Bitmask"; case EcsEnumType: return "Enum"; case EcsStructType: return "Struct"; case EcsArrayType: return "Array"; case EcsVectorType: return "Vector"; case EcsOpaqueType: return "Opaque"; default: return "unknown"; } } static int flecs_init_type( ecs_world_t *world, ecs_entity_t type, ecs_type_kind_t kind, ecs_size_t size, ecs_size_t alignment) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); EcsMetaType *meta_type = ecs_ensure(world, type, EcsMetaType); if (meta_type->kind == 0) { meta_type->existing = ecs_has(world, type, EcsComponent); /* Ensure that component has a default constructor, to prevent crashing * serializers on uninitialized values. */ ecs_type_info_t *ti = flecs_type_info_ensure(world, type); if (!ti->hooks.ctor) { ti->hooks.ctor = ecs_default_ctor; } } else { if (meta_type->kind != kind) { ecs_err("type '%s' reregistered as '%s' (was '%s')", ecs_get_name(world, type), flecs_type_kind_str(kind), flecs_type_kind_str(meta_type->kind)); return -1; } } if (!meta_type->existing) { EcsComponent *comp = ecs_ensure(world, type, EcsComponent); comp->size = size; comp->alignment = alignment; ecs_modified(world, type, EcsComponent); } else { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (comp->size < size) { ecs_err("computed size (%d) for '%s' is larger than actual type (%d)", size, ecs_get_name(world, type), comp->size); return -1; } if (comp->alignment < alignment) { ecs_err("computed alignment (%d) for '%s' is larger than actual type (%d)", alignment, ecs_get_name(world, type), comp->alignment); return -1; } if (comp->size == size && comp->alignment != alignment) { if (comp->alignment < alignment) { ecs_err("computed size for '%s' matches with actual type but " "alignment is different (%d vs. %d)", ecs_get_name(world, type), alignment, comp->alignment); } } meta_type->partial = comp->size != size; } meta_type->kind = kind; ecs_modified(world, type, EcsMetaType); return 0; } #define init_type_t(world, type, kind, T) \ flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) static void flecs_set_struct_member( ecs_member_t *member, ecs_entity_t entity, const char *name, ecs_entity_t type, int32_t count, int32_t offset, ecs_entity_t unit, EcsMemberRanges *ranges) { member->member = entity; member->type = type; member->count = count; member->unit = unit; member->offset = offset; if (!count) { member->count = 1; } ecs_os_strset(ECS_CONST_CAST(char**, &member->name), name); if (ranges) { member->range = ranges->value; member->error_range = ranges->error; member->warning_range = ranges->warning; } else { ecs_os_zeromem(&member->range); ecs_os_zeromem(&member->error_range); ecs_os_zeromem(&member->warning_range); } } static int flecs_add_member_to_struct( ecs_world_t *world, ecs_entity_t type, ecs_entity_t member, EcsMember *m, EcsMemberRanges *ranges) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); const char *name = ecs_get_name(world, member); if (!name) { char *path = ecs_get_fullpath(world, type); ecs_err("member for struct '%s' does not have a name", path); ecs_os_free(path); return -1; } if (!m->type) { char *path = ecs_get_fullpath(world, member); ecs_err("member '%s' does not have a type", path); ecs_os_free(path); return -1; } if (ecs_get_typeid(world, m->type) == 0) { char *path = ecs_get_fullpath(world, member); char *ent_path = ecs_get_fullpath(world, m->type); ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); ecs_os_free(path); ecs_os_free(ent_path); return -1; } ecs_entity_t unit = m->unit; if (unit) { if (!ecs_has(world, unit, EcsUnit)) { ecs_err("entity '%s' for member '%s' is not a unit", ecs_get_name(world, unit), name); return -1; } if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", ecs_get_name(world, m->type), ecs_get_name(world, unit), name); return -1; } } else { if (ecs_has(world, m->type, EcsUnit)) { unit = m->type; m->unit = unit; } } EcsStruct *s = ecs_ensure(world, type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); /* First check if member is already added to struct */ ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t i, count = ecs_vec_count(&s->members); for (i = 0; i < count; i ++) { if (members[i].member == member) { flecs_set_struct_member(&members[i], member, name, m->type, m->count, m->offset, unit, ranges); break; } } /* If member wasn't added yet, add a new element to vector */ if (i == count) { ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member, name, m->type, m->count, m->offset, unit, ranges); /* Reobtain members array in case it was reallocated */ members = ecs_vec_first_t(&s->members, ecs_member_t); count ++; } bool explicit_offset = false; if (m->offset) { explicit_offset = true; } /* Compute member offsets and size & alignment of struct */ ecs_size_t size = 0; ecs_size_t alignment = 0; if (!explicit_offset) { for (i = 0; i < count; i ++) { ecs_member_t *elem = &members[i]; ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); if (!mbr_comp) { char *path = ecs_get_fullpath(world, elem->type); ecs_err("member '%s' is not a type", path); ecs_os_free(path); return -1; } ecs_size_t member_size = mbr_comp->size; ecs_size_t member_alignment = mbr_comp->alignment; if (!member_size || !member_alignment) { char *path = ecs_get_fullpath(world, elem->type); ecs_err("member '%s' has 0 size/alignment", path); ecs_os_free(path); return -1; } member_size *= elem->count; size = ECS_ALIGN(size, member_alignment); elem->size = member_size; elem->offset = size; /* Synchronize offset with Member component */ if (elem->member == member) { m->offset = elem->offset; } else { EcsMember *other = ecs_ensure(world, elem->member, EcsMember); other->offset = elem->offset; } size += member_size; if (member_alignment > alignment) { alignment = member_alignment; } } } else { /* If members have explicit offsets, we can't rely on computed * size/alignment values. Calculate size as if this is the last member * instead, since this will validate if the member fits in the struct. * It doesn't matter if the size is smaller than the actual struct size * because flecs_init_type function compares computed size with actual * (component) size to determine if the type is partial. */ ecs_member_t *elem = &members[i]; ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); if (!mbr_comp) { char *path = ecs_get_fullpath(world, elem->type); ecs_err("member '%s' is not a type", path); ecs_os_free(path); return -1; } ecs_size_t member_size = mbr_comp->size; ecs_size_t member_alignment = mbr_comp->alignment; if (!member_size || !member_alignment) { char *path = ecs_get_fullpath(world, elem->type); ecs_err("member '%s' has 0 size/alignment", path); ecs_os_free(path); return -1; } member_size *= elem->count; elem->size = member_size; size = elem->offset + member_size; const EcsComponent* comp = ecs_get(world, type, EcsComponent); if (comp) { alignment = comp->alignment; } else { alignment = member_alignment; } } if (size == 0) { ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); return -1; } if (alignment == 0) { ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); return -1; } /* Align struct size to struct alignment */ size = ECS_ALIGN(size, alignment); ecs_modified(world, type, EcsStruct); /* Do this last as it triggers the update of EcsMetaTypeSerialized */ if (flecs_init_type(world, type, EcsStructType, size, alignment)) { return -1; } /* If current struct is also a member, assign to itself */ if (ecs_has(world, type, EcsMember)) { EcsMember *type_mbr = ecs_ensure(world, type, EcsMember); ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); type_mbr->type = type; type_mbr->count = 1; ecs_modified(world, type, EcsMember); } return 0; } static int flecs_add_constant_to_enum( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsEnum *ptr = ecs_ensure(world, type, EcsEnum); /* Remove constant from map if it was already added */ ecs_map_iter_t it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); if (c->constant == e) { ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); } } /* Check if constant sets explicit value */ int32_t value = 0; bool value_set = false; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { char *path = ecs_get_fullpath(world, e); ecs_err("expected i32 type for enum constant '%s'", path); ecs_os_free(path); return -1; } const int32_t *value_ptr = ecs_get_pair_object( world, e, EcsConstant, ecs_i32_t); ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); value = *value_ptr; value_set = true; } /* Make sure constant value doesn't conflict if set / find the next value */ it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); if (value_set) { if (c->value == value) { char *path = ecs_get_fullpath(world, e); ecs_err("conflicting constant value %d for '%s' (other is '%s')", value, path, c->name); ecs_os_free(path); return -1; } } else { if (c->value >= value) { value = c->value + 1; } } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_enum_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, ecs_enum_constant_t, (ecs_map_key_t)value); c->name = ecs_os_strdup(ecs_get_name(world, e)); c->value = value; c->constant = e; ecs_i32_t *cptr = ecs_ensure_pair_object( world, e, EcsConstant, ecs_i32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; cptr = ecs_ensure_id(world, e, type); cptr[0] = value; return 0; } static int flecs_add_constant_to_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsBitmask *ptr = ecs_ensure(world, type, EcsBitmask); /* Remove constant from map if it was already added */ ecs_map_iter_t it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); if (c->constant == e) { ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); } } /* Check if constant sets explicit value */ uint32_t value = 1; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { char *path = ecs_get_fullpath(world, e); ecs_err("expected u32 type for bitmask constant '%s'", path); ecs_os_free(path); return -1; } const uint32_t *value_ptr = ecs_get_pair_object( world, e, EcsConstant, ecs_u32_t); ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); value = *value_ptr; } else { value = 1u << (ecs_u32_t)ecs_map_count(&ptr->constants); } /* Make sure constant value doesn't conflict */ it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); if (c->value == value) { char *path = ecs_get_fullpath(world, e); ecs_err("conflicting constant value for '%s' (other is '%s')", path, c->name); ecs_os_free(path); return -1; } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_bitmask_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, ecs_bitmask_constant_t, value); c->name = ecs_os_strdup(ecs_get_name(world, e)); c->value = value; c->constant = e; ecs_u32_t *cptr = ecs_ensure_pair_object( world, e, EcsConstant, ecs_u32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; cptr = ecs_ensure_id(world, e, type); cptr[0] = value; return 0; } static void flecs_set_primitive(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; switch(type->kind) { case EcsBool: init_type_t(world, e, EcsPrimitiveType, bool); break; case EcsChar: init_type_t(world, e, EcsPrimitiveType, char); break; case EcsByte: init_type_t(world, e, EcsPrimitiveType, bool); break; case EcsU8: init_type_t(world, e, EcsPrimitiveType, uint8_t); break; case EcsU16: init_type_t(world, e, EcsPrimitiveType, uint16_t); break; case EcsU32: init_type_t(world, e, EcsPrimitiveType, uint32_t); break; case EcsU64: init_type_t(world, e, EcsPrimitiveType, uint64_t); break; case EcsI8: init_type_t(world, e, EcsPrimitiveType, int8_t); break; case EcsI16: init_type_t(world, e, EcsPrimitiveType, int16_t); break; case EcsI32: init_type_t(world, e, EcsPrimitiveType, int32_t); break; case EcsI64: init_type_t(world, e, EcsPrimitiveType, int64_t); break; case EcsF32: init_type_t(world, e, EcsPrimitiveType, float); break; case EcsF64: init_type_t(world, e, EcsPrimitiveType, double); break; case EcsUPtr: init_type_t(world, e, EcsPrimitiveType, uintptr_t); break; case EcsIPtr: init_type_t(world, e, EcsPrimitiveType, intptr_t); break; case EcsString: init_type_t(world, e, EcsPrimitiveType, char*); break; case EcsEntity: init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); break; case EcsId: init_type_t(world, e, EcsPrimitiveType, ecs_id_t); break; } } } static void flecs_set_member(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMember *member = ecs_field(it, EcsMember, 1); EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, ecs_id(EcsMemberRanges), it->offset); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); continue; } flecs_add_member_to_struct(world, parent, e, &member[i], ranges ? &ranges[i] : NULL); } } static void flecs_set_member_ranges(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 1); EcsMember *member = ecs_table_get_id(world, it->table, ecs_id(EcsMember), it->offset); if (!member) { return; } int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); continue; } flecs_add_member_to_struct(world, parent, e, &member[i], &ranges[i]); } } static void flecs_add_enum(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 (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { continue; } ecs_add_id(world, e, EcsExclusive); ecs_add_id(world, e, EcsOneOf); ecs_add_id(world, e, EcsTag); } } static void flecs_add_bitmask(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 (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { continue; } } } static void flecs_add_constant(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); continue; } if (ecs_has(world, parent, EcsEnum)) { flecs_add_constant_to_enum(world, parent, e, it->event_id); } else if (ecs_has(world, parent, EcsBitmask)) { flecs_add_constant_to_bitmask(world, parent, e, it->event_id); } } } static void flecs_set_array(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsArray *array = ecs_field(it, EcsArray, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; int32_t elem_count = array[i].count; if (!elem_type) { ecs_err("array '%s' has no element type", ecs_get_name(world, e)); continue; } if (!elem_count) { ecs_err("array '%s' has size 0", ecs_get_name(world, e)); continue; } const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); if (flecs_init_type(world, e, EcsArrayType, elem_ptr->size * elem_count, elem_ptr->alignment)) { continue; } } } static void flecs_set_vector(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsVector *array = ecs_field(it, EcsVector, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; if (!elem_type) { ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); continue; } if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { continue; } } } static void flecs_set_custom_type(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsOpaque *serialize = ecs_field(it, EcsOpaque, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = serialize[i].as_type; if (!elem_type) { ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); continue; } const EcsComponent *comp = ecs_get(world, e, EcsComponent); if (!comp || !comp->size || !comp->alignment) { ecs_err("custom type '%s' has no size/alignment, register as component first", ecs_get_name(world, e)); continue; } if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { continue; } } } bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data) { char *derived_symbol = NULL; const char *symbol = data->symbol; ecs_entity_t base = data->base; ecs_entity_t over = data->over; ecs_entity_t prefix = data->prefix; ecs_unit_translation_t translation = data->translation; if (base) { if (!ecs_has(world, base, EcsUnit)) { ecs_err("entity '%s' for unit '%s' used as base is not a unit", ecs_get_name(world, base), ecs_get_name(world, t)); goto error; } } if (over) { if (!base) { ecs_err("invalid unit '%s': cannot specify over without base", ecs_get_name(world, t)); goto error; } if (!ecs_has(world, over, EcsUnit)) { ecs_err("entity '%s' for unit '%s' used as over is not a unit", ecs_get_name(world, over), ecs_get_name(world, t)); goto error; } } if (prefix) { if (!base) { ecs_err("invalid unit '%s': cannot specify prefix without base", ecs_get_name(world, t)); goto error; } const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); if (!prefix_ptr) { ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", ecs_get_name(world, over), ecs_get_name(world, t)); goto error; } if (translation.factor || translation.power) { if (prefix_ptr->translation.factor != translation.factor || prefix_ptr->translation.power != translation.power) { ecs_err( "factor for unit '%s' is inconsistent with prefix '%s'", ecs_get_name(world, t), ecs_get_name(world, prefix)); goto error; } } else { translation = prefix_ptr->translation; } } if (base) { bool must_match = false; /* Must base symbol match symbol? */ ecs_strbuf_t sbuf = ECS_STRBUF_INIT; if (prefix) { const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (ptr->symbol) { ecs_strbuf_appendstr(&sbuf, ptr->symbol); must_match = true; } } const EcsUnit *uptr = ecs_get(world, base, EcsUnit); ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); if (uptr->symbol) { ecs_strbuf_appendstr(&sbuf, uptr->symbol); } if (over) { uptr = ecs_get(world, over, EcsUnit); ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); if (uptr->symbol) { ecs_strbuf_appendch(&sbuf, '/'); ecs_strbuf_appendstr(&sbuf, uptr->symbol); must_match = true; } } derived_symbol = ecs_strbuf_get(&sbuf); if (derived_symbol && !ecs_os_strlen(derived_symbol)) { ecs_os_free(derived_symbol); derived_symbol = NULL; } if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { if (must_match) { ecs_err("symbol '%s' for unit '%s' does not match base" " symbol '%s'", symbol, ecs_get_name(world, t), derived_symbol); goto error; } } if (!symbol && derived_symbol && (prefix || over)) { ecs_os_free(data->symbol); data->symbol = derived_symbol; } else { ecs_os_free(derived_symbol); } } data->base = base; data->over = over; data->prefix = prefix; data->translation = translation; return true; error: ecs_os_free(derived_symbol); return false; } static void flecs_set_unit(ecs_iter_t *it) { EcsUnit *u = ecs_field(it, EcsUnit, 1); ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; flecs_unit_validate(world, e, &u[i]); } } static void flecs_unit_quantity_monitor(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; if (it->event == EcsOnAdd) { for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_add_pair(world, e, EcsQuantity, e); } } else { for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_remove_pair(world, e, EcsQuantity, e); } } } static void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMetaType *type = ecs_field(it, EcsMetaType, 1); int i; for (i = 0; i < it->count; i ++) { /* If a component is defined from reflection data, configure it with the * default constructor. This ensures that a new component value does not * contain uninitialized memory, which could cause serializers to crash * when for example inspecting string fields. */ if (!type->existing) { ecs_entity_t e = it->entities[i]; const ecs_type_info_t *ti = ecs_get_type_info(world, e); if (!ti || !ti->hooks.ctor) { ecs_set_hooks_id(world, e, &(ecs_type_hooks_t){ .ctor = ecs_default_ctor }); } } } } static void flecs_member_on_set(ecs_iter_t *it) { EcsMember *mbr = it->ptrs[0]; if (!mbr->count) { mbr->count = 1; } } void FlecsMetaImport( ecs_world_t *world) { ECS_MODULE(world, FlecsMeta); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsMetaType); flecs_bootstrap_component(world, EcsMetaTypeSerialized); flecs_bootstrap_component(world, EcsPrimitive); flecs_bootstrap_component(world, EcsEnum); flecs_bootstrap_component(world, EcsBitmask); flecs_bootstrap_component(world, EcsMember); flecs_bootstrap_component(world, EcsMemberRanges); flecs_bootstrap_component(world, EcsStruct); flecs_bootstrap_component(world, EcsArray); flecs_bootstrap_component(world, EcsVector); flecs_bootstrap_component(world, EcsOpaque); flecs_bootstrap_component(world, EcsUnit); flecs_bootstrap_component(world, EcsUnitPrefix); flecs_bootstrap_tag(world, EcsConstant); flecs_bootstrap_tag(world, EcsQuantity); ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); ecs_set_hooks(world, EcsMetaTypeSerialized, { .ctor = ecs_default_ctor, .move = ecs_move(EcsMetaTypeSerialized), .copy = ecs_copy(EcsMetaTypeSerialized), .dtor = ecs_dtor(EcsMetaTypeSerialized) }); ecs_set_hooks(world, EcsStruct, { .ctor = ecs_default_ctor, .move = ecs_move(EcsStruct), .copy = ecs_copy(EcsStruct), .dtor = ecs_dtor(EcsStruct) }); ecs_set_hooks(world, EcsMember, { .ctor = ecs_default_ctor, .on_set = flecs_member_on_set }); ecs_set_hooks(world, EcsMemberRanges, { .ctor = ecs_default_ctor }); ecs_set_hooks(world, EcsEnum, { .ctor = ecs_default_ctor, .move = ecs_move(EcsEnum), .copy = ecs_copy(EcsEnum), .dtor = ecs_dtor(EcsEnum) }); ecs_set_hooks(world, EcsBitmask, { .ctor = ecs_default_ctor, .move = ecs_move(EcsBitmask), .copy = ecs_copy(EcsBitmask), .dtor = ecs_dtor(EcsBitmask) }); ecs_set_hooks(world, EcsUnit, { .ctor = ecs_default_ctor, .move = ecs_move(EcsUnit), .copy = ecs_copy(EcsUnit), .dtor = ecs_dtor(EcsUnit) }); ecs_set_hooks(world, EcsUnitPrefix, { .ctor = ecs_default_ctor, .move = ecs_move(EcsUnitPrefix), .copy = ecs_copy(EcsUnitPrefix), .dtor = ecs_dtor(EcsUnitPrefix) }); /* Register triggers to finalize type information from component data */ ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ world, EcsFlecsInternals); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_primitive }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member_ranges }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_enum }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask }); ecs_observer(world, { .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_constant }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_add_constant }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_array }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_vector }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_custom_type }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_unit }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_serialized_init }); ecs_observer(world, { .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_init_default_ctor }); ecs_observer(world, { .filter.terms = { { .id = ecs_id(EcsUnit) }, { .id = EcsQuantity } }, .events = { EcsMonitor }, .callback = flecs_unit_quantity_monitor }); ecs_set_scope(world, old_scope); /* Initialize primitive types */ #define ECS_PRIMITIVE(world, type, primitive_kind)\ ecs_entity_init(world, &(ecs_entity_desc_t){\ .id = ecs_id(ecs_##type##_t),\ .name = #type,\ .symbol = #type });\ ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ .kind = primitive_kind\ }); ECS_PRIMITIVE(world, bool, EcsBool); ECS_PRIMITIVE(world, char, EcsChar); ECS_PRIMITIVE(world, byte, EcsByte); ECS_PRIMITIVE(world, u8, EcsU8); ECS_PRIMITIVE(world, u16, EcsU16); ECS_PRIMITIVE(world, u32, EcsU32); ECS_PRIMITIVE(world, u64, EcsU64); ECS_PRIMITIVE(world, uptr, EcsUPtr); ECS_PRIMITIVE(world, i8, EcsI8); ECS_PRIMITIVE(world, i16, EcsI16); ECS_PRIMITIVE(world, i32, EcsI32); ECS_PRIMITIVE(world, i64, EcsI64); ECS_PRIMITIVE(world, iptr, EcsIPtr); ECS_PRIMITIVE(world, f32, EcsF32); ECS_PRIMITIVE(world, f64, EcsF64); ECS_PRIMITIVE(world, string, EcsString); ECS_PRIMITIVE(world, entity, EcsEntity); ECS_PRIMITIVE(world, id, EcsId); #undef ECS_PRIMITIVE ecs_set_hooks(world, ecs_string_t, { .ctor = ecs_default_ctor, .copy = ecs_copy(ecs_string_t), .move = ecs_move(ecs_string_t), .dtor = ecs_dtor(ecs_string_t) }); /* Set default child components */ ecs_add_pair(world, ecs_id(EcsStruct), EcsDefaultChildComponent, ecs_id(EcsMember)); ecs_add_pair(world, ecs_id(EcsMember), EcsDefaultChildComponent, ecs_id(EcsMember)); ecs_add_pair(world, ecs_id(EcsEnum), EcsDefaultChildComponent, EcsConstant); ecs_add_pair(world, ecs_id(EcsBitmask), EcsDefaultChildComponent, EcsConstant); /* Relationship properties */ ecs_add_id(world, EcsQuantity, EcsExclusive); ecs_add_id(world, EcsQuantity, EcsTag); /* Import reflection definitions for builtin types */ flecs_meta_import_definitions(world); } #endif /** * @file meta/serialized.c * @brief Serialize type into flat operations array to speed up deserialization. */ #ifdef FLECS_META static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops); ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { return EcsOpPrimitive + kind; } static ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); return comp->size; } static ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) { ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t); op->kind = kind; op->offset = 0; op->count = 1; op->op_count = 1; op->size = 0; op->name = NULL; op->members = NULL; op->type = 0; op->member_index = 0; return op; } static ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index); ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); return op; } static int flecs_meta_serialize_primitive( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); if (!ptr) { char *name = ecs_get_fullpath(world, type); ecs_err("entity '%s' is not a primitive type", name); ecs_os_free(name); return -1; } ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind)); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_enum( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); op->offset = offset; op->type = type; op->size = ECS_SIZEOF(ecs_i32_t); return 0; } static int flecs_meta_serialize_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); op->offset = offset; op->type = type; op->size = ECS_SIZEOF(ecs_u32_t); return 0; } static int flecs_meta_serialize_array( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_array_component( ecs_world_t *world, ecs_entity_t type, ecs_vec_t *ops) { const EcsArray *ptr = ecs_get(world, type, EcsArray); if (!ptr) { return -1; /* Should never happen, will trigger internal error */ } if (flecs_meta_serialize_type(world, ptr->type, 0, ops) != 0) { return -1; } ecs_meta_type_op_t *first = ecs_vec_first(ops); first->count = ptr->count; return 0; } static int flecs_meta_serialize_vector( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_custom_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_struct( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsStruct *ptr = ecs_get(world, type, EcsStruct); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur, first = ecs_vec_count(ops); ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); ecs_member_t *members = ecs_vec_first(&ptr->members); int32_t i, count = ecs_vec_count(&ptr->members); ecs_hashmap_t *member_index = NULL; if (count) { op->members = member_index = flecs_name_index_new( world, &world->allocator); } for (i = 0; i < count; i ++) { ecs_member_t *member = &members[i]; cur = ecs_vec_count(ops); if (flecs_meta_serialize_type(world, member->type, offset + member->offset, ops) != 0) { continue; } op = flecs_meta_ops_get(ops, cur); if (!op->type) { op->type = member->type; } if (op->count <= 1) { op->count = member->count; } const char *member_name = member->name; op->name = member_name; op->op_count = ecs_vec_count(ops) - cur; op->member_index = i; flecs_name_index_ensure( member_index, flecs_ito(uint64_t, cur - first - 1), member_name, 0, 0); } flecs_meta_ops_add(ops, EcsOpPop); flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; return 0; } static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); if (!ptr) { char *path = ecs_get_fullpath(world, type); ecs_err("missing EcsMetaType for type %s'", path); ecs_os_free(path); return -1; } switch(ptr->kind) { case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); } return 0; } static int flecs_meta_serialize_component( ecs_world_t *world, ecs_entity_t type, ecs_vec_t *ops) { const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); if (!ptr) { char *path = ecs_get_fullpath(world, type); ecs_err("missing EcsMetaType for type %s'", path); ecs_os_free(path); return -1; } if (ptr->kind == EcsArrayType) { return flecs_meta_serialize_array_component(world, type, ops); } else { return flecs_meta_serialize_type(world, type, 0, ops); } } void ecs_meta_type_serialized_init( ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_vec_t ops; ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); flecs_meta_serialize_component(world, e, &ops); EcsMetaTypeSerialized *ptr = ecs_ensure( world, e, EcsMetaTypeSerialized); if (ptr->ops.array) { ecs_meta_dtor_serialized(ptr); } ptr->ops = ops; } } #endif /** * @file addons/os_api_impl/os_api_impl.c * @brief Builtin implementation for OS API. */ #ifdef FLECS_OS_API_IMPL #ifdef ECS_TARGET_WINDOWS /** * @file addons/os_api_impl/posix_impl.inl * @brief Builtin Windows implementation for OS API. */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include typedef struct ecs_win_thread_t { HANDLE thread; ecs_os_thread_callback_t callback; void *arg; } ecs_win_thread_t; static DWORD flecs_win_thread(void *ptr) { ecs_win_thread_t *thread = ptr; thread->callback(thread->arg); return 0; } static ecs_os_thread_t win_thread_new( ecs_os_thread_callback_t callback, void *arg) { ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t); thread->arg= arg; thread->callback = callback; thread->thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL); return (ecs_os_thread_t)(uintptr_t)thread; } static void* win_thread_join( ecs_os_thread_t thr) { ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr; DWORD r = WaitForSingleObject(thread->thread, INFINITE); if (r == WAIT_FAILED) { ecs_err("win_thread_join: WaitForSingleObject failed"); } ecs_os_free(thread); return NULL; } static ecs_os_thread_id_t win_thread_self(void) { return (ecs_os_thread_id_t)GetCurrentThreadId(); } static int32_t win_ainc( int32_t *count) { return InterlockedIncrement((volatile long*)count); } static int32_t win_adec( int32_t *count) { return InterlockedDecrement((volatile long*)count); } static int64_t win_lainc( int64_t *count) { return InterlockedIncrement64(count); } static int64_t win_ladec( int64_t *count) { return InterlockedDecrement64(count); } static ecs_os_mutex_t win_mutex_new(void) { CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); InitializeCriticalSection(mutex); return (ecs_os_mutex_t)(uintptr_t)mutex; } static void win_mutex_free( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; DeleteCriticalSection(mutex); ecs_os_free(mutex); } static void win_mutex_lock( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; EnterCriticalSection(mutex); } static void win_mutex_unlock( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; LeaveCriticalSection(mutex); } static ecs_os_cond_t win_cond_new(void) { CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); InitializeConditionVariable(cond); return (ecs_os_cond_t)(uintptr_t)cond; } static void win_cond_free( ecs_os_cond_t c) { (void)c; } static void win_cond_signal( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeConditionVariable(cond); } static void win_cond_broadcast( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeAllConditionVariable(cond); } static void win_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; SleepConditionVariableCS(cond, mutex, INFINITE); } static bool win_time_initialized; static double win_time_freq; static LARGE_INTEGER win_time_start; static ULONG win_current_resolution; static void win_time_setup(void) { if ( win_time_initialized) { return; } win_time_initialized = true; LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&win_time_start); win_time_freq = (double)freq.QuadPart / 1000000000.0; } static void win_sleep( int32_t sec, int32_t nanosec) { HANDLE timer; LARGE_INTEGER ft; ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); } static void win_enable_high_timer_resolution(bool enable) { HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); if (!hntdll) { return; } union { LONG (__stdcall *f)( ULONG desired, BOOLEAN set, ULONG * current); FARPROC p; } func; func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); if(!func.p) { return; } ULONG current, resolution = 10000; /* 1 ms */ if (!enable && win_current_resolution) { func.f(win_current_resolution, 0, ¤t); win_current_resolution = 0; return; } else if (!enable) { return; } if (resolution == win_current_resolution) { return; } if (win_current_resolution) { func.f(win_current_resolution, 0, ¤t); } if (func.f(resolution, 1, ¤t)) { /* Try setting a lower resolution */ resolution *= 2; if(func.f(resolution, 1, ¤t)) return; } win_current_resolution = resolution; } static uint64_t win_time_now(void) { uint64_t now; LARGE_INTEGER qpc_t; QueryPerformanceCounter(&qpc_t); now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); return now; } static void win_fini(void) { if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(false); } } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = win_thread_new; api.thread_join_ = win_thread_join; api.thread_self_ = win_thread_self; api.task_new_ = win_thread_new; api.task_join_ = win_thread_join; api.ainc_ = win_ainc; api.adec_ = win_adec; api.lainc_ = win_lainc; api.ladec_ = win_ladec; api.mutex_new_ = win_mutex_new; api.mutex_free_ = win_mutex_free; api.mutex_lock_ = win_mutex_lock; api.mutex_unlock_ = win_mutex_unlock; api.cond_new_ = win_cond_new; api.cond_free_ = win_cond_free; api.cond_signal_ = win_cond_signal; api.cond_broadcast_ = win_cond_broadcast; api.cond_wait_ = win_cond_wait; api.sleep_ = win_sleep; api.now_ = win_time_now; api.fini_ = win_fini; win_time_setup(); if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(true); } ecs_os_set_api(&api); } #else /** * @file addons/os_api_impl/posix_impl.inl * @brief Builtin POSIX implementation for OS API. */ #include "pthread.h" #if defined(__APPLE__) && defined(__MACH__) #include #elif defined(__EMSCRIPTEN__) #include #else #include #endif /* This mutex is used to emulate atomic operations when the gnu builtins are * not supported. This is probably not very fast but if the compiler doesn't * support the gnu built-ins, then speed is probably not a priority. */ #ifndef __GNUC__ static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; #endif static ecs_os_thread_t posix_thread_new( ecs_os_thread_callback_t callback, void *arg) { pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); if (pthread_create (thread, NULL, callback, arg) != 0) { ecs_os_abort(); } return (ecs_os_thread_t)(uintptr_t)thread; } static void* posix_thread_join( ecs_os_thread_t thread) { void *arg; pthread_t *thr = (pthread_t*)(uintptr_t)thread; pthread_join(*thr, &arg); ecs_os_free(thr); return arg; } static ecs_os_thread_id_t posix_thread_self(void) { return (ecs_os_thread_id_t)pthread_self(); } static int32_t posix_ainc( int32_t *count) { int value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) += 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int32_t posix_adec( int32_t *count) { int32_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) -= 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int64_t posix_lainc( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) += 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int64_t posix_ladec( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) -= 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static ecs_os_mutex_t posix_mutex_new(void) { pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); if (pthread_mutex_init(mutex, NULL)) { abort(); } return (ecs_os_mutex_t)(uintptr_t)mutex; } static void posix_mutex_free( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; pthread_mutex_destroy(mutex); ecs_os_free(mutex); } static void posix_mutex_lock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_lock(mutex)) { abort(); } } static void posix_mutex_unlock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_unlock(mutex)) { abort(); } } static ecs_os_cond_t posix_cond_new(void) { pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); if (pthread_cond_init(cond, NULL)) { abort(); } return (ecs_os_cond_t)(uintptr_t)cond; } static void posix_cond_free( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_destroy(cond)) { abort(); } ecs_os_free(cond); } static void posix_cond_signal( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_signal(cond)) { abort(); } } static void posix_cond_broadcast( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_broadcast(cond)) { abort(); } } static void posix_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_cond_wait(cond, mutex)) { abort(); } } static bool posix_time_initialized; #if defined(__APPLE__) && defined(__MACH__) static mach_timebase_info_data_t posix_osx_timebase; static uint64_t posix_time_start; #else static uint64_t posix_time_start; #endif static void posix_time_setup(void) { if (posix_time_initialized) { return; } posix_time_initialized = true; #if defined(__APPLE__) && defined(__MACH__) mach_timebase_info(&posix_osx_timebase); posix_time_start = mach_absolute_time(); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; #endif } static void posix_sleep( int32_t sec, int32_t nanosec) { struct timespec sleepTime; ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); sleepTime.tv_sec = sec; sleepTime.tv_nsec = nanosec; if (nanosleep(&sleepTime, NULL)) { ecs_err("nanosleep failed"); } } /* prevent 64-bit overflow when computing relative timestamp see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 */ #if defined(ECS_TARGET_DARWIN) static int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { int64_t q = value / denom; int64_t r = value % denom; return q * numer + r * numer / denom; } #endif static uint64_t posix_time_now(void) { ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); uint64_t now; #if defined(ECS_TARGET_DARWIN) now = (uint64_t) posix_int64_muldiv( (int64_t)mach_absolute_time(), (int64_t)posix_osx_timebase.numer, (int64_t)posix_osx_timebase.denom); #elif defined(__EMSCRIPTEN__) now = (long long)(emscripten_get_now() * 1000.0 * 1000); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); #endif return now; } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = posix_thread_new; api.thread_join_ = posix_thread_join; api.thread_self_ = posix_thread_self; api.task_new_ = posix_thread_new; api.task_join_ = posix_thread_join; api.ainc_ = posix_ainc; api.adec_ = posix_adec; api.lainc_ = posix_lainc; api.ladec_ = posix_ladec; api.mutex_new_ = posix_mutex_new; api.mutex_free_ = posix_mutex_free; api.mutex_lock_ = posix_mutex_lock; api.mutex_unlock_ = posix_mutex_unlock; api.cond_new_ = posix_cond_new; api.cond_free_ = posix_cond_free; api.cond_signal_ = posix_cond_signal; api.cond_broadcast_ = posix_cond_broadcast; api.cond_wait_ = posix_cond_wait; api.sleep_ = posix_sleep; api.now_ = posix_time_now; posix_time_setup(); ecs_os_set_api(&api); } #endif #endif /** * @file addons/ipeline/pipeline.c * @brief Functions for building and running pipelines. */ #ifdef FLECS_PIPELINE static void flecs_pipeline_free( ecs_pipeline_state_t *p) { if (p) { ecs_world_t *world = p->query->filter.world; ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); ecs_vec_fini_t(a, &p->systems, ecs_entity_t); ecs_os_free(p->iters); ecs_query_fini(p->query); ecs_os_free(p); } } static ECS_MOVE(EcsPipeline, dst, src, { flecs_pipeline_free(dst->state); dst->state = src->state; src->state = NULL; }) static ECS_DTOR(EcsPipeline, ptr, { flecs_pipeline_free(ptr->state); }) typedef enum ecs_write_kind_t { WriteStateNone = 0, WriteStateToStage, } ecs_write_kind_t; typedef struct ecs_write_state_t { bool write_barrier; ecs_map_t ids; ecs_map_t wildcard_ids; } ecs_write_state_t; static ecs_write_kind_t flecs_pipeline_get_write_state( ecs_write_state_t *write_state, ecs_id_t id) { ecs_write_kind_t result = WriteStateNone; if (write_state->write_barrier) { /* Any component could have been written */ return WriteStateToStage; } if (id == EcsWildcard) { /* Using a wildcard for id indicates read barrier. Return true if any * components could have been staged */ if (ecs_map_count(&write_state->ids) || ecs_map_count(&write_state->wildcard_ids)) { return WriteStateToStage; } } if (!ecs_id_is_wildcard(id)) { if (ecs_map_get(&write_state->ids, id)) { result = WriteStateToStage; } } else { ecs_map_iter_t it = ecs_map_iter(&write_state->ids); while (ecs_map_next(&it)) { if (ecs_id_match(ecs_map_key(&it), id)) { return WriteStateToStage; } } } if (ecs_map_count(&write_state->wildcard_ids)) { ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); while (ecs_map_next(&it)) { if (ecs_id_match(id, ecs_map_key(&it))) { return WriteStateToStage; } } } return result; } static void flecs_pipeline_set_write_state( ecs_write_state_t *write_state, ecs_id_t id) { if (id == EcsWildcard) { /* If writing to wildcard, flag all components as written */ write_state->write_barrier = true; return; } ecs_map_t *ids; if (ecs_id_is_wildcard(id)) { ids = &write_state->wildcard_ids; } else { ids = &write_state->ids; } ecs_map_ensure(ids, id)[0] = true; } static void flecs_pipeline_reset_write_state( ecs_write_state_t *write_state) { ecs_map_clear(&write_state->ids); ecs_map_clear(&write_state->wildcard_ids); write_state->write_barrier = false; } static bool flecs_pipeline_check_term( ecs_world_t *world, ecs_term_t *term, bool is_active, ecs_write_state_t *write_state) { (void)world; ecs_term_id_t *src = &term->src; if (src->flags & EcsInOutNone) { return false; } ecs_id_t id = term->id; ecs_oper_kind_t oper = term->oper; ecs_inout_kind_t inout = term->inout; bool from_any = ecs_term_match_0(term); bool from_this = ecs_term_match_this(term); bool is_shared = !from_any && (!from_this || !(src->flags & EcsSelf)); ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); if (from_this && ws >= WriteStateToStage) { /* A staged write could have happened for an id that's matched on the * main storage. Even if the id isn't read, still insert a merge so that * a write to the main storage after the staged write doesn't get * overwritten. */ return true; } if (inout == EcsInOutDefault) { if (from_any) { /* If no inout kind is specified for terms without a source, this is * not interpreted as a read/write annotation but just a (component) * id that's passed to a system. */ return false; } else if (is_shared) { inout = EcsIn; } else { /* Default for owned terms is InOut */ inout = EcsInOut; } } if (oper == EcsNot && inout == EcsOut) { /* If a Not term is combined with Out, it signals that the system * intends to add a component that the entity doesn't yet have */ from_any = true; } if (from_any) { switch(inout) { case EcsOut: case EcsInOut: if (is_active) { /* Only flag component as written if system is active */ flecs_pipeline_set_write_state(write_state, id); } break; case EcsInOutDefault: case EcsInOutNone: case EcsIn: break; } switch(inout) { case EcsIn: case EcsInOut: if (ws == WriteStateToStage) { /* If a system does a get/ensure, the component is fetched from * the main store so it must be merged first */ return true; } /* fall through */ case EcsInOutDefault: case EcsInOutNone: case EcsOut: break; } } return false; } static bool flecs_pipeline_check_terms( ecs_world_t *world, ecs_filter_t *filter, bool is_active, ecs_write_state_t *ws) { bool needs_merge = false; ecs_term_t *terms = filter->terms; int32_t t, term_count = filter->term_count; /* Check This terms first. This way if a term indicating writing to a stage * was added before the term, it won't cause merging. */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } /* Now check staged terms */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (!ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } return needs_merge; } static EcsPoly* flecs_pipeline_term_system( ecs_iter_t *it) { int32_t index = ecs_table_get_column_index( it->real_world, it->table, ecs_poly_id(EcsSystem)); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); return poly; } static bool flecs_pipeline_build( ecs_world_t *world, ecs_pipeline_state_t *pq) { ecs_iter_t it = ecs_query_iter(world, pq->query); if (pq->match_count == pq->query->match_count) { /* No need to rebuild the pipeline */ ecs_iter_fini(&it); return false; } world->info.pipeline_build_count_total ++; pq->rebuild_count ++; ecs_allocator_t *a = &world->allocator; ecs_pipeline_op_t *op = NULL; ecs_write_state_t ws = {0}; ecs_map_init(&ws.ids, a); ecs_map_init(&ws.wildcard_ids, a); ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); ecs_vec_reset_t(a, &pq->systems, ecs_entity_t); bool multi_threaded = false; bool no_readonly = false; bool first = true; /* Iterate systems in pipeline, add ops for running / merging */ while (ecs_query_next(&it)) { EcsPoly *poly = flecs_pipeline_term_system(&it); bool is_active = ecs_table_get_type_index( world, it.table, EcsEmpty) == -1; int32_t i; for (i = 0; i < it.count; i ++) { ecs_poly_assert(poly[i].poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly[i].poly; ecs_query_t *q = sys->query; bool needs_merge = false; needs_merge = flecs_pipeline_check_terms( world, &q->filter, is_active, &ws); if (is_active) { if (first) { multi_threaded = sys->multi_threaded; no_readonly = sys->no_readonly; first = false; } if (sys->multi_threaded != multi_threaded) { needs_merge = true; multi_threaded = sys->multi_threaded; } if (sys->no_readonly != no_readonly) { needs_merge = true; no_readonly = sys->no_readonly; } } if (no_readonly) { needs_merge = true; } if (needs_merge) { /* After merge all components will be merged, so reset state */ flecs_pipeline_reset_write_state(&ws); /* An inactive system can insert a merge if one of its * components got written, which could make the system * active. If this is the only system in the pipeline operation, * it results in an empty operation when we get here. If that's * the case, reuse the empty operation for the next op. */ if (op && op->count) { op = NULL; } /* Re-evaluate columns to set write flags if system is active. * If system is inactive, it can't write anything and so it * should not insert unnecessary merges. */ needs_merge = false; if (is_active) { needs_merge = flecs_pipeline_check_terms( world, &q->filter, true, &ws); } /* The component states were just reset, so if we conclude that * another merge is needed something is wrong. */ ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); } if (!op) { op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); op->offset = ecs_vec_count(&pq->systems); op->count = 0; op->multi_threaded = false; op->no_readonly = false; op->time_spent = 0; op->commands_enqueued = 0; } /* Don't increase count for inactive systems, as they are ignored by * the query used to run the pipeline. */ if (is_active) { ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] = it.entities[i]; if (!op->count) { op->multi_threaded = multi_threaded; op->no_readonly = no_readonly; } op->count ++; } } } if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { ecs_vec_remove_last(&pq->ops); } ecs_map_fini(&ws.ids); ecs_map_fini(&ws.wildcard_ids); op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); if (!op) { ecs_dbg("#[green]pipeline#[reset] is empty"); return true; } else { /* Add schedule to debug tracing */ ecs_dbg("#[bold]pipeline rebuild"); ecs_log_push_1(); ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", op->multi_threaded, !op->no_readonly); ecs_log_push_1(); int32_t i, count = ecs_vec_count(&pq->systems); int32_t op_index = 0, ran_since_merge = 0; ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_entity_t system = systems[i]; const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly->poly; #ifdef FLECS_LOG_1 char *path = ecs_get_fullpath(world, system); const char *doc_name = NULL; #ifdef FLECS_DOC const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, EcsDocDescription, EcsName); if (doc_name_id) { doc_name = doc_name_id->value; } #endif if (doc_name) { ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); } else { ecs_dbg("#[green]system#[reset] %s", path); } ecs_os_free(path); #endif ecs_assert(op[op_index].offset + ran_since_merge == i, ECS_INTERNAL_ERROR, NULL); ran_since_merge ++; if (ran_since_merge == op[op_index].count) { ecs_dbg("#[magenta]merge#[reset]"); ecs_log_pop_1(); ran_since_merge = 0; op_index ++; if (op_index < ecs_vec_count(&pq->ops)) { ecs_dbg( "#[green]schedule#[reset]: " "threading: %d, staging: %d:", op[op_index].multi_threaded, !op[op_index].no_readonly); } ecs_log_push_1(); } if (sys->last_frame == (world->info.frame_count_total + 1)) { if (op_index < ecs_vec_count(&pq->ops)) { pq->cur_op = &op[op_index]; pq->cur_i = i; } else { pq->cur_op = NULL; pq->cur_i = 0; } } } ecs_log_pop_1(); ecs_log_pop_1(); } pq->match_count = pq->query->match_count; ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), ECS_INTERNAL_ERROR, NULL); return true; } static void flecs_pipeline_next_system( ecs_pipeline_state_t *pq) { if (!pq->cur_op) { return; } pq->cur_i ++; if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { pq->cur_op ++; if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { pq->cur_op = NULL; } } } bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); /* If any entity mutations happened that could have affected query matching * notify appropriate queries so caches are up to date. This includes the * pipeline query. */ if (start_of_frame) { ecs_run_aperiodic(world, 0); } ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); bool rebuilt = flecs_pipeline_build(world, pq); if (start_of_frame) { /* Initialize iterators */ int32_t i, count = pq->iter_count; for (i = 0; i < count; i ++) { ecs_world_t *stage = ecs_get_stage(world, i); pq->iters[i] = ecs_query_iter(stage, pq->query); } pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); pq->cur_i = 0; } else { flecs_pipeline_next_system(pq); } return rebuilt; } void ecs_run_pipeline( ecs_world_t *world, ecs_entity_t pipeline, ecs_ftime_t delta_time) { if (!pipeline) { pipeline = world->pipeline; } /* create any worker task threads request */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } EcsPipeline *p = ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); flecs_workers_progress(world, p->state, delta_time); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } } int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time) { ecs_pipeline_state_t* pq = world->pq; ecs_pipeline_op_t* op = pq->cur_op; int32_t i = pq->cur_i; ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(&pq->systems); ecs_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); int32_t ran_since_merge = i - op->offset; for (; i < count; i++) { ecs_entity_t system = systems[i]; const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t* sys = (ecs_system_t*)poly->poly; /* Keep track of the last frame for which the system has ran, so we * know from where to resume the schedule in case the schedule * changes during a merge. */ sys->last_frame = world->info.frame_count_total + 1; ecs_stage_t* s = NULL; if (!op->no_readonly) { /* If system is no_readonly it operates on the actual world, not * the stage. Only pass stage to system if it's readonly. */ s = stage; } ecs_run_intern(world, s, system, sys, stage_index, stage_count, delta_time, 0, 0, NULL); world->info.systems_ran_frame++; ran_since_merge++; if (ran_since_merge == op->count) { /* Merge */ break; } } return i; } void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); int32_t stage_count = ecs_get_stage_count(world); bool multi_threaded = world->worker_cond != 0; ecs_assert(!stage_index, ECS_INVALID_OPERATION, NULL); // Update the pipeline the workers will execute world->pq = pq; // Update the pipeline before waking the workers. flecs_pipeline_update(world, pq, true); // If there are no operations to execute in the pipeline bail early, // no need to wake the workers since they have nothing to do. while (pq->cur_op != NULL) { if (pq->cur_i == ecs_vec_count(&pq->systems)) { flecs_pipeline_update(world, pq, false); continue; } bool no_readonly = pq->cur_op->no_readonly; bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; pq->no_readonly = no_readonly; if (!no_readonly) { ecs_readonly_begin(world, multi_threaded); } ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); if (op_multi_threaded) { flecs_signal_workers(world); } ecs_time_t st = { 0 }; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&st); } const int32_t i = flecs_run_pipeline_ops( world, stage, stage_index, stage_count, delta_time); if (measure_time) { /* Don't include merge time in system time */ world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } if (op_multi_threaded) { flecs_wait_for_sync(world); } if (!no_readonly) { ecs_time_t mt = { 0 }; if (measure_time) { ecs_time_measure(&mt); } int32_t si; for (si = 0; si < stage_count; si ++) { ecs_stage_t *s = &world->stages[si]; pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); } ecs_readonly_end(world); if (measure_time) { pq->cur_op->time_spent += ecs_time_measure(&mt); } } /* Store the current state of the schedule after we synchronized the * threads, to avoid race conditions. */ pq->cur_i = i; flecs_pipeline_update(world, pq, false); } } static void flecs_run_startup_systems( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_dependson(EcsOnStart)); if (!idr || !flecs_table_cache_count(&idr->cache)) { /* Don't bother creating startup pipeline if no systems exist */ return; } ecs_dbg_2("#[bold]startup#[reset]"); ecs_log_push_2(); int32_t stage_count = world->stage_count; world->stage_count = 1; /* Prevents running startup systems on workers */ /* Creating a pipeline is relatively expensive, but this only happens * for the first frame. The startup pipeline is deleted afterwards, which * eliminates the overhead of keeping its query cache in sync. */ ecs_dbg_2("#[bold]create startup pipeline#[reset]"); ecs_log_push_2(); ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ .query = { .filter.terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } }, .order_by = flecs_entity_compare } }); ecs_log_pop_2(); /* Run & delete pipeline */ ecs_dbg_2("#[bold]run startup systems#[reset]"); ecs_log_push_2(); ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); flecs_workers_progress(world, p->state, 0); ecs_log_pop_2(); ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); ecs_log_push_2(); ecs_delete(world, start_pip); ecs_log_pop_2(); world->stage_count = stage_count; ecs_log_pop_2(); error: return; } bool ecs_progress( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); /* If this is the first frame, run startup systems */ if (world->info.frame_count_total == 0) { flecs_run_startup_systems(world); } /* create any worker task threads request */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); ecs_log_push_3(); const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); flecs_workers_progress(world, p->state, delta_time); ecs_log_pop_3(); ecs_frame_end(world); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return false; } void ecs_set_time_scale( ecs_world_t *world, ecs_ftime_t scale) { world->info.time_scale = scale; } void ecs_reset_clock( ecs_world_t *world) { world->info.world_time_total = 0; world->info.world_time_total_raw = 0; } void ecs_set_pipeline( ecs_world_t *world, ecs_entity_t pipeline) { ecs_poly_assert(world, ecs_world_t); ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, ECS_INVALID_PARAMETER, "not a pipeline"); world->pipeline = pipeline; error: return; } ecs_entity_t ecs_get_pipeline( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->pipeline; error: return 0; } ecs_entity_t ecs_pipeline_init( ecs_world_t *world, const ecs_pipeline_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world, 0); } ecs_query_desc_t qd = desc->query; if (!qd.order_by) { qd.order_by = flecs_entity_compare; } qd.filter.entity = result; ecs_query_t *query = ecs_query_init(world, &qd); if (!query) { ecs_delete(world, result); return 0; } ecs_check(query->filter.terms != NULL, ECS_INVALID_PARAMETER, "pipeline query cannot be empty"); ecs_check(query->filter.terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, "pipeline must start with System term"); ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); pq->query = query; pq->match_count = -1; pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); ecs_set(world, result, EcsPipeline, { pq }); return result; error: return 0; } /* -- Module implementation -- */ static void FlecsPipelineFini( ecs_world_t *world, void *ctx) { (void)ctx; if (ecs_get_stage_count(world)) { ecs_set_threads(world, 0); } ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } #define flecs_bootstrap_phase(world, phase, depends_on)\ flecs_bootstrap_tag(world, phase);\ flecs_bootstrap_phase_(world, phase, depends_on) static void flecs_bootstrap_phase_( ecs_world_t *world, ecs_entity_t phase, ecs_entity_t depends_on) { ecs_add_id(world, phase, EcsPhase); if (depends_on) { ecs_add_pair(world, phase, EcsDependsOn, depends_on); } } void FlecsPipelineImport( ecs_world_t *world) { ECS_MODULE(world, FlecsPipeline); ECS_IMPORT(world, FlecsSystem); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsPipeline), "Module that schedules and runs systems"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsPipeline); flecs_bootstrap_tag(world, EcsPhase); /* Create anonymous phases to which the builtin phases will have DependsOn * relationships. This ensures that, for example, EcsOnUpdate doesn't have a * direct DependsOn relationship on EcsPreUpdate, which ensures that when * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ ecs_entity_t phase_0 = ecs_new(world, 0); ecs_entity_t phase_1 = ecs_new_w_pair(world, EcsDependsOn, phase_0); ecs_entity_t phase_2 = ecs_new_w_pair(world, EcsDependsOn, phase_1); ecs_entity_t phase_3 = ecs_new_w_pair(world, EcsDependsOn, phase_2); ecs_entity_t phase_4 = ecs_new_w_pair(world, EcsDependsOn, phase_3); ecs_entity_t phase_5 = ecs_new_w_pair(world, EcsDependsOn, phase_4); ecs_entity_t phase_6 = ecs_new_w_pair(world, EcsDependsOn, phase_5); ecs_entity_t phase_7 = ecs_new_w_pair(world, EcsDependsOn, phase_6); ecs_entity_t phase_8 = ecs_new_w_pair(world, EcsDependsOn, phase_7); flecs_bootstrap_phase(world, EcsOnStart, 0); flecs_bootstrap_phase(world, EcsPreFrame, 0); flecs_bootstrap_phase(world, EcsOnLoad, phase_0); flecs_bootstrap_phase(world, EcsPostLoad, phase_1); flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); flecs_bootstrap_phase(world, EcsOnValidate, phase_4); flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); flecs_bootstrap_phase(world, EcsPreStore, phase_6); flecs_bootstrap_phase(world, EcsOnStore, phase_7); flecs_bootstrap_phase(world, EcsPostFrame, phase_8); ecs_set_hooks(world, EcsPipeline, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsPipeline), .move = ecs_move(EcsPipeline) }); world->pipeline = ecs_pipeline(world, { .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), .query = { .filter.terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } }, .order_by = flecs_entity_compare } }); /* Cleanup thread administration when world is destroyed */ ecs_atfini(world, FlecsPipelineFini, NULL); } #endif /** * @file addons/pipeline/worker.c * @brief Functions for running pipelines on one or more threads. */ #ifdef FLECS_PIPELINE /* Synchronize workers */ static void flecs_sync_worker( ecs_world_t* world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } /* Signal that thread is waiting */ ecs_os_mutex_lock(world->sync_mutex); if (++world->workers_waiting == (stage_count - 1)) { /* Only signal main thread when all threads are waiting */ ecs_os_cond_signal(world->sync_cond); } /* Wait until main thread signals that thread can continue */ ecs_os_cond_wait(world->worker_cond, world->sync_mutex); ecs_os_mutex_unlock(world->sync_mutex); } /* Worker thread */ static void* flecs_worker(void *arg) { ecs_stage_t *stage = arg; ecs_world_t *world = stage->world; ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); ecs_dbg_2("worker %d: start", stage->id); /* Start worker, increase counter so main thread knows how many * workers are ready */ ecs_os_mutex_lock(world->sync_mutex); world->workers_running ++; if (!(world->flags & EcsWorldQuitWorkers)) { ecs_os_cond_wait(world->worker_cond, world->sync_mutex); } ecs_os_mutex_unlock(world->sync_mutex); while (!(world->flags & EcsWorldQuitWorkers)) { ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); ecs_dbg_3("worker %d: run", stage->id); flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count, world->info.delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); flecs_sync_worker(world); } ecs_dbg_2("worker %d: finalizing", stage->id); ecs_os_mutex_lock(world->sync_mutex); world->workers_running --; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_2("worker %d: stop", stage->id); return NULL; } /* Start threads */ void flecs_create_worker_threads( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); int32_t stages = ecs_get_stage_count(world); for (int32_t i = 1; i < stages; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(stage, ecs_stage_t); ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); if (ecs_using_task_threads(world)) { /* workers are using tasks in an external task manager provided to * the OS API */ stage->thread = ecs_os_task_new(flecs_worker, stage); } else { /* workers are using long-running os threads */ stage->thread = ecs_os_thread_new(flecs_worker, stage); } ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); } } static void flecs_start_workers( ecs_world_t *world, int32_t threads) { ecs_set_stage_count(world, threads); ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); if (!ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } } /* Wait until all workers are running */ static void flecs_wait_for_workers( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } bool wait = true; do { ecs_os_mutex_lock(world->sync_mutex); if (world->workers_running == (stage_count - 1)) { wait = false; } ecs_os_mutex_unlock(world->sync_mutex); } while (wait); } /* Wait until all threads are waiting on sync point */ void flecs_wait_for_sync( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); ecs_os_mutex_lock(world->sync_mutex); if (world->workers_waiting != (stage_count - 1)) { ecs_os_cond_wait(world->sync_cond, world->sync_mutex); } /* We shouldn't have been signalled unless all workers are waiting on sync */ ecs_assert(world->workers_waiting == (stage_count - 1), ECS_INTERNAL_ERROR, NULL); world->workers_waiting = 0; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_3("#[bold]pipeline: workers synced"); } /* Signal workers that they can start/resume work */ void flecs_signal_workers( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: signal workers"); ecs_os_mutex_lock(world->sync_mutex); ecs_os_cond_broadcast(world->worker_cond); ecs_os_mutex_unlock(world->sync_mutex); } void flecs_join_worker_threads( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); bool threads_active = false; /* Test if threads are created. Cannot use workers_running, since this is * a potential race if threads haven't spun up yet. */ ecs_stage_t *stages = world->stages; int i, count = world->stage_count; for (i = 1; i < count; i ++) { ecs_stage_t *stage = &stages[i]; if (stage->thread) { threads_active = true; break; } }; /* If no threads are active, just return */ if (!threads_active) { return; } /* Make sure all threads are running, to ensure they catch the signal */ flecs_wait_for_workers(world); /* Signal threads should quit */ world->flags |= EcsWorldQuitWorkers; flecs_signal_workers(world); /* Join all threads with main */ for (i = 1; i < count; i ++) { if (ecs_using_task_threads(world)) { ecs_os_task_join(stages[i].thread); } else { ecs_os_thread_join(stages[i].thread); } stages[i].thread = 0; } world->flags &= ~EcsWorldQuitWorkers; ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } /* -- Private functions -- */ void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); /* Make sure workers are running and ready */ flecs_wait_for_workers(world); /* Run pipeline on main thread */ ecs_world_t *stage = ecs_get_stage(world, 0); ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); flecs_run_pipeline(stage, pq, delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } static void flecs_set_threads_internal( ecs_world_t *world, int32_t threads, bool use_task_api) { ecs_assert(threads <= 1 || (use_task_api ? ecs_os_has_task_support() : ecs_os_has_threading()), ECS_MISSING_OS_API, NULL); int32_t stage_count = ecs_get_stage_count(world); bool worker_method_changed = (use_task_api != world->workers_use_task_api); if ((stage_count != threads) || worker_method_changed) { /* Stop existing threads */ if (stage_count > 1) { flecs_join_worker_threads(world); ecs_set_stage_count(world, 1); if (world->worker_cond) { ecs_os_cond_free(world->worker_cond); } if (world->sync_cond) { ecs_os_cond_free(world->sync_cond); } if (world->sync_mutex) { ecs_os_mutex_free(world->sync_mutex); } } world->workers_use_task_api = use_task_api; /* Start threads if number of threads > 1 */ if (threads > 1) { world->worker_cond = ecs_os_cond_new(); world->sync_cond = ecs_os_cond_new(); world->sync_mutex = ecs_os_mutex_new(); flecs_start_workers(world, threads); } } } /* -- Public functions -- */ void ecs_set_threads( ecs_world_t *world, int32_t threads) { flecs_set_threads_internal(world, threads, false /* use thread API */); } void ecs_set_task_threads( ecs_world_t *world, int32_t task_threads) { flecs_set_threads_internal(world, task_threads, true /* use task API */); } bool ecs_using_task_threads( ecs_world_t *world) { return world->workers_use_task_api; } #endif /** * @file addons/rules/api.c * @brief User facing API for rules. */ /** * @file addons/rules/rules.h * @brief Internal types and functions for rules addon. */ #ifdef FLECS_RULES typedef uint8_t ecs_var_id_t; typedef int16_t ecs_rule_lbl_t; typedef ecs_flags64_t ecs_write_flags_t; #define EcsRuleMaxVarCount (64) #define EcsVarNone ((ecs_var_id_t)-1) #define EcsThisName "this" /* -- Variable types -- */ typedef enum { EcsVarEntity, /* Variable that stores an entity id */ EcsVarTable, /* Variable that stores a table */ EcsVarAny /* Used when requesting either entity or table var */ } ecs_var_kind_t; typedef struct ecs_rule_var_t { int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */ bool anonymous; /* variable is anonymous */ ecs_var_id_t id; /* variable id */ ecs_var_id_t table_id; /* id to table variable, if any */ ecs_var_id_t base_id; /* id to base entity variable, for lookups */ const char *name; /* variable name */ const char *lookup; /* Lookup string for variable */ #ifdef FLECS_DEBUG const char *label; /* for debugging */ #endif } ecs_rule_var_t; /* -- Instruction kinds -- */ typedef enum { EcsRuleAnd, /* And operator: find or match id against variable source */ EcsRuleAndId, /* And operator for fixed id (no wildcards/variables) */ EcsRuleAndAny, /* And operator with support for matching Any src/id */ EcsRuleTriv, /* Trivial search */ EcsRuleTrivData, /* Trivial search with setting data fields */ EcsRuleTrivWildcard, /* Trivial search with (exclusive) wildcard ids */ EcsRuleSelectAny, /* Dedicated instruction for _ queries where the src is unknown */ EcsRuleUp, /* Up traversal */ EcsRuleUpId, /* Up traversal for fixed id (like AndId) */ EcsRuleSelfUp, /* Self|up traversal */ EcsRuleSelfUpId, /* Self|up traversal for fixed id (like AndId) */ EcsRuleWith, /* Match id against fixed or variable source */ EcsRuleTrav, /* Support for transitive/reflexive queries */ EcsRuleIds, /* Test for existence of ids matching wildcard */ EcsRuleIdsRight, /* Find ids in use that match (R, *) wildcard */ EcsRuleIdsLeft, /* Find ids in use that match (*, T) wildcard */ EcsRuleEach, /* Iterate entities in table, populate entity variable */ EcsRuleStore, /* Store table or entity in variable */ EcsRuleReset, /* Reset value of variable to wildcard (*) */ EcsRuleOr, /* Or operator */ EcsRuleOptional, /* Optional operator */ EcsRuleIf, /* Conditional execution */ EcsRuleNot, /* Sets iterator state after term was not matched */ EcsRuleEnd, /* End of control flow block */ EcsRulePredEq, /* Test if variable is equal to, or assign to if not set */ EcsRulePredNeq, /* Test if variable is not equal to */ EcsRulePredEqName, /* Same as EcsRulePredEq but with matching by name */ EcsRulePredNeqName, /* Same as EcsRulePredNeq but with matching by name */ EcsRulePredEqMatch, /* Same as EcsRulePredEq but with fuzzy matching by name */ EcsRulePredNeqMatch, /* Same as EcsRulePredNeq but with fuzzy matching by name */ EcsRuleLookup, /* Lookup relative to variable */ EcsRuleSetVars, /* Populate it.sources from variables */ EcsRuleSetThis, /* Populate This entity variable */ EcsRuleSetFixed, /* Set fixed source entity ids */ EcsRuleSetIds, /* Set fixed (component) ids */ EcsRuleSetId, /* Set id if not set */ EcsRuleContain, /* Test if table contains entity */ EcsRulePairEq, /* Test if both elements of pair are the same */ EcsRulePopulate, /* Populate any data fields */ EcsRulePopulateSelf, /* Populate only self (owned) data fields */ EcsRuleYield, /* Yield result back to application */ EcsRuleNothing /* Must be last */ } ecs_rule_op_kind_t; /* Op flags to indicate if ecs_rule_ref_t is entity or variable */ #define EcsRuleIsEntity (1 << 0) #define EcsRuleIsVar (1 << 1) #define EcsRuleIsSelf (1 << 6) /* Op flags used to shift EcsRuleIsEntity and EcsRuleIsVar */ #define EcsRuleSrc 0 #define EcsRuleFirst 2 #define EcsRuleSecond 4 /* References to variable or entity */ typedef union { ecs_var_id_t var; ecs_entity_t entity; } ecs_rule_ref_t; /* Query instruction */ typedef struct ecs_rule_op_t { uint8_t kind; /* Instruction kind */ ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ int8_t field_index; /* Query field corresponding with operation */ int8_t term_index; /* Query term corresponding with operation */ ecs_rule_lbl_t prev; /* Backtracking label (no data) */ ecs_rule_lbl_t next; /* Forwarding label. Must come after prev */ ecs_rule_lbl_t other; /* Misc register used for control flow */ ecs_flags16_t match_flags; /* Flags that modify matching behavior */ ecs_rule_ref_t src; ecs_rule_ref_t first; ecs_rule_ref_t second; ecs_flags64_t written; /* Bitset with variables written by op */ } ecs_rule_op_t; /* And context */ typedef struct { ecs_id_record_t *idr; ecs_table_cache_iter_t it; int16_t column; int16_t remaining; } ecs_rule_and_ctx_t; /* Down traversal cache (for resolving up queries w/unknown source) */ typedef struct { ecs_table_t *table; bool leaf; /* Table owns and inherits id (for Up queries without Self) */ } ecs_trav_down_elem_t; typedef struct { ecs_vec_t elems; /* vector */ bool ready; } ecs_trav_down_t; typedef struct { ecs_entity_t src; ecs_id_t id; int32_t column; bool ready; } ecs_trav_up_t; typedef struct { ecs_map_t src; /* map or map */ ecs_id_t with; ecs_flags32_t dir; } ecs_trav_up_cache_t; /* And up context */ typedef struct { ecs_rule_and_ctx_t and; ecs_table_t *table; int32_t row; int32_t end; ecs_entity_t trav; ecs_id_t with; ecs_id_t matched; ecs_id_record_t *idr_with; ecs_id_record_t *idr_trav; ecs_trav_down_t *down; int32_t cache_elem; ecs_trav_up_cache_t cache; } ecs_rule_up_ctx_t; /* Cache for storing results of upward/downward "all" traversal. This type of * traversal iterates and caches the entire tree. */ typedef struct { ecs_entity_t entity; ecs_id_record_t *idr; int32_t column; } ecs_trav_elem_t; typedef struct { ecs_id_t id; ecs_id_record_t *idr; ecs_vec_t entities; bool up; } ecs_trav_cache_t; /* Trav context */ typedef struct { ecs_rule_and_ctx_t and; int32_t index; int32_t offset; int32_t count; ecs_trav_cache_t cache; bool yield_reflexive; } ecs_rule_trav_ctx_t; /* Eq context */ typedef struct { ecs_table_range_t range; int32_t index; int16_t name_col; bool redo; } ecs_rule_eq_ctx_t; /* Each context */ typedef struct { int32_t row; } ecs_rule_each_ctx_t; /* Setthis context */ typedef struct { ecs_table_range_t range; } ecs_rule_setthis_ctx_t; /* Ids context */ typedef struct { ecs_id_record_t *cur; } ecs_rule_ids_ctx_t; /* Control flow context */ typedef struct { ecs_rule_lbl_t op_index; ecs_id_t field_id; } ecs_rule_ctrl_ctx_t; /* Trivial context */ typedef struct { ecs_table_cache_iter_t it; const ecs_table_record_t *tr; } ecs_rule_trivial_ctx_t; typedef struct ecs_rule_op_ctx_t { union { ecs_rule_and_ctx_t and; ecs_rule_up_ctx_t up; ecs_rule_trav_ctx_t trav; ecs_rule_ids_ctx_t ids; ecs_rule_eq_ctx_t eq; ecs_rule_each_ctx_t each; ecs_rule_setthis_ctx_t setthis; ecs_rule_ctrl_ctx_t ctrl; ecs_rule_trivial_ctx_t trivial; } is; } ecs_rule_op_ctx_t; typedef struct { /* Labels used for control flow */ ecs_rule_lbl_t lbl_query; /* Used to find the op that does the actual searching */ ecs_rule_lbl_t lbl_begin; ecs_rule_lbl_t lbl_cond_eval; ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ bool in_or; /* Whether we're in an or chain */ } ecs_rule_compile_ctrlflow_t; /* Rule compiler state */ typedef struct { ecs_vec_t *ops; ecs_write_flags_t written; /* Bitmask to check which variables have been written */ ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ /* Maintain control flow per scope */ ecs_rule_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; ecs_rule_compile_ctrlflow_t *cur; /* Current scope */ int32_t scope; /* Nesting level of query scopes */ ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ } ecs_rule_compile_ctx_t; /* Rule run state */ typedef struct { uint64_t *written; /* Bitset to check which variables have been written */ ecs_rule_lbl_t op_index; /* Currently evaluated operation */ ecs_var_t *vars; /* Variable storage */ ecs_iter_t *it; /* Iterator */ ecs_rule_op_ctx_t *op_ctx; /* Operation context (stack) */ ecs_world_t *world; /* Reference to world */ const ecs_rule_t *rule; /* Reference to rule */ const ecs_rule_var_t *rule_vars; /* Reference to rule variable array */ ecs_flags32_t *source_set; /* Whether ecs_iter_t::sources is written by instruction */ ecs_rule_iter_t *rit; } ecs_rule_run_ctx_t; typedef struct { ecs_rule_var_t var; const char *name; } ecs_rule_var_cache_t; struct ecs_rule_t { ecs_header_t hdr; /* Poly header */ ecs_filter_t filter; /* Filter */ /* Variables */ ecs_rule_var_t *vars; /* Variables */ int32_t var_count; /* Number of variables */ int32_t var_pub_count; /* Number of public variables */ bool has_table_this; /* Does rule have [$this] */ ecs_hashmap_t tvar_index; /* Name index for table variables */ ecs_hashmap_t evar_index; /* Name index for entity variables */ ecs_rule_var_cache_t vars_cache; /* For trivial rules with only This variables */ char **var_names; /* Array with variable names for iterator */ ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */ ecs_rule_op_t *ops; /* Operations */ int32_t op_count; /* Number of operations */ /* Mixins */ ecs_iterable_t iterable; ecs_poly_dtor_t dtor; #ifdef FLECS_DEBUG int32_t var_size; /* Used for out of bounds check during compilation */ #endif }; /* Convert integer to label */ ecs_rule_lbl_t flecs_itolbl( int64_t val); /* Get ref flags (IsEntity) or IsVar) for ref (Src, First, Second) */ ecs_flags16_t flecs_rule_ref_flags( ecs_flags16_t flags, ecs_flags16_t kind); /* Check if variable is written */ bool flecs_rule_is_written( ecs_var_id_t var_id, uint64_t written); /* Check if ref is written (calls flecs_rule_is_written)*/ bool flecs_ref_is_written( const ecs_rule_op_t *op, const ecs_rule_ref_t *ref, ecs_flags16_t kind, uint64_t written); /* Compile filter to list of operations */ int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_rule_t *rule); /* Get allocator from iterator */ ecs_allocator_t* flecs_rule_get_allocator( const ecs_iter_t *it); /* Traversal cache for transitive queries. Finds all reachable entities by * following a relationship */ /* Find all entities when traversing downwards */ void flecs_rule_get_trav_down_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity); /* Find all entities when traversing upwards */ void flecs_rule_get_trav_up_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table); /* Free traversal cache */ void flecs_rule_trav_cache_fini( ecs_allocator_t *a, ecs_trav_cache_t *cache); /* Traversal caches for up traversal. Enables searching upwards until an entity * with the queried for id has been found. */ /* Traverse downwards from starting entity to find all tables for which the * specified entity is the source of the queried for id ('with'). */ ecs_trav_down_t* flecs_rule_get_down_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_with, bool self); /* Free down traversal cache */ void flecs_rule_down_cache_fini( ecs_allocator_t *a, ecs_trav_up_cache_t *cache); ecs_trav_up_t* flecs_rule_get_up_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_table_t *table, ecs_id_t with, ecs_entity_t trav, ecs_id_record_t *idr_with, ecs_id_record_t *idr_trav); /* Free up traversal cache */ void flecs_rule_up_cache_fini( ecs_trav_up_cache_t *cache); /* Convert instruction kind to string */ const char* flecs_rule_op_str( uint16_t kind); /* Iterator for trivial queries. */ bool flecs_rule_trivial_search( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, bool first, int32_t until); /* Iterator for trivial queries. */ bool flecs_rule_trivial_search_nodata( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, bool first, int32_t until); /* Iterator for trivial queries with wildcard matching. */ bool flecs_rule_trivial_search_w_wildcards( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, bool first, int32_t until); /* Trivial test for constrained $this. */ bool flecs_rule_trivial_test( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, bool first, int32_t term_count); /* Trivial test for constrained $this with wildcard matching. */ bool flecs_rule_trivial_test_w_wildcards( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, bool first, int32_t term_count); #endif #include #ifdef FLECS_RULES static ecs_mixins_t ecs_rule_t_mixins = { .type_name = "ecs_rule_t", .elems = { [EcsMixinWorld] = offsetof(ecs_rule_t, filter.world), [EcsMixinEntity] = offsetof(ecs_rule_t, filter.entity), [EcsMixinIterable] = offsetof(ecs_rule_t, iterable), [EcsMixinDtor] = offsetof(ecs_rule_t, dtor) } }; const char* flecs_rule_op_str( uint16_t kind) { switch(kind) { case EcsRuleAnd: return "and "; case EcsRuleAndId: return "andid "; case EcsRuleAndAny: return "andany "; case EcsRuleTriv: return "triv "; case EcsRuleTrivData: return "trivpop "; case EcsRuleTrivWildcard: return "trivwc "; case EcsRuleSelectAny: return "any "; case EcsRuleUp: return "up "; case EcsRuleUpId: return "upid "; case EcsRuleSelfUp: return "selfup "; case EcsRuleSelfUpId: return "selfupid"; case EcsRuleWith: return "with "; case EcsRuleTrav: return "trav "; case EcsRuleIds: return "ids "; case EcsRuleIdsRight: return "idsr "; case EcsRuleIdsLeft: return "idsl "; case EcsRuleEach: return "each "; case EcsRuleStore: return "store "; case EcsRuleReset: return "reset "; case EcsRuleOr: return "or "; case EcsRuleOptional: return "option "; case EcsRuleIf: return "if "; case EcsRuleEnd: return "end "; case EcsRuleNot: return "not "; case EcsRulePredEq: return "eq "; case EcsRulePredNeq: return "neq "; case EcsRulePredEqName: return "eq_nm "; case EcsRulePredNeqName: return "neq_nm "; case EcsRulePredEqMatch: return "eq_m "; case EcsRulePredNeqMatch: return "neq_m "; case EcsRuleLookup: return "lookup "; case EcsRuleSetVars: return "setvars "; case EcsRuleSetThis: return "setthis "; case EcsRuleSetFixed: return "setfix "; case EcsRuleSetIds: return "setids "; case EcsRuleSetId: return "setid "; case EcsRuleContain: return "contain "; case EcsRulePairEq: return "pair_eq "; case EcsRulePopulate: return "pop "; case EcsRulePopulateSelf: return "popself "; case EcsRuleYield: return "yield "; case EcsRuleNothing: return "nothing "; default: return "!invalid"; } } /* Implementation for iterable mixin */ static void flecs_rule_iter_mixin_init( const ecs_world_t *world, const ecs_poly_t *poly, ecs_iter_t *iter, ecs_term_t *filter) { ecs_poly_assert(poly, ecs_rule_t); if (filter) { iter[1] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_rule_iter(world, ECS_CONST_CAST(ecs_rule_t*, poly)); } } static void flecs_rule_fini( ecs_rule_t *rule) { if (rule->vars != &rule->vars_cache.var) { ecs_os_free(rule->vars); } ecs_os_free(rule->ops); ecs_os_free(rule->src_vars); flecs_name_index_fini(&rule->tvar_index); flecs_name_index_fini(&rule->evar_index); ecs_filter_fini(&rule->filter); ecs_poly_free(rule, ecs_rule_t); } void ecs_rule_fini( ecs_rule_t *rule) { if (rule->filter.entity) { /* If filter is associated with entity, use poly dtor path */ ecs_delete(rule->filter.world, rule->filter.entity); } else { flecs_rule_fini(rule); } } ecs_rule_t* ecs_rule_init( ecs_world_t *world, const ecs_filter_desc_t *const_desc) { ecs_rule_t *result = ecs_poly_new(ecs_rule_t); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Initialize the query */ ecs_filter_desc_t desc = *const_desc; desc.storage = &result->filter; /* Use storage of rule */ result->filter = ECS_FILTER_INIT; if (ecs_filter_init(world, &desc) == NULL) { goto error; } result->iterable.init = flecs_rule_iter_mixin_init; /* Compile filter to operations */ if (flecs_rule_compile(world, stage, result)) { goto error; } ecs_entity_t entity = const_desc->entity; result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; if (entity) { EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); poly->poly = result; ecs_poly_modified(world, entity, ecs_rule_t); } return result; error: ecs_rule_fini(result); return NULL; } static int32_t flecs_rule_op_ref_str( const ecs_rule_t *rule, ecs_rule_ref_t *ref, ecs_flags16_t flags, ecs_strbuf_t *buf) { int32_t color_chars = 0; if (flags & EcsRuleIsVar) { ecs_assert(ref->var < rule->var_count, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *var = &rule->vars[ref->var]; ecs_strbuf_appendlit(buf, "#[green]$#[reset]"); if (var->kind == EcsVarTable) { ecs_strbuf_appendch(buf, '['); } ecs_strbuf_appendlit(buf, "#[green]"); if (var->name) { ecs_strbuf_appendstr(buf, var->name); } else { if (var->id) { #ifdef FLECS_DEBUG if (var->label) { ecs_strbuf_appendstr(buf, var->label); ecs_strbuf_appendch(buf, '\''); } #endif ecs_strbuf_append(buf, "%d", var->id); } else { ecs_strbuf_appendlit(buf, "this"); } } ecs_strbuf_appendlit(buf, "#[reset]"); if (var->kind == EcsVarTable) { ecs_strbuf_appendch(buf, ']'); } color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); } else if (flags & EcsRuleIsEntity) { char *path = ecs_get_fullpath(rule->filter.world, ref->entity); ecs_strbuf_appendlit(buf, "#[blue]"); ecs_strbuf_appendstr(buf, path); ecs_strbuf_appendlit(buf, "#[reset]"); ecs_os_free(path); color_chars = ecs_os_strlen("#[blue]#[reset]"); } return color_chars; } char* ecs_rule_str_w_profile( const ecs_rule_t *rule, const ecs_iter_t *it) { ecs_poly_assert(rule, ecs_rule_t); ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_rule_op_t *ops = rule->ops; int32_t i, count = rule->op_count, indent = 0; for (i = 0; i < count; i ++) { ecs_rule_op_t *op = &ops[i]; ecs_flags16_t flags = op->flags; ecs_flags16_t src_flags = flecs_rule_ref_flags(flags, EcsRuleSrc); ecs_flags16_t first_flags = flecs_rule_ref_flags(flags, EcsRuleFirst); ecs_flags16_t second_flags = flecs_rule_ref_flags(flags, EcsRuleSecond); if (it) { #ifdef FLECS_DEBUG const ecs_rule_iter_t *rit = &it->priv.iter.rule; ecs_strbuf_append(&buf, "#[green]%4d -> #[red]%4d <- #[grey] | ", rit->profile[i].count[0], rit->profile[i].count[1]); #endif } ecs_strbuf_append(&buf, "#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", i, op->prev, op->next); int32_t hidden_chars, start = ecs_strbuf_written(&buf); if (op->kind == EcsRuleEnd) { indent --; } ecs_strbuf_append(&buf, "%*s", indent, ""); ecs_strbuf_appendstr(&buf, flecs_rule_op_str(op->kind)); ecs_strbuf_appendstr(&buf, " "); int32_t written = ecs_strbuf_written(&buf); for (int32_t j = 0; j < (12 - (written - start)); j ++) { ecs_strbuf_appendch(&buf, ' '); } hidden_chars = flecs_rule_op_ref_str(rule, &op->src, src_flags, &buf); if (op->kind == EcsRuleNot || op->kind == EcsRuleOr || op->kind == EcsRuleOptional || op->kind == EcsRuleIf) { indent ++; } if (!first_flags && !second_flags) { ecs_strbuf_appendstr(&buf, "\n"); continue; } written = ecs_strbuf_written(&buf) - hidden_chars; for (int32_t j = 0; j < (30 - (written - start)); j ++) { ecs_strbuf_appendch(&buf, ' '); } if (!first_flags && !second_flags) { ecs_strbuf_appendstr(&buf, "\n"); continue; } ecs_strbuf_appendstr(&buf, "("); flecs_rule_op_ref_str(rule, &op->first, first_flags, &buf); if (second_flags) { ecs_strbuf_appendstr(&buf, ", "); flecs_rule_op_ref_str(rule, &op->second, second_flags, &buf); } else { switch (op->kind) { case EcsRulePredEqName: case EcsRulePredNeqName: case EcsRulePredEqMatch: case EcsRulePredNeqMatch: { int8_t term_index = op->term_index; ecs_strbuf_appendstr(&buf, ", #[yellow]\""); ecs_strbuf_appendstr(&buf, rule->filter.terms[term_index].second.name); ecs_strbuf_appendstr(&buf, "\"#[reset]"); break; } case EcsRuleLookup: { ecs_var_id_t src_id = op->src.var; ecs_strbuf_appendstr(&buf, ", #[yellow]\""); ecs_strbuf_appendstr(&buf, rule->vars[src_id].lookup); ecs_strbuf_appendstr(&buf, "\"#[reset]"); break; } default: break; } } ecs_strbuf_appendch(&buf, ')'); ecs_strbuf_appendch(&buf, '\n'); } #ifdef FLECS_LOG char *str = ecs_strbuf_get(&buf); flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); ecs_os_free(str); #endif return ecs_strbuf_get(&buf); } char* ecs_rule_str( const ecs_rule_t *rule) { return ecs_rule_str_w_profile(rule, NULL); } const ecs_filter_t* ecs_rule_get_filter( const ecs_rule_t *rule) { return &rule->filter; } const char* ecs_rule_parse_vars( ecs_rule_t *rule, ecs_iter_t *it, const char *expr) { ecs_poly_assert(rule, ecs_rule_t); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL) char token[ECS_MAX_TOKEN_SIZE]; const char *ptr = expr; bool paren = false; const char *name = NULL; if (rule->filter.entity) { name = ecs_get_name(rule->filter.world, rule->filter.entity); } ptr = ecs_parse_ws_eol(ptr); if (!ptr[0]) { return ptr; } if (ptr[0] == '(') { paren = true; ptr = ecs_parse_ws_eol(ptr + 1); if (ptr[0] == ')') { return ptr + 1; } } do { ptr = ecs_parse_ws_eol(ptr); ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } int var = ecs_rule_find_var(rule, token); if (var == -1) { ecs_parser_error(name, expr, (ptr - expr), "unknown variable '%s'", token); return NULL; } ptr = ecs_parse_ws_eol(ptr); if (ptr[0] != ':') { ecs_parser_error(name, expr, (ptr - expr), "missing ':'"); return NULL; } ptr = ecs_parse_ws_eol(ptr + 1); ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } ecs_entity_t val = ecs_lookup(rule->filter.world, token); if (!val) { ecs_parser_error(name, expr, (ptr - expr), "unresolved entity '%s'", token); return NULL; } ecs_iter_set_var(it, var, val); ptr = ecs_parse_ws_eol(ptr); if (ptr[0] == ')') { if (!paren) { ecs_parser_error(name, expr, (ptr - expr), "unexpected closing parenthesis"); return NULL; } ptr ++; break; } else if (ptr[0] == ',') { ptr ++; } else if (!ptr[0]) { if (paren) { ecs_parser_error(name, expr, (ptr - expr), "missing closing parenthesis"); return NULL; } break; } else { ecs_parser_error(name, expr, (ptr - expr), "expected , or end of string"); return NULL; } } while (true); return ptr; error: return NULL; } #endif /** * @file addons/rules/compile.c * @brief Compile rule program from filter. */ #ifdef FLECS_RULES #define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ ecs_rule_lbl_t flecs_itolbl(int64_t val) { return flecs_ito(int16_t, val); } static ecs_var_id_t flecs_itovar(int64_t val) { return flecs_ito(uint8_t, val); } static ecs_var_id_t flecs_utovar(uint64_t val) { return flecs_uto(uint8_t, val); } #ifdef FLECS_DEBUG #define flecs_set_var_label(var, lbl) (var)->label = lbl #else #define flecs_set_var_label(var, lbl) #endif static bool flecs_rule_is_builtin_pred( ecs_term_t *term) { if (term->first.flags & EcsIsEntity) { ecs_entity_t id = term->first.id; if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { return true; } } return false; } bool flecs_rule_is_written( ecs_var_id_t var_id, uint64_t written) { if (var_id == EcsVarNone) { return true; } ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); return (written & (1ull << var_id)) != 0; } static void flecs_rule_write( ecs_var_id_t var_id, uint64_t *written) { ecs_assert(var_id < EcsRuleMaxVarCount, ECS_INTERNAL_ERROR, NULL); *written |= (1ull << var_id); } static void flecs_rule_write_ctx( ecs_var_id_t var_id, ecs_rule_compile_ctx_t *ctx, bool cond_write) { bool is_written = flecs_rule_is_written(var_id, ctx->written); flecs_rule_write(var_id, &ctx->written); if (!is_written) { if (cond_write) { flecs_rule_write(var_id, &ctx->cond_written); } } } ecs_flags16_t flecs_rule_ref_flags( ecs_flags16_t flags, ecs_flags16_t kind) { return (flags >> kind) & (EcsRuleIsVar | EcsRuleIsEntity); } bool flecs_ref_is_written( const ecs_rule_op_t *op, const ecs_rule_ref_t *ref, ecs_flags16_t kind, uint64_t written) { ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, kind); if (flags & EcsRuleIsEntity) { ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); if (ref->entity) { return true; } } else if (flags & EcsRuleIsVar) { return flecs_rule_is_written(ref->var, written); } return false; } static bool flecs_rule_var_is_anonymous( const ecs_rule_t *rule, ecs_var_id_t var_id) { ecs_rule_var_t *var = &rule->vars[var_id]; return var->anonymous; } static ecs_var_id_t flecs_rule_find_var_id( const ecs_rule_t *rule, const char *name, ecs_var_kind_t kind) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); /* Backwards compatibility */ if (!ecs_os_strcmp(name, "This")) { name = "this"; } if (kind == EcsVarTable) { if (!ecs_os_strcmp(name, EcsThisName)) { if (rule->has_table_this) { return 0; } else { return EcsVarNone; } } if (!flecs_name_index_is_init(&rule->tvar_index)) { return EcsVarNone; } uint64_t index = flecs_name_index_find( &rule->tvar_index, name, 0, 0); if (index == 0) { return EcsVarNone; } return flecs_utovar(index); } if (kind == EcsVarEntity) { if (!flecs_name_index_is_init(&rule->evar_index)) { return EcsVarNone; } uint64_t index = flecs_name_index_find( &rule->evar_index, name, 0, 0); if (index == 0) { return EcsVarNone; } return flecs_utovar(index); } ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); /* If searching for any kind of variable, start with most specific */ ecs_var_id_t index = flecs_rule_find_var_id(rule, name, EcsVarEntity); if (index != EcsVarNone) { return index; } return flecs_rule_find_var_id(rule, name, EcsVarTable); } int32_t ecs_rule_var_count( const ecs_rule_t *rule) { return rule->var_pub_count; } int32_t ecs_rule_find_var( const ecs_rule_t *rule, const char *name) { ecs_var_id_t var_id = flecs_rule_find_var_id(rule, name, EcsVarEntity); if (var_id == EcsVarNone) { if (rule->filter.flags & EcsFilterMatchThis) { if (!ecs_os_strcmp(name, "This")) { name = "this"; } if (!ecs_os_strcmp(name, EcsThisName)) { var_id = 0; } } if (var_id == EcsVarNone) { return -1; } } return (int32_t)var_id; } const char* ecs_rule_var_name( const ecs_rule_t *rule, int32_t var_id) { if (var_id) { return rule->vars[var_id].name; } else { return EcsThisName; } } bool ecs_rule_var_is_entity( const ecs_rule_t *rule, int32_t var_id) { return rule->vars[var_id].kind == EcsVarEntity; } static const char* flecs_term_id_var_name( ecs_term_id_t *term_id) { if (!(term_id->flags & EcsIsVariable)) { return NULL; } if (term_id->id == EcsThis) { return EcsThisName; } return term_id->name; } static bool flecs_term_id_is_wildcard( ecs_term_id_t *term_id) { if ((term_id->flags & EcsIsVariable) && ((term_id->id == EcsWildcard) || (term_id->id == EcsAny))) { return true; } return false; } static ecs_var_id_t flecs_rule_add_var( ecs_rule_t *rule, const char *name, ecs_vec_t *vars, ecs_var_kind_t kind) { const char *dot = NULL; if (name) { dot = strchr(name, '.'); if (dot) { kind = EcsVarEntity; /* lookup variables are always entities */ } } ecs_hashmap_t *var_index = NULL; ecs_var_id_t var_id = EcsVarNone; if (name) { if (kind == EcsVarAny) { var_id = flecs_rule_find_var_id(rule, name, EcsVarEntity); if (var_id != EcsVarNone) { return var_id; } var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); if (var_id != EcsVarNone) { return var_id; } kind = EcsVarTable; } else { var_id = flecs_rule_find_var_id(rule, name, kind); if (var_id != EcsVarNone) { return var_id; } } if (kind == EcsVarTable) { var_index = &rule->tvar_index; } else { var_index = &rule->evar_index; } /* If we're creating an entity var, check if it has a table variant */ if (kind == EcsVarEntity && var_id == EcsVarNone) { var_id = flecs_rule_find_var_id(rule, name, EcsVarTable); } } ecs_rule_var_t *var; ecs_var_id_t result; if (vars) { var = ecs_vec_append_t(NULL, vars, ecs_rule_var_t); result = var->id = flecs_itovar(ecs_vec_count(vars)); } else { ecs_dbg_assert(rule->var_count < rule->var_size, ECS_INTERNAL_ERROR, NULL); var = &rule->vars[rule->var_count]; result = var->id = flecs_itovar(rule->var_count); rule->var_count ++; } var->kind = flecs_ito(int8_t, kind); var->name = name; var->table_id = var_id; var->base_id = 0; var->lookup = NULL; flecs_set_var_label(var, NULL); if (name) { flecs_name_index_init_if(var_index, NULL); flecs_name_index_ensure(var_index, var->id, name, 0, 0); var->anonymous = name[0] == '_'; /* Handle variables that require a by-name lookup, e.g. $this.wheel */ if (dot != NULL) { ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); var->lookup = dot + 1; } } return result; } static ecs_var_id_t flecs_rule_add_var_for_term_id( ecs_rule_t *rule, ecs_term_id_t *term_id, ecs_vec_t *vars, ecs_var_kind_t kind) { const char *name = flecs_term_id_var_name(term_id); if (!name) { return EcsVarNone; } return flecs_rule_add_var(rule, name, vars, kind); } /* This function walks over terms to discover which variables are used in the * query. It needs to provide the following functionality: * - create table vars for all variables used as source * - create entity vars for all variables not used as source * - create entity vars for all non-$this vars * - create anonymous vars to store the content of wildcards * - create anonymous vars to store result of lookups (for $var.child_name) * - create anonymous vars for resolving component inheritance * - create array that stores the source variable for each field * - ensure table vars for non-$this variables are anonymous * - ensure variables created inside scopes are anonymous * - place anonymous variables after public variables in vars array */ static int flecs_rule_discover_vars( ecs_stage_t *stage, ecs_rule_t *rule) { ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ ecs_vec_reset_t(NULL, vars, ecs_rule_var_t); ecs_term_t *terms = rule->filter.terms; int32_t a, i, anonymous_count = 0, count = rule->filter.term_count; int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; bool table_this = false, entity_before_table_this = false; /* For This table lookups during discovery. This will be overwritten after * discovery with whether the rule actually has a This table variable. */ rule->has_table_this = true; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_id_t *first = &term->first; ecs_term_id_t *second = &term->second; ecs_term_id_t *src = &term->src; if (first->id == EcsScopeOpen) { /* Keep track of which variables are first used in scope, so that we * can mark them as anonymous. Terms inside a scope are collapsed * into a single result, which means that outside of the scope the * value of those variables is undefined. */ if (!scope) { scoped_var_index = ecs_vec_count(vars); } scope ++; continue; } else if (first->id == EcsScopeClose) { if (!--scope) { /* Any new variables declared after entering a scope should be * marked as anonymous. */ int32_t v; for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { ecs_vec_get_t(vars, ecs_rule_var_t, v)->anonymous = true; } } continue; } ecs_var_id_t first_var_id = flecs_rule_add_var_for_term_id( rule, first, vars, EcsVarEntity); if (first_var_id == EcsVarNone) { /* If first is not a variable, check if we need to insert anonymous * variable for resolving component inheritance */ if (term->flags & EcsTermIdInherited) { anonymous_count += 2; /* table & entity variable */ } /* If first is a wildcard, insert anonymous variable */ if (flecs_term_id_is_wildcard(first)) { anonymous_count ++; } } if ((src->flags & EcsIsVariable) && (src->id != EcsThis)) { const char *var_name = flecs_term_id_var_name(src); if (var_name) { ecs_var_id_t var_id = flecs_rule_find_var_id( rule, var_name, EcsVarEntity); if (var_id == EcsVarNone || var_id == first_var_id) { var_id = flecs_rule_add_var( rule, var_name, vars, EcsVarEntity); } if (var_id != EcsVarNone) { /* Mark variable as one for which we need to create a table * variable. Don't create table variable now, so that we can * store it in the non-public part of the variable array. */ ecs_rule_var_t *var = ecs_vec_get_t( vars, ecs_rule_var_t, (int32_t)var_id - 1); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); if (!var->lookup) { var->kind = EcsVarAny; anonymous_table_count ++; } if (!(term->flags & EcsTermNoData)) { /* Can't have an anonymous variable as source of a term * that returns a component. We need to return each * instance of the component, whereas anonymous * variables are not guaranteed to be resolved to * individual entities. */ if (var->anonymous) { ecs_err( "can't use anonymous variable '%s' as source of " "data term", var->name); goto error; } } /* Track which variable ids are used as field source */ if (!rule->src_vars) { rule->src_vars = ecs_os_calloc_n(ecs_var_id_t, rule->filter.field_count); } rule->src_vars[term->field_index] = var_id; } } else { if (flecs_term_id_is_wildcard(src)) { anonymous_count ++; } } } else if ((src->flags & EcsIsVariable) && (src->id == EcsThis)) { if (flecs_rule_is_builtin_pred(term) && term->oper == EcsOr) { flecs_rule_add_var(rule, EcsThisName, vars, EcsVarEntity); } } if (flecs_rule_add_var_for_term_id( rule, second, vars, EcsVarEntity) == EcsVarNone) { /* If second is a wildcard, insert anonymous variable */ if (flecs_term_id_is_wildcard(second)) { anonymous_count ++; } } if (src->flags & EcsIsVariable && second->flags & EcsIsVariable) { if (term->flags & EcsTermTransitive) { /* Anonymous variable to store temporary id for finding * targets for transitive relationship, see compile_term. */ anonymous_count ++; } } /* Track if a This entity variable is used before a potential This table * variable. If this happens, the rule has no This table variable */ if (src->id == EcsThis) { table_this = true; } if (first->id == EcsThis || second->id == EcsThis) { if (!table_this) { entity_before_table_this = true; } } } int32_t var_count = ecs_vec_count(vars); ecs_var_id_t placeholder = EcsVarNone - 1; bool replace_placeholders = false; /* Ensure lookup variables have table and/or entity variables */ for (i = 0; i < var_count; i ++) { ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); if (var->lookup) { char *var_name = ecs_os_strdup(var->name); var_name[var->lookup - var->name - 1] = '\0'; ecs_var_id_t base_table_id = flecs_rule_find_var_id( rule, var_name, EcsVarTable); if (base_table_id != EcsVarNone) { var->table_id = base_table_id; } else if (anonymous_table_count) { /* Scan for implicit anonymous table variables that haven't been * inserted yet (happens after this step). Doing this here vs. * ensures that anonymous variables are appended at the end of * the variable array, while also ensuring that variable ids are * stable (no swapping of table var ids that are in use). */ for (a = 0; a < var_count; a ++) { ecs_rule_var_t *avar = ecs_vec_get_t( vars, ecs_rule_var_t, a); if (avar->kind == EcsVarAny) { if (!ecs_os_strcmp(avar->name, var_name)) { base_table_id = (ecs_var_id_t)(a + 1); break; } } } if (base_table_id != EcsVarNone) { /* Set marker so we can set the new table id afterwards */ var->table_id = placeholder; replace_placeholders = true; } } ecs_var_id_t base_entity_id = flecs_rule_find_var_id( rule, var_name, EcsVarEntity); if (base_entity_id == EcsVarNone) { /* Get name from table var (must exist). We can't use allocated * name since variables don't own names. */ const char *base_name = NULL; if (base_table_id != EcsVarNone && base_table_id) { ecs_rule_var_t *base_table_var = ecs_vec_get_t( vars, ecs_rule_var_t, (int32_t)base_table_id - 1); base_name = base_table_var->name; } else { base_name = EcsThisName; } base_entity_id = flecs_rule_add_var( rule, base_name, vars, EcsVarEntity); var = ecs_vec_get_t(vars, ecs_rule_var_t, i); } var->base_id = base_entity_id; ecs_os_free(var_name); } } var_count = ecs_vec_count(vars); /* Add non-This table variables */ if (anonymous_table_count) { anonymous_table_count = 0; for (i = 0; i < var_count; i ++) { ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); if (var->kind == EcsVarAny) { var->kind = EcsVarEntity; ecs_var_id_t var_id = flecs_rule_add_var( rule, var->name, vars, EcsVarTable); ecs_vec_get_t(vars, ecs_rule_var_t, i)->table_id = var_id; anonymous_table_count ++; } } var_count = ecs_vec_count(vars); } /* If any forward references to newly added anonymous tables exist, replace * them with the actual table variable ids. */ if (replace_placeholders) { for (i = 0; i < var_count; i ++) { ecs_rule_var_t *var = ecs_vec_get_t(vars, ecs_rule_var_t, i); if (var->table_id == placeholder) { char *var_name = ecs_os_strdup(var->name); var_name[var->lookup - var->name - 1] = '\0'; var->table_id = flecs_rule_find_var_id( rule, var_name, EcsVarTable); ecs_assert(var->table_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); ecs_os_free(var_name); } } } /* Always include spot for This variable, even if rule doesn't use it */ var_count ++; ecs_rule_var_t *rule_vars = &rule->vars_cache.var; if ((var_count + anonymous_count) > 1) { rule_vars = ecs_os_malloc( (ECS_SIZEOF(ecs_rule_var_t) + ECS_SIZEOF(char*)) * (var_count + anonymous_count)); } rule->vars = rule_vars; rule->var_count = var_count; rule->var_pub_count = var_count; rule->has_table_this = !entity_before_table_this; #ifdef FLECS_DEBUG rule->var_size = var_count + anonymous_count; #endif char **var_names = ECS_ELEM(rule_vars, ECS_SIZEOF(ecs_rule_var_t), var_count + anonymous_count); rule->var_names = (char**)var_names; rule_vars[0].kind = EcsVarTable; rule_vars[0].name = NULL; flecs_set_var_label(&rule_vars[0], NULL); rule_vars[0].id = 0; rule_vars[0].table_id = EcsVarNone; rule_vars[0].lookup = NULL; var_names[0] = ECS_CONST_CAST(char*, rule_vars[0].name); rule_vars ++; var_names ++; var_count --; if (var_count) { ecs_rule_var_t *user_vars = ecs_vec_first_t(vars, ecs_rule_var_t); ecs_os_memcpy_n(rule_vars, user_vars, ecs_rule_var_t, var_count); for (i = 0; i < var_count; i ++) { var_names[i] = ECS_CONST_CAST(char*, rule_vars[i].name); } } /* Hide anonymous table variables from application */ rule->var_pub_count -= anonymous_table_count; /* Sanity check to make sure that the public part of the variable array only * contains entity variables. */ #ifdef FLECS_DEBUG for (i = 1 /* first element = $this */; i < rule->var_pub_count; i ++) { ecs_assert(rule->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } #endif return 0; error: return -1; } static ecs_var_id_t flecs_rule_most_specific_var( ecs_rule_t *rule, const char *name, ecs_var_kind_t kind, ecs_rule_compile_ctx_t *ctx) { if (kind == EcsVarTable || kind == EcsVarEntity) { return flecs_rule_find_var_id(rule, name, kind); } ecs_var_id_t evar = flecs_rule_find_var_id(rule, name, EcsVarEntity); if ((evar != EcsVarNone) && flecs_rule_is_written(evar, ctx->written)) { /* If entity variable is available and written to, it contains the most * specific result and should be used. */ return evar; } ecs_var_id_t tvar = flecs_rule_find_var_id(rule, name, EcsVarTable); if ((tvar != EcsVarNone) && !flecs_rule_is_written(tvar, ctx->written)) { /* If variable of any kind is requested and variable hasn't been written * yet, write to table variable */ return tvar; } /* If table var is written, and entity var doesn't exist or is not written, * return table var */ if (tvar != EcsVarNone) { return tvar; } else { return evar; } } static ecs_rule_lbl_t flecs_rule_op_insert( ecs_rule_op_t *op, ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_rule_op_t); int32_t count = ecs_vec_count(ctx->ops); *elem = *op; if (count > 1) { if (ctx->cur->lbl_begin == -1) { /* Variables written by previous instruction can't be written by * this instruction, except when this is a union. */ elem->written &= ~elem[-1].written; } } elem->next = flecs_itolbl(count); elem->prev = flecs_itolbl(count - 2); return flecs_itolbl(count - 1); } static ecs_rule_op_t* flecs_rule_begin_block( ecs_rule_op_kind_t kind, ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t op = {0}; op.kind = flecs_ito(uint8_t, kind); ctx->cur->lbl_begin = flecs_rule_op_insert(&op, ctx); return ecs_vec_get_t(ctx->ops, ecs_rule_op_t, ctx->cur->lbl_begin); } static void flecs_rule_end_block( ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t new_op = {0}; new_op.kind = EcsRuleEnd; ecs_rule_lbl_t end = flecs_rule_op_insert(&new_op, ctx); ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); ops[ctx->cur->lbl_begin].next = end; ecs_rule_op_t *end_op = &ops[end]; if (ctx->cur->lbl_query != -1) { ecs_rule_op_t *query_op = &ops[ctx->cur->lbl_query]; end_op->prev = ctx->cur->lbl_begin; end_op->src = query_op->src; end_op->first = query_op->first; end_op->second = query_op->second; end_op->flags = query_op->flags; end_op->field_index = query_op->field_index; } else { end_op->prev = ctx->cur->lbl_begin; end_op->field_index = -1; } ctx->cur->lbl_begin = -1; } static void flecs_rule_begin_block_cond_eval( ecs_rule_op_t *op, ecs_rule_compile_ctx_t *ctx, ecs_write_flags_t cond_write_state) { ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; ecs_write_flags_t cond_mask = 0; if (flecs_rule_ref_flags(op->flags, EcsRuleFirst) == EcsRuleIsVar) { first_var = op->first.var; ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << first_var); } if (flecs_rule_ref_flags(op->flags, EcsRuleSecond) == EcsRuleIsVar) { second_var = op->second.var; ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << second_var); } if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) == EcsRuleIsVar) { src_var = op->src.var; ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << src_var); } /* Variables set in an OR chain are marked as conditional writes. However, * writes from previous terms in the current OR chain shouldn't be treated * as variables that are conditionally set, so instead use the write mask * from before the chain started. */ if (ctx->ctrlflow->in_or) { cond_write_state = ctx->ctrlflow->cond_written_or; } /* If this term uses conditionally set variables, insert instruction that * jumps over the term if the variables weren't set yet. */ if (cond_mask & cond_write_state) { ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); ecs_rule_op_t jmp_op = {0}; jmp_op.kind = EcsRuleIf; if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { jmp_op.flags |= (EcsRuleIsVar << EcsRuleFirst); jmp_op.first.var = first_var; } if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { jmp_op.flags |= (EcsRuleIsVar << EcsRuleSecond); jmp_op.second.var = second_var; } if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { jmp_op.flags |= (EcsRuleIsVar << EcsRuleSrc); jmp_op.src.var = src_var; } flecs_rule_op_insert(&jmp_op, ctx); } else { ctx->cur->lbl_cond_eval = -1; } } static void flecs_rule_end_block_cond_eval( ecs_rule_compile_ctx_t *ctx) { if (ctx->cur->lbl_cond_eval == -1) { return; } ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); ecs_rule_op_t end_op = {0}; end_op.kind = EcsRuleEnd; ecs_rule_lbl_t end = flecs_rule_op_insert(&end_op, ctx); ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); ops[ctx->cur->lbl_cond_eval].next = end; ecs_rule_op_t *end_op_ptr = &ops[end]; ecs_rule_op_t *query_op = &ops[ctx->cur->lbl_query]; end_op_ptr->prev = ctx->cur->lbl_cond_eval; end_op_ptr->src = query_op->src; end_op_ptr->first = query_op->first; end_op_ptr->second = query_op->second; end_op_ptr->flags = query_op->flags; end_op_ptr->field_index = query_op->field_index; } static void flecs_rule_begin_block_or( ecs_rule_op_t *op, ecs_term_t *term, ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t *or_op = flecs_rule_begin_block(EcsRuleNot, ctx); or_op->kind = EcsRuleOr; /* Set the source of the evaluate terms as source of the Or instruction. * This lets the engine determine whether the variable has already been * written. When the source is not yet written, an OR operation needs to * take the union of all the terms in the OR chain. When the variable is * known, it will return after the first matching term. * * In case a term in the OR expression is an equality predicate which * compares the left hand side with a variable, the variable acts as an * alias, so we can always assume that it's written. */ bool add_src = true; if (term->first.id == EcsPredEq && term->second.flags & EcsIsVariable) { if (!(flecs_rule_is_written(op->src.var, ctx->written))) { add_src = false; } } if (add_src) { if (op->flags & (EcsRuleIsVar << EcsRuleSrc)) { or_op->flags = (EcsRuleIsVar << EcsRuleSrc); or_op->src = op->src; } } } static void flecs_rule_end_block_or( ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t op = {0}; op.kind = EcsRuleEnd; ecs_rule_lbl_t end = flecs_rule_op_insert(&op, ctx); ecs_rule_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_rule_op_t); int32_t i, j, prev_or = -2; for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { if (ops[i].next == FlecsRuleOrMarker) { if (prev_or != -2) { ops[prev_or].prev = flecs_itolbl(i); } ops[i].next = flecs_itolbl(end); prev_or = i; } else { /* Combine operation with next OR marker. This supports OR chains * with terms that require multiple operations to test. */ for (j = i + 1; j < end; j ++) { if (ops[j].next == FlecsRuleOrMarker) { if (j == (end - 1)) { ops[i].prev = ctx->cur->lbl_begin; } else { ops[i].prev = flecs_itolbl(j + 1); } break; } } } } ops[ctx->cur->lbl_begin].next = flecs_itolbl(end); ops[end].prev = ctx->cur->lbl_begin; ops[end - 1].prev = ctx->cur->lbl_begin; /* Scan which variables were conditionally written in the OR chain and * reset instructions after the OR chain. If a variable is set in part one * of a chain but not part two, there would be nothing writing to the * variable in part two, leaving it to the previous value. To address this * a reset is inserted that resets the variable value on redo. */ for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); if (!prev && cur) { ecs_rule_op_t reset_op = {0}; reset_op.kind = EcsRuleReset; reset_op.flags |= (EcsRuleIsVar << EcsRuleSrc); reset_op.src.var = flecs_itovar(i); flecs_rule_op_insert(&reset_op, ctx); } } ctx->ctrlflow->in_or = false; ctx->cur->lbl_begin = -1; } static void flecs_rule_insert_each( ecs_var_id_t tvar, ecs_var_id_t evar, ecs_rule_compile_ctx_t *ctx, bool cond_write) { ecs_rule_op_t each = {0}; each.kind = EcsRuleEach; each.src.var = evar; each.first.var = tvar; each.flags = (EcsRuleIsVar << EcsRuleSrc) | (EcsRuleIsVar << EcsRuleFirst); flecs_rule_write_ctx(evar, ctx, cond_write); flecs_rule_write(evar, &each.written); flecs_rule_op_insert(&each, ctx); } static void flecs_rule_insert_lookup( ecs_var_id_t base_var, ecs_var_id_t evar, ecs_rule_compile_ctx_t *ctx, bool cond_write) { ecs_rule_op_t lookup = {0}; lookup.kind = EcsRuleLookup; lookup.src.var = evar; lookup.first.var = base_var; lookup.flags = (EcsRuleIsVar << EcsRuleSrc) | (EcsRuleIsVar << EcsRuleFirst); flecs_rule_write_ctx(evar, ctx, cond_write); flecs_rule_write(evar, &lookup.written); flecs_rule_op_insert(&lookup, ctx); } static void flecs_rule_insert_unconstrained_transitive( ecs_rule_t *rule, ecs_rule_op_t *op, ecs_rule_compile_ctx_t *ctx, bool cond_write) { /* Create anonymous variable to store the target ids. This will return the * list of targets without constraining the variable of the term, which * needs to stay variable to find all transitive relationships for a src. */ ecs_var_id_t tgt = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); flecs_set_var_label(&rule->vars[tgt], rule->vars[op->second.var].name); /* First, find ids to start traversal from. This fixes op.second. */ ecs_rule_op_t find_ids = {0}; find_ids.kind = EcsRuleIdsRight; find_ids.field_index = -1; find_ids.first = op->first; find_ids.second = op->second; find_ids.flags = op->flags; find_ids.flags &= (ecs_flags8_t)~((EcsRuleIsVar|EcsRuleIsEntity) << EcsRuleSrc); find_ids.second.var = tgt; flecs_rule_write_ctx(tgt, ctx, cond_write); flecs_rule_write(tgt, &find_ids.written); flecs_rule_op_insert(&find_ids, ctx); /* Next, iterate all tables for the ids. This fixes op.src */ ecs_rule_op_t and_op = {0}; and_op.kind = EcsRuleAnd; and_op.field_index = op->field_index; and_op.first = op->first; and_op.second = op->second; and_op.src = op->src; and_op.flags = op->flags | EcsRuleIsSelf; and_op.second.var = tgt; flecs_rule_write_ctx(and_op.src.var, ctx, cond_write); flecs_rule_write(and_op.src.var, &and_op.written); flecs_rule_op_insert(&and_op, ctx); } static void flecs_rule_insert_inheritance( ecs_rule_t *rule, ecs_term_t *term, ecs_rule_op_t *op, ecs_rule_compile_ctx_t *ctx, bool cond_write) { /* Anonymous variable to store the resolved component ids */ ecs_var_id_t tvar = flecs_rule_add_var(rule, NULL, NULL, EcsVarTable); ecs_var_id_t evar = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); flecs_set_var_label(&rule->vars[tvar], ecs_get_name(rule->filter.world, term->first.id)); flecs_set_var_label(&rule->vars[evar], ecs_get_name(rule->filter.world, term->first.id)); ecs_rule_op_t trav_op = {0}; trav_op.kind = EcsRuleTrav; trav_op.field_index = -1; trav_op.first.entity = term->first.trav; trav_op.second.entity = term->first.id; trav_op.src.var = tvar; trav_op.flags = EcsRuleIsSelf; trav_op.flags |= (EcsRuleIsEntity << EcsRuleFirst); trav_op.flags |= (EcsRuleIsEntity << EcsRuleSecond); trav_op.flags |= (EcsRuleIsVar << EcsRuleSrc); trav_op.written |= (1ull << tvar); if (term->first.flags & EcsSelf) { trav_op.match_flags |= EcsTermReflexive; } flecs_rule_op_insert(&trav_op, ctx); flecs_rule_insert_each(tvar, evar, ctx, cond_write); ecs_rule_ref_t r = { .var = evar }; op->first = r; op->flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); op->flags |= (EcsRuleIsVar << EcsRuleFirst); } static void flecs_rule_compile_term_id( ecs_world_t *world, ecs_rule_t *rule, ecs_rule_op_t *op, ecs_term_id_t *term_id, ecs_rule_ref_t *ref, ecs_flags8_t ref_kind, ecs_var_kind_t kind, ecs_rule_compile_ctx_t *ctx, bool create_wildcard_vars) { (void)world; if (!ecs_term_id_is_set(term_id)) { return; } if (term_id->flags & EcsIsVariable) { op->flags |= (ecs_flags8_t)(EcsRuleIsVar << ref_kind); const char *name = flecs_term_id_var_name(term_id); if (name) { ref->var = flecs_rule_most_specific_var(rule, name, kind, ctx); ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } else if (create_wildcard_vars) { bool is_wildcard = flecs_term_id_is_wildcard(term_id); if (is_wildcard && (kind == EcsVarAny)) { ref->var = flecs_rule_add_var(rule, NULL, NULL, EcsVarTable); } else { ref->var = flecs_rule_add_var(rule, NULL, NULL, EcsVarEntity); } if (is_wildcard) { flecs_set_var_label(&rule->vars[ref->var], ecs_get_name(world, term_id->id)); } ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } } if (term_id->flags & EcsIsEntity) { op->flags |= (ecs_flags8_t)(EcsRuleIsEntity << ref_kind); ref->entity = term_id->id; } } static int flecs_rule_compile_ensure_vars( ecs_rule_t *rule, ecs_rule_op_t *op, ecs_rule_ref_t *ref, ecs_flags16_t ref_kind, ecs_rule_compile_ctx_t *ctx, bool cond_write, bool *written_out) { ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); bool written = false; if (flags & EcsRuleIsVar) { ecs_var_id_t var_id = ref->var; ecs_rule_var_t *var = &rule->vars[var_id]; if (var->kind == EcsVarEntity && !flecs_rule_is_written(var_id, ctx->written)) { /* If entity variable is not yet written but a table variant exists * that has been written, insert each operation to translate from * entity variable to table */ ecs_var_id_t tvar = var->table_id; if ((tvar != EcsVarNone) && flecs_rule_is_written(tvar, ctx->written)) { if (var->lookup) { if (!flecs_rule_is_written(tvar, ctx->written)) { ecs_err("dependent variable of '$%s' is not written", var->name); return -1; } if (!flecs_rule_is_written(var->base_id, ctx->written)) { flecs_rule_insert_each( tvar, var->base_id, ctx, cond_write); } } else { flecs_rule_insert_each(tvar, var_id, ctx, cond_write); } /* Variable was written, just not as entity */ written = true; } else if (var->lookup) { if (!flecs_rule_is_written(var->base_id, ctx->written)) { ecs_err("dependent variable of '$%s' is not written", var->name); return -1; } } } written |= flecs_rule_is_written(var_id, ctx->written); } else { /* If it's not a variable, it's always written */ written = true; } if (written_out) { *written_out = written; } return 0; } static bool flecs_rule_compile_lookup( ecs_rule_t *rule, ecs_var_id_t var_id, ecs_rule_compile_ctx_t *ctx, bool cond_write) { ecs_rule_var_t *var = &rule->vars[var_id]; if (var->lookup) { flecs_rule_insert_lookup(var->base_id, var_id, ctx, cond_write); return true; } else { return false; } } static void flecs_rule_insert_contains( ecs_rule_t *rule, ecs_var_id_t src_var, ecs_var_id_t other_var, ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t contains = {0}; if ((src_var != other_var) && (src_var == rule->vars[other_var].table_id)) { contains.kind = EcsRuleContain; contains.src.var = src_var; contains.first.var = other_var; contains.flags |= (EcsRuleIsVar << EcsRuleSrc) | (EcsRuleIsVar << EcsRuleFirst); flecs_rule_op_insert(&contains, ctx); } } static void flecs_rule_insert_pair_eq( int32_t field_index, ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t contains = {0}; contains.kind = EcsRulePairEq; contains.field_index = flecs_ito(int8_t, field_index); flecs_rule_op_insert(&contains, ctx); } static bool flecs_rule_term_fixed_id( ecs_filter_t *filter, ecs_term_t *term) { /* Transitive/inherited terms have variable ids */ if (term->flags & (EcsTermTransitive|EcsTermIdInherited)) { return false; } /* Or terms can match different ids */ if (term->oper == EcsOr) { return false; } if ((term != filter->terms) && term[-1].oper == EcsOr) { return false; } /* Wildcards can assume different ids */ if (ecs_id_is_wildcard(term->id)) { return false; } /* Any terms can have fixed ids, but they require special handling */ if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { return false; } /* First terms that are Not or Optional require special handling */ if (term->oper == EcsNot || term->oper == EcsOptional) { if (term == filter->terms) { return false; } } return true; } static int flecs_rule_compile_builtin_pred( ecs_rule_t *rule, ecs_term_t *term, ecs_rule_op_t *op, ecs_write_flags_t write_state) { ecs_entity_t id = term->first.id; ecs_rule_op_kind_t eq[] = {EcsRulePredEq, EcsRulePredNeq}; ecs_rule_op_kind_t eq_name[] = {EcsRulePredEqName, EcsRulePredNeqName}; ecs_rule_op_kind_t eq_match[] = {EcsRulePredEqMatch, EcsRulePredNeqMatch}; ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); if (id == EcsPredEq) { if (term->second.flags & EcsIsName) { op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); } else { op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); } } else if (id == EcsPredMatch) { op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); } op->first = op->src; op->src = (ecs_rule_ref_t){0}; op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleSrc); op->flags &= (ecs_flags8_t)~((EcsRuleIsEntity|EcsRuleIsVar) << EcsRuleFirst); op->flags |= EcsRuleIsVar << EcsRuleFirst; if (flags_2nd & EcsRuleIsVar) { if (!(write_state & (1ull << op->second.var))) { ecs_err("uninitialized variable '%s' on right-hand side of " "equality operator", ecs_rule_var_name(rule, op->second.var)); return -1; } } if (!(write_state & (1ull << op->first.var))) { /* If this is an == operator with a right-hand side that resolves to a * single entity, the left-hand side is allowed to be undefined, as the * instruction will be evaluated as an assignment. */ if (op->kind != EcsRulePredEq && op->kind != EcsRulePredEqName) { ecs_err("uninitialized variable '%s' on left-hand side of " "equality operator", ecs_rule_var_name(rule, op->first.var)); return -1; } } return 0; } static int flecs_rule_ensure_scope_var( ecs_rule_t *rule, ecs_rule_op_t *op, ecs_rule_ref_t *ref, ecs_flags16_t ref_kind, ecs_rule_compile_ctx_t *ctx) { ecs_var_id_t var = ref->var; if (rule->vars[var].kind == EcsVarEntity && !flecs_rule_is_written(var, ctx->written)) { ecs_var_id_t table_var = rule->vars[var].table_id; if (table_var != EcsVarNone && flecs_rule_is_written(table_var, ctx->written)) { if (flecs_rule_compile_ensure_vars( rule, op, ref, ref_kind, ctx, false, NULL)) { goto error; } } } return 0; error: return -1; } static int flecs_rule_ensure_scope_vars( ecs_world_t *world, ecs_rule_t *rule, ecs_rule_compile_ctx_t *ctx, ecs_term_t *term) { /* If the scope uses variables as entity that have only been written as * table, resolve them as entities before entering the scope. */ ecs_term_t *cur = term; while(cur->first.id != EcsScopeClose) { /* Dummy operation to obtain variable information for term */ ecs_rule_op_t op = {0}; flecs_rule_compile_term_id(world, rule, &op, &cur->first, &op.first, EcsRuleFirst, EcsVarEntity, ctx, false); flecs_rule_compile_term_id(world, rule, &op, &cur->second, &op.second, EcsRuleSecond, EcsVarEntity, ctx, false); if (op.flags & (EcsRuleIsVar << EcsRuleFirst)) { if (flecs_rule_ensure_scope_var( rule, &op, &op.first, EcsRuleFirst, ctx)) { goto error; } } if (op.flags & (EcsRuleIsVar << EcsRuleSecond)) { if (flecs_rule_ensure_scope_var( rule, &op, &op.second, EcsRuleSecond, ctx)) { goto error; } } cur ++; } return 0; error: return -1; } static void flecs_rule_compile_push( ecs_rule_compile_ctx_t *ctx) { ctx->cur = &ctx->ctrlflow[++ ctx->scope]; ctx->cur->lbl_begin = -1; ctx->cur->lbl_begin = -1; } static void flecs_rule_compile_pop( ecs_rule_compile_ctx_t *ctx) { ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } static bool flecs_rule_term_is_or( const ecs_filter_t *filter, const ecs_term_t *term) { bool first_term = term == filter->terms; return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); } static int flecs_rule_compile_term( ecs_world_t *world, ecs_rule_t *rule, ecs_term_t *term, ecs_rule_compile_ctx_t *ctx) { ecs_filter_t *filter = &rule->filter; bool first_term = term == filter->terms; bool first_is_var = term->first.flags & EcsIsVariable; bool second_is_var = term->second.flags & EcsIsVariable; bool src_is_var = term->src.flags & EcsIsVariable; bool builtin_pred = flecs_rule_is_builtin_pred(term); bool is_not = (term->oper == EcsNot) && !builtin_pred; bool is_or = flecs_rule_term_is_or(filter, term); bool first_or = false, last_or = false; bool cond_write = term->oper == EcsOptional || is_or; ecs_rule_op_t op = {0}; if (is_or) { first_or = first_term || (term[-1].oper != EcsOr); last_or = term->oper != EcsOr; } /* !_ (don't match anything) terms always return nothing. */ if (is_not && term->id == EcsAny) { op.kind = EcsRuleNothing; flecs_rule_op_insert(&op, ctx); return 0; } if (is_or && (first_term || term[-1].oper != EcsOr)) { ctx->ctrlflow->cond_written_or = ctx->cond_written; ctx->ctrlflow->in_or = true; } if (!term->src.id && term->src.flags & EcsIsEntity) { /* If the term has a 0 source, check if it's a scope open/close */ if (term->first.id == EcsScopeOpen) { if (flecs_rule_ensure_scope_vars(world, rule, ctx, term)) { goto error; } if (term->oper == EcsNot) { ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); flecs_rule_begin_block(EcsRuleNot, ctx); } else { ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); } flecs_rule_compile_push(ctx); } else if (term->first.id == EcsScopeClose) { flecs_rule_compile_pop(ctx); if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { ctx->cur->lbl_query = -1; flecs_rule_end_block(ctx); } } else { /* Noop */ } return 0; } if (builtin_pred) { if (term->second.id == EcsWildcard || term->second.id == EcsAny) { /* Noop */ return 0; } } /* Default instruction for And operators. If the source is fixed (like for * singletons or terms with an entity source), use With, which like And but * just matches against a source (vs. finding a source). */ op.kind = src_is_var ? EcsRuleAnd : EcsRuleWith; op.field_index = flecs_ito(int8_t, term->field_index); op.term_index = flecs_ito(int8_t, term - filter->terms); /* If rule is transitive, use Trav(ersal) instruction */ if (term->flags & EcsTermTransitive) { ecs_assert(ecs_term_id_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); op.kind = EcsRuleTrav; } else { /* Ignore cascade & parent flags */ ecs_flags32_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsParent); if (term->flags & (EcsTermMatchAny|EcsTermMatchAnySrc)) { op.kind = EcsRuleAndAny; } else if ((term->src.flags & trav_flags) == EcsUp) { op.kind = EcsRuleUp; } else if ((term->src.flags & trav_flags) == (EcsSelf|EcsUp)) { op.kind = EcsRuleSelfUp; } } /* If term has fixed id, insert simpler instruction that skips dealing with * wildcard terms and variables */ if (flecs_rule_term_fixed_id(filter, term)) { if (op.kind == EcsRuleAnd) { op.kind = EcsRuleAndId; } else if (op.kind == EcsRuleSelfUp) { op.kind = EcsRuleSelfUpId; } else if (op.kind == EcsRuleUp) { op.kind = EcsRuleUpId; } } /* Save write state at start of term so we can use it to reliably track * variables got written by this term. */ ecs_write_flags_t cond_write_state = ctx->cond_written; /* Resolve component inheritance if necessary */ ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; /* Resolve variables and entities for operation arguments */ flecs_rule_compile_term_id(world, rule, &op, &term->first, &op.first, EcsRuleFirst, EcsVarEntity, ctx, true); flecs_rule_compile_term_id(world, rule, &op, &term->second, &op.second, EcsRuleSecond, EcsVarEntity, ctx, true); if (first_is_var) first_var = op.first.var; if (second_is_var) second_var = op.second.var; flecs_rule_compile_term_id(world, rule, &op, &term->src, &op.src, EcsRuleSrc, EcsVarAny, ctx, true); if (src_is_var) src_var = op.src.var; bool src_written = flecs_rule_is_written(src_var, ctx->written); /* Insert each instructions for table -> entity variable if needed */ bool first_written, second_written; if (flecs_rule_compile_ensure_vars( rule, &op, &op.first, EcsRuleFirst, ctx, cond_write, &first_written)) { goto error; } if (flecs_rule_compile_ensure_vars( rule, &op, &op.second, EcsRuleSecond, ctx, cond_write, &second_written)) { goto error; } /* If the query starts with a Not or Optional term, insert an operation that * matches all entities. */ if (first_term && src_is_var && !src_written && term->src.id != EcsAny) { bool pred_match = builtin_pred && term->first.id == EcsPredMatch; if (term->oper == EcsNot || term->oper == EcsOptional || pred_match) { ecs_rule_op_t match_any = {0}; match_any.kind = EcsAnd; match_any.flags = EcsRuleIsSelf | (EcsRuleIsEntity << EcsRuleFirst); match_any.flags |= (EcsRuleIsVar << EcsRuleSrc); match_any.src = op.src; match_any.field_index = -1; if (!pred_match) { match_any.first.entity = EcsAny; } else { /* If matching by name, instead of finding all tables, just find * the ones with a name. */ match_any.first.entity = ecs_id(EcsIdentifier); match_any.second.entity = EcsName; match_any.flags |= (EcsRuleIsEntity << EcsRuleSecond); } match_any.written = (1ull << src_var); flecs_rule_op_insert(&match_any, ctx); flecs_rule_write_ctx(op.src.var, ctx, false); /* Update write administration */ src_written = true; } } /* A bit of special logic for OR expressions and equality predicates. If the * left-hand of an equality operator is a table, and there are multiple * operators in an Or expression, the Or chain should match all entities in * the table that match the right hand sides of the operator expressions. * For this to work, the src variable needs to be resolved as entity, as an * Or chain would otherwise only yield the first match from a table. */ if (src_is_var && src_written && builtin_pred && term->oper == EcsOr) { /* Or terms are required to have the same source, so we don't have to * worry about the last term in the chain. */ if (rule->vars[src_var].kind == EcsVarTable) { flecs_rule_compile_term_id(world, rule, &op, &term->src, &op.src, EcsRuleSrc, EcsVarEntity, ctx, true); src_var = op.src.var; } } if (flecs_rule_compile_ensure_vars( rule, &op, &op.src, EcsRuleSrc, ctx, cond_write, NULL)) { goto error; } /* If source is Any (_) and first and/or second are unconstrained, insert an * ids instruction instead of an And */ if (term->flags & EcsTermMatchAnySrc) { op.kind = EcsRuleIds; /* Use up-to-date written values after potentially inserting each */ if (!first_written || !second_written) { if (!first_written) { /* If first is unknown, traverse left: <- (*, t) */ if (term->first.id != EcsAny) { op.kind = EcsRuleIdsLeft; } } else { /* If second is wildcard, traverse right: (r, *) -> */ if (term->second.id != EcsAny) { op.kind = EcsRuleIdsRight; } } op.src.entity = 0; op.flags &= (ecs_flags8_t)~(EcsRuleIsVar << EcsRuleSrc); /* ids has no src */ op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleSrc); } /* If source variable is not written and we're querying just for Any, insert * a dedicated instruction that uses the Any record in the id index. Any * queries that are evaluated against written sources can use Wildcard * records, which is what the AndAny instruction does. */ } else if (!src_written && term->id == EcsAny && op.kind == EcsRuleAndAny) { /* Lookup variables ($var.child_name) are always written */ if (!rule->vars[src_var].lookup) { op.kind = EcsRuleSelectAny; /* Uses Any (_) id record */ } } /* If this is a transitive term and both the target and source are unknown, * find the targets for the relationship first. This clusters together * tables for the same target, which allows for more efficient usage of the * traversal caches. */ if (term->flags & EcsTermTransitive && src_is_var && second_is_var) { if (!src_written && !second_written) { flecs_rule_insert_unconstrained_transitive( rule, &op, ctx, cond_write); } } /* Check if this term has variables that have been conditionally written, * like variables written by an optional term. */ if (ctx->cond_written) { if (!is_or || first_or) { flecs_rule_begin_block_cond_eval(&op, ctx, cond_write_state); } } /* Handle Not, Optional, Or operators */ if (is_not) { flecs_rule_begin_block(EcsRuleNot, ctx); } else if (term->oper == EcsOptional) { flecs_rule_begin_block(EcsRuleOptional, ctx); } else if (first_or) { flecs_rule_begin_block_or(&op, term, ctx); } /* If term has component inheritance enabled, insert instruction to walk * down the relationship tree of the id. */ if (term->flags & EcsTermIdInherited) { flecs_rule_insert_inheritance(rule, term, &op, ctx, cond_write); } op.match_flags = term->flags; if (first_is_var) { op.first.var = first_var; op.flags &= (ecs_flags8_t)~(EcsRuleIsEntity << EcsRuleFirst); op.flags |= (EcsRuleIsVar << EcsRuleFirst); } if (term->src.flags & EcsSelf) { op.flags |= EcsRuleIsSelf; } /* Insert instructions for lookup variables */ ecs_write_flags_t write_state = ctx->written; if (first_is_var) { if (flecs_rule_compile_lookup(rule, first_var, ctx, cond_write)) { write_state |= (1ull << first_var); // lookups are resolved inline } } if (src_is_var) { if (flecs_rule_compile_lookup(rule, src_var, ctx, cond_write)) { write_state |= (1ull << src_var); // lookups are resolved inline } } if (second_is_var) { if (flecs_rule_compile_lookup(rule, second_var, ctx, cond_write)) { write_state |= (1ull << second_var); // lookups are resolved inline } } if (builtin_pred) { if (flecs_rule_compile_builtin_pred(rule, term, &op, write_state)) { goto error; } } /* If we're writing the $this variable, filter out disabled/prefab entities * unless the filter explicitly matches them. * This could've been done with regular With instructions, but since * filtering out disabled/prefab entities is the default and this check is * cheap to perform on table flags, it's worth special casing. */ if (!src_written && src_var == 0) { ecs_flags32_t filter_flags = filter->flags; if (!(filter_flags & EcsFilterMatchDisabled) || !(filter_flags & EcsFilterMatchPrefab)) { ecs_flags32_t table_flags = 0; if (!(filter_flags & EcsFilterMatchDisabled)) { table_flags |= EcsTableIsDisabled; } if (!(filter_flags & EcsFilterMatchPrefab)) { table_flags |= EcsTableIsPrefab; } op.other = flecs_itolbl(table_flags); } } /* After evaluating a term, a used variable is always written */ if (src_is_var) { flecs_rule_write(src_var, &op.written); flecs_rule_write_ctx(op.src.var, ctx, cond_write); } if (first_is_var) { flecs_rule_write(first_var, &op.written); flecs_rule_write_ctx(first_var, ctx, cond_write); } if (second_is_var) { flecs_rule_write(second_var, &op.written); flecs_rule_write_ctx(second_var, ctx, cond_write); } flecs_rule_op_insert(&op, ctx); ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); if (is_or) { ecs_rule_op_t *op_ptr = ecs_vec_get_t(ctx->ops, ecs_rule_op_t, ctx->cur->lbl_query); op_ptr->next = FlecsRuleOrMarker; } /* Handle self-references between src and first/second variables */ if (src_is_var) { if (first_is_var) { flecs_rule_insert_contains(rule, src_var, first_var, ctx); } if (second_is_var && first_var != second_var) { flecs_rule_insert_contains(rule, src_var, second_var, ctx); } } /* Handle self references between first and second variables */ if (first_is_var && !first_written && (first_var == second_var)) { flecs_rule_insert_pair_eq(term->field_index, ctx); } /* Handle closing of Not, Optional and Or operators */ if (is_not) { flecs_rule_end_block(ctx); } else if (term->oper == EcsOptional) { flecs_rule_end_block(ctx); } else if (last_or) { flecs_rule_end_block_or(ctx); } /* Handle closing of conditional evaluation */ if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { if (!is_or || last_or) { flecs_rule_end_block_cond_eval(ctx); } } /* Ensure that term id is set after evaluating Not */ if (term->flags & EcsTermIdInherited) { if (is_not) { ecs_rule_op_t set_id = {0}; set_id.kind = EcsRuleSetId; set_id.first.entity = term->id; set_id.flags = (EcsRuleIsEntity << EcsRuleFirst); set_id.field_index = flecs_ito(int8_t, term->field_index); flecs_rule_op_insert(&set_id, ctx); } } return 0; error: return -1; } static bool flecs_rule_var_is_unknown( ecs_rule_t *rule, ecs_var_id_t var_id, ecs_rule_compile_ctx_t *ctx) { ecs_rule_var_t *vars = rule->vars; if (ctx->written & (1ull << var_id)) { return false; } else { ecs_var_id_t table_var = vars[var_id].table_id; if (table_var != EcsVarNone) { return flecs_rule_var_is_unknown(rule, table_var, ctx); } } return true; } /* Returns whether term is unkown. A term is unknown when it has variable * elements (first, second, src) that are all unknown. */ static bool flecs_rule_term_is_unknown( ecs_rule_t *rule, ecs_term_t *term, ecs_rule_compile_ctx_t *ctx) { ecs_rule_op_t dummy = {0}; flecs_rule_compile_term_id(NULL, rule, &dummy, &term->first, &dummy.first, EcsRuleFirst, EcsVarEntity, ctx, false); flecs_rule_compile_term_id(NULL, rule, &dummy, &term->second, &dummy.second, EcsRuleSecond, EcsVarEntity, ctx, false); flecs_rule_compile_term_id(NULL, rule, &dummy, &term->src, &dummy.src, EcsRuleSrc, EcsVarAny, ctx, false); bool has_vars = dummy.flags & ((EcsRuleIsVar << EcsRuleFirst) | (EcsRuleIsVar << EcsRuleSecond) | (EcsRuleIsVar << EcsRuleSrc)); if (!has_vars) { /* If term has no variables (typically terms with a static src) there * can't be anything that's unknown. */ return false; } if (dummy.flags & (EcsRuleIsVar << EcsRuleFirst)) { if (!flecs_rule_var_is_unknown(rule, dummy.first.var, ctx)) { return false; } } if (dummy.flags & (EcsRuleIsVar << EcsRuleSecond)) { if (!flecs_rule_var_is_unknown(rule, dummy.second.var, ctx)) { return false; } } if (dummy.flags & (EcsRuleIsVar << EcsRuleSrc)) { if (!flecs_rule_var_is_unknown(rule, dummy.src.var, ctx)) { return false; } } return true; } /* Find the next known term from specified offset. This function is used to find * a term that can be evaluated before a term that is unknown. Evaluating known * before unknown terms can significantly decrease the search space. */ static int32_t flecs_rule_term_next_known( ecs_rule_t *rule, ecs_rule_compile_ctx_t *ctx, int32_t offset, ecs_flags64_t compiled) { ecs_filter_t *filter = &rule->filter; ecs_term_t *terms = filter->terms; int32_t i, count = filter->term_count; for (i = offset; i < count; i ++) { ecs_term_t *term = &terms[i]; if (compiled & (1ull << i)) { continue; } /* Only evaluate And terms */ if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ continue; } /* Don't reorder terms before/after scopes */ if (term->first.id == EcsScopeOpen || term->first.id == EcsScopeClose) { return -1; } if (flecs_rule_term_is_unknown(rule, term, ctx)) { continue; } return i; } return -1; } /* If the first part of a query contains more than one trivial term, insert a * special instruction which batch-evaluates multiple terms. */ static int32_t flecs_rule_insert_trivial_search( ecs_rule_t *rule, ecs_rule_compile_ctx_t *ctx) { ecs_filter_t *filter = &rule->filter; ecs_term_t *terms = filter->terms; int32_t i, term_count = filter->term_count; /* Find trivial terms, which can be handled in single instruction */ int32_t trivial_wildcard_terms = 0; int32_t trivial_data_terms = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (!(term->flags & EcsTermIsTrivial)) { break; } /* We can only add trivial terms to plan if they no up traversal */ if ((term->src.flags & EcsTraverseFlags) != EcsSelf) { break; } if (ecs_id_is_wildcard(term->id)) { trivial_wildcard_terms ++; } if (!(term->flags & EcsTermNoData)) { trivial_data_terms ++; } } int32_t trivial_terms = i; if (trivial_terms >= 2) { /* If there's more than 1 trivial term, batch them in trivial search */ ecs_rule_op_t trivial = {0}; if (trivial_wildcard_terms) { trivial.kind = EcsRuleTrivWildcard; } else { if (trivial_data_terms) { /* Check to see if there are remaining data terms. If there are, * we'll have to insert an instruction later that populates all * fields, so don't do double work here. */ for (i = trivial_terms; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (!(term->flags & EcsTermIsTrivial)) { break; } } if (trivial_terms == term_count || i != term_count) { /* Nobody else is going to set the data fields, so we should * do it here. */ trivial.kind = EcsRuleTrivData; } } if (!trivial.kind) { trivial.kind = EcsRuleTriv; } } /* Store on the operation how many trivial terms should be evaluated */ trivial.other = (ecs_rule_lbl_t)trivial_terms; flecs_rule_op_insert(&trivial, ctx); } else { /* If fewer than 1 trivial term, there's no point in batching them */ trivial_terms = 0; } return trivial_terms; } /* Insert instruction to populate data fields. */ static void flecs_rule_insert_populate( ecs_rule_t *rule, ecs_rule_compile_ctx_t *ctx, int32_t trivial_terms) { ecs_filter_t *filter = &rule->filter; int32_t i, term_count = filter->term_count; /* Insert instruction that populates data. This instruction does not * have to be inserted if the filter provides no data, or if all terms * of the filter are trivial, in which case the trivial search operation * also sets the data. */ if (!(filter->flags & EcsFilterNoData) && (trivial_terms != term_count)) { int32_t data_fields = 0; bool only_self = true; /* There are two instructions for setting data fields, a fast one * that only supports owned fields, and one that supports any kind * of field. Loop through (remaining) terms to check which one we * need to use. */ for (i = trivial_terms; i < term_count; i ++) { ecs_term_t *term = &filter->terms[i]; if (term->flags & EcsTermNoData) { /* Don't care about terms that have no data */ continue; } data_fields ++; if (!ecs_term_match_this(term)) { break; } if (term->src.flags & EcsUp) { break; } } if (i != filter->term_count) { only_self = false; /* Needs the more complex operation */ } if (data_fields) { if (only_self) { ecs_rule_op_t nothing = {0}; nothing.kind = EcsRulePopulateSelf; flecs_rule_op_insert(¬hing, ctx); } else { ecs_rule_op_t nothing = {0}; nothing.kind = EcsRulePopulate; flecs_rule_op_insert(¬hing, ctx); } } } } int flecs_rule_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_rule_t *rule) { ecs_filter_t *filter = &rule->filter; ecs_term_t *terms = filter->terms; ecs_rule_compile_ctx_t ctx = {0}; ecs_vec_reset_t(NULL, &stage->operations, ecs_rule_op_t); ctx.ops = &stage->operations; ctx.cur = ctx.ctrlflow; ctx.cur->lbl_begin = -1; ctx.cur->lbl_begin = -1; ecs_vec_clear(ctx.ops); /* Find all variables defined in query */ if (flecs_rule_discover_vars(stage, rule)) { return -1; } /* If rule contains fixed source terms, insert operation to set sources */ int32_t i, term_count = filter->term_count; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->src.flags & EcsIsEntity) { ecs_rule_op_t set_fixed = {0}; set_fixed.kind = EcsRuleSetFixed; flecs_rule_op_insert(&set_fixed, &ctx); break; } } /* If the rule contains terms with fixed ids (no wildcards, variables), * insert instruction that initializes ecs_iter_t::ids. This allows for the * insertion of simpler instructions later on. */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (flecs_rule_term_fixed_id(filter, term) || (term->src.flags & EcsIsEntity && !term->src.id)) { ecs_rule_op_t set_ids = {0}; set_ids.kind = EcsRuleSetIds; flecs_rule_op_insert(&set_ids, &ctx); break; } } /* Insert trivial term search if query allows for it */ int32_t trivial_terms = flecs_rule_insert_trivial_search(rule, &ctx); /* Compile remaining query terms to instructions */ ecs_flags64_t compiled = 0; for (i = trivial_terms; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int32_t compile = i; if (compiled & (1ull << i)) { continue; /* Already compiled */ } bool can_reorder = true; if (term->oper != EcsAnd || flecs_rule_term_is_or(&rule->filter, term)){ can_reorder = false; } /* If variables have been written, but this term has no known variables, * first try to resolve terms that have known variables. This can * significantly reduce the search space. * Only perform this optimization after at least one variable has been * written to, as all terms are unknown otherwise. */ if (can_reorder && ctx.written && flecs_rule_term_is_unknown(rule, term, &ctx)) { int32_t term_index = flecs_rule_term_next_known( rule, &ctx, i + 1, compiled); if (term_index != -1) { term = &rule->filter.terms[term_index]; compile = term_index; i --; /* Repeat current term */ } } if (flecs_rule_compile_term(world, rule, term, &ctx)) { return -1; } compiled |= (1ull << compile); } ecs_var_id_t this_id = flecs_rule_find_var_id(rule, "This", EcsVarEntity); /* If This variable has been written as entity, insert an operation to * assign it to it.entities for consistency. */ if (this_id != EcsVarNone && (ctx.written & (1ull << this_id))) { ecs_rule_op_t set_this = {0}; set_this.kind = EcsRuleSetThis; set_this.flags |= (EcsRuleIsVar << EcsRuleFirst); set_this.first.var = this_id; flecs_rule_op_insert(&set_this, &ctx); } /* Make sure non-This variables are written as entities */ if (rule->vars) { for (i = 0; i < rule->var_count; i ++) { ecs_rule_var_t *var = &rule->vars[i]; if (var->id && var->kind == EcsVarTable && var->name) { ecs_var_id_t var_id = flecs_rule_find_var_id(rule, var->name, EcsVarEntity); if (!flecs_rule_is_written(var_id, ctx.written)) { /* Skip anonymous variables */ if (!flecs_rule_var_is_anonymous(rule, var_id)) { flecs_rule_insert_each(var->id, var_id, &ctx, false); } } } } } /* If rule contains non-This variables as term source, build lookup array */ if (rule->src_vars) { ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); bool only_anonymous = true; for (i = 0; i < filter->field_count; i ++) { ecs_var_id_t var_id = rule->src_vars[i]; if (!var_id) { continue; } if (!flecs_rule_var_is_anonymous(rule, var_id)) { only_anonymous = false; break; } else { /* Don't fetch component data for anonymous variables. Because * not all metadata (such as it.sources) is initialized for * anonymous variables, and because they may only be available * as table variables (each is not guaranteed to be inserted for * anonymous variables) the iterator may not have sufficient * information to resolve component data. */ for (int32_t t = 0; t < filter->term_count; t ++) { ecs_term_t *term = &filter->terms[t]; if (term->field_index == i) { term->inout = EcsInOutNone; } } } } /* Don't insert setvar instruction if all vars are anonymous */ if (!only_anonymous) { ecs_rule_op_t set_vars = {0}; set_vars.kind = EcsRuleSetVars; flecs_rule_op_insert(&set_vars, &ctx); } for (i = 0; i < filter->field_count; i ++) { ecs_var_id_t var_id = rule->src_vars[i]; if (!var_id) { continue; } if (rule->vars[var_id].kind == EcsVarTable) { var_id = flecs_rule_find_var_id(rule, rule->vars[var_id].name, EcsVarEntity); /* Variables used as source that aren't This must be entities */ ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } rule->src_vars[i] = var_id; } } /* If filter is empty, insert Nothing instruction */ if (!term_count) { ecs_rule_op_t nothing = {0}; nothing.kind = EcsRuleNothing; flecs_rule_op_insert(¬hing, &ctx); } else { /* Insert instruction to populate data fields */ flecs_rule_insert_populate(rule, &ctx, trivial_terms); /* Insert yield. If program reaches this operation, a result was found */ ecs_rule_op_t yield = {0}; yield.kind = EcsRuleYield; flecs_rule_op_insert(&yield, &ctx); } int32_t op_count = ecs_vec_count(ctx.ops); if (op_count) { rule->op_count = op_count; rule->ops = ecs_os_malloc_n(ecs_rule_op_t, op_count); ecs_rule_op_t *rule_ops = ecs_vec_first_t(ctx.ops, ecs_rule_op_t); ecs_os_memcpy_n(rule->ops, rule_ops, ecs_rule_op_t, op_count); } return 0; } #endif /** * @file addons/rules/engine.c * @brief Rules engine implementation. */ #ifdef FLECS_RULES static bool flecs_rule_dispatch( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx); static bool flecs_rule_run_until( bool redo, ecs_rule_run_ctx_t *ctx, const ecs_rule_op_t *ops, ecs_rule_lbl_t first, ecs_rule_lbl_t cur, ecs_rule_op_kind_t until); ecs_allocator_t* flecs_rule_get_allocator( const ecs_iter_t *it) { ecs_world_t *world = it->world; if (ecs_poly_is(world, ecs_world_t)) { return &world->allocator; } else { ecs_assert(ecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); return &((ecs_stage_t*)world)->allocator; } } static ecs_rule_op_ctx_t* flecs_op_ctx_( const ecs_rule_run_ctx_t *ctx) { return &ctx->op_ctx[ctx->op_index]; } #define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) static void flecs_reset_source_set_flag( const ecs_rule_run_ctx_t *ctx, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); (*ctx->source_set) &= ~(1u << field_index); } static void flecs_set_source_set_flag( const ecs_rule_run_ctx_t *ctx, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); (*ctx->source_set) |= (1u << field_index); } static ecs_table_range_t flecs_range_from_entity( ecs_entity_t e, const ecs_rule_run_ctx_t *ctx) { ecs_record_t *r = flecs_entities_get(ctx->world, e); if (!r) { return (ecs_table_range_t){ 0 }; } return (ecs_table_range_t){ .table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1 }; } static ecs_table_range_t flecs_rule_var_get_range( int32_t var_id, const ecs_rule_run_ctx_t *ctx) { ecs_assert(var_id < ctx->rule->var_count, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; ecs_table_t *table = var->range.table; if (table) { return var->range; } ecs_entity_t entity = var->entity; if (entity && entity != EcsWildcard) { var->range = flecs_range_from_entity(entity, ctx); return var->range; } return (ecs_table_range_t){ 0 }; } static ecs_table_t* flecs_rule_var_get_table( int32_t var_id, const ecs_rule_run_ctx_t *ctx) { ecs_var_t *var = &ctx->vars[var_id]; ecs_table_t *table = var->range.table; if (table) { return table; } ecs_entity_t entity = var->entity; if (entity && entity != EcsWildcard) { var->range = flecs_range_from_entity(entity, ctx); return var->range.table; } return NULL; } static ecs_table_t* flecs_rule_get_table( const ecs_rule_op_t *op, const ecs_rule_ref_t *ref, ecs_flags16_t ref_kind, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); if (flags & EcsRuleIsEntity) { return ecs_get_table(ctx->world, ref->entity); } else { return flecs_rule_var_get_table(ref->var, ctx); } } static ecs_table_range_t flecs_rule_get_range( const ecs_rule_op_t *op, const ecs_rule_ref_t *ref, ecs_flags16_t ref_kind, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t flags = flecs_rule_ref_flags(op->flags, ref_kind); if (flags & EcsRuleIsEntity) { ecs_assert(!(flags & EcsRuleIsVar), ECS_INTERNAL_ERROR, NULL); return flecs_range_from_entity(ref->entity, ctx); } else { ecs_var_t *var = &ctx->vars[ref->var]; if (var->range.table) { return ctx->vars[ref->var].range; } else if (var->entity) { return flecs_range_from_entity(var->entity, ctx); } } return (ecs_table_range_t){0}; } static ecs_entity_t flecs_rule_var_get_entity( ecs_var_id_t var_id, const ecs_rule_run_ctx_t *ctx) { ecs_assert(var_id < (ecs_var_id_t)ctx->rule->var_count, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; ecs_entity_t entity = var->entity; if (entity) { return entity; } ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = var->range.table; ecs_entity_t *entities = table->data.entities.array; var->entity = entities[var->range.offset]; return var->entity; } static void flecs_rule_var_reset( ecs_var_id_t var_id, const ecs_rule_run_ctx_t *ctx) { ctx->vars[var_id].entity = EcsWildcard; ctx->vars[var_id].range.table = NULL; } static void flecs_rule_var_set_table( const ecs_rule_op_t *op, ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_rule_run_ctx_t *ctx) { (void)op; ecs_assert(ctx->rule_vars[var_id].kind == EcsVarTable, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_rule_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->entity = 0; var->range = (ecs_table_range_t){ .table = table, .offset = offset, .count = count }; } static void flecs_rule_var_set_entity( const ecs_rule_op_t *op, ecs_var_id_t var_id, ecs_entity_t entity, const ecs_rule_run_ctx_t *ctx) { (void)op; ecs_assert(var_id < (ecs_var_id_t)ctx->rule->var_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_rule_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->range.table = NULL; var->entity = entity; } static void flecs_rule_set_vars( const ecs_rule_op_t *op, ecs_id_t id, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); if (flags_1st & EcsRuleIsVar) { ecs_var_id_t var = op->first.var; if (op->written & (1ull << var)) { if (ECS_IS_PAIR(id)) { flecs_rule_var_set_entity( op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); } else { flecs_rule_var_set_entity(op, var, id, ctx); } } } if (flags_2nd & EcsRuleIsVar) { ecs_var_id_t var = op->second.var; if (op->written & (1ull << var)) { flecs_rule_var_set_entity( op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); } } } static ecs_table_range_t flecs_get_ref_range( const ecs_rule_ref_t *ref, ecs_flags16_t flag, const ecs_rule_run_ctx_t *ctx) { if (flag & EcsRuleIsEntity) { return flecs_range_from_entity(ref->entity, ctx); } else if (flag & EcsRuleIsVar) { return flecs_rule_var_get_range(ref->var, ctx); } return (ecs_table_range_t){0}; } static ecs_entity_t flecs_get_ref_entity( const ecs_rule_ref_t *ref, ecs_flags16_t flag, const ecs_rule_run_ctx_t *ctx) { if (flag & EcsRuleIsEntity) { return ref->entity; } else if (flag & EcsRuleIsVar) { return flecs_rule_var_get_entity(ref->var, ctx); } return 0; } static ecs_id_t flecs_rule_op_get_id_w_written( const ecs_rule_op_t *op, uint64_t written, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); ecs_entity_t first = 0, second = 0; if (flags_1st) { if (flecs_ref_is_written(op, &op->first, EcsRuleFirst, written)) { first = flecs_get_ref_entity(&op->first, flags_1st, ctx); } else if (flags_1st & EcsRuleIsVar) { first = EcsWildcard; } } if (flags_2nd) { if (flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); } else if (flags_2nd & EcsRuleIsVar) { second = EcsWildcard; } } if (flags_2nd & (EcsRuleIsVar | EcsRuleIsEntity)) { return ecs_pair(first, second); } else { return flecs_entities_get_alive(ctx->world, first); } } static ecs_id_t flecs_rule_op_get_id( const ecs_rule_op_t *op, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; return flecs_rule_op_get_id_w_written(op, written, ctx); } static int16_t flecs_rule_next_column( ecs_table_t *table, ecs_id_t id, int32_t column) { if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { column = column + 1; } else { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); column = ecs_search_offset(NULL, table, column + 1, id, NULL); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); } return flecs_ito(int16_t, column); } static void flecs_rule_it_set_column( ecs_iter_t *it, int32_t field_index, int32_t column) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); it->columns[field_index] = column + 1; } static ecs_id_t flecs_rule_it_set_id( ecs_iter_t *it, ecs_table_t *table, int32_t field_index, int32_t column) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); return it->ids[field_index] = table->type.array[column]; } static void flecs_rule_set_match( const ecs_rule_op_t *op, ecs_table_t *table, int32_t column, const ecs_rule_run_ctx_t *ctx) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); int32_t field_index = op->field_index; if (field_index == -1) { return; } ecs_iter_t *it = ctx->it; flecs_rule_it_set_column(it, field_index, column); ecs_id_t matched = flecs_rule_it_set_id(it, table, field_index, column); flecs_rule_set_vars(op, matched, ctx); } static void flecs_rule_set_trav_match( const ecs_rule_op_t *op, int32_t column, ecs_entity_t trav, ecs_entity_t second, const ecs_rule_run_ctx_t *ctx) { int32_t field_index = op->field_index; if (field_index == -1) { return; } ecs_iter_t *it = ctx->it; ecs_id_t matched = ecs_pair(trav, second); it->ids[op->field_index] = matched; if (column != -1) { flecs_rule_it_set_column(it, op->field_index, column); } flecs_rule_set_vars(op, matched, ctx); } static bool flecs_rule_table_filter( ecs_table_t *table, ecs_rule_lbl_t other, ecs_flags32_t filter_mask) { uint32_t filter = flecs_ito(uint32_t, other); return (table->flags & filter_mask & filter) != 0; } static bool flecs_rule_select_w_id( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx, ecs_id_t id, ecs_flags32_t filter_mask) { ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_id_record_t *idr = op_ctx->idr; ecs_table_record_t *tr; ecs_table_t *table; if (!redo) { if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } if (ctx->rule->filter.flags & EcsFilterMatchEmptyTables) { if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { return false; } } else { if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { return false; } } } repeat: if (!redo || !op_ctx->remaining) { tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); if (!tr) { return false; } op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); table = tr->hdr.table; flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); } else { tr = (ecs_table_record_t*)op_ctx->it.cur; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); table = tr->hdr.table; op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); op_ctx->remaining --; } if (flecs_rule_table_filter(table, op->other, filter_mask)) { goto repeat; } flecs_rule_set_match(op, table, op_ctx->column, ctx); return true; } static bool flecs_rule_select( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_id_t id = 0; if (!redo) { id = flecs_rule_op_get_id(op, ctx); } return flecs_rule_select_w_id(op, redo, ctx, id, (EcsTableIsPrefab|EcsTableIsDisabled)); } static bool flecs_rule_with( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_id_record_t *idr = op_ctx->idr; const ecs_table_record_t *tr; ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); if (!table) { return false; } if (!redo) { ecs_id_t id = flecs_rule_op_get_id(op, ctx); if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } tr = flecs_id_record_get_table(idr, table); if (!tr) { return false; } op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count); } else { if (--op_ctx->remaining <= 0) { return false; } op_ctx->column = flecs_rule_next_column(table, idr->id, op_ctx->column); ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } flecs_rule_set_match(op, table, op_ctx->column, ctx); return true; } static bool flecs_rule_and( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_rule_with(op, redo, ctx); } else { return flecs_rule_select(op, redo, ctx); } } static bool flecs_rule_select_id( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx, ecs_flags32_t table_filter) { ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; int8_t field = op->field_index; ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); if (!redo) { ecs_id_t id = it->ids[field]; ecs_id_record_t *idr = op_ctx->idr; if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } if (ctx->rule->filter.flags & EcsFilterMatchEmptyTables) { if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { return false; } } else { if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { return false; } } } repeat: {} const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (flecs_rule_table_filter(table, op->other, table_filter)) { goto repeat; } flecs_rule_var_set_table(op, op->src.var, table, 0, 0, ctx); flecs_rule_it_set_column(it, field, tr->index); return true; } static bool flecs_rule_with_id( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; int8_t field = op->field_index; ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = flecs_rule_get_table(op, &op->src, EcsRuleSrc, ctx); if (!table) { return false; } ecs_id_t id = it->ids[field]; ecs_id_record_t *idr = op_ctx->idr; if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return false; } flecs_rule_it_set_column(it, field, tr->index); return true; } static bool flecs_rule_and_id( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_rule_with_id(op, redo, ctx); } else { return flecs_rule_select_id(op, redo, ctx, (EcsTableIsPrefab|EcsTableIsDisabled)); } } static bool flecs_rule_up_select( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx, bool self, bool id_only) { ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_world_t *world = ctx->world; ecs_iter_t *it = ctx->it; bool redo_select = redo; const ecs_filter_t *filter = &ctx->rule->filter; /* Early out if traversal relationship doesn't exist */ op_ctx->trav = filter->terms[op->term_index].src.trav; if (!op_ctx->idr_trav) { op_ctx->idr_trav = flecs_id_record_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ if (!self) { return false; } else if (id_only) { return flecs_rule_select_id(op, redo, ctx, (EcsTableIsPrefab|EcsTableIsDisabled)); } else { return flecs_rule_select(op, redo, ctx); } } if (!redo) { op_ctx->with = flecs_rule_op_get_id(op, ctx); op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); if (!op_ctx->idr_with) { return false; } op_ctx->down = NULL; op_ctx->cache_elem = 0; } ecs_trav_down_t *down = op_ctx->down; do { while (!down) { ecs_table_t *table = op_ctx->table; if (!table) { ecs_table_range_t range; it->sources[op->field_index] = 0; do { bool result; if (id_only) { result = flecs_rule_select_id(op, redo_select, ctx, 0); } else { result = flecs_rule_select_w_id(op, redo_select, ctx, op_ctx->with, 0); } if (!result) { return false; } redo_select = true; range = flecs_rule_get_range( op, &op->src, EcsRuleSrc, ctx); ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); } while (!self && range.table->_->traversable_count == 0); if (!range.count) { range.count = ecs_table_count(range.table); } table = op_ctx->table = range.table; op_ctx->row = range.offset; op_ctx->end = range.offset + range.count; op_ctx->matched = it->ids[op->field_index]; if (self) { if (!flecs_rule_table_filter(table, op->other, (EcsTableIsPrefab|EcsTableIsDisabled))) { flecs_reset_source_set_flag(ctx, op->field_index); op_ctx->row --; return true; } } redo_select = true; } else { op_ctx->row ++; } if (table->_->traversable_count == 0) { op_ctx->table = NULL; continue; } else { int32_t row; ecs_entity_t entity = 0; ecs_entity_t *entities = flecs_table_entities_array(table); for (row = op_ctx->row; row < op_ctx->end; row ++) { entity = entities[row]; ecs_record_t *record = flecs_entities_get(world, entity); if (record->row & EcsEntityIsTraversable) { it->sources[op->field_index] = entity; break; } } if (row == op_ctx->end) { op_ctx->table = NULL; continue; } down = op_ctx->down = flecs_rule_get_down_cache(ctx, &op_ctx->cache, op_ctx->trav, entity, op_ctx->idr_with, self); op_ctx->cache_elem = -1; } } next_elem: if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { down = NULL; continue; } ecs_trav_down_elem_t *elem = ecs_vec_get_t( &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); flecs_rule_var_set_table(op, op->src.var, elem->table, 0, 0, ctx); flecs_rule_set_vars(op, op_ctx->matched, ctx); if (flecs_rule_table_filter(elem->table, op->other, (EcsTableIsPrefab|EcsTableIsDisabled))) { goto next_elem; } break; } while (true); flecs_set_source_set_flag(ctx, op->field_index); return true; } static bool flecs_rule_up_with( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { const ecs_filter_t *filter = &ctx->rule->filter; ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; /* Early out if traversal relationship doesn't exist */ op_ctx->trav = filter->terms[op->term_index].src.trav; if (!op_ctx->idr_trav) { op_ctx->idr_trav = flecs_id_record_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ return false; } if (!redo) { op_ctx->trav = filter->terms[op->term_index].src.trav; op_ctx->with = flecs_rule_op_get_id(op, ctx); op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); if (!op_ctx->idr_with) { return false; } ecs_table_range_t range = flecs_rule_get_range( op, &op->src, EcsRuleSrc, ctx); if (!range.table) { return false; } ecs_trav_up_t *up = flecs_rule_get_up_cache(ctx, &op_ctx->cache, range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, op_ctx->idr_trav); if (!up) { return false; } it->sources[op->field_index] = flecs_entities_get_alive( ctx->world, up->src); it->columns[op->field_index] = up->column + 1; it->ids[op->field_index] = up->id; flecs_rule_set_vars(op, up->id, ctx); flecs_set_source_set_flag(ctx, op->field_index); return true; } else { return false; } } static bool flecs_rule_self_up_with( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx, bool id_only) { if (!redo) { bool result; if (id_only) { result = flecs_rule_with_id(op, redo, ctx); } else { result = flecs_rule_with(op, redo, ctx); } if (result) { ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); op_ctx->trav = 0; if (flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar) { ecs_iter_t *it = ctx->it; it->sources[op->field_index] = 0; } return true; } flecs_reset_source_set_flag(ctx, op->field_index); return flecs_rule_up_with(op, redo, ctx); } else { ecs_rule_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); if (op_ctx->trav == 0) { return flecs_rule_with(op, redo, ctx); } } return false; } static bool flecs_rule_up( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { return flecs_rule_up_with(op, redo, ctx); } else { return flecs_rule_up_select(op, redo, ctx, false, false); } } static bool flecs_rule_self_up( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { return flecs_rule_self_up_with(op, redo, ctx, false); } else { return flecs_rule_up_select(op, redo, ctx, true, false); } } static bool flecs_rule_up_id( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { return flecs_rule_up_with(op, redo, ctx); } else { return flecs_rule_up_select(op, redo, ctx, false, true); } } static bool flecs_rule_self_up_id( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { return flecs_rule_self_up_with(op, redo, ctx, true); } else { return flecs_rule_up_select(op, redo, ctx, true, true); } } static bool flecs_rule_select_any( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { return flecs_rule_select_w_id(op, redo, ctx, EcsAny, (EcsTableIsPrefab|EcsTableIsDisabled)); } static bool flecs_rule_and_any( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t match_flags = op->match_flags; if (redo) { if (match_flags & EcsTermMatchAnySrc) { return false; } } uint64_t written = ctx->written[ctx->op_index]; int32_t remaining = 1; bool result; if (flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { result = flecs_rule_with(op, redo, ctx); } else { result = flecs_rule_select(op, redo, ctx); remaining = 0; } if (!redo) { ecs_rule_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); if (match_flags & EcsTermMatchAny && op_ctx->remaining) { op_ctx->remaining = flecs_ito(int16_t, remaining); } } int32_t field = op->field_index; if (field != -1) { ctx->it->ids[field] = flecs_rule_op_get_id(op, ctx); } return result; } static bool flecs_rule_triv( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); int32_t until = flecs_ito(int32_t, op->other); uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_rule_trivial_test(ctx->rule, ctx, !redo, until); } else { return flecs_rule_trivial_search_nodata(ctx->rule, ctx, op_ctx, !redo, until); } } static bool flecs_rule_triv_data( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); int32_t until = flecs_ito(int32_t, op->other); uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_rule_trivial_test(ctx->rule, ctx, !redo, until); } else { return flecs_rule_trivial_search(ctx->rule, ctx, op_ctx, !redo, until); } } static bool flecs_rule_triv_wildcard( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); int32_t until = flecs_ito(int32_t, op->other); uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_rule_trivial_test_w_wildcards(ctx->rule, ctx, !redo, until); } else { return flecs_rule_trivial_search_w_wildcards(ctx->rule, ctx, op_ctx, !redo, until); } } static bool flecs_rule_trav_fixed_src_reflexive( const ecs_rule_op_t *op, const ecs_rule_run_ctx_t *ctx, ecs_table_range_t *range, ecs_entity_t trav, ecs_entity_t second) { ecs_table_t *table = range->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = table->data.entities.array; int32_t count = range->count; if (!count) { count = ecs_table_count(table); } int32_t i = range->offset, end = i + count; for (; i < end; i ++) { if (entities[i] == second) { /* Even though table doesn't have the specific relationship * pair, the relationship is reflexive and the target entity * is stored in the table. */ break; } } if (i == end) { /* Table didn't contain target entity */ return false; } if (count > 1) { /* If the range contains more than one entity, set the range to * return only the entity matched by the reflexive property. */ ecs_assert(flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[op->src.var]; ecs_table_range_t *var_range = &var->range; var_range->offset = i; var_range->count = 1; var->entity = entities[i]; } flecs_rule_set_trav_match(op, -1, trav, second, ctx); return true; } static bool flecs_rule_trav_unknown_src_reflexive( const ecs_rule_op_t *op, const ecs_rule_run_ctx_t *ctx, ecs_entity_t trav, ecs_entity_t second) { ecs_assert(flecs_rule_ref_flags(op->flags, EcsRuleSrc) & EcsRuleIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_id_t src_var = op->src.var; flecs_rule_var_set_entity(op, src_var, second, ctx); flecs_rule_var_get_table(src_var, ctx); flecs_rule_set_trav_match(op, -1, trav, second, ctx); return true; } static bool flecs_rule_trav_fixed_src_up_fixed_second( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { if (redo) { return false; /* If everything's fixed, can only have a single result */ } ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); ecs_flags16_t f_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); ecs_flags16_t f_src = flecs_rule_ref_flags(op->flags, EcsRuleSrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; /* Check if table has transitive relationship by traversing upwards */ int32_t column = ecs_search_relation(ctx->world, table, 0, ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, NULL); if (column == -1) { if (op->match_flags & EcsTermReflexive) { return flecs_rule_trav_fixed_src_reflexive(op, ctx, &range, trav, second); } else { return false; } } flecs_rule_set_trav_match(op, column, trav, second, ctx); return true; } static bool flecs_rule_trav_unknown_src_up_fixed_second( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); ecs_flags16_t f_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { ecs_record_t *r_second = flecs_entities_get(ctx->world, second); bool traversable = r_second && r_second->row & EcsEntityIsTraversable; bool reflexive = op->match_flags & EcsTermReflexive; if (!traversable && !reflexive) { trav_ctx->cache.id = 0; /* If there's no record for the entity, it can't have a subtree so * forward operation to a regular select. */ return flecs_rule_select(op, redo, ctx); } /* Entity is traversable, which means it could have a subtree */ flecs_rule_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->index = -1; return flecs_rule_trav_unknown_src_reflexive( op, ctx, trav, second); } } else { if (!trav_ctx->cache.id) { /* No traversal cache, which means this is a regular select */ return flecs_rule_select(op, redo, ctx); } } if (trav_ctx->index == -1) { redo = false; /* First result after handling reflexive relationship */ trav_ctx->index = 0; } /* Forward to select */ int32_t count = ecs_vec_count(&trav_ctx->cache.entities); ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); for (; trav_ctx->index < count; trav_ctx->index ++) { ecs_trav_elem_t *el = &elems[trav_ctx->index]; trav_ctx->and.idr = el->idr; /* prevents lookup by select */ if (flecs_rule_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), (EcsTableIsPrefab|EcsTableIsDisabled))) { return true; } redo = false; } return false; } static bool flecs_rule_trav_yield_reflexive_src( const ecs_rule_op_t *op, const ecs_rule_run_ctx_t *ctx, ecs_table_range_t *range, ecs_entity_t trav) { ecs_var_t *vars = ctx->vars; ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); int32_t offset = trav_ctx->offset, count = trav_ctx->count; bool src_is_var = op->flags & (EcsRuleIsVar << EcsRuleSrc); if (trav_ctx->index >= (offset + count)) { /* Restore previous offset, count */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; vars[src_var].range.offset = offset; vars[src_var].range.count = count; vars[src_var].entity = 0; } return false; } ecs_entity_t entity = ecs_vec_get_t( &range->table->data.entities, ecs_entity_t, trav_ctx->index)[0]; flecs_rule_set_trav_match(op, -1, trav, entity, ctx); /* Hijack existing variable to return one result at a time */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; ecs_table_t *table = vars[src_var].range.table; ecs_assert(!table || table == ecs_get_table(ctx->world, entity), ECS_INTERNAL_ERROR, NULL); (void)table; vars[src_var].entity = entity; vars[src_var].range = flecs_range_from_entity(entity, ctx); } return true; } static bool flecs_rule_trav_fixed_src_up_unknown_second( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); ecs_flags16_t f_src = flecs_rule_ref_flags(op->flags, EcsRuleSrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; ecs_rule_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { flecs_rule_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->yield_reflexive = true; trav_ctx->index = range.offset; trav_ctx->offset = range.offset; trav_ctx->count = range.count ? range.count : ecs_table_count(table); } } else { trav_ctx->index ++; } if (trav_ctx->yield_reflexive) { if (flecs_rule_trav_yield_reflexive_src(op, ctx, &range, trav)) { return true; } trav_ctx->yield_reflexive = false; trav_ctx->index = 0; } if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { return false; } ecs_trav_elem_t *el = ecs_vec_get_t( &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); flecs_rule_set_trav_match(op, el->column, trav, el->entity, ctx); return true; } static bool flecs_rule_trav( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (!flecs_ref_is_written(op, &op->src, EcsRuleSrc, written)) { if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { /* This can't happen, src or second should have been resolved */ ecs_abort(ECS_INTERNAL_ERROR, "invalid instruction sequence: unconstrained traversal"); } else { return flecs_rule_trav_unknown_src_up_fixed_second(op, redo, ctx); } } else { if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written)) { return flecs_rule_trav_fixed_src_up_unknown_second(op, redo, ctx); } else { return flecs_rule_trav_fixed_src_up_fixed_second(op, redo, ctx); } } } static bool flecs_rule_ids( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } ecs_id_record_t *cur; ecs_id_t id = flecs_rule_op_get_id(op, ctx); { cur = flecs_id_record_get(ctx->world, id); if (!cur || !cur->cache.tables.count) { return false; } } flecs_rule_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; it->columns[op->field_index] = -1; /* Mark field as set */ } return true; } static bool flecs_rule_idsright( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_id_record_t *cur; if (!redo) { ecs_id_t id = flecs_rule_op_get_id(op, ctx); if (!ecs_id_is_wildcard(id)) { /* If id is not a wildcard, we can directly return it. This can * happen if a variable was constrained by an iterator. */ op_ctx->cur = NULL; flecs_rule_set_vars(op, id, ctx); return true; } cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); if (!cur) { return false; } } else { if (!op_ctx->cur) { return false; } } do { cur = op_ctx->cur = op_ctx->cur->first.next; } while (cur && !cur->cache.tables.count); /* Skip empty ids */ if (!cur) { return false; } flecs_rule_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_rule_op_get_id_w_written(op, op->written, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; it->columns[op->field_index] = -1; /* Mark field as set */ } return true; } static bool flecs_rule_idsleft( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_id_record_t *cur; if (!redo) { ecs_id_t id = flecs_rule_op_get_id(op, ctx); if (!ecs_id_is_wildcard(id)) { /* If id is not a wildcard, we can directly return it. This can * happen if a variable was constrained by an iterator. */ op_ctx->cur = NULL; flecs_rule_set_vars(op, id, ctx); return true; } cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); if (!cur) { return false; } } else { if (!op_ctx->cur) { return false; } } do { cur = op_ctx->cur = op_ctx->cur->second.next; } while (cur && !cur->cache.tables.count); /* Skip empty ids */ if (!cur) { return false; } flecs_rule_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_rule_op_get_id_w_written(op, op->written, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; it->columns[op->field_index] = -1; /* Mark field as set */ } return true; } static bool flecs_rule_each( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { ecs_rule_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); int32_t row; ecs_table_range_t range = flecs_rule_var_get_range(op->first.var, ctx); ecs_table_t *table = range.table; if (!table) { return false; } if (!redo) { row = op_ctx->row = range.offset; } else { int32_t end = range.count; if (end) { end += range.offset; } else { end = table->data.entities.count; } row = ++ op_ctx->row; if (op_ctx->row >= end) { return false; } } ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = table->data.entities.array; ecs_entity_t e; do { if (row == ecs_table_count(table)) { return false; } e = entities[row ++]; ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); /* Exclude entities that are used as markers by rule engine */ } while ((e == EcsWildcard) || (e == EcsAny) || (e == EcsThis) || (e == EcsVariable)); flecs_rule_var_set_entity(op, op->src.var, e, ctx); return true; } static bool flecs_rule_store( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { if (!redo) { flecs_rule_var_set_entity(op, op->src.var, op->first.entity, ctx); return true; } else { return false; } } static bool flecs_rule_reset( const ecs_rule_op_t *op, bool redo, const ecs_rule_run_ctx_t *ctx) { if (!redo) { return true; } else { flecs_rule_var_reset(op->src.var, ctx); return false; } } static void flecs_rule_reset_after_block( const ecs_rule_op_t *start_op, ecs_rule_run_ctx_t *ctx, ecs_rule_ctrl_ctx_t *op_ctx) { ecs_rule_lbl_t op_index = start_op->next; const ecs_rule_op_t *op = &ctx->rit->ops[op_index]; ctx->written[op_index] = ctx->written[ctx->op_index]; ctx->op_index = op_index; int32_t field = op->field_index; if (field == -1) { goto done; } ecs_iter_t *it = ctx->it; /* Not terms return no data */ it->columns[field] = 0; /* Ignore variables written by Not operation */ uint64_t *written = ctx->written; uint64_t written_cur = written[op->prev + 1]; ecs_flags16_t flags_1st = flecs_rule_ref_flags(op->flags, EcsRuleFirst); ecs_flags16_t flags_2nd = flecs_rule_ref_flags(op->flags, EcsRuleSecond); /* Overwrite id with cleared out variables */ ecs_id_t id = flecs_rule_op_get_id(op, ctx); if (id) { it->ids[field] = id; } /* Reset variables */ if (flags_1st & EcsRuleIsVar) { if (!flecs_ref_is_written(op, &op->first, EcsRuleFirst, written_cur)){ flecs_rule_var_reset(op->first.var, ctx); } } if (flags_2nd & EcsRuleIsVar) { if (!flecs_ref_is_written(op, &op->second, EcsRuleSecond, written_cur)){ flecs_rule_var_reset(op->second.var, ctx); } } /* If term has entity src, set it because no other instruction might */ if (op->flags & (EcsRuleIsEntity << EcsRuleSrc)) { it->sources[field] = op->src.entity; } done: op_ctx->op_index = op_index; } static const char* flecs_rule_name_arg( const ecs_rule_op_t *op, ecs_rule_run_ctx_t *ctx) { int8_t term_index = op->term_index; ecs_term_t *term = &ctx->rule->filter.terms[term_index]; return term->second.name; } static bool flecs_rule_compare_range( const ecs_table_range_t *l, const ecs_table_range_t *r) { if (l->table != r->table) { return false; } if (l->count) { int32_t l_end = l->offset + l->count; int32_t r_end = r->offset + r->count; if (r->offset < l->offset) { return false; } if (r_end > l_end) { return false; } } else { /* Entire table is matched */ } return true; } static bool flecs_rule_pred_eq_w_range( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx, ecs_table_range_t r) { if (redo) { return false; } uint64_t written = ctx->written[ctx->op_index]; ecs_var_id_t first_var = op->first.var; if (!(written & (1ull << first_var))) { /* left = unknown, right = known. Assign right-hand value to left */ ecs_var_id_t l = first_var; ctx->vars[l].range = r; if (r.count == 1) { ctx->vars[l].entity = ecs_vec_get_t(&r.table->data.entities, ecs_entity_t, r.offset)[0]; } return true; } else { ecs_table_range_t l = flecs_rule_get_range( op, &op->first, EcsRuleFirst, ctx); if (!flecs_rule_compare_range(&l, &r)) { return false; } ctx->vars[first_var].range.offset = r.offset; ctx->vars[first_var].range.count = r.count; return true; } } static bool flecs_rule_pred_eq( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized eq operand"); ecs_table_range_t r = flecs_rule_get_range( op, &op->second, EcsRuleSecond, ctx); return flecs_rule_pred_eq_w_range(op, redo, ctx, r); } static bool flecs_rule_pred_eq_name( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { const char *name = flecs_rule_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return false; } ecs_table_range_t r = flecs_range_from_entity(e, ctx); return flecs_rule_pred_eq_w_range(op, redo, ctx, r); } static bool flecs_rule_pred_neq_w_range( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx, ecs_table_range_t r) { ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); ecs_var_id_t first_var = op->first.var; ecs_table_range_t l = flecs_rule_get_range( op, &op->first, EcsRuleFirst, ctx); /* If tables don't match, neq always returns once */ if (l.table != r.table) { return true && !redo; } int32_t l_offset; int32_t l_count; if (!redo) { /* Make sure we're working with the correct table count */ if (!l.count && l.table) { l.count = ecs_table_count(l.table); } l_offset = l.offset; l_count = l.count; /* Cache old value */ op_ctx->range = l; } else { l_offset = op_ctx->range.offset; l_count = op_ctx->range.count; } /* If the table matches, a Neq returns twice: once for the slice before the * excluded slice, once for the slice after the excluded slice. If the right * hand range starts & overlaps with the left hand range, there is only * one slice. */ ecs_var_t *var = &ctx->vars[first_var]; if (!redo && r.offset > l_offset) { int32_t end = r.offset; if (end > l_count) { end = l_count; } /* Return first slice */ var->range.table = l.table; var->range.offset = l_offset; var->range.count = end - l_offset; op_ctx->redo = false; return true; } else if (!op_ctx->redo) { int32_t l_end = op_ctx->range.offset + l_count; int32_t r_end = r.offset + r.count; if (l_end <= r_end) { /* If end of existing range falls inside the excluded range, there's * nothing more to return */ var->range = l; return false; } /* Return second slice */ var->range.table = l.table; var->range.offset = r_end; var->range.count = l_end - r_end; /* Flag so we know we're done the next redo */ op_ctx->redo = true; return true; } else { /* Restore previous value */ var->range = l; return false; } } static bool flecs_rule_pred_match( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx, bool is_neq) { ecs_rule_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); uint64_t written = ctx->written[ctx->op_index]; ecs_assert(flecs_ref_is_written(op, &op->first, EcsRuleFirst, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized match operand"); (void)written; ecs_var_id_t first_var = op->first.var; const char *match = flecs_rule_name_arg(op, ctx); ecs_table_range_t l; if (!redo) { l = flecs_rule_get_range(op, &op->first, EcsRuleFirst, ctx); if (!l.table) { return false; } if (!l.count) { l.count = ecs_table_count(l.table); } op_ctx->range = l; op_ctx->index = l.offset; op_ctx->name_col = flecs_ito(int16_t, ecs_table_get_type_index(ctx->world, l.table, ecs_pair(ecs_id(EcsIdentifier), EcsName))); if (op_ctx->name_col == -1) { return is_neq; } op_ctx->name_col = flecs_ito(int16_t, l.table->column_map[op_ctx->name_col]); ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); } else { if (op_ctx->name_col == -1) { /* Table has no name */ return false; } l = op_ctx->range; } const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data.array; int32_t count = l.offset + l.count, offset = -1; for (; op_ctx->index < count; op_ctx->index ++) { const char *name = names[op_ctx->index].value; bool result = strstr(name, match); if (is_neq) { result = !result; } if (!result) { if (offset != -1) { break; } } else { if (offset == -1) { offset = op_ctx->index; } } } if (offset == -1) { ctx->vars[first_var].range = op_ctx->range; return false; } ctx->vars[first_var].range.offset = offset; ctx->vars[first_var].range.count = (op_ctx->index - offset); return true; } static bool flecs_rule_pred_eq_match( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { return flecs_rule_pred_match(op, redo, ctx, false); } static bool flecs_rule_pred_neq_match( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { return flecs_rule_pred_match(op, redo, ctx, true); } static bool flecs_rule_pred_neq( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsRuleSecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized neq operand"); ecs_table_range_t r = flecs_rule_get_range( op, &op->second, EcsRuleSecond, ctx); return flecs_rule_pred_neq_w_range(op, redo, ctx, r); } static bool flecs_rule_pred_neq_name( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { const char *name = flecs_rule_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return true && !redo; } ecs_table_range_t r = flecs_range_from_entity(e, ctx); return flecs_rule_pred_neq_w_range(op, redo, ctx, r); } static bool flecs_rule_lookup( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } const ecs_rule_t *rule = ctx->rule; ecs_entity_t first = flecs_rule_var_get_entity(op->first.var, ctx); ecs_rule_var_t *var = &rule->vars[op->src.var]; ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, NULL, NULL, false); if (!result) { flecs_rule_var_set_entity(op, op->src.var, EcsWildcard, ctx); return false; } flecs_rule_var_set_entity(op, op->src.var, result, ctx); return true; } static bool flecs_rule_setvars( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { (void)op; const ecs_rule_t *rule = ctx->rule; const ecs_filter_t *filter = &rule->filter; ecs_var_id_t *src_vars = rule->src_vars; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; ecs_flags32_t source_set = *ctx->source_set; for (i = 0; i < filter->field_count; i ++) { ecs_var_id_t var_id = src_vars[i]; if (!var_id) { continue; } if (source_set & (1u << i)) { continue; } it->sources[i] = flecs_rule_var_get_entity(var_id, ctx); } return true; } static bool flecs_rule_setthis( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { ecs_rule_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); ecs_var_t *vars = ctx->vars; ecs_var_t *this_var = &vars[op->first.var]; if (!redo) { /* Save values so we can restore them later */ op_ctx->range = vars[0].range; /* Constrain This table variable to a single entity from the table */ vars[0].range = flecs_range_from_entity(this_var->entity, ctx); vars[0].entity = this_var->entity; return true; } else { /* Restore previous values, so that instructions that are operating on * the table variable use all the entities in the table. */ vars[0].range = op_ctx->range; vars[0].entity = 0; return false; } } static bool flecs_rule_setfixed( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { (void)op; const ecs_rule_t *rule = ctx->rule; const ecs_filter_t *filter = &rule->filter; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; for (i = 0; i < filter->term_count; i ++) { ecs_term_t *term = &filter->terms[i]; ecs_term_id_t *src = &term->src; if (src->flags & EcsIsEntity) { it->sources[term->field_index] = src->id; } } return true; } static bool flecs_rule_setids( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { (void)op; const ecs_rule_t *rule = ctx->rule; const ecs_filter_t *filter = &rule->filter; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; for (i = 0; i < filter->term_count; i ++) { ecs_term_t *term = &filter->terms[i]; it->ids[term->field_index] = term->id; } return true; } static bool flecs_rule_setid( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); ctx->it->ids[op->field_index] = op->first.entity; return true; } /* Check if entity is stored in table */ static bool flecs_rule_contain( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } ecs_var_id_t src_id = op->src.var; ecs_var_id_t first_id = op->first.var; ecs_table_t *table = flecs_rule_var_get_table(src_id, ctx); ecs_entity_t e = flecs_rule_var_get_entity(first_id, ctx); return table == ecs_get_table(ctx->world, e); } /* Check if first and second id of pair from last operation are the same */ static bool flecs_rule_pair_eq( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } ecs_iter_t *it = ctx->it; ecs_id_t id = it->ids[op->field_index]; return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); } static bool flecs_rule_select_or( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_rule_iter_t *rit = &it->priv.iter.rule; ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); ecs_rule_lbl_t first = flecs_itolbl(ctx->op_index + 1); if (!redo) { op_ctx->op_index = first; } const ecs_rule_op_t *cur = &rit->ops[op_ctx->op_index]; bool result = false; /* Evaluate operations in OR chain one by one. */ do { ctx->op_index = op_ctx->op_index; ctx->written[op_ctx->op_index] = op->written; result = flecs_rule_dispatch(cur, redo, ctx); if (result) { ctx->written[op_ctx->op_index] |= cur->written; /* If a previous operation in the OR chain returned a result for the * same matched source, skip it so we don't yield for each matching * element in the chain. */ ecs_rule_lbl_t prev_index; for (prev_index = first; prev_index < op_ctx->op_index; prev_index ++) { const ecs_rule_op_t *prev = &rit->ops[prev_index]; ctx->op_index = prev_index; ctx->written[prev_index] = ctx->written[op_ctx->op_index]; if (flecs_rule_dispatch(prev, false, ctx)) { break; } } if (prev_index != op_ctx->op_index) { /* Duplicate match was found, redo search */ redo = true; continue; } break; } /* No result was found, go to next operation in chain */ op_ctx->op_index ++; cur = &rit->ops[op_ctx->op_index]; redo = false; } while (cur->kind != EcsRuleEnd); return result; } static bool flecs_rule_run_block( bool redo, ecs_rule_run_ctx_t *ctx, ecs_rule_ctrl_ctx_t *op_ctx) { ecs_iter_t *it = ctx->it; ecs_rule_iter_t *rit = &it->priv.iter.rule; if (!redo) { op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); } else if (ctx->rit->ops[op_ctx->op_index].kind == EcsRuleEnd) { return false; } ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; return flecs_rule_run_until( redo, ctx, rit->ops, ctx->op_index, op_ctx->op_index, EcsRuleEnd); } static bool flecs_rule_with_or( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); bool result = flecs_rule_run_block(redo, ctx, op_ctx); if (result) { /* If a match was found, no need to keep searching for this source */ op_ctx->op_index = op->next; } return result; } static bool flecs_rule_or( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (op->flags & (EcsRuleIsVar << EcsRuleSrc)) { uint64_t written = ctx->written[ctx->op_index]; if (!(written & (1ull << op->src.var))) { return flecs_rule_select_or(op, redo, ctx); } } return flecs_rule_with_or(op, redo, ctx); } static bool flecs_rule_run_block_w_reset( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); bool result = flecs_rule_run_block(redo, ctx, op_ctx); if (!result) { flecs_rule_reset_after_block(op, ctx, op_ctx); } return result; } static bool flecs_rule_not( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (redo) { return false; } return !flecs_rule_run_block_w_reset(op, redo, ctx); } static bool flecs_rule_optional( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { bool result = flecs_rule_run_block_w_reset(op, redo, ctx); if (!redo) { return true; /* Return at least once */ } else { return result; } } static bool flecs_rule_eval_if( const ecs_rule_op_t *op, ecs_rule_run_ctx_t *ctx, const ecs_rule_ref_t *ref, ecs_flags16_t ref_kind) { if (flecs_rule_ref_flags(op->flags, ref_kind) == EcsRuleIsVar) { if (ctx->vars[ref->var].entity == EcsWildcard) { ecs_rule_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); flecs_rule_reset_after_block(op, ctx, op_ctx); return false; } } return true; } static bool flecs_rule_if( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { if (!redo) { if (!flecs_rule_eval_if(op, ctx, &op->src, EcsRuleSrc) || !flecs_rule_eval_if(op, ctx, &op->first, EcsRuleFirst) || !flecs_rule_eval_if(op, ctx, &op->second, EcsRuleSecond)) { return true; } } return flecs_rule_run_block_w_reset(op, redo, ctx); } static bool flecs_rule_end( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { (void)op; (void)ctx; return !redo; } static bool flecs_rule_populate( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { (void)op; if (!redo) { ecs_iter_t *it = ctx->it; if (it->flags & EcsIterNoData) { return true; } ECS_BIT_CLEAR(it->flags, EcsIterHasShared); const ecs_rule_t *rule = ctx->rule; const ecs_filter_t *filter = &rule->filter; int32_t i, field_count = filter->field_count; ecs_flags64_t data_fields = filter->data_fields; ecs_table_range_t *range = &ctx->vars[0].range; ecs_table_t *table = range->table; if (table && !range->count) { range->count = ecs_table_count(table); } for (i = 0; i < field_count; i ++) { if (!(data_fields & (1llu << i))) { continue; } int32_t index = it->columns[i]; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); if (!index) { continue; } ecs_entity_t src = it->sources[i]; if (!src) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (range->count && table->column_map) { int32_t column = table->column_map[index - 1]; if (column != -1) { it->ptrs[i] = ECS_ELEM( table->data.columns[column].data.array, it->sizes[i], range->offset); continue; } } } else { ecs_record_t *r = flecs_entities_get(ctx->world, src); ecs_table_t *src_table = r->table; if (src_table->column_map) { int32_t column = src_table->column_map[index - 1]; if (column != -1) { it->ptrs[i] = ecs_vec_get( &src_table->data.columns[column].data, it->sizes[i], ECS_RECORD_TO_ROW(r->row)); ECS_BIT_SET(it->flags, EcsIterHasShared); continue; } } } } return true; } else { return false; } } static bool flecs_rule_populate_self( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { (void)op; if (!redo) { const ecs_rule_t *rule = ctx->rule; const ecs_filter_t *filter = &rule->filter; int32_t i, field_count = filter->field_count; ecs_flags64_t data_fields = filter->data_fields; ecs_iter_t *it = ctx->it; ecs_table_range_t *range = &ctx->vars[0].range; ecs_table_t *table = range->table; if (!table->column_map) { return true; } if (!ecs_table_count(table)) { return true; } for (i = 0; i < field_count; i ++) { if (!(data_fields & (1llu << i))) { continue; } int32_t index = it->columns[i]; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Only owned */ if (!index) { continue; } int32_t column = table->column_map[index - 1]; if (column != -1) { it->ptrs[i] = ECS_ELEM( table->data.columns[column].data.array, it->sizes[i], range->offset); } } return true; } else { return false; } } static bool flecs_rule_dispatch( const ecs_rule_op_t *op, bool redo, ecs_rule_run_ctx_t *ctx) { switch(op->kind) { case EcsRuleAnd: return flecs_rule_and(op, redo, ctx); case EcsRuleAndId: return flecs_rule_and_id(op, redo, ctx); case EcsRuleAndAny: return flecs_rule_and_any(op, redo, ctx); case EcsRuleTriv: return flecs_rule_triv(op, redo, ctx); case EcsRuleTrivData: return flecs_rule_triv_data(op, redo, ctx); case EcsRuleTrivWildcard: return flecs_rule_triv_wildcard(op, redo, ctx); case EcsRuleSelectAny: return flecs_rule_select_any(op, redo, ctx); case EcsRuleUp: return flecs_rule_up(op, redo, ctx); case EcsRuleUpId: return flecs_rule_up_id(op, redo, ctx); case EcsRuleSelfUp: return flecs_rule_self_up(op, redo, ctx); case EcsRuleSelfUpId: return flecs_rule_self_up_id(op, redo, ctx); case EcsRuleWith: return flecs_rule_with(op, redo, ctx); case EcsRuleTrav: return flecs_rule_trav(op, redo, ctx); case EcsRuleIds: return flecs_rule_ids(op, redo, ctx); case EcsRuleIdsRight: return flecs_rule_idsright(op, redo, ctx); case EcsRuleIdsLeft: return flecs_rule_idsleft(op, redo, ctx); case EcsRuleEach: return flecs_rule_each(op, redo, ctx); case EcsRuleStore: return flecs_rule_store(op, redo, ctx); case EcsRuleReset: return flecs_rule_reset(op, redo, ctx); case EcsRuleOr: return flecs_rule_or(op, redo, ctx); case EcsRuleOptional: return flecs_rule_optional(op, redo, ctx); case EcsRuleIf: return flecs_rule_if(op, redo, ctx); case EcsRuleEnd: return flecs_rule_end(op, redo, ctx); case EcsRuleNot: return flecs_rule_not(op, redo, ctx); case EcsRulePredEq: return flecs_rule_pred_eq(op, redo, ctx); case EcsRulePredNeq: return flecs_rule_pred_neq(op, redo, ctx); case EcsRulePredEqName: return flecs_rule_pred_eq_name(op, redo, ctx); case EcsRulePredNeqName: return flecs_rule_pred_neq_name(op, redo, ctx); case EcsRulePredEqMatch: return flecs_rule_pred_eq_match(op, redo, ctx); case EcsRulePredNeqMatch: return flecs_rule_pred_neq_match(op, redo, ctx); case EcsRuleLookup: return flecs_rule_lookup(op, redo, ctx); case EcsRuleSetVars: return flecs_rule_setvars(op, redo, ctx); case EcsRuleSetThis: return flecs_rule_setthis(op, redo, ctx); case EcsRuleSetFixed: return flecs_rule_setfixed(op, redo, ctx); case EcsRuleSetIds: return flecs_rule_setids(op, redo, ctx); case EcsRuleSetId: return flecs_rule_setid(op, redo, ctx); case EcsRuleContain: return flecs_rule_contain(op, redo, ctx); case EcsRulePairEq: return flecs_rule_pair_eq(op, redo, ctx); case EcsRulePopulate: return flecs_rule_populate(op, redo, ctx); case EcsRulePopulateSelf: return flecs_rule_populate_self(op, redo, ctx); case EcsRuleYield: return false; case EcsRuleNothing: return false; } return false; } static bool flecs_rule_run_until( bool redo, ecs_rule_run_ctx_t *ctx, const ecs_rule_op_t *ops, ecs_rule_lbl_t first, ecs_rule_lbl_t cur, ecs_rule_op_kind_t until) { ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); ctx->op_index = cur; const ecs_rule_op_t *op = &ops[ctx->op_index]; ecs_assert(op->kind != until, ECS_INTERNAL_ERROR, NULL); do { #ifdef FLECS_DEBUG ctx->rit->profile[ctx->op_index].count[redo] ++; #endif bool result = flecs_rule_dispatch(op, redo, ctx); cur = (&op->prev)[result]; redo = cur < ctx->op_index; if (!redo) { ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; } ctx->op_index = cur; op = &ops[ctx->op_index]; if (cur <= first) { return false; } if (op->kind == until) { return true; } } while (true); } static void flecs_rule_iter_init( ecs_rule_run_ctx_t *ctx) { ecs_assert(ctx->written != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_t *it = ctx->it; const ecs_rule_t *rule = ctx->rule; ecs_flags64_t it_written = it->constrained_vars; ctx->written[0] = it_written; if (it_written && ctx->rule->src_vars) { /* If variables were constrained, check if there are any table * variables that have a constrained entity variable. */ ecs_var_t *vars = ctx->vars; int32_t i, count = rule->filter.field_count; for (i = 0; i < count; i ++) { ecs_var_id_t var_id = rule->src_vars[i]; ecs_rule_var_t *var = &rule->vars[var_id]; if (!(it_written & (1ull << var_id)) || (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) { continue; } /* Initialize table variable with constrained entity variable */ ecs_var_t *tvar = &vars[var->table_id]; tvar->range = flecs_range_from_entity(vars[var_id].entity, ctx); ctx->written[0] |= (1ull << var->table_id); /* Mark as written */ } } ecs_flags32_t flags = rule->filter.flags; if (flags & EcsFilterIsTrivial) { if ((flags & EcsFilterMatchOnlySelf) || !flecs_table_cache_count(&ctx->world->idr_isa_wildcard->cache)) { if (it_written) { it->offset = ctx->vars[0].range.offset; it->count = ctx->vars[0].range.count; if (!it->count) { ecs_assert(!it->offset, ECS_INVALID_PARAMETER, NULL); it->count = ecs_table_count(ctx->vars[0].range.table); } it->flags |= EcsIterTrivialTest; flecs_rule_setids(&rule->ops[0], false, ctx); } else { if (flags & EcsFilterHasWildcards) { it->flags |= EcsIterTrivialSearchWildcard; flecs_rule_setids(&rule->ops[0], false, ctx); } else if (flags & EcsFilterNoData) { it->flags |= EcsIterTrivialSearchNoData; flecs_rule_setids(&rule->ops[0], false, ctx); } else { it->flags |= EcsIterTrivialSearch; flecs_rule_setids(&rule->ops[0], false, ctx); } } } } flecs_iter_validate(it); } bool ecs_rule_next_instanced( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); ecs_rule_iter_t *rit = &it->priv.iter.rule; ecs_rule_run_ctx_t ctx; ctx.world = it->real_world; ctx.rule = rit->rule; ctx.it = it; ctx.vars = rit->vars; ctx.rule_vars = rit->rule_vars; ctx.written = rit->written; ctx.op_ctx = rit->op_ctx; ctx.source_set = &rit->source_set; ctx.rit = rit; const ecs_rule_op_t *ops = rit->ops; bool redo = true; if (!(it->flags & EcsIterIsValid)) { ecs_assert(ctx.rule != NULL, ECS_INVALID_PARAMETER, NULL); flecs_rule_iter_init(&ctx); redo = false; } else { it->frame_offset += it->count; } /* Specialized iterator modes for trivial queries */ if (it->flags & EcsIterTrivialSearch) { ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; int32_t fields = ctx.rule->filter.term_count; if (!flecs_rule_trivial_search(ctx.rule, &ctx, op_ctx, !redo, fields)) { goto done; } it->table = ctx.vars[0].range.table; it->count = ecs_table_count(it->table); it->entities = flecs_table_entities_array(it->table); return true; } else if (it->flags & EcsIterTrivialSearchNoData) { ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; int32_t fields = ctx.rule->filter.term_count; if (!flecs_rule_trivial_search_nodata(ctx.rule, &ctx, op_ctx, !redo, fields)) { goto done; } it->table = ctx.vars[0].range.table; it->count = ecs_table_count(it->table); it->entities = flecs_table_entities_array(it->table); return true; } else if (it->flags & EcsIterTrivialTest) { int32_t fields = ctx.rule->filter.term_count; if (!flecs_rule_trivial_test(ctx.rule, &ctx, !redo, fields)) { goto done; } return true; } else if (it->flags & EcsIterTrivialSearchWildcard) { ecs_rule_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; int32_t fields = ctx.rule->filter.term_count; if (!flecs_rule_trivial_search_w_wildcards(ctx.rule, &ctx, op_ctx, !redo, fields)) { goto done; } it->table = ctx.vars[0].range.table; it->count = ecs_table_count(it->table); it->entities = flecs_table_entities_array(it->table); return true; } /* Default iterator mode */ if (flecs_rule_run_until(redo, &ctx, ops, -1, rit->op, EcsRuleYield)) { ecs_assert(ops[ctx.op_index].kind == EcsRuleYield, ECS_INTERNAL_ERROR, NULL); ecs_table_range_t *range = &ctx.vars[0].range; ecs_table_t *table = range->table; int32_t count = range->count; if (table) { if (!count) { count = ecs_table_count(table); } it->table = table; it->offset = range->offset; it->count = count; it->entities = ECS_ELEM_T( table->data.entities.array, ecs_entity_t, it->offset); } else if (count == 1) { it->count = 1; it->entities = &ctx.vars[0].entity; } rit->op = flecs_itolbl(ctx.op_index - 1); return true; } done: ecs_iter_fini(it); return false; } bool ecs_rule_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_rule_next_instanced(it)); error: return false; } static void flecs_rule_iter_fini_ctx( ecs_iter_t *it, ecs_rule_iter_t *rit) { const ecs_rule_t *rule = rit->rule; int32_t i, count = rule->op_count; ecs_rule_op_t *ops = rule->ops; ecs_rule_op_ctx_t *ctx = rit->op_ctx; ecs_allocator_t *a = flecs_rule_get_allocator(it); for (i = 0; i < count; i ++) { ecs_rule_op_t *op = &ops[i]; switch(op->kind) { case EcsRuleTrav: flecs_rule_trav_cache_fini(a, &ctx[i].is.trav.cache); break; case EcsRuleUp: case EcsRuleSelfUp: case EcsRuleUpId: case EcsRuleSelfUpId: { ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; if (cache->dir == EcsDown) { flecs_rule_down_cache_fini(a, cache); } else { flecs_rule_up_cache_fini(cache); } break; } default: break; } } } static void flecs_rule_iter_fini( ecs_iter_t *it) { ecs_rule_iter_t *rit = &it->priv.iter.rule; ecs_assert(rit->rule != NULL, ECS_INVALID_OPERATION, NULL); ecs_poly_assert(rit->rule, ecs_rule_t); int32_t op_count = rit->rule->op_count; int32_t var_count = rit->rule->var_count; #ifdef FLECS_DEBUG if (it->flags & EcsIterProfile) { char *str = ecs_rule_str_w_profile(rit->rule, it); printf("%s\n", str); ecs_os_free(str); } flecs_iter_free_n(rit->profile, ecs_rule_op_profile_t, op_count); #endif flecs_rule_iter_fini_ctx(it, rit); flecs_iter_free_n(rit->vars, ecs_var_t, var_count); flecs_iter_free_n(rit->written, ecs_write_flags_t, op_count); flecs_iter_free_n(rit->op_ctx, ecs_rule_op_ctx_t, op_count); rit->vars = NULL; rit->written = NULL; rit->op_ctx = NULL; rit->rule = NULL; } ecs_iter_t ecs_rule_iter( const ecs_world_t *world, const ecs_rule_t *rule) { ecs_iter_t it = {0}; ecs_rule_iter_t *rit = &it.priv.iter.rule; ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); // Ok, only for stats ECS_CONST_CAST(ecs_rule_t*, rule)->filter.eval_count ++; ecs_run_aperiodic(rule->filter.world, EcsAperiodicEmptyTables); int32_t i, var_count = rule->var_count, op_count = rule->op_count; it.world = ECS_CONST_CAST(ecs_world_t*, world); it.real_world = rule->filter.world; it.query = &rule->filter; it.terms = rule->filter.terms; it.next = ecs_rule_next; it.fini = flecs_rule_iter_fini; it.field_count = rule->filter.field_count; it.sizes = rule->filter.sizes; it.system = rule->filter.entity; flecs_filter_apply_iter_flags(&it, &rule->filter); flecs_iter_init(world, &it, flecs_iter_cache_ids | flecs_iter_cache_columns | flecs_iter_cache_sources | flecs_iter_cache_ptrs); rit->rule = rule; rit->rule_vars = rule->vars; rit->ops = rule->ops; rit->source_set = 0; if (var_count) { rit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } if (op_count) { rit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); rit->op_ctx = flecs_iter_calloc_n(&it, ecs_rule_op_ctx_t, op_count); } #ifdef FLECS_DEBUG rit->profile = flecs_iter_calloc_n(&it, ecs_rule_op_profile_t, op_count); #endif for (i = 1; i < var_count; i ++) { rit->vars[i].entity = EcsWildcard; } it.variables = rit->vars; it.variable_count = rule->var_pub_count; it.variable_names = rule->var_names; error: return it; } #endif /** * @file addons/rules/trav_cache.c * @brief Cache that stores the result of graph traversal. */ #ifdef FLECS_RULES static void flecs_rule_build_down_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_rule_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); if (!idr) { return; } ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); elem->entity = entity; elem->idr = idr; ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->row & EcsEntityIsTraversable) { flecs_rule_build_down_cache( world, a, ctx, cache, trav, entities[i]); } } } } } static void flecs_rule_build_up_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_rule_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table, const ecs_table_record_t *tr, int32_t root_column) { ecs_id_t *ids = table->type.array; int32_t i = tr->index, end = i + tr->count; bool is_root = root_column == -1; for (; i < end; i ++) { ecs_entity_t second = ecs_pair_second(world, ids[i]); if (is_root) { root_column = i; } ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); el->entity = second; el->column = root_column; el->idr = NULL; ecs_record_t *r = flecs_entities_get_any(world, second); if (r->table) { ecs_table_record_t *r_tr = flecs_id_record_get_table( cache->idr, r->table); if (!r_tr) { return; } flecs_rule_build_up_cache(world, a, ctx, cache, trav, r->table, r_tr, root_column); } } } void flecs_rule_trav_cache_fini( ecs_allocator_t *a, ecs_trav_cache_t *cache) { ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); } void flecs_rule_get_trav_down_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity) { if (cache->id != ecs_pair(trav, entity) || cache->up) { ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); flecs_rule_build_down_cache(world, a, ctx, cache, trav, entity); cache->id = ecs_pair(trav, entity); cache->up = false; } } void flecs_rule_get_trav_up_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); ecs_id_record_t *idr = cache->idr; if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { idr = cache->idr = flecs_id_record_get(world, ecs_pair(trav, EcsWildcard)); if (!idr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } ecs_id_t id = table->type.array[tr->index]; if (cache->id != id || !cache->up) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); flecs_rule_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); cache->id = id; cache->up = true; } } #endif #ifdef FLECS_RULES static void flecs_trav_entity_down_isa( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_with, bool self); static ecs_trav_down_t* flecs_trav_entity_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_trav, ecs_id_record_t *idr_with, bool self); static ecs_trav_down_t* flecs_trav_down_ensure( const ecs_rule_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t entity) { ecs_trav_down_t **trav = ecs_map_ensure_ref( &cache->src, ecs_trav_down_t, entity); if (!trav[0]) { trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); } return trav[0]; } static ecs_trav_down_t* flecs_trav_table_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, const ecs_table_t *table, ecs_id_record_t *idr_with, bool self) { ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); if (!table->_->traversable_count) { return dst; } ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t entity = entities[i]; ecs_record_t *record = flecs_entities_get(world, entity); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, entity)); if (!idr_trav) { continue; } flecs_trav_entity_down(world, a, cache, dst, trav, entity, idr_trav, idr_with, self); } } return dst; } static void flecs_trav_entity_down_isa( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_with, bool self) { if (trav == EcsIsA || !world->idr_isa_wildcard) { return; } ecs_id_record_t *idr_isa = flecs_id_record_get( world, ecs_pair(EcsIsA, entity)); if (!idr_isa) { return; } ecs_table_cache_iter_t it; if (flecs_table_cache_all_iter(&idr_isa->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->_->traversable_count) { continue; } ecs_entity_t *entities = ecs_vec_first(&table->data.entities); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *record = flecs_entities_get(world, e); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); flecs_trav_entity_down(world, a, cache, dst, trav, e, idr_trav, idr_with, self); } } } } } static ecs_trav_down_t* flecs_trav_entity_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_trav, ecs_id_record_t *idr_with, bool self) { ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); flecs_trav_entity_down_isa( world, a, cache, dst, trav, entity, idr_with, self); int32_t first = ecs_vec_count(&dst->elems); ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr_trav->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = tr->hdr.table; bool leaf = false; if (flecs_id_record_get_table(idr_with, table) != NULL) { if (self) { continue; } leaf = true; } /* If record is not the first instance of (trav, *), don't add it * to the cache. */ int32_t index = tr->index; if (index) { ecs_id_t id = table->type.array[index - 1]; if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { int32_t col = ecs_search_relation(world, table, 0, idr_with->id, trav, EcsUp, NULL, NULL, &tr); ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); if (col != index) { /* First relationship through which the id is * reachable is not the current one, so skip. */ continue; } } } ecs_trav_down_elem_t *elem = ecs_vec_append_t( a, &dst->elems, ecs_trav_down_elem_t); elem->table = table; elem->leaf = leaf; } } /* Breadth first walk */ int32_t t, last = ecs_vec_count(&dst->elems); for (t = first; t < last; t ++) { ecs_trav_down_elem_t *elem = ecs_vec_get_t( &dst->elems, ecs_trav_down_elem_t, t); if (!elem->leaf) { flecs_trav_table_down(world, a, cache, dst, trav, elem->table, idr_with, self); } } return dst; } ecs_trav_down_t* flecs_rule_get_down_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t e, ecs_id_record_t *idr_with, bool self) { ecs_world_t *world = ctx->it->real_world; ecs_assert(!(cache->dir & EcsUp), ECS_INTERNAL_ERROR, NULL); cache->dir = EcsDown; ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); ecs_map_init_if(&cache->src, a); ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); if (result->ready) { return result; } ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); if (!idr_trav) { if (trav != EcsIsA) { flecs_trav_entity_down_isa( world, a, cache, result, trav, e, idr_with, self); } result->ready = true; return result; } ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); flecs_trav_entity_down( world, a, cache, result, trav, e, idr_trav, idr_with, self); result->ready = true; return result; } void flecs_rule_down_cache_fini( ecs_allocator_t *a, ecs_trav_up_cache_t *cache) { ecs_map_iter_t it = ecs_map_iter(&cache->src); while (ecs_map_next(&it)) { ecs_trav_down_t *t = ecs_map_ptr(&it); ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); } ecs_map_fini(&cache->src); } #endif #ifdef FLECS_RULES static ecs_trav_up_t* flecs_trav_up_ensure( const ecs_rule_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, uint64_t table_id) { ecs_trav_up_t **trav = ecs_map_ensure_ref( &cache->src, ecs_trav_up_t, table_id); if (!trav[0]) { trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); } return trav[0]; } static int32_t flecs_trav_type_search( ecs_trav_up_t *up, const ecs_table_t *table, ecs_id_record_t *idr_with, ecs_type_t *type) { ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); if (tr) { up->id = type->array[tr->index]; return up->column = tr->index; } return -1; } static int32_t flecs_trav_type_offset_search( ecs_trav_up_t *up, int32_t offset, ecs_id_t with, ecs_type_t *type) { ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); while (offset < type->count) { ecs_id_t type_id = type->array[offset ++]; if (ecs_id_match(type_id, with)) { up->id = flecs_to_public_id(type_id); return up->column = offset - 1; } } return -1; } static ecs_trav_up_t* flecs_trav_table_up( const ecs_rule_run_ctx_t *ctx, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, const ecs_world_t *world, ecs_entity_t src, ecs_id_t with, ecs_id_t rel, ecs_id_record_t *idr_with, ecs_id_record_t *idr_trav) { ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); if (up->ready) { return up; } ecs_record_t *src_record = flecs_entities_get_any(world, src); ecs_table_t *table = src_record->table; if (!table) { goto not_found; } ecs_type_t type = table->type; if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { up->src = src; goto found; } ecs_flags32_t flags = table->flags; if ((flags & EcsTableHasPairs) && rel) { bool is_a = idr_trav == world->idr_isa_wildcard; if (is_a) { if (!(flags & EcsTableHasIsA)) { goto not_found; } if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { goto not_found; } } ecs_trav_up_t up_pair = {0}; int32_t r_column = flecs_trav_type_search( &up_pair, table, idr_trav, &type); while (r_column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, tgt, with, rel, idr_with, idr_trav); if (up_parent->column != -1) { up->src = up_parent->src; up->column = up_parent->column; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, r_column + 1, rel, &type); } if (!is_a) { idr_trav = world->idr_isa_wildcard; r_column = flecs_trav_type_search( &up_pair, table, idr_trav, &type); while (r_column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, tgt, with, rel, idr_with, idr_trav); if (up_parent->column != -1) { up->src = up_parent->src; up->column = up_parent->column; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, r_column + 1, rel, &type); } } } not_found: up->column = -1; found: up->ready = true; return up; } ecs_trav_up_t* flecs_rule_get_up_cache( const ecs_rule_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_table_t *table, ecs_id_t with, ecs_entity_t trav, ecs_id_record_t *idr_with, ecs_id_record_t *idr_trav) { if (cache->with && cache->with != with) { flecs_rule_up_cache_fini(cache); } ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_rule_get_allocator(ctx->it); ecs_map_init_if(&cache->src, a); ecs_assert(!(cache->dir & EcsDown), ECS_INTERNAL_ERROR, NULL); cache->dir = EcsUp; cache->with = with; ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); if (!tr) { return NULL; /* Table doesn't have the relationship */ } int32_t i = tr->index, end = i + tr->count; for (; i < end; i ++) { ecs_id_t id = table->type.array[i]; ecs_entity_t tgt = ECS_PAIR_SECOND(id); ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); if (result->src != 0) { return result; } } return NULL; } void flecs_rule_up_cache_fini( ecs_trav_up_cache_t *cache) { ecs_map_fini(&cache->src); } #endif /** * @file addons/rules/engine.c * @brief Iterator for trivial queries. */ #ifdef FLECS_RULES static bool flecs_rule_trivial_init( ecs_world_t *world, const ecs_filter_t *filter) { int32_t t, count = filter->term_count; ecs_term_t *terms = filter->terms; for (t = 0; t < count; t ++) { ecs_term_t *term = &terms[t]; if (!term->idr) { term->idr = flecs_id_record_get(world, term->id); if (!term->idr) { /* Id doesn't exist, so query can't match */ return false; } } } return true; } bool flecs_rule_trivial_test( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, bool first, int32_t term_count) { if (first) { const ecs_filter_t *filter = &rule->filter; int32_t t; ecs_term_t *terms = filter->terms; ecs_iter_t *it = ctx->it; if (!flecs_rule_trivial_init(ctx->world, filter)) { return false; } ecs_table_t *table = ctx->vars[0].range.table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, NULL); for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; const ecs_table_record_t *tr = flecs_id_record_get_table( term->idr, table); if (!tr) { return false; } it->columns[t] = tr->index + 1; if (it->count && tr->column != -1) { it->ptrs[t] = ecs_vec_get( &table->data.columns[tr->column].data, it->sizes[t], it->offset); } } it->table = table; it->entities = &flecs_table_entities_array(table)[it->offset]; return true; } else { return false; } } static bool flecs_rule_trivial_search_init( const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, const ecs_filter_t *filter, bool first) { if (first) { ecs_term_t *terms = filter->terms; if (!flecs_rule_trivial_init(ctx->world, filter)) { return false; } if (filter->flags & EcsFilterMatchEmptyTables) { if (!flecs_table_cache_all_iter(&terms[0].idr->cache, &op_ctx->it)){ return false; } } else { if (!flecs_table_cache_iter(&terms[0].idr->cache, &op_ctx->it)) { return false; } } } return true; } bool flecs_rule_trivial_search( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, bool first, int32_t term_count) { const ecs_filter_t *filter = &rule->filter; int32_t t; ecs_term_t *terms = filter->terms; ecs_iter_t *it = ctx->it; if (!flecs_rule_trivial_search_init(ctx, op_ctx, filter, first)) { return false; } do { const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableIsPrefab|EcsTableIsDisabled)) { continue; } for (t = 1; t < term_count; t ++) { ecs_term_t *term = &terms[t]; const ecs_table_record_t *tr_with = flecs_id_record_get_table( term->idr, table); if (!tr_with) { break; } it->columns[t] = tr_with->index + 1; if (tr_with->column != -1) { it->ptrs[t] = ecs_vec_first( &table->data.columns[tr_with->column].data); } } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; it->columns[0] = tr->index + 1; if (tr->column != -1) { it->ptrs[0] = ecs_vec_first( &table->data.columns[tr->column].data); } break; } } while (true); return true; } bool flecs_rule_trivial_search_w_wildcards( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, bool first, int32_t term_count) { bool result = flecs_rule_trivial_search( rule, ctx, op_ctx, first, term_count); if (result) { ecs_iter_t *it = ctx->it; ecs_table_t *table = ctx->vars[0].range.table; int32_t t; for (t = 0; t < term_count; t ++) { it->ids[t] = table->type.array[it->columns[t] - 1]; } } return result; } bool flecs_rule_trivial_test_w_wildcards( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, bool first, int32_t term_count) { bool result = flecs_rule_trivial_test( rule, ctx, first, term_count); if (result) { ecs_iter_t *it = ctx->it; ecs_table_t *table = ctx->vars[0].range.table; int32_t t; for (t = 0; t < term_count; t ++) { it->ids[t] = table->type.array[it->columns[t] - 1]; } } return result; } bool flecs_rule_trivial_search_nodata( const ecs_rule_t *rule, const ecs_rule_run_ctx_t *ctx, ecs_rule_trivial_ctx_t *op_ctx, bool first, int32_t term_count) { const ecs_filter_t *filter = &rule->filter; int32_t t; ecs_term_t *terms = filter->terms; ecs_iter_t *it = ctx->it; if (!flecs_rule_trivial_search_init(ctx, op_ctx, filter, first)) { return false; } do { const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableIsPrefab|EcsTableIsDisabled)) { continue; } for (t = 1; t < term_count; t ++) { ecs_term_t *term = &terms[t]; const ecs_table_record_t *tr_with = flecs_id_record_get_table( term->idr, table); if (!tr_with) { break; } it->columns[t] = tr_with->index + 1; } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; it->columns[0] = tr->index + 1; break; } } while (true); return true; } #endif /** * @file addons/system/system.c * @brief System addon. */ #ifdef FLECS_SYSTEM ecs_mixins_t ecs_system_t_mixins = { .type_name = "ecs_system_t", .elems = { [EcsMixinWorld] = offsetof(ecs_system_t, world), [EcsMixinEntity] = offsetof(ecs_system_t, entity), [EcsMixinDtor] = offsetof(ecs_system_t, dtor) } }; /* -- Public API -- */ ecs_entity_t ecs_run_intern( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param) { ecs_ftime_t time_elapsed = delta_time; ecs_entity_t tick_source = system_data->tick_source; /* Support legacy behavior */ if (!param) { param = system_data->ctx; } if (tick_source) { const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); if (tick) { time_elapsed = tick->time_elapsed; /* If timer hasn't fired we shouldn't run the system */ if (!tick->tick) { return 0; } } else { /* If a timer has been set but the timer entity does not have the * EcsTimer component, don't run the system. This can be the result * of a single-shot timer that has fired already. Not resetting the * timer field of the system will ensure that the system won't be * ran after the timer has fired. */ return 0; } } if (ecs_should_log_3()) { char *path = ecs_get_fullpath(world, system); ecs_dbg_3("worker %d: %s", stage_index, path); ecs_os_free(path); } ecs_time_t time_start; bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); if (measure_time) { ecs_os_get_time(&time_start); } ecs_world_t *thread_ctx = world; if (stage) { thread_ctx = stage->thread_ctx; } else { stage = &world->stages[0]; } /* Prepare the query iterator */ ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); ecs_iter_t *it = &qit; qit.system = system; qit.delta_time = delta_time; qit.delta_system_time = time_elapsed; qit.frame_offset = offset; qit.param = param; qit.ctx = system_data->ctx; qit.binding_ctx = system_data->binding_ctx; flecs_defer_begin(world, stage); if (offset || limit) { pit = ecs_page_iter(it, offset, limit); it = &pit; } if (stage_count > 1 && system_data->multi_threaded) { wit = ecs_worker_iter(it, stage_index, stage_count); it = &wit; } ecs_entity_t old_system = flecs_stage_set_system(stage, system); ecs_iter_action_t action = system_data->action; it->callback = action; ecs_run_action_t run = system_data->run; if (run) { run(it); } else if (system_data->query->filter.term_count) { if (it == &qit) { while (ecs_query_next(&qit)) { action(&qit); } } else { while (ecs_iter_next(it)) { action(it); } } } else { action(&qit); ecs_iter_fini(&qit); } flecs_stage_set_system(stage, old_system); if (measure_time) { system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); } flecs_defer_end(world, stage); return it->interrupted_by; } /* -- Public API -- */ ecs_entity_t ecs_run_w_filter( ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param) { ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, offset, limit, param); } ecs_entity_t ecs_run_worker( ecs_world_t *world, ecs_entity_t system, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, void *param) { ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_run_intern( world, stage, system, system_data, stage_index, stage_count, delta_time, 0, 0, param); } ecs_entity_t ecs_run( ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, void *param) { return ecs_run_w_filter(world, system, delta_time, 0, 0, param); } ecs_query_t* ecs_system_get_query( const ecs_world_t *world, ecs_entity_t system) { const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); if (s) { return s->query; } else { return NULL; } } void* ecs_system_get_ctx( const ecs_world_t *world, ecs_entity_t system) { const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); if (s) { return s->ctx; } else { return NULL; } } void* ecs_system_get_binding_ctx( const ecs_world_t *world, ecs_entity_t system) { const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); if (s) { return s->binding_ctx; } else { return NULL; } } /* System deinitialization */ static void flecs_system_fini(ecs_system_t *sys) { if (sys->ctx_free) { sys->ctx_free(sys->ctx); } if (sys->binding_ctx_free) { sys->binding_ctx_free(sys->binding_ctx); } ecs_poly_free(sys, ecs_system_t); } static void flecs_system_init_timer( ecs_world_t *world, ecs_entity_t entity, const ecs_system_desc_t *desc) { if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || ECS_NEQZERO(desc->tick_source)) { #ifdef FLECS_TIMER if (ECS_NEQZERO(desc->interval)) { ecs_set_interval(world, entity, desc->interval); } if (desc->rate) { ecs_entity_t tick_source = desc->tick_source; if (!tick_source) { tick_source = entity; } ecs_set_rate(world, entity, desc->rate, tick_source); } else if (desc->tick_source) { ecs_set_tick_source(world, entity, desc->tick_source); } #else (void)world; (void)entity; ecs_abort(ECS_UNSUPPORTED, "timer module not available"); #endif } } ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t entity = desc->entity; if (!entity) { entity = ecs_new(world, 0); } EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t); if (!poly->poly) { ecs_system_t *system = ecs_poly_new(ecs_system_t); ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); poly->poly = system; system->world = world; system->dtor = (ecs_poly_dtor_t)flecs_system_fini; system->entity = entity; ecs_query_desc_t query_desc = desc->query; query_desc.filter.entity = entity; ecs_query_t *query = ecs_query_init(world, &query_desc); if (!query) { ecs_delete(world, entity); return 0; } /* Prevent the system from moving while we're initializing */ flecs_defer_begin(world, &world->stages[0]); system->query = query; system->query_entity = query->filter.entity; system->run = desc->run; system->action = desc->callback; system->ctx = desc->ctx; system->binding_ctx = desc->binding_ctx; system->ctx_free = desc->ctx_free; system->binding_ctx_free = desc->binding_ctx_free; system->tick_source = desc->tick_source; system->multi_threaded = desc->multi_threaded; system->no_readonly = desc->no_readonly; flecs_system_init_timer(world, entity, desc); if (ecs_get_name(world, entity)) { ecs_trace("#[green]system#[reset] %s created", ecs_get_name(world, entity)); } ecs_defer_end(world); } else { ecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t *system = (ecs_system_t*)poly->poly; if (desc->run) { system->run = desc->run; } if (desc->callback) { system->action = desc->callback; } if (system->ctx_free) { if (system->ctx && system->ctx != desc->ctx) { system->ctx_free(system->ctx); } } if (system->binding_ctx_free) { if (system->binding_ctx && system->binding_ctx != desc->binding_ctx) { system->binding_ctx_free(system->binding_ctx); } } if (desc->ctx) { system->ctx = desc->ctx; } if (desc->binding_ctx) { system->binding_ctx = desc->binding_ctx; } if (desc->ctx_free) { system->ctx_free = desc->ctx_free; } if (desc->binding_ctx_free) { system->binding_ctx_free = desc->binding_ctx_free; } if (desc->query.filter.instanced) { ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); } if (desc->multi_threaded) { system->multi_threaded = desc->multi_threaded; } if (desc->no_readonly) { system->no_readonly = desc->no_readonly; } flecs_system_init_timer(world, entity, desc); } ecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } void FlecsSystemImport( ecs_world_t *world) { ECS_MODULE(world, FlecsSystem); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsSystem), "Module that implements Flecs systems"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_tag(world, EcsSystem); flecs_bootstrap_component(world, EcsTickSource); /* Make sure to never inherit system component. This makes sure that any * term created for the System component will default to 'self' traversal, * which improves efficiency of the query. */ ecs_add_id(world, EcsSystem, EcsDontInherit); } #endif