/** * @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 storage_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. */ #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. It is implemented as * a sparse set. This file contains convenience macros for working with the * entity index. */ #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H #define ecs_eis(world) (&((world)->store.entity_index)) #define flecs_entities_get(world, entity) flecs_sparse_get_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_try(world, entity) flecs_sparse_try_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_get_any(world, entity) flecs_sparse_get_any_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_ensure(world, entity) flecs_sparse_ensure_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_remove(world, entity) flecs_sparse_remove_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_set_generation(world, entity) flecs_sparse_set_generation(ecs_eis(world), entity) #define flecs_entities_is_alive(world, entity) flecs_sparse_is_alive(ecs_eis(world), entity) #define flecs_entities_is_valid(world, entity) flecs_sparse_is_valid(ecs_eis(world), entity) #define flecs_entities_get_current(world, entity) flecs_sparse_get_current(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_sparse_exists(ecs_eis(world), entity) #define flecs_entities_new_id(world) flecs_sparse_new_id(ecs_eis(world)) #define flecs_entities_new_ids(world, count) flecs_sparse_new_ids(ecs_eis(world), count) #define flecs_entities_set_size(world, size) flecs_sparse_set_size(ecs_eis(world), size) #define flecs_entities_count(world) flecs_sparse_count(ecs_eis(world)) #define flecs_entities_fini(world) flecs_sparse_fini(ecs_eis(world)) #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 *cur; } ecs_stack_t; void flecs_stack_init( ecs_stack_t *stack); void flecs_stack_fini( ecs_stack_t *stack); 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)) 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)) 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); ecs_stack_cursor_t flecs_stack_get_cursor( ecs_stack_t *stack); void flecs_stack_restore_cursor( ecs_stack_t *stack, const ecs_stack_cursor_t *cursor); #endif /** * @file bitset.h * @brief Bitset data structure. */ #ifndef FLECS_BITSET_H #define FLECS_BITSET_H #ifdef __cplusplus extern "C" { #endif typedef struct ecs_bitset_t { uint64_t *data; int32_t count; ecs_size_t size; } ecs_bitset_t; /** Initialize bitset. */ FLECS_DBG_API void flecs_bitset_init( ecs_bitset_t *bs); /** Deinialize bitset. */ FLECS_DBG_API void flecs_bitset_fini( ecs_bitset_t *bs); /** Add n elements to bitset. */ FLECS_DBG_API void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count); /** Ensure element exists. */ FLECS_DBG_API void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count); /** Set element. */ FLECS_DBG_API void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value); /** Get element. */ FLECS_DBG_API bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem); /** Return number of elements. */ FLECS_DBG_API int32_t flecs_bitset_count( const ecs_bitset_t *bs); /** Remove from bitset. */ FLECS_DBG_API void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem); /** Swap values in bitset. */ FLECS_DBG_API void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b); #ifdef __cplusplus } #endif #endif /** * @file switch_list.h * @brief Interleaved linked list for storing mutually exclusive values. */ #ifndef FLECS_SWITCH_LIST_H #define FLECS_SWITCH_LIST_H typedef struct ecs_switch_header_t { int32_t element; /* First element for value */ int32_t count; /* Number of elements for value */ } ecs_switch_header_t; typedef struct ecs_switch_node_t { int32_t next; /* Next node in list */ int32_t prev; /* Prev node in list */ } ecs_switch_node_t; struct ecs_switch_t { ecs_map_t hdrs; /* map */ ecs_vec_t nodes; /* vec */ ecs_vec_t values; /* vec */ }; /** Init new switch. */ FLECS_DBG_API void flecs_switch_init( ecs_switch_t* sw, ecs_allocator_t *allocator, int32_t elements); /** Fini switch. */ FLECS_DBG_API void flecs_switch_fini( ecs_switch_t *sw); /** Remove all values. */ FLECS_DBG_API void flecs_switch_clear( ecs_switch_t *sw); /** Add element to switch, initialize value to 0 */ FLECS_DBG_API void flecs_switch_add( ecs_switch_t *sw); /** Set number of elements in switch list */ FLECS_DBG_API void flecs_switch_set_count( ecs_switch_t *sw, int32_t count); /** Get number of elements */ FLECS_DBG_API int32_t flecs_switch_count( ecs_switch_t *sw); /** Ensure that element exists. */ FLECS_DBG_API void flecs_switch_ensure( ecs_switch_t *sw, int32_t count); /** Add n elements. */ FLECS_DBG_API void flecs_switch_addn( ecs_switch_t *sw, int32_t count); /** Set value of element. */ FLECS_DBG_API void flecs_switch_set( ecs_switch_t *sw, int32_t element, uint64_t value); /** Remove element. */ FLECS_DBG_API void flecs_switch_remove( ecs_switch_t *sw, int32_t element); /** Get value for element. */ FLECS_DBG_API uint64_t flecs_switch_get( const ecs_switch_t *sw, int32_t element); /** Swap element. */ FLECS_DBG_API void flecs_switch_swap( ecs_switch_t *sw, int32_t elem_1, int32_t elem_2); /** Get vector with all values. Use together with count(). */ FLECS_DBG_API ecs_vec_t* flecs_switch_values( const ecs_switch_t *sw); /** Return number of different values. */ FLECS_DBG_API int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value); /** Return first element for value. */ FLECS_DBG_API int32_t flecs_switch_first( const ecs_switch_t *sw, uint64_t value); /** Return next element for value. Use with first(). */ FLECS_DBG_API int32_t flecs_switch_next( const ecs_switch_t *sw, int32_t elem); #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif /* Used in id records to keep track of entities used with id flags */ extern const ecs_entity_t EcsFlag; #define ECS_MAX_JOBS_PER_WORKER (16) /* Magic number for a flecs object */ #define ECS_OBJECT_MAGIC (0x6563736f) /* Tags associated with poly for (Poly, tag) components */ #define ecs_world_t_tag invalid #define ecs_stage_t_tag invalid #define ecs_query_t_tag EcsQuery #define ecs_rule_t_tag EcsQuery #define ecs_table_t_tag invalid #define ecs_filter_t_tag EcsQuery #define ecs_observer_t_tag EcsObserver /* Mixin kinds */ typedef enum ecs_mixin_kind_t { EcsMixinWorld, EcsMixinEntity, EcsMixinObservable, EcsMixinIterable, EcsMixinDtor, EcsMixinMax } ecs_mixin_kind_t; /* The mixin array contains pointers to mixin members for different kinds of * flecs objects. This allows the API to retrieve data from an object regardless * of its type. Each mixin array is only stored once per type */ struct ecs_mixins_t { const char *type_name; /* Include name of mixin type so debug code doesn't * need to know about every object */ ecs_size_t elems[EcsMixinMax]; }; /* Mixin tables */ extern ecs_mixins_t ecs_world_t_mixins; extern ecs_mixins_t ecs_stage_t_mixins; extern ecs_mixins_t ecs_filter_t_mixins; extern ecs_mixins_t ecs_query_t_mixins; extern ecs_mixins_t ecs_trigger_t_mixins; extern ecs_mixins_t ecs_observer_t_mixins; /* Types that have no mixins */ #define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) /* Scope for flecs internals, like observers used for builtin features */ extern const ecs_entity_t EcsFlecsInternals; /** Type used for internal string hashmap */ typedef struct ecs_hashed_string_t { char *value; ecs_size_t length; uint64_t hash; } ecs_hashed_string_t; /* Table event type for notifying tables of world events */ typedef enum ecs_table_eventkind_t { EcsTableTriggersForId, EcsTableNoTriggersForId, } ecs_table_eventkind_t; typedef struct ecs_table_event_t { ecs_table_eventkind_t kind; /* Query event */ ecs_query_t *query; /* Component info event */ ecs_entity_t component; /* Event match */ ecs_entity_t event; /* If the nubmer of fields gets out of hand, this can be turned into a union * but since events are very temporary objects, this works for now and makes * initializing an event a bit simpler. */ } ecs_table_event_t; /** Stage-specific component data */ struct ecs_data_t { ecs_vec_t entities; /* Entity identifiers */ ecs_vec_t records; /* Ptrs to records in main entity index */ ecs_vec_t *columns; /* Component columns */ ecs_switch_t *sw_columns; /* Switch columns */ ecs_bitset_t *bs_columns; /* Bitset columns */ }; /** Cache of added/removed components for non-trivial edges between tables */ #define ECS_TABLE_DIFF_INIT { .added = {0}} 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; /** 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; /** 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; /** 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_type_t type; /* Identifies table type in type_index */ ecs_flags32_t flags; /* Flags for testing table properties */ uint16_t storage_count; /* Number of components (excluding tags) */ uint16_t generation; /* Used for table cleanup */ struct ecs_table_record_t *records; /* Array with table records */ ecs_table_t *storage_table; /* Table without tags */ ecs_id_t *storage_ids; /* Component ids (prevent indirection) */ int32_t *storage_map; /* Map type <-> data type * - 0..count(T): type -> data_type * - count(T)..count(S): data_type -> type */ ecs_graph_node_t node; /* Graph node */ ecs_data_t data; /* Component storage */ ecs_type_info_t **type_info; /* Cached type info */ int32_t *dirty_state; /* Keep track of changes in columns */ int16_t sw_count; int16_t sw_offset; int16_t bs_count; int16_t bs_offset; int32_t refcount; /* Increased when used as storage table */ int32_t lock; /* Prevents modifications */ int32_t observed_count; /* Number of observed entities in table */ uint16_t record_count; /* Table record count including wildcards */ }; /** 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 column */ 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 column */ typedef struct flecs_bitset_term_t { ecs_bitset_t *bs_column; int32_t column_index; } flecs_bitset_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 */ bool has_filter; } ecs_entity_filter_t; typedef struct ecs_entity_filter_iter_t { ecs_entity_filter_t *entity_filter; int32_t *columns; ecs_table_range_t range; int32_t bs_offset; int32_t sw_offset; int32_t sw_smallest; } ecs_entity_filter_iter_t; typedef struct ecs_query_table_match_t ecs_query_table_match_t; /** List node used to iterate tables in a query. * The list of nodes dictates the order in which tables should be iterated by a * query. A single node may refer to the table multiple times with different * offset/count parameters, which enables features such as sorting. */ struct ecs_query_table_node_t { ecs_query_table_node_t *next, *prev; ecs_table_t *table; /* The current table. */ uint64_t group_id; /* Value used to organize tables in groups */ int32_t offset; /* Starting point in table */ int32_t count; /* Number of entities to iterate in table */ ecs_query_table_match_t *match; /* Reference to the match */ }; /** Type containing data for a table matched with a query. * A single table can have multiple matches, if the query contains wildcards. */ struct ecs_query_table_match_t { ecs_query_table_node_t node; /* Embedded list node */ 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_size_t *sizes; /* Sizes for ids for current table */ ecs_vec_t refs; /* Cached components for non-this terms */ 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; int32_t *monitor; /* Used to monitor table for changes */ }; /** A single table can occur multiple times in the cache when a term matches * multiple table columns. */ 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_node_t *first; ecs_query_table_node_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 sizes; 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_vector_t *table_slices; /* 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_vector_t *subqueries; /* Flags for query properties */ ecs_flags32_t flags; /* Monitor generation */ int32_t monitor_generation; int32_t cascade_by; /* Identify cascade column */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ /* Mixins */ ecs_iterable_t iterable; ecs_poly_dtor_t dtor; /* Query-level allocators */ ecs_query_allocators_t allocators; }; /** All observers for a specific (component) id */ typedef struct ecs_event_id_record_t { /* Triggers for Self */ ecs_map_t self; /* map */ ecs_map_t self_up; /* map */ ecs_map_t up; /* map */ ecs_map_t observers; /* map */ /* Triggers for SuperSet, SubSet */ ecs_map_t set_observers; /* map */ /* Triggers for Self with non-This subject */ ecs_map_t entity_observers; /* map */ /* Number of active observers for (component) id */ int32_t observer_count; } ecs_event_id_record_t; /* World level allocators are for operations that are not multithreaded */ typedef struct ecs_world_allocators_t { ecs_map_params_t ptr; ecs_map_params_t query_table_list; ecs_block_allocator_t query_table; ecs_block_allocator_t query_table_match; ecs_block_allocator_t graph_edge_lo; ecs_block_allocator_t graph_edge; ecs_block_allocator_t id_record; ecs_block_allocator_t id_record_chunk; ecs_block_allocator_t table_diff; ecs_block_allocator_t sparse_chunk; ecs_block_allocator_t hashmap; /* Temporary vectors used for creating table diff id sequences */ ecs_table_diff_builder_t diff_builder; } ecs_world_allocators_t; /* Stage level allocators are for operations that can be multithreaded */ typedef struct ecs_stage_allocators_t { ecs_stack_t iter_stack; ecs_stack_t deser_stack; ecs_block_allocator_t cmd_entry_chunk; } ecs_stage_allocators_t; /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { EcsOpClone, EcsOpBulkNew, EcsOpAdd, EcsOpRemove, EcsOpSet, EcsOpEmplace, EcsOpMut, EcsOpModified, EcsOpAddModified, EcsOpDelete, EcsOpClear, EcsOpOnDeleteAction, EcsOpEnable, EcsOpDisable, EcsOpSkip } ecs_cmd_kind_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / get_mut) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; } ecs_cmd_t; /* Entity specific metadata for command in defer queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; /** A stage is a context that allows for safely using the API from multiple * threads. Stage pointers can be passed to the world argument of API * operations, which causes the operation to be ran on the stage instead of the * world. */ struct ecs_stage_t { ecs_header_t hdr; /* Unique id that identifies the stage */ int32_t id; /* Deferred command queue */ int32_t defer; ecs_vec_t commands; ecs_stack_t defer_stack; /* Temp memory used by deferred commands */ ecs_sparse_t cmd_entries; /* - command combining */ /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ ecs_world_t *world; /* Reference to world */ ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_vector_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 */ ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Properties */ bool auto_merge; /* Should this stage automatically merge? */ bool async; /* Is stage asynchronous? (write only) */ /* Thread specific allocators */ ecs_stage_allocators_t allocators; ecs_allocator_t allocator; }; /* Component monitor */ typedef struct ecs_monitor_t { ecs_vector_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_sparse_t entity_index; /* sparse */ /* 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_vector_t *records; /* Stack of ids being deleted. */ ecs_vector_t *marked_ids; /* vector */ } ecs_store_t; /* fini actions */ typedef struct ecs_action_elem_t { ecs_fini_action_t action; void *ctx; } ecs_action_elem_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; /* -- Mixins -- */ ecs_world_t *self; ecs_observable_t observable; ecs_iterable_t iterable; /* Unique id per generated event used to prevent duplicate notifications */ int32_t event_id; /* Is entity range checking enabled? */ bool range_check_enabled; /* -- Data storage -- */ ecs_store_t store; /* -- Pending table event buffers -- */ ecs_sparse_t *pending_buffer; /* sparse */ ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ ecs_stage_t *stages; /* Stages */ int32_t stage_count; /* Number of stages */ /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ /* -- 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 *context; /* Application context */ ecs_vector_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_record.h * @brief Index for looking up tables by (component) id. */ #ifndef FLECS_ID_RECORD_H #define FLECS_ID_RECORD_H /* Payload for id cache */ struct ecs_table_record_t { ecs_table_cache_hdr_t hdr; /* Table cache header */ int32_t column; /* First column where id occurs in table */ int32_t count; /* Number of times id occurs in table */ }; /* Linked list of id records */ typedef struct ecs_id_record_elem_t { struct ecs_id_record_t *prev, *next; } ecs_id_record_elem_t; typedef struct ecs_reachable_elem_t { const ecs_table_record_t *tr; ecs_record_t *record; ecs_entity_t src; ecs_id_t id; #ifndef NDEBUG ecs_table_t *table; #endif } ecs_reachable_elem_t; typedef struct ecs_reachable_cache_t { int32_t generation; int32_t current; ecs_vec_t ids; /* vec */ } ecs_reachable_cache_t; /* Payload for id index which contains all datastructures for an id. */ struct ecs_id_record_t { /* Cache with all tables that contain the id. Must be first member. */ ecs_table_cache_t cache; /* table_cache */ /* Flags for id */ ecs_flags32_t flags; /* 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; /* Name lookup index (currently only used for ChildOf pairs) */ ecs_hashmap_t *name_index; /* Cached pointer to type info for id, if id contains data. */ const ecs_type_info_t *type_info; /* Id of record */ ecs_id_t id; /* Parent id record. For pair records the parent is the (R, *) record. */ ecs_id_record_t *parent; /* 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 acyclic; /* (*, O) with only acyclic relationships */ }; /* 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); /* 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 */ const 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); void flecs_observers_notify( ecs_iter_t *it, ecs_observable_t *observable, const ecs_type_t *ids, ecs_entity_t event); void flecs_set_observers_notify( ecs_iter_t *it, ecs_observable_t *observable, const ecs_type_t *ids, ecs_entity_t event, ecs_id_t set_id); bool flecs_check_observers_for_event( const ecs_poly_t *world, 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, ecs_size_t *sizes); bool flecs_iter_next_row( ecs_iter_t *it); bool flecs_iter_next_instanced( ecs_iter_t *it, bool result); #endif /** * @file table.c * @brief Table storage implementation. */ #ifndef FLECS_TABLE_H #define FLECS_TABLE_H /* Init table */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from); /** Copy type. */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src); /** Free type. */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type); /** Find or create table for a set of components */ ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type); /* Initialize columns for data */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from a table. */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table); /* Reset a table to its initial state */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from the table. Do not invoke OnRemove systems */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table); /* Clear table data. Don't call OnRemove handlers. */ void flecs_table_clear_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data); /* Return number of entities in data */ int32_t flecs_table_data_count( const ecs_data_t *data); /* Add a new entry to the table for the specified entity */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_record_t *record, bool construct, bool on_add); /* Delete an entity from the table. */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct); /* Make sure table records are in correct table cache list */ bool flecs_table_records_update_empty( ecs_table_t *table); /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, int32_t new_index, ecs_table_t *old_table, int32_t old_index, bool construct); /* Grow table with specified number of records. Populate table with entities, * starting from specified entity id. */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count, const ecs_entity_t *ids); /* Set table to a fixed size. Useful for preallocating memory in advance. */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count); /* Shrink table to contents */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table); /* Get dirty state for table columns */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table); /* Initialize root table */ void flecs_init_root_table( ecs_world_t *world); /* Unset components in table */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table); /* Replace data */ void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data); /* Merge data of one table into another table */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *new_table, ecs_table_t *old_table, ecs_data_t *new_data, ecs_data_t *old_data); void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2); 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); 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); 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_clear_edges( ecs_world_t *world, ecs_table_t *table); void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table); ecs_vec_t *ecs_table_column_for_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id); int32_t flecs_table_column_to_union_index( const ecs_table_t *table, int32_t column); /* Increase refcount of table (prevents deletion) */ void flecs_table_claim( ecs_world_t *world, ecs_table_t *table); /* Decreases refcount of table (may delete) */ bool flecs_table_release( ecs_world_t *world, ecs_table_t *table); /* Increase observer count of table */ void flecs_table_observer_add( ecs_table_t *table, int32_t value); /* 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 /** * @file poly.h * @brief Functions for managing poly objects. */ #ifndef FLECS_POLY_H #define FLECS_POLY_H #include /* Initialize poly */ void* _ecs_poly_init( ecs_poly_t *object, int32_t kind, ecs_size_t size, ecs_mixins_t *mixins); #define ecs_poly_init(object, type)\ _ecs_poly_init(object, type##_magic, sizeof(type), &type##_mixins) /* Deinitialize object for specified type */ void _ecs_poly_fini( ecs_poly_t *object, int32_t kind); #define ecs_poly_fini(object, type)\ _ecs_poly_fini(object, type##_magic) /* Utility functions for creating an object on the heap */ #define ecs_poly_new(type)\ (type*)ecs_poly_init(ecs_os_calloc_t(type), type) #define ecs_poly_free(obj, type)\ ecs_poly_fini(obj, type);\ ecs_os_free(obj) /* Get or create poly component for an entity */ EcsPoly* _ecs_poly_bind( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_bind(world, entity, T) \ _ecs_poly_bind(world, entity, T##_tag) void _ecs_poly_modified( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_modified(world, entity, T) \ _ecs_poly_modified(world, entity, T##_tag) /* Get poly component for an entity */ const EcsPoly* _ecs_poly_bind_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_bind_get(world, entity, T) \ _ecs_poly_bind_get(world, entity, T##_tag) ecs_poly_t* _ecs_poly_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_get(world, entity, T) \ ((T*)_ecs_poly_get(world, entity, T##_tag)) /* Utilities for testing/asserting an object type */ #ifndef FLECS_NDEBUG void* _ecs_poly_assert( const ecs_poly_t *object, int32_t type, const char *file, int32_t line); #define ecs_poly_assert(object, type)\ _ecs_poly_assert(object, type##_magic, __FILE__, __LINE__) #define ecs_poly(object, T) ((T*)ecs_poly_assert(object, T)) #else #define ecs_poly_assert(object, type) #define ecs_poly(object, T) ((T*)object) #endif /* Utility functions for getting a mixin from an object */ ecs_iterable_t* ecs_get_iterable( const ecs_poly_t *poly); ecs_observable_t* ecs_get_observable( const ecs_poly_t *object); ecs_poly_dtor_t* ecs_get_dtor( const ecs_poly_t *poly); #endif /** * @file stage.h * @brief Stage functions. */ #ifndef FLECS_STAGE_H #define FLECS_STAGE_H /* Initialize stage data structures */ void flecs_stage_init( ecs_world_t *world, ecs_stage_t *stage); /* Deinitialize stage */ void flecs_stage_fini( ecs_world_t *world, ecs_stage_t *stage); /* Post-frame merge actions */ void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_cmd( ecs_stage_t *stage); bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component); bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value); bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out); bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity); bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity); bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action); bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component, bool enable); bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t op_kind, ecs_entity_t entity, ecs_entity_t component, ecs_size_t size, void *value, bool need_value); bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage); #endif /** * @file world.c * @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/qsort.h * @brief Quicksort implementation. */ /* From: https://github.com/svpv/qsort/blob/master/qsort.h * Use custom qsort implementation rather than relying on the version in libc to * ensure that results are consistent across platforms. */ /* * Copyright (c) 2013, 2017 Alexey Tourbin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * This is a traditional Quicksort implementation which mostly follows * [Sedgewick 1978]. Sorting is performed entirely on array indices, * while actual access to the array elements is abstracted out with the * user-defined `LESS` and `SWAP` primitives. * * Synopsis: * QSORT(N, LESS, SWAP); * where * N - the number of elements in A[]; * LESS(i, j) - compares A[i] to A[j]; * SWAP(i, j) - exchanges A[i] with A[j]. */ #ifndef QSORT_H #define QSORT_H /* Sort 3 elements. */ #define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ do { \ if (Q_LESS(q_a2, q_a1)) { \ if (Q_LESS(q_a3, q_a2)) \ Q_SWAP(q_a1, q_a3); \ else { \ Q_SWAP(q_a1, q_a2); \ if (Q_LESS(q_a3, q_a2)) \ Q_SWAP(q_a2, q_a3); \ } \ } \ else if (Q_LESS(q_a3, q_a2)) { \ Q_SWAP(q_a2, q_a3); \ if (Q_LESS(q_a2, q_a1)) \ Q_SWAP(q_a1, q_a2); \ } \ } while (0) /* Partition [q_l,q_r] around a pivot. After partitioning, * [q_l,q_j] are the elements that are less than or equal to the pivot, * while [q_i,q_r] are the elements greater than or equal to the pivot. */ #define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ do { \ /* The middle element, not to be confused with the median. */ \ Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \ /* Reorder the second, the middle, and the last items. \ * As [Edelkamp Weiss 2016] explain, using the second element \ * instead of the first one helps avoid bad behaviour for \ * decreasingly sorted arrays. This method is used in recent \ * versions of gcc's std::sort, see gcc bug 58437#c13, although \ * the details are somewhat different (cf. #c14). */ \ Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \ /* Place the median at the beginning. */ \ Q_SWAP(q_l, q_m); \ /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ * q_i and q_j are initially off by one, they get decremented \ * in the do-while loops. */ \ q_i = q_l + 1; q_j = q_r; \ while (1) { \ do q_i++; while (Q_LESS(q_i, q_l)); \ do q_j--; while (Q_LESS(q_l, q_j)); \ if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \ Q_SWAP(q_i, q_j); \ } \ /* Compensate for the i==j case. */ \ q_i = q_j + 1; \ /* Put the median to its final place. */ \ Q_SWAP(q_l, q_j); \ /* The median is not part of the left subfile. */ \ q_j--; \ } while (0) /* Insertion sort is applied to small subfiles - this is contrary to * Sedgewick's suggestion to run a separate insertion sort pass after * the partitioning is done. The reason I don't like a separate pass * is that it triggers extra comparisons, because it can't see that the * medians are already in their final positions and need not be rechecked. * Since I do not assume that comparisons are cheap, I also do not try * to eliminate the (q_j > q_l) boundary check. */ #define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ do { \ Q_UINT q_i, q_j; \ /* For each item starting with the second... */ \ for (q_i = q_l + 1; q_i <= q_r; q_i++) \ /* move it down the array so that the first part is sorted. */ \ for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \ Q_SWAP(q_j, q_j - 1); \ } while (0) /* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to * Q_THRESH, the algorithm performs recursive partitioning. When the size * drops below Q_THRESH, the algorithm switches to insertion sort. * The minimum valid value is probably 5 (with 5 items, the second and * the middle items, the middle itself being rounded down, are distinct). */ #define Q_THRESH 16 /* The main loop. */ #define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ do { \ Q_UINT q_l = 0; \ Q_UINT q_r = (Q_N) - 1; \ Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ struct { Q_UINT q_l, q_r; } \ /* On 32-bit platforms, to sort a "char[3GB+]" array, \ * it may take full 32 stack frames. On 64-bit CPUs, \ * though, the address space is limited to 48 bits. \ * The usage is further reduced if Q_N has a 32-bit type. */ \ q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ while (1) { \ if (q_r - q_l + 1 >= Q_THRESH) { \ Q_UINT q_i, q_j; \ Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ * Dealing with them depends on which one is bigger. */ \ if (q_j - q_l >= q_r - q_i) \ Q_SUBFILES(q_l, q_j, q_i, q_r); \ else \ Q_SUBFILES(q_i, q_r, q_l, q_j); \ } \ else { \ Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ /* Pop subfiles from the stack, until it gets empty. */ \ if (q_sp == 0) break; \ q_sp--; \ q_l = q_st[q_sp].q_l; \ q_r = q_st[q_sp].q_r; \ } \ } \ } while (0) /* The missing part: dealing with subfiles. * Assumes that the first subfile is not smaller than the second. */ #define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ do { \ /* If the second subfile is only a single element, it needs \ * no further processing. The first subfile will be processed \ * on the next iteration (both subfiles cannot be only a single \ * element, due to Q_THRESH). */ \ if (q_l2 == q_r2) { \ q_l = q_l1; \ q_r = q_r1; \ } \ else { \ /* Otherwise, both subfiles need processing. \ * Push the larger subfile onto the stack. */ \ q_st[q_sp].q_l = q_l1; \ q_st[q_sp].q_r = q_r1; \ q_sp++; \ /* Process the smaller subfile on the next iteration. */ \ q_l = q_l2; \ q_r = q_r2; \ } \ } while (0) /* And now, ladies and gentlemen, may I proudly present to you... */ #define QSORT(Q_N, Q_LESS, Q_SWAP) \ do { \ if ((Q_N) > 1) \ /* We could check sizeof(Q_N) and use "unsigned", but at least \ * on x86_64, this has the performance penalty of up to 5%. */ \ Q_LOOP(ecs_size_t, Q_N, Q_LESS, Q_SWAP); \ } while (0) void ecs_qsort( void *base, ecs_size_t nitems, ecs_size_t size, int (*compar)(const void *, const void*)); #define ecs_qsort_t(base, nitems, T, compar) \ ecs_qsort(base, nitems, ECS_SIZEOF(T), compar) #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); ecs_hashmap_t* flecs_name_index_new( ecs_world_t *world, ecs_allocator_t *allocator); void flecs_name_index_fini( ecs_hashmap_t *map); void flecs_name_index_free( ecs_hashmap_t *map); ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *dst); ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash); const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t id, uint64_t hash); void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name); #endif //////////////////////////////////////////////////////////////////////////////// //// Bootstrap API //////////////////////////////////////////////////////////////////////////////// /* Bootstrap world */ void flecs_bootstrap( ecs_world_t *world); #define flecs_bootstrap_component(world, id_)\ ecs_component_init(world, &(ecs_component_desc_t){\ .entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ .type.size = sizeof(id_),\ .type.alignment = ECS_ALIGNOF(id_)\ }); #define flecs_bootstrap_tag(world, name)\ ecs_ensure(world, name);\ ecs_add_id(world, name, EcsFinal);\ ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ ecs_set(world, name, EcsComponent, {.size = 0});\ ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ ecs_set_symbol(world, name, #name) /* Bootstrap functions for other parts in the code */ void flecs_bootstrap_hierarchy(ecs_world_t *world); //////////////////////////////////////////////////////////////////////////////// //// Entity API //////////////////////////////////////////////////////////////////////////////// /* Mark an entity as being watched. This is used to trigger automatic rematching * when entities used in system expressions change their components. */ ecs_record_t* flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, 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); //////////////////////////////////////////////////////////////////////////////// //// 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); //////////////////////////////////////////////////////////////////////////////// //// 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); /* Get next power of 2 */ int32_t flecs_next_pow_of_2( int32_t n); /* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the * entity index */ ecs_record_t flecs_to_row( uint64_t value); /* Get 64bit integer from ecs_record_t */ uint64_t flecs_from_row( ecs_record_t record); /* Convert a symbol name to an entity name by removing the prefix */ const char* flecs_name_from_symbol( ecs_world_t *world, const char *type_name); /* Compare function for entity ids */ int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); /* Compare function for entity ids which can be used with qsort */ int flecs_entity_compare_qsort( const void *e1, const void *e2); /* 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); #define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) void _assert_func( bool cond, const char *cond_str, const char *file, int32_t line, const char *func); void flecs_dump_backtrace( FILE *stream); void ecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf); bool flecs_isident( char ch); int32_t flecs_search_relation_w_idr( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, ecs_id_record_t *idr); #endif /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severly 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); ecs_assert(size == ecs_vec_size(&table->data.records), ECS_INTERNAL_ERROR, NULL); ecs_assert(count == ecs_vec_count(&table->data.records), ECS_INTERNAL_ERROR, NULL); int32_t i; int32_t sw_offset = table->sw_offset; int32_t sw_count = table->sw_count; int32_t bs_offset = table->bs_offset; int32_t bs_count = table->bs_count; 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); ecs_table_t *storage_table = table->storage_table; if (storage_table) { ecs_assert(table->storage_count == storage_table->type.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->storage_ids == storage_table->type.array, ECS_INTERNAL_ERROR, NULL); int32_t storage_count = table->storage_count; ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); int32_t *storage_map = table->storage_map; ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *storage_ids = table->storage_ids; for (i = 0; i < type_count; i ++) { if (storage_map[i] != -1) { ecs_assert(ids[i] == storage_ids[storage_map[i]], ECS_INTERNAL_ERROR, NULL); } } ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < storage_count; i ++) { ecs_vec_t *column = &table->data.columns[i]; ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); int32_t storage_map_id = storage_map[i + type_count]; ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ids[storage_map_id] == storage_ids[i], ECS_INTERNAL_ERROR, NULL); } } else { ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL); } if (sw_count) { ecs_assert(table->data.sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < sw_count; i ++) { ecs_switch_t *sw = &table->data.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->data.bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &table->data.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->observed_count == 0) || (table->flags & EcsTableHasObserved), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif static void flecs_table_init_storage_map( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->storage_table) { return; } ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t t, ids_count = type.count; ecs_id_t *storage_ids = table->storage_ids; int32_t s, storage_ids_count = table->storage_count; if (!ids_count) { table->storage_map = NULL; return; } table->storage_map = flecs_walloc_n(world, int32_t, ids_count + storage_ids_count); int32_t *t2s = table->storage_map; int32_t *s2t = &table->storage_map[ids_count]; for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) { ecs_id_t id = ids[t]; ecs_id_t storage_id = storage_ids[s]; if (id == storage_id) { t2s[t] = s; s2t[s] = t; } else { t2s[t] = -1; } /* Ids can never get ahead of storage id, as ids are a superset of the * storage ids */ ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL); t += (id <= storage_id); s += (id == storage_id); } /* Storage ids is always a subset of ids, so all should be iterated */ ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); /* Initialize remainder of type -> storage_type map */ for (; (t < ids_count); t ++) { t2s[t] = -1; } } 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_type_info( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *records = table->records; int32_t i, count = table->type.count; table->type_info = flecs_walloc_n(world, ecs_type_info_t*, count); for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; /* All ids in the storage table must be components with type info */ const ecs_type_info_t *ti = idr->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); table->flags |= flecs_type_info_flags(ti); table->type_info[i] = (ecs_type_info_t*)ti; } } static void flecs_table_init_storage_table( ecs_world_t *world, ecs_table_t *table) { if (table->storage_table) { return; } ecs_type_t type = table->type; int32_t i, count = type.count; ecs_id_t *ids = type.array; ecs_table_record_t *records = table->records; ecs_id_t array[ECS_ID_CACHE_SIZE]; ecs_type_t storage_ids = { .array = array }; if (count > ECS_ID_CACHE_SIZE) { storage_ids.array = flecs_walloc_n(world, ecs_id_t, count); } for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = ids[i]; if (idr->type_info != NULL) { storage_ids.array[storage_ids.count ++] = id; } } if (storage_ids.count && storage_ids.count != count) { ecs_table_t *storage_table = flecs_table_find_or_create(world, &storage_ids); table->storage_table = storage_table; table->storage_count = flecs_ito(uint16_t, storage_ids.count); table->storage_ids = storage_table->type.array; table->type_info = storage_table->type_info; table->flags |= storage_table->flags; storage_table->refcount ++; } else if (storage_ids.count) { table->storage_table = table; table->storage_count = flecs_ito(uint16_t, count); table->storage_ids = type.array; flecs_table_init_type_info(world, table); } if (storage_ids.array != array) { flecs_wfree_n(world, ecs_id_t, count, storage_ids.array); } if (!table->storage_map) { flecs_table_init_storage_map(world, table); } } void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { int32_t sw_count = table->sw_count; int32_t bs_count = table->bs_count; ecs_data_t *storage = &table->data; int32_t i, count = table->storage_count; /* Root tables don't have columns */ if (!count && !sw_count && !bs_count) { storage->columns = NULL; } ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); ecs_vec_init_t(NULL, &storage->records, ecs_record_t*, 0); if (count) { ecs_vec_t *columns = flecs_wcalloc_n(world, ecs_vec_t, count); storage->columns = columns; #ifdef FLECS_DEBUG ecs_type_info_t **ti = table->type_info; for (i = 0; i < count; i ++) { ecs_vec_init(NULL, &columns[i], ti[i]->size, 0); } #endif } if (sw_count) { storage->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); for (i = 0; i < sw_count; i ++) { flecs_switch_init(&storage->sw_columns[i], &world->allocator, 0); } } if (bs_count) { storage->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); for (i = 0; i < bs_count; i ++) { flecs_bitset_init(&storage->bs_columns[i]); } } } 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; /* Iterate components to initialize table flags */ int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; /* As we're iterating over the table components, also set the table * flags. These allow us to quickly determine if the table contains * data that needs to be handled in a special way. */ 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 (r == EcsUnion) { table->flags |= EcsTableHasUnion; if (!table->sw_count) { table->sw_offset = flecs_ito(int16_t, i); } table->sw_count ++; } else if (r == ecs_id(EcsPoly)) { table->flags |= EcsTableHasBuiltins; } } else { if (ECS_HAS_ID_FLAG(id, TOGGLE)) { table->flags |= EcsTableHasToggle; if (!table->bs_count) { table->bs_offset = flecs_ito(int16_t, i); } table->bs_count ++; } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { table->flags |= EcsTableHasOverrides; } } } } } static void flecs_table_append_to_records( ecs_world_t *world, ecs_table_t *table, ecs_vector_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_vector_add(records, ecs_table_record_t); tr->column = 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); } void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { flecs_table_init_flags(world, table); int32_t dst_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_vector_t *records = world->store.records; ecs_vector_clear(records); ecs_id_record_t *idr; 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) { 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_vector_add(&records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->column = 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_vector_add(&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->column = 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_vector_set_min_size(&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_vector_get(records, ecs_table_record_t, dst_i); idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vector_add(&records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->column = dst_i; tr->count = 0; r = ECS_PAIR_FIRST(dst_id); } 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_vector_add(&records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; tr->column = 0; tr->count = last_id + 1; } if (last_pair - first_pair) { tr = ecs_vector_add(&records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; tr->column = first_pair; tr->count = last_pair - first_pair; } if (dst_count) { tr = ecs_vector_add(&records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; tr->column = 0; tr->count = 1; } if (dst_count && !has_childof) { tr = ecs_vector_add(&records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0; tr->column = 0; tr->count = 1; } /* Now that all records have been added, copy them to array */ int32_t i, dst_record_count = ecs_vector_count(records); ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, dst_record_count, ecs_vector_first(records, ecs_table_record_t)); table->record_count = flecs_ito(uint16_t, dst_record_count); table->records = dst_tr; /* 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; } world->store.records = records; flecs_table_init_storage_table(world, table); flecs_table_init_data(world, table); } 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); } 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; } } 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); } } /* -- Private functions -- */ static void flecs_on_component_callback( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, ecs_entity_t event, ecs_vec_t *column, ecs_entity_t *entities, ecs_id_t id, int32_t row, int32_t count, ecs_type_info_t *ti) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_t it = { .field_count = 1 }; it.entities = entities; ecs_size_t size = ti->size; void *ptr = ecs_vec_get(column, size, row); 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[0] = 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; flecs_iter_validate(&it); callback(&it); ecs_iter_fini(&it); } static void flecs_ctor_component( ecs_type_info_t *ti, ecs_vec_t *column, int32_t row, int32_t count) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { void *ptr = ecs_vec_get(column, ti->size, row); ctor(ptr, count, ti); } } static void flecs_run_add_hooks( ecs_world_t *world, ecs_table_t *table, ecs_type_info_t *ti, ecs_vec_t *column, ecs_entity_t *entities, ecs_id_t id, int32_t row, int32_t count, bool construct) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { flecs_ctor_component(ti, column, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { flecs_on_component_callback(world, table, on_add, EcsOnAdd, column, entities, id, row, count, ti); } } static void flecs_dtor_component( ecs_type_info_t *ti, ecs_vec_t *column, int32_t row, int32_t count) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { void *ptr = ecs_vec_get(column, ti->size, row); dtor(ptr, count, ti); } } static void flecs_run_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_type_info_t *ti, ecs_vec_t *column, ecs_entity_t *entities, ecs_id_t id, int32_t row, int32_t count, bool dtor) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, entities, id, row, count, ti); } if (dtor) { flecs_dtor_component(ti, column, row, count); } } static void flecs_dtor_all_components( 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); ecs_id_t *ids = table->storage_ids; int32_t ids_count = table->storage_count; ecs_record_t **records = data->records.array; ecs_entity_t *entities = data->entities.array; int32_t i, c, end = row + count; (void)records; if (is_delete && table->observed_count) { /* If table contains monitored entities with acyclic 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_vec_t *column = &data->columns[c]; ecs_type_info_t *ti = table->type_info[c]; ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, &entities[row], ids[c], row, count, ti); } } /* Destruct components */ for (c = 0; c < ids_count; c++) { flecs_dtor_component(table->type_info[c], &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); ecs_assert(!e || records[i] == flecs_entities_get(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i]->table == table, 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 records[i]->table = NULL; records[i]->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); ecs_assert(!e || records[i] == flecs_entities_get(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i]->table == table, 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_assert(!e || records[i] == flecs_entities_get(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i]->table == table, ECS_INTERNAL_ERROR, NULL); records[i]->table = NULL; records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; (void)e; } } } } 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_dtor_all_components(world, table, data, 0, count, update_entity_index, is_delete); } /* Sanity check */ ecs_assert(data->records.count == data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *columns = data->columns; if (columns) { int32_t c, column_count = table->storage_count; for (c = 0; c < column_count; c ++) { /* Sanity check */ ecs_assert(columns[c].count == data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_vec_fini(&world->allocator, &columns[c], table->type_info[c]->size); } flecs_wfree_n(world, ecs_vec_t, column_count, columns); data->columns = NULL; } ecs_switch_t *sw_columns = data->sw_columns; if (sw_columns) { int32_t c, column_count = table->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); data->sw_columns = NULL; } ecs_bitset_t *bs_columns = data->bs_columns; if (bs_columns) { int32_t c, column_count = table->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); data->bs_columns = NULL; } ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); ecs_vec_fini_t(&world->allocator, &data->records, ecs_record_t*); if (deactivate && count) { flecs_table_set_empty(world, table); } table->observed_count = 0; table->flags &= ~EcsTableHasObserved; } /* 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; ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL); if (!is_root && !(world->flags & EcsWorldQuit)) { 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(&world->store.table_map, &ids, ecs_table_t*); } flecs_wfree_n(world, int32_t, table->storage_count + 1, table->dirty_state); flecs_wfree_n(world, int32_t, table->storage_count + table->type.count, table->storage_map); flecs_table_records_unregister(world, table); ecs_table_t *storage_table = table->storage_table; if (storage_table == table) { if (table->type_info) { flecs_wfree_n(world, ecs_type_info_t*, table->storage_count, table->type_info); } } else if (storage_table) { flecs_table_release(world, storage_table); } /* Update counters */ world->info.table_count --; world->info.table_record_count -= table->record_count; world->info.table_storage_count -= table->storage_count; world->info.table_delete_total ++; if (!table->storage_count) { world->info.tag_table_count --; } else { world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); } 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(); } void flecs_table_claim( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); table->refcount ++; (void)world; } bool flecs_table_release( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); if (--table->refcount == 0) { flecs_table_free(world, table); return true; } return false; } /* 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); } void flecs_table_observer_add( ecs_table_t *table, int32_t value) { int32_t result = table->observed_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { table->flags &= ~EcsTableHasObserved; } else if (result == value) { table->flags |= EcsTableHasObserved; } } 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] ++; } } 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) { int32_t index = ecs_search(world, table->storage_table, component, 0); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); table->dirty_state[index + 1] ++; } } 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) { int32_t i_old = 0, src_column_count = src_table->sw_count; int32_t i_new = 0, dst_column_count = dst_table->sw_count; if (!src_column_count && !dst_column_count) { return; } ecs_switch_t *src_columns = src_table->data.sw_columns; ecs_switch_t *dst_columns = dst_table->data.sw_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_table->sw_offset; int32_t offset_old = src_table->sw_offset; 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); } } } static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { int32_t i_old = 0, src_column_count = src_table->bs_count; int32_t i_new = 0, dst_column_count = dst_table->bs_count; if (!src_column_count && !dst_column_count) { return; } ecs_bitset_t *src_columns = src_table->data.bs_columns; ecs_bitset_t *dst_columns = dst_table->data.bs_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_table->bs_offset; int32_t offset_old = src_table->bs_offset; 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); } } } static void* flecs_table_grow_column( ecs_world_t *world, ecs_vec_t *column, ecs_type_info_t *ti, int32_t to_add, int32_t dst_size, bool construct) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); int32_t size = ti->size; int32_t count = column->count; int32_t src_size = column->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->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, ti->size); *column = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { ecs_vec_set_size(&world->allocator, column, size, dst_size); } result = ecs_vec_grow(&world->allocator, column, 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->size == dst_size, ECS_INTERNAL_ERROR, NULL); return result; } 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->storage_count; int32_t sw_count = table->sw_count; int32_t bs_count = table->bs_count; ecs_vec_t *columns = data->columns; ecs_switch_t *sw_columns = data->sw_columns; ecs_bitset_t *bs_columns = data->bs_columns; /* Add record to record ptr array */ ecs_vec_set_size_t(&world->allocator, &data->records, ecs_record_t*, size); ecs_record_t **r = ecs_vec_last_t(&data->records, ecs_record_t*) + 1; data->records.count += to_add; if (data->records.size > size) { size = data->records.size; } /* 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; ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); /* 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); } ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); /* Add elements to each column array */ ecs_type_info_t **type_info = table->type_info; for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &columns[i]; ecs_type_info_t *ti = type_info[i]; flecs_table_grow_column(world, column, ti, to_add, size, true); ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); flecs_run_add_hooks(world, table, ti, column, e, table->type.array[i], cur_count, to_add, false); } /* 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; } static void flecs_table_fast_append( ecs_world_t *world, ecs_type_info_t **type_info, ecs_vec_t *columns, int32_t count) { /* Add elements to each column array */ int32_t i; for (i = 0; i < count; i ++) { ecs_type_info_t *ti = type_info[i]; ecs_vec_t *column = &columns[i]; ecs_vec_append(&world->allocator, column, ti->size); } } int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_record_t *record, bool construct, bool on_add) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, 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->storage_count; ecs_vec_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; /* Add record ptr to array with record ptrs */ ecs_record_t **r = ecs_vec_append_t(&world->allocator, &data->records, ecs_record_t*); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); *r = record; /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t **type_info = table->type_info; /* Fast path: no switch columns, no lifecycle actions */ if (!(table->flags & EcsTableIsComplex)) { flecs_table_fast_append(world, type_info, columns, column_count); if (!count) { flecs_table_set_empty(world, table); /* See below */ } return count; } int32_t sw_count = table->sw_count; int32_t bs_count = table->bs_count; ecs_switch_t *sw_columns = data->sw_columns; ecs_bitset_t *bs_columns = data->bs_columns; 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_vec_t *column = &columns[i]; ecs_type_info_t *ti = type_info[i]; flecs_table_grow_column(world, column, ti, 1, size, construct); ecs_iter_action_t on_add_hook; if (on_add && (on_add_hook = ti->hooks.on_add)) { flecs_on_component_callback(world, table, on_add_hook, EcsOnAdd, column, &entities[count], table->storage_ids[i], count, 1, ti); } ecs_assert(columns[i].size == data->entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns[i].count == data->entities.count, ECS_INTERNAL_ERROR, NULL); } /* 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; } static void flecs_table_fast_delete_last( ecs_vec_t *columns, int32_t column_count) { int i; for (i = 0; i < column_count; i ++) { ecs_vec_remove_last(&columns[i]); } } static void flecs_table_fast_delete( ecs_type_info_t **type_info, ecs_vec_t *columns, int32_t column_count, int32_t index) { int i; for (i = 0; i < column_count; i ++) { ecs_type_info_t *ti = type_info[i]; ecs_vec_t *column = &columns[i]; ecs_vec_remove(column, ti->size, index); } } 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); 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); /* Move last record ptr to index */ ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); ecs_record_t **records = data->records.array; ecs_record_t *record_to_move = records[count]; records[index] = record_to_move; ecs_vec_remove_last(&data->records); /* Update record of moved entity in entity index */ if (index != count) { 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_type_info_t **type_info = table->type_info; ecs_vec_t *columns = data->columns; int32_t column_count = table->storage_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(type_info, columns, column_count, index); } flecs_table_check_sanity(table); return; } ecs_id_t *ids = table->storage_ids; /* 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_run_remove_hooks(world, table, type_info[i], &columns[i], &entity_to_delete, ids[i], 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_vec_t *column = &columns[i]; ecs_type_info_t *ti = type_info[i]; ecs_size_t size = ti->size; void *dst = ecs_vec_get(column, size, index); void *src = ecs_vec_last(column, size); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (destruct && on_remove) { flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, ids[i], index, 1, ti); } ecs_move_t move_dtor = ti->hooks.move_dtor; if (move_dtor) { move_dtor(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } ecs_vec_remove_last(column); } } else { flecs_table_fast_delete(type_info, columns, column_count, index); } } /* Remove elements from switch columns */ ecs_switch_t *sw_columns = data->sw_columns; int32_t sw_count = table->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 = data->bs_columns; int32_t bs_count = table->bs_count; for (i = 0; i < bs_count; i ++) { flecs_bitset_remove(&bs_columns[i], index); } flecs_table_check_sanity(table); } 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->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; ecs_vec_t *src_columns = src_table->data.columns; ecs_vec_t *dst_columns = dst_table->data.columns; ecs_type_info_t **dst_type_info = dst_table->type_info; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; if (dst_id == src_id) { ecs_vec_t *dst_column = &dst_columns[i_new]; ecs_vec_t *src_column = &src_columns[i_old]; ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; void *dst = ecs_vec_get(dst_column, size, dst_index); void *src = ecs_vec_get(src_column, size, src_index); ecs_os_memcpy(dst, src, size); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } } 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); ecs_type_info_t **dst_type_info = dst_table->type_info; ecs_type_info_t **src_type_info = src_table->type_info; int32_t i_new = 0, dst_column_count = dst_table->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; ecs_vec_t *src_columns = src_table->data.columns; ecs_vec_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; if (dst_id == src_id) { ecs_vec_t *dst_column = &dst_columns[i_new]; ecs_vec_t *src_column = &src_columns[i_old]; ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *dst = ecs_vec_get(dst_column, size, dst_index); void *src = ecs_vec_get(src_column, size, src_index); 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_run_add_hooks(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_id, dst_index, 1, construct); } else { flecs_run_remove_hooks(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_id, 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_run_add_hooks(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1, construct); } for (; (i_old < src_column_count); i_old ++) { flecs_run_remove_hooks(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_ids[i_old], src_index, 1, use_move_dtor); } flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_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; } 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); } } 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); ecs_vec_reclaim_t(&world->allocator, &data->records, ecs_record_t*); int32_t i, count = table->storage_count; ecs_type_info_t **type_info = table->type_info; for (i = 0; i < count; i ++) { ecs_vec_t *column = &data->columns[i]; ecs_type_info_t *ti = type_info[i]; ecs_vec_reclaim(&world->allocator, column, ti->size); } return has_payload; } int32_t flecs_table_data_count( const ecs_data_t *data) { return data ? data->entities.count : 0; } static void flecs_table_swap_switch_columns( ecs_table_t *table, ecs_data_t *data, 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 = data->sw_columns; for (i = 0; i < column_count; i ++) { ecs_switch_t *sw = &columns[i]; flecs_switch_swap(sw, row_1, row_2); } } static void flecs_table_swap_bitset_columns( ecs_table_t *table, ecs_data_t *data, 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 = data->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } 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 **records = table->data.records.array; ecs_record_t *record_ptr_1 = records[row_1]; ecs_record_t *record_ptr_2 = records[row_2]; 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); records[row_1] = record_ptr_2; records[row_2] = record_ptr_1; flecs_table_swap_switch_columns(table, &table->data, row_1, row_2); flecs_table_swap_bitset_columns(table, &table->data, row_1, row_2); ecs_vec_t *columns = table->data.columns; if (!columns) { flecs_table_check_sanity(table); return; } ecs_type_info_t **type_info = table->type_info; /* 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->storage_count; for (i = 0; i < column_count; i++) { ecs_type_info_t* ti = type_info[i]; temp_buffer_size = ECS_MAX(temp_buffer_size, ti->size); } void* tmp = ecs_os_alloca(temp_buffer_size); /* Swap columns */ for (i = 0; i < column_count; i ++) { ecs_type_info_t *ti = type_info[i]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = columns[i].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_merge_column( ecs_world_t *world, ecs_vec_t *dst, ecs_vec_t *src, int32_t size, int32_t column_size, ecs_type_info_t *ti) { 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; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { int32_t src_count = src->count; if (ti) { flecs_table_grow_column(world, dst, ti, src_count, column_size, true); } else { if (column_size) { ecs_vec_set_size(&world->allocator, dst, size, column_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; /* Move values into column */ ecs_move_t move = NULL; if (ti) { move = ti->hooks.move; } 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, size); } } static void flecs_merge_table_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->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; ecs_type_info_t **dst_type_info = dst_table->type_info; ecs_type_info_t **src_type_info = src_table->type_info; ecs_vec_t *src = src_data->columns; ecs_vec_t *dst = dst_data->columns; ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ flecs_merge_column(world, &dst_data->entities, &src_data->entities, ECS_SIZEOF(ecs_entity_t), 0, NULL); ecs_assert(dst_data->entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); int32_t column_size = dst_data->entities.size; /* Merge record pointers */ flecs_merge_column(world, &dst_data->records, &src_data->records, ECS_SIZEOF(ecs_record_t*), 0, NULL); for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; ecs_type_info_t *dst_ti = dst_type_info[i_new]; int32_t size = dst_ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); if (dst_id == src_id) { flecs_merge_column(world, &dst[i_new], &src[i_old], size, column_size, dst_ti); flecs_table_mark_table_dirty(world, dst_table, i_new + 1); ecs_assert(dst[i_new].size == dst_data->entities.size, ECS_INTERNAL_ERROR, NULL); i_new ++; i_old ++; } else if (dst_id < src_id) { /* New column, make sure vector is large enough. */ ecs_vec_t *column = &dst[i_new]; ecs_vec_set_count(&world->allocator, column, size, src_count + dst_count); flecs_ctor_component(dst_ti, column, 0, src_count + dst_count); i_new ++; } else if (dst_id > src_id) { /* Old column does not occur in new table, destruct */ ecs_vec_t *column = &src[i_old]; ecs_type_info_t *ti = src_type_info[i_old]; flecs_dtor_component(ti, column, 0, src_count); ecs_vec_fini(&world->allocator, column, ti->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_vec_t *column = &dst[i_new]; ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_set_count(&world->allocator, column, size, src_count + dst_count); flecs_ctor_component(ti, column, 0, src_count + dst_count); } /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { ecs_vec_t *column = &src[i_old]; ecs_type_info_t *ti = src_type_info[i_old]; flecs_dtor_component(ti, column, 0, src_count); ecs_vec_fini(&world->allocator, column, ti->size); } /* Mark entity column as dirty */ flecs_table_mark_table_dirty(world, dst_table, 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); } 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(dst_table); flecs_table_check_sanity(src_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; ecs_record_t **src_records = src_data->records.array; /* 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; if (dst_table != src_table) { record = src_records[i]; ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); } else { 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_merge_table_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_observer_add(dst_table, src_table->observed_count); flecs_table_observer_add(src_table, -src_table->observed_count); ecs_assert(src_table->observed_count == 0, ECS_INTERNAL_ERROR, NULL); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } 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); } int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table) { if (!table->dirty_state) { int32_t column_count = table->storage_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; } 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; } } 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); } } } bool ecs_table_has_module( ecs_table_t *table) { return table->flags & EcsTableHasModule; } ecs_vec_t* ecs_table_column_for_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { ecs_table_t *storage_table = table->storage_table; if (!storage_table) { return NULL; } ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id); if (tr) { return &table->data.columns[tr->column]; } return NULL; } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } ecs_table_t* ecs_table_get_storage_table( const ecs_table_t *table) { return table->storage_table; } int32_t ecs_table_type_to_storage_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 *storage_map = table->storage_map; if (storage_map) { return storage_map[index]; } error: return -1; } int32_t ecs_table_storage_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->storage_map[offset + index]; error: return -1; } 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; } 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; } 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->type.count, ECS_INVALID_PARAMETER, NULL); int32_t storage_index = table->storage_map[index]; if (storage_index == -1) { return NULL; } void *result = table->data.columns[storage_index].array; if (offset) { ecs_size_t size = table->type_info[storage_index]->size; result = ECS_ELEM(result, size, offset); } return result; error: return NULL; } int32_t ecs_table_get_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; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return -1; } return tr->column; error: return -1; } 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_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } 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; } void* ecs_record_get_column( const ecs_record_t *r, int32_t column, size_t c_size) { (void)c_size; ecs_table_t *table = r->table; ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); ecs_type_info_t *ti = table->type_info[column]; ecs_vec_t *c = &table->data.columns[column]; ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, ECS_INVALID_PARAMETER, NULL); return ecs_vec_get(c, ti->size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } void ecs_table_swap_rows( ecs_world_t* world, ecs_table_t* table, int32_t row_1, int32_t row_2) { flecs_table_swap(world, table, row_1, row_2); } int32_t flecs_table_observed_count( const ecs_table_t *table) { return table->observed_count; } /** * @file poly.c * @brief Functions for managing poly objects. * * The poly framework makes it possible to generalize common functionality for * different kinds of API objects, as well as improved type safety checks. Poly * objects have a header that identifiers what kind of object it is. This can * then be used to discover a set of "mixins" implemented by the type. * * Mixins are like a vtable, but for members. Each type populates the table with * offsets to the members that correspond with the mixin. If an entry in the * mixin table is not set, the type does not support the mixin. * * An example is the Iterable mixin, which makes it possible to create an * iterator for any poly object (like filters, queries, the world) that * implements the Iterable mixin. */ static const char* mixin_kind_str[] = { [EcsMixinWorld] = "world", [EcsMixinEntity] = "entity", [EcsMixinObservable] = "observable", [EcsMixinIterable] = "iterable", [EcsMixinDtor] = "dtor", [EcsMixinMax] = "max (should never be requested by application)" }; ecs_mixins_t ecs_world_t_mixins = { .type_name = "ecs_world_t", .elems = { [EcsMixinWorld] = offsetof(ecs_world_t, self), [EcsMixinObservable] = offsetof(ecs_world_t, observable), [EcsMixinIterable] = offsetof(ecs_world_t, iterable) } }; ecs_mixins_t ecs_stage_t_mixins = { .type_name = "ecs_stage_t", .elems = { [EcsMixinWorld] = offsetof(ecs_stage_t, world) } }; ecs_mixins_t ecs_query_t_mixins = { .type_name = "ecs_query_t", .elems = { [EcsMixinWorld] = offsetof(ecs_query_t, filter.world), [EcsMixinEntity] = offsetof(ecs_query_t, filter.entity), [EcsMixinIterable] = offsetof(ecs_query_t, iterable), [EcsMixinDtor] = offsetof(ecs_query_t, dtor) } }; ecs_mixins_t ecs_observer_t_mixins = { .type_name = "ecs_observer_t", .elems = { [EcsMixinWorld] = offsetof(ecs_observer_t, filter.world), [EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity), [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) } }; ecs_mixins_t ecs_filter_t_mixins = { .type_name = "ecs_filter_t", .elems = { [EcsMixinWorld] = offsetof(ecs_filter_t, world), [EcsMixinEntity] = offsetof(ecs_filter_t, entity), [EcsMixinIterable] = offsetof(ecs_filter_t, iterable), [EcsMixinDtor] = offsetof(ecs_filter_t, dtor) } }; static void* assert_mixin( const ecs_poly_t *poly, ecs_mixin_kind_t kind) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); const ecs_mixins_t *mixins = hdr->mixins; ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); ecs_size_t offset = mixins->elems[kind]; ecs_assert(offset != 0, ECS_INVALID_PARAMETER, "mixin %s not available for type %s", mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); (void)mixin_kind_str; /* Object has mixin, return its address */ return ECS_OFFSET(hdr, offset); } void* _ecs_poly_init( ecs_poly_t *poly, int32_t type, ecs_size_t size, ecs_mixins_t *mixins) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_os_memset(poly, 0, size); hdr->magic = ECS_OBJECT_MAGIC; hdr->type = type; hdr->mixins = mixins; return poly; } void _ecs_poly_fini( ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); (void)type; ecs_header_t *hdr = poly; /* Don't deinit poly that wasn't initialized */ ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); hdr->magic = 0; } EcsPoly* _ecs_poly_bind( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { /* Add tag to the entity for easy querying. This will make it possible to * query for `Query` instead of `(Poly, Query) */ if (!ecs_has_id(world, entity, tag)) { ecs_add_id(world, entity, tag); } /* Never defer creation of a poly object */ bool deferred = false; if (ecs_is_deferred(world)) { deferred = true; ecs_defer_suspend(world); } /* If this is a new poly, leave the actual creation up to the caller so they * call tell the difference between a create or an update */ EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); if (deferred) { ecs_defer_resume(world); } return result; } void _ecs_poly_modified( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); } const EcsPoly* _ecs_poly_bind_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { return ecs_get_pair(world, entity, EcsPoly, tag); } ecs_poly_t* _ecs_poly_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { const EcsPoly *p = _ecs_poly_bind_get(world, entity, tag); if (p) { return p->poly; } return NULL; } #define assert_object(cond, file, line, type_name)\ _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ assert(cond) #ifndef FLECS_NDEBUG void* _ecs_poly_assert( const ecs_poly_t *poly, int32_t type, const char *file, int32_t line) { assert_object(poly != NULL, file, line, 0); const ecs_header_t *hdr = poly; const char *type_name = hdr->mixins->type_name; assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); assert_object(hdr->type == type, file, line, type_name); return (ecs_poly_t*)poly; } #endif bool _ecs_poly_is( const ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); return hdr->type == type; } ecs_iterable_t* ecs_get_iterable( const ecs_poly_t *poly) { return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); } ecs_observable_t* ecs_get_observable( const ecs_poly_t *poly) { return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); } const ecs_world_t* ecs_get_world( const ecs_poly_t *poly) { if (((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 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 { 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->storage_count, ECS_NOT_A_COMPONENT, NULL); ecs_type_info_t *ti = table->type_info[column_index]; ecs_vec_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = ti, .ptr = ecs_vec_get(column, ti->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); if (!table->storage_table) { ecs_check(ecs_search(world, table, id, 0) == -1, ECS_NOT_A_COMPONENT, NULL); return (flecs_component_ptr_t){0}; } ecs_table_record_t *tr = flecs_table_record_get( world, table->storage_table, id); if (!tr) { ecs_check(ecs_search(world, table, id, 0) == -1, 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 */ const 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->column, end = tr_isa->count + tr_isa->column; 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); if (!r) { continue; } table = r->table; if (!table) { continue; } const ecs_table_record_t *tr = NULL; ecs_table_t *storage_table = table->storage_table; if (storage_table) { tr = flecs_id_record_get_table(table_index, storage_table); } else { ecs_check(!ecs_owns_id(world, base, id), ECS_NOT_A_COMPONENT, NULL); } if (!tr) { ptr = flecs_get_base_component(world, table, id, table_index, recur_depth + 1); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); ptr = flecs_get_component_w_index(table, tr->column, row).ptr; } } while (!ptr && (i < end)); return ptr; error: return NULL; } static void flecs_instantiate_slot( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance, ecs_entity_t slot_of, ecs_entity_t slot, ecs_entity_t child) { if (base == slot_of) { /* Instance inherits from slot_of, add slot to instance */ ecs_add_pair(world, instance, slot, child); } else { /* Slot is registered for other prefab, travel hierarchy * upwards to find instance that inherits from slot_of */ ecs_entity_t parent = instance; int32_t depth = 0; do { if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { const char *name = ecs_get_name(world, slot); if (name == NULL) { char *slot_of_str = ecs_get_fullpath(world, slot_of); ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " "slot (slots must be named)", slot_of_str); ecs_os_free(slot_of_str); return; } /* The 'slot' variable is currently pointing to a child (or * grandchild) of the current base. Find the original slot by * looking it up under the prefab it was registered. */ if (depth == 0) { /* If the current instance is an instance of slot_of, just * lookup the slot by name, which is faster than having to * create a relative path. */ slot = ecs_lookup_child(world, slot_of, name); } else { /* If the slot is more than one level away from the slot_of * parent, use a relative path to find the slot */ char *path = ecs_get_path_w_sep(world, parent, child, ".", NULL); slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", NULL, false); ecs_os_free(path); } if (slot == 0) { char *slot_of_str = ecs_get_fullpath(world, slot_of); char *slot_str = ecs_get_fullpath(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } ecs_add_pair(world, parent, slot, child); break; } depth ++; } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); if (parent == 0) { char *slot_of_str = ecs_get_fullpath(world, slot_of); char *slot_str = ecs_get_fullpath(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } } error: return; } static ecs_table_t* flecs_find_table_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_add(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_remove(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static void flecs_instantiate_children( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count, ecs_table_t *child_table) { if (!ecs_table_count(child_table)) { return; } ecs_type_t type = child_table->type; ecs_data_t *child_data = &child_table->data; ecs_entity_t slot_of = 0; ecs_entity_t *ids = type.array; int32_t type_count = type.count; /* Instantiate child table for each instance */ /* Create component array for creating the table */ ecs_type_t components = { .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) }; void **component_data = ecs_os_alloca_n(void*, type_count + 1); /* Copy in component identifiers. Find the base index in the component * array, since we'll need this to replace the base with the instance id */ int j, i, childof_base_index = -1, pos = 0; for (i = 0; i < type_count; i ++) { ecs_id_t id = ids[i]; /* If id has DontInherit flag don't inherit it, except for the name * and ChildOf pairs. The name is preserved so applications can lookup * the instantiated children by name. The ChildOf pair is replaced later * with the instance parent. */ if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && ECS_PAIR_FIRST(id) != EcsChildOf) { if (id == EcsUnion) { /* This should eventually be handled by the DontInherit property * but right now there is no way to selectively apply it to * EcsUnion itself: it would also apply to (Union, *) pairs, * which would make all union relationships uninheritable. * * The reason this is explicitly skipped is so that slot * instances don't all end up with the Union property. */ continue; } ecs_table_record_t *tr = &child_table->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdDontInherit) { continue; } } /* If child is a slot, keep track of which parent to add it to, but * don't add slot relationship to child of instance. If this is a child * of a prefab, keep the SlotOf relationship intact. */ if (!(table->flags & EcsTableIsPrefab)) { if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); slot_of = ecs_pair_second(world, id); continue; } } /* Keep track of the element that creates the ChildOf relationship with * the prefab parent. We need to replace this element to make sure the * created children point to the instance and not the prefab */ if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == base)) { childof_base_index = pos; } int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); if (storage_index != -1) { ecs_vec_t *column = &child_data->columns[storage_index]; component_data[pos] = ecs_vec_first(column); } else { component_data[pos] = NULL; } components.array[pos] = id; pos ++; } /* Table must contain children of base */ ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); /* If children are added to a prefab, make sure they are prefabs too */ if (table->flags & EcsTableIsPrefab) { components.array[pos] = EcsPrefab; component_data[pos] = NULL; pos ++; } components.count = pos; /* Instantiate the prefab child table for each new instance */ ecs_entity_t *instances = ecs_vec_first(&table->data.entities); int32_t child_count = ecs_vec_count(&child_data->entities); bool has_union = child_table->flags & EcsTableHasUnion; for (i = row; i < count + row; i ++) { ecs_entity_t instance = instances[i]; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *i_table = NULL; /* Replace ChildOf element in the component array with instance id */ components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); /* Find or create table */ for (j = 0; j < components.count; j ++) { i_table = flecs_find_table_add( world, i_table, components.array[j], &diff); } ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == components.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself * which would cause infinite recursion. */ ecs_entity_t *children = ecs_vec_first(&child_data->entities); #ifdef FLECS_DEBUG for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); } #else /* Bit of boilerplate to ensure that we don't get warnings about the * error label not being used. */ ecs_check(true, ECS_INVALID_OPERATION, NULL); #endif /* Create children */ int32_t child_row; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, &components, child_count, component_data, false, &child_row, &table_diff); flecs_table_diff_builder_fini(world, &diff); /* If children have union relationships, initialize */ if (has_union) { int32_t u, u_count = child_table->sw_count; for (u = 0; u < u_count; u ++) { ecs_switch_t *src_sw = &child_table->data.sw_columns[i]; ecs_switch_t *dst_sw = &i_table->data.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_table_t *base_table = ecs_get_table(world, ecs_get_alive(world, base)); 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; } const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->column - table->sw_offset; ecs_switch_t *sw = &table->data.sw_columns[column]; ecs_entity_t union_case = 0; union_case = ECS_PAIR_SECOND(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|EcsTableHasObserved)) { 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|EcsTableHasObserved))) { 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 ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append(world, table, entity, record, ctor, true); record->table = table; record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); ecs_assert(ecs_vec_count(&table->data.entities) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); return record; } static void flecs_move_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_table_t *src_table = record->table; int32_t src_row = ECS_RECORD_TO_ROW(record->row); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&src_table->data.entities) > src_row, ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); /* Append new row to destination table */ int32_t dst_row = flecs_table_append(world, dst_table, entity, record, false, false); /* Invoke remove actions for removed components */ flecs_notify_on_remove( world, src_table, dst_table, src_row, 1, &diff->removed); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); /* Update entity index & delete old data after running remove actions */ record->table = dst_table; record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); flecs_table_delete(world, src_table, src_row, false); flecs_notify_on_add( world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); 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; uint32_t row_flags = 0; int observed = 0; if (record) { src_table = record->table; row_flags = record->row & ECS_ROW_FLAGS_MASK; observed = (row_flags & EcsEntityObservedAcyclic) != 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_observer_add(dst_table, observed); 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_observer_add(src_table, -observed); } else { flecs_table_observer_add(dst_table, observed); 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 (row_flags) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } if ((!src_table || !src_table->type.count) && world->range_check_enabled) { ecs_check(!world->info.max_id || entity <= world->info.max_id, ECS_OUT_OF_RANGE, 0); ecs_check(entity >= world->info.min_id, ECS_OUT_OF_RANGE, 0); } error: flecs_journal_end(); return; } static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **component_data, bool is_move, int32_t *row_out, ecs_table_diff_t *diff) { int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); } if (!table) { return entities; } ecs_type_t type = table->type; if (!type.count) { return entities; } ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; component_array.array = type.array; component_array.count = type.count; } ecs_data_t *data = &table->data; int32_t row = flecs_table_appendn(world, table, data, count, entities); /* Update entity index. */ int i; ecs_record_t **records = ecs_vec_first(&data->records); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); r->table = table; r->row = ECS_ROW_TO_RECORD(row + i, 0); records[row + i] = r; } flecs_defer_begin(world, &world->stages[0]); flecs_notify_on_add(world, table, NULL, row, count, &diff->added, (component_data == NULL) ? 0 : EcsEventNoOnSet); if (component_data) { int32_t c_i; ecs_table_t *storage_table = table->storage_table; 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, storage_table, id); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t index = tr->column; ecs_type_info_t *ti = table->type_info[index]; ecs_vec_t *column = &table->data.columns[index]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(column, 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); } }; flecs_notify_on_set(world, table, row, count, NULL, true); } flecs_defer_end(world, &world->stages[0]); if (row_out) { *row_out = row; } if (sparse_count) { entities = flecs_sparse_ids(ecs_eis(world)); return &entities[sparse_count]; } else { return entities; } } static void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_id_t id, bool construct) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = record->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, record, dst_table, &diff, construct, EcsEventNoOnSet); /* No OnSet, this function is only called from * functions that are about to set the component. */ } static void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *src_table = r->table; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = r->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static flecs_component_ptr_t flecs_get_mut( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r) { flecs_component_ptr_t dst = {0}; ecs_poly_assert(world, ecs_world_t); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check((id & ECS_COMPONENT_MASK) == id || ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); if (r->table) { dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); if (dst.ptr) { return dst; } } /* If entity didn't have component yet, add it */ flecs_add_id_w_record(world, entity, r, id, true); /* Flush commands so the pointer we're fetching is stable */ flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); error: return dst; } /* -- Private functions -- */ static void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook) { ecs_assert(ti->size != 0, ECS_INVALID_PARAMETER, NULL); 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[0] = 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; flecs_iter_validate(&it); hook(&it); ecs_iter_fini(&it); } void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *ids, bool owned) { ecs_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); ecs_type_t local_ids; if (!ids) { local_ids.array = table->storage_ids; local_ids.count = table->storage_count; ids = &local_ids; } if (owned) { ecs_table_t *storage_table = table->storage_table; 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, storage_table, id); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->column; const ecs_type_info_t *ti = table->type_info[column]; ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_vec_t *c = &table->data.columns[column]; void *ptr = ecs_vec_get(c, ti->size, row); flecs_invoke_hook(world, table, count, 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 }); } } ecs_record_t* flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) { ecs_record_t *record = flecs_entities_get(world, entity); if (!record) { ecs_record_t *r = flecs_entities_ensure(world, entity); r->row = flag; r->table = NULL; } else { if (flag == EcsEntityObservedAcyclic) { if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { flecs_table_observer_add(table, 1); } } } record->row |= flag; } return record; } /* -- 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_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.added = *removed; } flecs_commit(world, entity, record, table, &diff, true, 0); 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_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(unsafe_world->info.last_id < UINT_MAX, ECS_INVALID_OPERATION, NULL); entity = (ecs_entity_t)ecs_os_ainc( (int32_t*)&unsafe_world->info.last_id); } 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_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 < ECS_HI_COMPONENT_ID) { do { id = unsafe_world->info.last_component_id ++; } while (ecs_exists(unsafe_world, id) && id <= ECS_HI_COMPONENT_ID); } if (!id || id >= ECS_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; } #ifdef FLECS_PARSER /* Traverse table graph by either adding or removing identifiers parsed from the * passed in expression. */ static ecs_table_t *flecs_traverse_from_expr( ecs_world_t *world, ecs_table_t *table, const char *name, const char *expr, ecs_table_diff_builder_t *diff, bool replace_and, bool *error) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (!(term.first.flags & (EcsSelf|EcsUp))) { term.first.flags = EcsSelf; } if (!(term.second.flags & (EcsSelf|EcsUp))) { term.second.flags = EcsSelf; } if (!(term.src.flags & (EcsSelf|EcsUp))) { term.src.flags = EcsSelf; } if (ecs_term_finalize(world, &term)) { ecs_term_fini(&term); if (error) { *error = true; } return NULL; } if (!ecs_id_is_valid(world, term.id)) { ecs_term_fini(&term); ecs_parser_error(name, expr, (ptr - expr), "invalid term for add expression"); return NULL; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ table = flecs_find_table_add(world, table, term.id, diff); } ecs_term_fini(&term); } if (!ptr) { if (error) { *error = true; } return NULL; } } return table; } /* Add/remove components based on the parsed expression. This operation is * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ static void flecs_defer_from_expr( ecs_world_t *world, ecs_entity_t entity, const char *name, const char *expr, bool is_add, bool replace_and) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (ecs_term_finalize(world, &term)) { return; } if (!ecs_id_is_valid(world, term.id)) { ecs_term_fini(&term); ecs_parser_error(name, expr, (ptr - expr), "invalid term for add expression"); return; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ if (is_add) { ecs_add_id(world, entity, term.id); } else { ecs_remove_id(world, entity, term.id); } } ecs_term_fini(&term); } } } #endif /* If operation is not deferred, add components by finding the target * table and moving the entity towards it. */ static int flecs_traverse_add( ecs_world_t *world, ecs_entity_t result, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { table = flecs_find_table_add(world, table, ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < ECS_ID_CACHE_SIZE) && (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 < ECS_ID_CACHE_SIZE) && (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) { /* To prevent creating two entities with the same name, temporarily go * out of readonly mode if it's safe to do so. */ ecs_suspend_readonly_state_t state; if (thread_count <= 1) { /* When not running on multiple threads we can temporarily leave * readonly mode which ensures that we don't accidentally create * two entities with the same name. */ ecs_world_t *real_world = flecs_suspend_readonly(world, &state); ecs_add_path_w_sep(real_world, entity, scope, name, sep, root_sep); flecs_resume_readonly(real_world, &state); } else { /* In multithreaded mode we can't leave readonly mode, which means * there is a risk of creating two entities with the same name. * Future improvements will be able to detect this. */ 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); const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name && !name[0]) { name = NULL; } 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 */ ecs_entity_t result = desc->id; 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 < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { if (ECS_HAS_ID_FLAG(id, PAIR) && (ECS_PAIR_FIRST(id) == EcsChildOf)) { scope = ECS_PAIR_SECOND(id); } } result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new_id(world); } flecs_new_entity = true; ecs_assert(ecs_get_type(world, result) == NULL, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure provided id is either alive or revivable */ ecs_ensure(world, result); name_assigned = ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName); if (name && name_assigned) { /* If entity has name, verify that name matches. The name provided * to the function could either have been relative to the current * scope, or fully qualified. */ char *path; ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { /* Fully qualified name was provided, so make sure to * compare with fully qualified name */ path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); } else { /* Relative name was provided, so make sure to compare with * relative name */ path = ecs_get_path_w_sep(world, scope, result, sep, ""); } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); ecs_os_free(path); return 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_sparse_new_ids(ecs_eis(world), count); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { int i; for (i = 0; i < count; i ++) { ecs_ensure(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = (ecs_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_sparse_ids(ecs_eis(world)); return &entities[sparse_count]; } error: return NULL; } 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); ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; ecs_entity_t result = desc->entity; if (!result) { result = ecs_new_low_id(world); } else { ecs_ensure(world, result); new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); if (!ptr->size) { ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; if (!new_component || ptr->size != desc->type.size) { if (!ptr->size) { ecs_trace("#[green]tag#[reset] %s created", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s created", ecs_get_name(world, result)); } } } else { if (ptr->size != desc->type.size) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != desc->type.alignment) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); ecs_os_free(path); } } 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 < ECS_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); if (!r) { return; /* Nothing to clear */ } 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 & EcsEntityObservedAcyclic) { flecs_table_observer_add(table, -1); } } flecs_defer_end(world, stage); error: return; } static void flecs_throw_invalid_delete( ecs_world_t *world, ecs_id_t id) { char *id_str = NULL; if (!(world->flags & EcsWorldQuit)) { id_str = ecs_id_str(world, id); ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); } error: ecs_os_free(id_str); } static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, ecs_entity_t action, bool delete_id) { ecs_marked_id_t *m = ecs_vector_add(&world->store.marked_ids, ecs_marked_id_t); m->idr = idr; m->id = idr->id; m->action = action; m->delete_id = delete_id; flecs_id_record_claim(world, idr); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id); static void flecs_targets_mark_for_delete( ecs_world_t *world, ecs_table_t *table) { ecs_id_record_t *idr; ecs_entity_t *entities = ecs_vec_first(&table->data.entities); ecs_record_t **records = ecs_vec_first(&table->data.records); int32_t i, count = ecs_vec_count(&table->data.entities); for (i = 0; i < count; i ++) { ecs_record_t *r = records[i]; if (!r) { continue; } /* If entity is not used as id or as relationship target, there won't * be any tables with a reference to it. */ ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; if (!(flags & (EcsEntityObservedId|EcsEntityObservedTarget))) { continue; } ecs_entity_t e = entities[i]; if (flags & EcsEntityObservedId) { 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 & EcsEntityObservedTarget) { if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_OBJECT(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_OBJECT(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) { /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ ecs_table_record_t *trr = &table->records[tr->column]; ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; result = ECS_ID_ON_DELETE_OBJECT(idrr->flags); } return result; } static void flecs_update_monitors_for_delete( ecs_world_t *world, ecs_id_t id) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){id}, .count = 1 }); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id) { if (idr->flags & EcsIdMarkedForDelete) { return; } idr->flags |= EcsIdMarkedForDelete; flecs_marked_id_push(world, idr, action, delete_id); ecs_id_t id = idr->id; bool delete_target = flecs_id_is_delete_target(id, action); /* Mark all tables with the id for delete */ ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableMarkedForDelete) { continue; } ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, delete_target); /* If this is a Delete action, recursively mark ids & tables */ if (cur_action == EcsDelete) { table->flags |= EcsTableMarkedForDelete; ecs_log_push_2(); flecs_targets_mark_for_delete(world, table); ecs_log_pop_2(); } else if (cur_action == EcsPanic) { flecs_throw_invalid_delete(world, id); } } } /* Same for empty tables */ if (flecs_table_cache_empty_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { tr->hdr.table->flags |= EcsTableMarkedForDelete; } } /* Signal query cache monitors */ flecs_update_monitors_for_delete(world, id); /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ if (ecs_id_is_wildcard(id)) { ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *cur = idr; if (ECS_PAIR_SECOND(id) == EcsWildcard) { while ((cur = cur->first.next)) { flecs_update_monitors_for_delete(world, cur->id); } } else { ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); while ((cur = cur->second.next)) { flecs_update_monitors_for_delete(world, cur->id); } } } } static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If there's no id record, there's nothing to delete */ return false; } if (!action) { /* If no explicit action is provided, derive it */ if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { /* Delete actions are determined by the component, or in the case * of a pair by the relationship. */ action = ECS_ID_ON_DELETE(idr->flags); } } if (action == EcsPanic) { /* This id is protected from deletion */ flecs_throw_invalid_delete(world, id); return false; } flecs_id_mark_for_delete(world, idr, action, delete_id); return true; } static void flecs_remove_from_table( ecs_world_t *world, ecs_table_t *table) { ecs_table_diff_t temp_diff; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst_table = table; /* To find the dst table, remove all ids that are marked for deletion */ int32_t i, t, count = ecs_vector_count(world->store.marked_ids); ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, ecs_marked_id_t); 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->column; do { ecs_id_t id = dst_table->type.array[t]; dst_table = flecs_table_traverse_remove( world, dst_table, &id, &temp_diff); flecs_table_diff_build_append_table(world, &diff, &temp_diff); } while (dst_table->type.count && (t = ecs_search_offset( world, dst_table, t, idr->id, NULL)) != -1); } ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); if (!dst_table->type.count) { /* If this removes all components, clear table */ flecs_table_clear_entities(world, table); } else { /* Otherwise, merge table into dst_table */ if (dst_table != table) { int32_t table_count = ecs_table_count(table); if (diff.removed.count && table_count) { ecs_log_push_3(); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, table_count, &td.removed); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table, &dst_table->data, &table->data); } } flecs_table_diff_builder_fini(world, &diff); } static bool flecs_on_delete_clear_tables( ecs_world_t *world) { int32_t i, last = ecs_vector_count(world->store.marked_ids), first = 0; ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, ecs_marked_id_t); /* Iterate in reverse order so that DAGs get deleted bottom to top */ 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_vector_first(world->store.marked_ids, ecs_marked_id_t); } /* Check if new ids were marked since we started */ int32_t new_last = ecs_vector_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_vector_count(world->store.marked_ids); ecs_marked_id_t *ids = ecs_vector_first(world->store.marked_ids, ecs_marked_id_t); for (i = 0; i < count; i ++) { 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 (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; } } 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_vector_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_vector_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_vector_first(world->store.marked_ids, ecs_marked_id_t); int32_t i; count = ecs_vector_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_vector_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 & EcsEntityObservedTarget) { 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 & EcsEntityObservedId) { flecs_on_delete(world, entity, 0, true); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); } if (row_flags & EcsEntityObservedAcyclic) { table = r->table; if (table) { flecs_table_observer_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_table_t *src_table; if (!src_r || !(src_table = src_r->table)) { goto done; } ecs_type_t src_type = src_table->type; ecs_table_diff_t diff = { .added = src_type }; ecs_record_t *dst_r = flecs_entities_get(world, dst); flecs_new_entity(world, dst, dst_r, src_table, &diff, true, true); int32_t row = ECS_RECORD_TO_ROW(dst_r->row); if (copy_value) { flecs_table_move(world, dst, src, src_table, row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); flecs_notify_on_set(world, src_table, row, 1, NULL, 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 = NULL; ecs_table_t *storage_table = table->storage_table; if (storage_table) { tr = flecs_id_record_get_table(idr, storage_table); } else { /* If the entity does not have a storage table (has no data) but it does * have the id, the id must be a tag, and getting a tag is illegal. */ ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL); } if (!tr) { return flecs_get_base_component(world, table, id, idr, 0); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_w_index(table, tr->column, row).ptr; error: return NULL; } void* ecs_get_mut_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set( world, stage, EcsOpMut, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_get_mut(world, entity, id, r).ptr; ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return result; error: return NULL; } 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); if (!r) { return 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); } const void* ecs_record_get_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } void* ecs_record_get_mut_id( ecs_world_t *stage, ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); ecs_check(record != NULL, ECS_INVALID_PARAMETER, "cannot create ref for empty entity"); ecs_ref_t result = { .entity = entity, .id = id, .record = record }; ecs_table_t *table = record->table; if (table) { result.tr = flecs_table_record_get(world, table, id); } return result; error: return (ecs_ref_t){0}; } void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return; } ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, ref->id); if (!tr) { return; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } error: return; } void* ecs_ref_get_id( const ecs_world_t *world, ecs_ref_t *ref, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return NULL; } int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, id); if (!tr) { return NULL; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } int32_t column = ecs_table_type_to_storage_index(table, tr->column); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_get_component_w_index(table, column, row).ptr; error: return NULL; } void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, "cannot emplace a component the entity already has"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return ptr; error: return NULL; } static void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; if (!flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } /* If the entity does not have the component, calling ecs_modified is * invalid. The assert needs to happen after the defer statement, as the * entity may not have the component when this function is called while * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } static void flecs_copy_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, EcsOpSet, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); const ecs_type_info_t *ti = dst.ti; ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } } else { ecs_os_memset(dst.ptr, 0, size); } flecs_table_mark_dirty(world, r->table, id); ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } flecs_defer_end(world, stage); error: return; } static void flecs_move_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, cmd_kind, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind == EcsOpEmplace) { move = ti->hooks.move_ctor; } else { move = ti->hooks.move; } if (move) { move(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, id); if (cmd_kind == EcsOpSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } flecs_defer_end(world, stage); error: return; } ecs_entity_t ecs_set_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, size_t size, const void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!entity || ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!entity) { entity = ecs_new_id(world); ecs_entity_t scope = stage->scope; if (scope) { ecs_add_pair(world, entity, EcsChildOf, scope); } } /* Safe to cast away const: function won't modify if move arg is false */ flecs_copy_ptr_w_id(world, stage, entity, id, size, (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_search(world, table, bs_id, 0); } if (index == -1) { ecs_add_id(world, entity, bs_id); ecs_enable_id(world, entity, id, enable); return; } 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->data.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_table_t *table; if (!r || !(table = r->table)) { return false; } ecs_entity_t bs_id = id | ECS_TOGGLE; int32_t index = ecs_search(world, table, bs_id, 0); 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); } index -= table->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &table->data.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(world, entity); ecs_table_t *table; if (!r || !(table = r->table)) { return false; } ecs_id_record_t *idr = flecs_id_record_get(world, id); int32_t column; if (idr) { const 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_switch_t *sw = &table->data.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(id); } } return true; error: return false; } ecs_entity_t ecs_get_target( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); 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_table_t *table; if (!r || !(table = r->table)) { goto not_found; } ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_table_record_t *tr = flecs_table_record_get(world, table, wc); if (!tr) { if (table->flags & EcsTableHasUnion) { wc = ecs_pair(EcsUnion, rel); tr = flecs_table_record_get(world, table, wc); if (tr) { ecs_switch_t *sw = &table->data.sw_columns[ tr->column - table->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_switch_get(sw, row); } } goto look_in_base; } if (index >= tr->count) { index -= tr->count; goto look_in_base; } return ecs_pair_second(world, table->type.array[tr->column + index]); look_in_base: if (table->flags & EcsTableHasIsA) { const ecs_table_record_t *isa_tr = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_assert(isa_tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = table->type.array; int32_t i = isa_tr->column, end = (i + isa_tr->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_target_for_id( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, ecs_id_t id) { 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 { 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; } } 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 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_entity_t entity, ecs_entity_t tag, const char *name) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { entity = ecs_new_id(world); } if (!name) { ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; } EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); 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_init(world, &(ecs_entity_desc_t){ .name = name }); } return flecs_set_identifier(world, entity, EcsName, name); } ecs_entity_t ecs_set_symbol( ecs_world_t *world, ecs_entity_t entity, const char *name) { return flecs_set_identifier(world, entity, EcsSymbol, name); } void ecs_set_alias( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_set_identifier(world, entity, EcsAlias, name); } ecs_id_t ecs_make_pair( ecs_entity_t relationship, ecs_entity_t target) { 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; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); /* 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 & ECS_ID_FLAG_BIT) { return ecs_entity_t_lo(entity) != 0; } /* 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; } if (ecs_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; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_entity_t current = flecs_entities_get_current(world, entity); if (!current || !ecs_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_ensure( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = (ecs_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, ecs_strip_generation(entity)); if (any == entity) { /* If alive and equal to the argument, there's nothing left to do */ return; } /* If the id is currently alive but did not match the argument, fail */ ecs_check(!any, ECS_INVALID_PARAMETER, NULL); /* Set generation if not alive. The sparse set checks if the provided * id matches its own generation which is necessary for alive ids. This * check would cause ecs_ensure to fail if the generation of the 'entity' * argument doesn't match with its generation. * * While this could've been addressed in the sparse set, this is a rare * scenario that can only be triggered by ecs_ensure. Implementing it here * allows the sparse set to not do this check, which is more efficient. */ flecs_entities_set_generation(world, entity); /* Ensure id exists. The underlying datastructure will verify that the * generation count matches the provided one. */ flecs_entities_ensure(world, entity); error: return; } void ecs_ensure_id( ecs_world_t *world, ecs_id_t id) { 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 (ecs_get_alive(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); ecs_ensure(world, r); } if (ecs_get_alive(world, o) == 0) { ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, "second element of pair is not alive"); ecs_ensure(world, o); } } else { ecs_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); if (record) { return record->table; } error: return NULL; } const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity) { ecs_table_t *table = ecs_get_table(world, entity); if (table) { return &table->type; } return NULL; } const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr && ECS_IS_PAIR(id)) { idr = flecs_id_record_get(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); if (!idr || !idr->type_info) { idr = NULL; } if (!idr) { ecs_entity_t first = ecs_pair_first(world, id); if (!first || !ecs_has_id(world, first, EcsTag)) { idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); if (!idr || !idr->type_info) { idr = NULL; } } } } if (idr) { return idr->type_info; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_type_info_get(world, id); } error: return NULL; } ecs_entity_t ecs_get_typeid( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return ti->component; } error: return 0; } ecs_entity_t 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; } 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 }); it.flags |= EcsIterIsFilter; 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_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; } const char* ecs_id_flag_str( ecs_entity_t entity) { if (ECS_HAS_ID_FLAG(entity, PAIR)) { return "PAIR"; } else if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { return "TOGGLE"; } else if (ECS_HAS_ID_FLAG(entity, AND)) { return "AND"; } else if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { return "OVERRIDE"; } else { return "UNKNOWN"; } } void ecs_id_str_buf( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, AND)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t obj = ECS_PAIR_SECOND(id); ecs_entity_t e; if ((e = ecs_get_alive(world, rel))) { rel = e; } if ((e = ecs_get_alive(world, obj))) { obj = e; } ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_entity_t e = id & ECS_COMPONENT_MASK; ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); } error: return; } char* ecs_id_str( const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_id_str_buf(world, id, &buf); return ecs_strbuf_get(&buf); } static void ecs_type_str_buf( const ecs_world_t *world, const ecs_type_t *type, ecs_strbuf_t *buf) { ecs_entity_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; if (i) { ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, ' '); } if (id == 1) { ecs_strbuf_appendlit(buf, "Component"); } else { ecs_id_str_buf(world, id, buf); } } } char* ecs_type_str( const ecs_world_t *world, const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_type_str_buf(world, type, &buf); return ecs_strbuf_get(&buf); } char* ecs_table_str( const ecs_world_t *world, const ecs_table_t *table) { if (table) { return ecs_type_str(world, &table->type); } else { return NULL; } } char* ecs_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); ecs_strbuf_appendlit(&buf, " ["); const ecs_type_t *type = ecs_get_type(world, entity); if (type) { ecs_type_str_buf(world, type, &buf); } ecs_strbuf_appendch(&buf, ']'); return ecs_strbuf_get(&buf); error: return NULL; } static void flecs_flush_bulk_new( ecs_world_t *world, ecs_cmd_t *cmd) { ecs_entity_t *entities = cmd->is._n.entities; if (cmd->id) { int i, count = cmd->is._n.count; for (i = 0; i < count; i ++) { flecs_entities_ensure(world, entities[i]); flecs_add_id(world, entities[i], cmd->id); } } ecs_os_free(entities); } static void flecs_dtor_value( ecs_world_t *world, ecs_id_t id, void *value, int32_t count) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { ecs_size_t size = ti->size; void *ptr; int i; for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { dtor(ptr, 1, ti); } } } static void flecs_discard_cmd( ecs_world_t *world, ecs_cmd_t *cmd) { if (cmd->kind != EcsOpBulkNew) { void *value = cmd->is._1.value; if (value) { flecs_dtor_value(world, cmd->id, value, 1); flecs_stack_free(value, cmd->is._1.size); } } else { ecs_os_free(cmd->is._n.entities); } } static bool flecs_remove_invalid( ecs_world_t *world, ecs_id_t id, ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); if (!flecs_entities_is_valid(world, rel)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } else { ecs_entity_t obj = ECS_PAIR_SECOND(id); if (!flecs_entities_is_valid(world, obj)) { /* Check the relationship's policy for deleted objects */ ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(rel, EcsWildcard)); if (idr) { ecs_entity_t action = ECS_ID_ON_DELETE_OBJECT(idr->flags); if (action == EcsDelete) { /* Entity should be deleted, don't bother checking * other ids */ return false; } else if (action == EcsPanic) { /* If policy is throw this object should not have * been deleted */ flecs_throw_invalid_delete(world, id); } else { *id_out = 0; return true; } } else { *id_out = 0; return true; } } } } else { id &= ECS_COMPONENT_MASK; if (!flecs_entities_is_valid(world, id)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } } return true; } static void flecs_cmd_batch_for_entity( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_entity_t entity, ecs_cmd_t *cmds, int32_t start) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = NULL; if (r) { table = r->table; } world->info.cmd.batched_entity_count ++; ecs_table_t *start_table = table; ecs_cmd_t *cmd; int32_t next_for_entity; ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ int32_t cur = start; ecs_id_t id; bool has_set = false; do { cmd = &cmds[cur]; id = cmd->id; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { /* First command for an entity has a negative index, flip sign */ next_for_entity *= -1; } /* Check if added id is still valid (like is the parent of a ChildOf * pair still alive), if not run cleanup actions for entity */ if (id) { if (flecs_remove_invalid(world, id, &id)) { if (!id) { /* Entity should remain alive but id should not be added */ cmd->kind = EcsOpSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsOpSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsOpAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsOpModified; kind = EcsOpAdd; /* fallthrough */ case EcsOpAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpModified: { if (start_table) { /* If a modified was inserted for an existing component, the value * of the component could have been changed. If this is the case, * call on_set hooks before the OnAdd/OnRemove observers are invoked * when moving the entity to a different table. * This ensures that if OnAdd/OnRemove observers access the modified * component value, the on_set hook has had the opportunity to * run first to set any computed values of the component. */ int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_component_ptr_t ptr = flecs_get_component_ptr( world, start_table, row, cmd->id); if (ptr.ptr) { ecs_type_info_t *ti = ptr.ti; ecs_iter_action_t on_set; if ((on_set = ti->hooks.on_set)) { flecs_invoke_hook(world, start_table, 1, &entity, ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); } } } break; } case EcsOpSet: case EcsOpMut: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; has_set = true; break; case EcsOpEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsOpRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpClear: table = &world->store.root; world->info.cmd.batched_command_count ++; break; default: break; } /* Add, remove and clear operations can be skipped since they have no * side effects besides adding/removing components */ if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) { cmd->kind = EcsOpSkip; } } while ((cur = next_for_entity)); /* Move entity to destination table in single operation */ flecs_table_diff_build_noalloc(diff, &table_diff); flecs_defer_begin(world, &world->stages[0]); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, &world->stages[0]); flecs_table_diff_builder_clear(diff); /* If ids were both removed and set, check if there are ids that were both * set and removed. If so, skip the set command so that the id won't get * re-added */ if (has_set && table_diff.removed.count) { cur = start; do { cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsOpSet: case EcsOpMut: { ecs_id_record_t *idr = cmd->idr; if (!idr) { idr = flecs_id_record_get(world, cmd->id); } if (!flecs_id_record_get_table(idr, table)) { /* Component was deleted */ cmd->kind = EcsOpSkip; world->info.cmd.batched_command_count --; world->info.cmd.discard_count ++; } break; } default: break; } } while ((cur = next_for_entity)); } } /* Leave safe section. Run all deferred commands. */ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); if (stage->defer < 0) { /* Defer suspending makes it possible to do operations on the storage * without flushing the commands in the queue */ return false; } ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); if (!--stage->defer) { /* Test whether we're flushing to another queue or whether we're * flushing to the storage */ bool merge_to_world = false; if (ecs_poly_is(world, ecs_world_t)) { merge_to_world = world->stages[0].defer == 0; } ecs_stage_t *dst_stage = flecs_stage_from_world(&world); if (ecs_vec_count(&stage->commands)) { ecs_vec_t commands = stage->commands; stage->commands.array = NULL; stage->commands.count = 0; stage->commands.size = 0; ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); ecs_stack_t stack = stage->defer_stack; flecs_stack_init(&stage->defer_stack); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_sparse_clear(&stage->cmd_entries); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_valid(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind == EcsOpSkip) || (e && !is_alive)) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsOpAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsOpRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsOpClone: ecs_clone(world, e, id, cmd->is._1.clone_value); break; case EcsOpSet: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsOpEmplace: if (merge_to_world) { ecs_emplace_id(world, e, id); } flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpMut: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpModified: flecs_modified_id_if(world, e, id); world->info.cmd.modified_count ++; break; case EcsOpAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id); world->info.cmd.add_count ++; world->info.cmd.modified_count ++; break; case EcsOpDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsOpClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsOpOnDeleteAction: flecs_on_delete(world, id, e, false); world->info.cmd.other_count ++; break; case EcsOpEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsOpDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsOpBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsOpSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); /* Restore defer queue */ ecs_vec_clear(&commands); stage->commands = commands; /* Restore stack */ flecs_stack_fini(&stage->defer_stack); stage->defer_stack = stack; flecs_stack_reset(&stage->defer_stack); flecs_table_diff_builder_fini(world, &diff); } return true; } return false; } /* Delete operations from queue without executing them. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); if (!--stage->defer) { ecs_vec_t commands = stage->commands; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->defer_stack); flecs_sparse_clear(&stage->cmd_entries); } return true; } error: return false; } /** * @file stage.c * @brief Staging implementation. * * A stage is an object that can be used to temporarily store mutations to a * world while a world is in readonly mode. ECS operations that are invoked on * a stage are stored in a command buffer, which is flushed during sync points, * or manually by the user. * * Stages contain additional state to enable other API functionality without * having to mutate the world, such as setting the current scope, and allocators * that are local to a stage. * * In a multi threaded application, each thread has its own stage which allows * threads to insert mutations without having to lock administration. */ static ecs_cmd_t* flecs_cmd_alloc( ecs_stage_t *stage) { ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->commands, ecs_cmd_t); ecs_os_zeromem(cmd); return cmd; } static ecs_cmd_t* flecs_cmd_new( ecs_stage_t *stage, ecs_entity_t e, bool is_delete, bool can_batch) { if (e) { ecs_vec_t *cmds = &stage->commands; ecs_cmd_entry_t *entry = flecs_sparse_try_t( &stage->cmd_entries, ecs_cmd_entry_t, e); int32_t cur = ecs_vec_count(cmds); if (entry) { int32_t last = entry->last; if (entry->last == -1) { /* Entity was deleted, don't insert command */ return NULL; } if (can_batch) { ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *last_op = &arr[last]; last_op->next_for_entity = cur; if (last == entry->first) { /* Flip sign bit so flush logic can tell which command * is the first for an entity */ last_op->next_for_entity *= -1; } } } else if (can_batch || is_delete) { entry = flecs_sparse_ensure_t(&stage->cmd_entries, ecs_cmd_entry_t, e); entry->first = cur; } if (can_batch) { entry->last = cur; } if (is_delete) { /* Prevent insertion of more commands for entity */ entry->last = -1; } } return flecs_cmd_alloc(stage); } static void flecs_stages_merge( ecs_world_t *world, bool force_merge) { bool is_stage = ecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); bool measure_frame_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; if (measure_frame_time) { ecs_os_get_time(&t_start); } ecs_dbg_3("#[magenta]merge"); ecs_log_push_3(); if (is_stage) { /* Check for consistency if force_merge is enabled. In practice this * function will never get called with force_merge disabled for just * a single stage. */ if (force_merge || stage->auto_merge) { ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, "mismatching defer_begin/defer_end detected"); flecs_defer_end(world, stage); } } else { /* Merge stages. Only merge if the stage has auto_merging turned on, or * if this is a forced merge (like when ecs_merge is called) */ int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); ecs_poly_assert(s, ecs_stage_t); if (force_merge || s->auto_merge) { flecs_defer_end(world, s); } } } flecs_eval_component_monitors(world); if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; /* If stage is asynchronous, deferring is always enabled */ if (stage->async) { flecs_defer_begin(world, stage); } ecs_log_pop_3(); } static void flecs_stage_auto_merge( ecs_world_t *world) { flecs_stages_merge(world, false); } static void flecs_stage_manual_merge( ecs_world_t *world) { flecs_stages_merge(world, true); } bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); (void)world; if (stage->defer < 0) return false; return (++ stage->defer) == 1; } bool flecs_defer_cmd( ecs_stage_t *stage) { if (stage->defer) { return (stage->defer > 0); } stage->defer ++; return false; } bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = EcsOpClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; } return true; } return false; } bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, true, false); if (cmd) { cmd->kind = EcsOpDelete; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpClear; cmd->entity = entity; } return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_alloc(stage); cmd->kind = EcsOpOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = enable ? EcsOpEnable : EcsOpDisable; cmd->entity = entity; cmd->id = id; } return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new_id as this is thread safe */ int i; for (i = 0; i < count; i ++) { ids[i] = ecs_new_id(world); } *ids_out = ids; /* Store data in op */ ecs_cmd_t *cmd = flecs_cmd_alloc(stage); if (cmd) { cmd->kind = EcsOpBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; } return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpAdd; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpRemove; cmd->id = id; cmd->entity = entity; } return true; } return false; } void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t cmd_kind, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value, bool need_value) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (!cmd) { if (need_value) { /* Entity is deleted by a previous command, but we still need to * return a temporary storage to the application. */ cmd_kind = EcsOpSkip; } else { /* No value needs to be returned, we can drop the command */ return NULL; } } /* Find type info for id */ const ecs_type_info_t *ti = NULL; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If idr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (stage->async || (world->flags & EcsWorldMultiThreaded)) { ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); } else { /* When not in multi threaded mode, it's safe to find or * create the id record. */ idr = flecs_id_record_ensure(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); /* Get type_info from id record. We could have called * ecs_get_type_info directly, but since this function can be * expensive for pairs, creating the id record ensures we can * find the type_info quickly for subsequent operations. */ ti = idr->type_info; } } else { ti = idr->type_info; } /* If the id isn't associated with a type, we can't set anything */ ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); /* Make sure the size of the value equals the type size */ ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); size = ti->size; /* Find existing component. Make sure it's owned, so that we won't use the * component of a prefab. */ void *existing = NULL; ecs_table_t *table = NULL, *storage_table; 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 && (storage_table = table->storage_table)) { const ecs_table_record_t *tr = flecs_id_record_get_table( idr, storage_table); if (tr) { /* Entity has the component */ ecs_vec_t *column = &table->data.columns[tr->column]; existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); } } } /* Get existing value from storage */ void *cmd_value = existing; bool emplace = cmd_kind == EcsOpEmplace; /* If the component does not yet exist, create a temporary value. This is * necessary so we can store a component value in the deferred command, * without adding the component to the entity which is not allowed in * deferred mode. */ if (!existing) { ecs_stack_t *stack = &stage->defer_stack; cmd_value = flecs_stack_alloc(stack, size, ti->alignment); /* If the component doesn't yet exist, construct it and move the * provided value into the component, if provided. Don't construct if * this is an emplace operation, in which case the application is * responsible for constructing. */ if (value) { if (emplace) { ecs_move_t move = ti->hooks.move_ctor; if (move) { move(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } } else if (!emplace) { /* If the command is not an emplace, construct the temp storage */ /* Check if entity inherits component */ void *base = NULL; if (table && (table->flags & EcsTableHasIsA)) { base = flecs_get_base_component(world, table, id, idr, 0); } if (!base) { /* Normal ctor */ ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(cmd_value, 1, ti); } } else { /* Override */ ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, base, 1, ti); } else { ecs_os_memcpy(cmd_value, base, size); } } } } else if (value) { /* If component exists and value is provided, copy */ ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(existing, value, 1, ti); } else { ecs_os_memcpy(existing, value, size); } } if (!cmd) { /* If cmd is NULL, entity was already deleted. Check if we need to * insert a command into the queue. */ if (!ti->hooks.dtor) { /* If temporary memory does not need to be destructed, it'll get * freed when the stack allocator is reset. This prevents us * from having to insert a command when the entity was * already deleted. */ return cmd_value; } cmd = flecs_cmd_alloc(stage); } if (!existing) { /* If component didn't exist yet, insert command that will create it */ cmd->kind = cmd_kind; cmd->id = id; cmd->idr = idr; cmd->entity = entity; cmd->is._1.size = size; cmd->is._1.value = cmd_value; } else { /* If component already exists, still insert an Add command to ensure * that any preceding remove commands won't remove the component. If the * operation is a set, also insert a Modified command. */ if (cmd_kind == EcsOpSet) { cmd->kind = EcsOpAddModified; } else { cmd->kind = EcsOpAdd; } cmd->id = id; cmd->entity = entity; } return cmd_value; error: return NULL; } void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage) { /* Execute post frame actions */ ecs_vector_each(stage->post_frame_actions, ecs_action_elem_t, action, { action->action(world, action->ctx); }); ecs_vector_free(stage->post_frame_actions); stage->post_frame_actions = NULL; } void flecs_stage_init( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_init(stage, ecs_stage_t); stage->world = world; stage->thread_ctx = world; stage->auto_merge = true; stage->async = false; flecs_stack_init(&stage->defer_stack); flecs_stack_init(&stage->allocators.iter_stack); flecs_stack_init(&stage->allocators.deser_stack); flecs_allocator_init(&stage->allocator); flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, FLECS_SPARSE_CHUNK_SIZE); ecs_vec_init_t(&stage->allocator, &stage->commands, ecs_cmd_t, 0); flecs_sparse_init_t(&stage->cmd_entries, &stage->allocator, &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } void flecs_stage_fini( ecs_world_t *world, ecs_stage_t *stage) { (void)world; ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); /* Make sure stage has no unmerged data */ ecs_assert(ecs_vec_count(&stage->commands) == 0, ECS_INTERNAL_ERROR, NULL); ecs_poly_fini(stage, ecs_stage_t); flecs_sparse_fini(&stage->cmd_entries); ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); flecs_stack_fini(&stage->defer_stack); flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); flecs_allocator_fini(&stage->allocator); } void ecs_set_stage_count( ecs_world_t *world, int32_t stage_count) { ecs_poly_assert(world, ecs_world_t); /* World must have at least one default stage */ ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), ECS_INTERNAL_ERROR, NULL); bool auto_merge = true; 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; } } 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_stage_t*)world; /* Index 0 is reserved for main stage */ return stage->id; } else if (ecs_poly_is(world, ecs_world_t)) { return 0; } else { ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } ecs_world_t* ecs_get_stage( const ecs_world_t *world, int32_t stage_id) { ecs_poly_assert(world, ecs_world_t); ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); return (ecs_world_t*)&world->stages[stage_id]; error: return NULL; } bool ecs_readonly_begin( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *stage = &world->stages[i]; stage->lookup_path = world->stages[0].lookup_path; ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, "deferred mode cannot be enabled when entering readonly mode"); flecs_defer_begin(world, stage); } bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); /* From this point on, the world is "locked" for mutations, and it is only * allowed to enqueue commands from stages */ ECS_BIT_SET(world->flags, EcsWorldReadonly); /* If world has more than one stage, signal we might be running on multiple * threads. This is a stricter version of readonly mode: while some * mutations like implicit component registration are still allowed in plain * readonly mode, no mutations are allowed when multithreaded. */ if (world->worker_cond) { ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); } return is_readonly; } void ecs_readonly_end( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); /* After this it is safe again to mutate the world directly */ ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); ecs_log_pop_3(); flecs_stage_auto_merge(world); error: return; } void ecs_merge( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); flecs_stage_manual_merge(world); error: return; } void ecs_set_automerge( ecs_world_t *world, bool auto_merge) { /* If a world is provided, set auto_merge globally for the world. This * doesn't actually do anything (the main stage never merges) but it serves * as the default for when stages are created. */ if (ecs_poly_is(world, ecs_world_t)) { world->stages[0].auto_merge = auto_merge; /* Propagate change to all stages */ int i, stage_count = ecs_get_stage_count(world); for (i = 0; i < stage_count; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); stage->auto_merge = auto_merge; } /* If a stage is provided, override the auto_merge value for the individual * stage. This allows an application to control per-stage which stage should * be automatically merged and which one shouldn't */ } else { ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; stage->auto_merge = auto_merge; } } bool ecs_stage_is_readonly( const ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); if (ecs_poly_is(stage, ecs_stage_t)) { if (((ecs_stage_t*)stage)->async) { return false; } } if (world->flags & EcsWorldReadonly) { if (ecs_poly_is(stage, ecs_world_t)) { return true; } } else { if (ecs_poly_is(stage, ecs_stage_t)) { return true; } } return false; } ecs_world_t* ecs_async_stage_new( ecs_world_t *world) { ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); flecs_stage_init(world, stage); stage->id = -1; stage->auto_merge = false; stage->async = true; flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } void ecs_async_stage_free( ecs_world_t *world) { ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL); flecs_stage_fini(stage->world, stage); ecs_os_free(stage); error: return; } bool ecs_stage_is_async( ecs_world_t *stage) { if (!stage) { return false; } if (!ecs_poly_is(stage, ecs_stage_t)) { return false; } return ((ecs_stage_t*)stage)->async; } bool ecs_is_deferred( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->defer != 0; error: return false; } /** * @file 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_CHUNK_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/vector.c * @brief Vector datastructure. * * This is an implementation of a simple vector type. The vector is allocated in * a single block of memory, with the element count, and allocated number of * elements encoded in the block. As this vector is used for user-types it has * been designed to support alignments higher than 8 bytes. This makes the size * of the vector header variable in size. To reduce the overhead associated with * retrieving or computing this size, the functions are wrapped in macro calls * that compute the header size at compile time. * * The API provides a number of _t macros, which accept a size and alignment. * These macros are used when no compile-time type is available. * * The vector guarantees contiguous access to its elements. When an element is * removed from the vector, the last element is copied to the removed element. * * The API requires passing in the type of the vector. This type is used to test * whether the size of the provided type equals the size of the type with which * the vector was created. In release mode this check is not performed. * * When elements are added to the vector, it will automatically resize to the * next power of two. This can change the pointer of the vector, which is why * operations that can increase the vector size, accept a double pointer to the * vector. */ struct ecs_vector_t { int32_t count; int32_t size; #ifndef FLECS_NDEBUG int64_t elem_size; /* Used in debug mode to validate size */ #endif }; /** Resize the vector buffer */ static ecs_vector_t* flecs_vector_resize( ecs_vector_t *vector, int16_t offset, int32_t size) { ecs_vector_t *result = ecs_os_realloc(vector, offset + size); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, 0); return result; } /* -- Public functions -- */ ecs_vector_t* _ecs_vector_new( ecs_size_t elem_size, int16_t offset, int32_t elem_count) { ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vector_t *result = ecs_os_malloc(offset + elem_size * elem_count); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); result->count = 0; result->size = elem_count; #ifndef FLECS_NDEBUG result->elem_size = elem_size; #endif return result; } ecs_vector_t* _ecs_vector_from_array( ecs_size_t elem_size, int16_t offset, int32_t elem_count, void *array) { ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vector_t *result = ecs_os_malloc(offset + elem_size * elem_count); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_memcpy(ECS_OFFSET(result, offset), array, elem_size * elem_count); result->count = elem_count; result->size = elem_count; #ifndef FLECS_NDEBUG result->elem_size = elem_size; #endif return result; } void ecs_vector_free( ecs_vector_t *vector) { ecs_os_free(vector); } void ecs_vector_clear( ecs_vector_t *vector) { if (vector) { vector->count = 0; } } void _ecs_vector_zero( ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset) { void *array = ECS_OFFSET(vector, offset); ecs_os_memset(array, 0, elem_size * vector->count); } void ecs_vector_assert_size( ecs_vector_t *vector, ecs_size_t elem_size) { (void)elem_size; if (vector) { ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); } } void* _ecs_vector_addn( ecs_vector_t **array_inout, ecs_size_t elem_size, int16_t offset, int32_t elem_count) { ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); if (elem_count == 1) { return _ecs_vector_add(array_inout, elem_size, offset); } ecs_vector_t *vector = *array_inout; if (!vector) { vector = _ecs_vector_new(elem_size, offset, 1); *array_inout = vector; } ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t max_count = vector->size; int32_t old_count = vector->count; int32_t new_count = old_count + elem_count; if ((new_count - 1) >= max_count) { if (!max_count) { max_count = elem_count; } else { while (max_count < new_count) { max_count *= 2; } } max_count = flecs_next_pow_of_2(max_count); vector = flecs_vector_resize(vector, offset, max_count * elem_size); vector->size = max_count; *array_inout = vector; } vector->count = new_count; return ECS_OFFSET(vector, offset + elem_size * old_count); } void* _ecs_vector_add( ecs_vector_t **array_inout, ecs_size_t elem_size, int16_t offset) { ecs_assert(array_inout != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vector_t *vector = *array_inout; int32_t count, size; if (vector) { ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); count = vector->count; size = vector->size; if (count >= size) { size *= 2; if (!size) { size = 2; } size = flecs_next_pow_of_2(size); vector = flecs_vector_resize(vector, offset, size * elem_size); *array_inout = vector; vector->size = size; } vector->count = count + 1; return ECS_OFFSET(vector, offset + elem_size * count); } vector = _ecs_vector_new(elem_size, offset, 2); *array_inout = vector; vector->count = 1; vector->size = 2; return ECS_OFFSET(vector, offset); } void* _ecs_vector_insert_at( ecs_vector_t **vec, ecs_size_t elem_size, int16_t offset, int32_t index) { ecs_assert(vec != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = vec[0]->count; if (index == count) { return _ecs_vector_add(vec, elem_size, offset); } ecs_assert(index < count, ECS_INTERNAL_ERROR, NULL); ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); _ecs_vector_add(vec, elem_size, offset); void *start = _ecs_vector_get(*vec, elem_size, offset, index); if (index < count) { ecs_os_memmove(ECS_OFFSET(start, elem_size), start, (count - index) * elem_size); } return start; } int32_t _ecs_vector_move_index( ecs_vector_t **dst, ecs_vector_t *src, ecs_size_t elem_size, int16_t offset, int32_t index) { if (dst && *dst) { ecs_dbg_assert((*dst)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); } ecs_dbg_assert(src->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); void *dst_elem = _ecs_vector_add(dst, elem_size, offset); void *src_elem = _ecs_vector_get(src, elem_size, offset, index); ecs_os_memcpy(dst_elem, src_elem, elem_size); return _ecs_vector_remove(src, elem_size, offset, index); } void ecs_vector_remove_last( ecs_vector_t *vector) { if (vector && vector->count) vector->count --; } bool _ecs_vector_pop( ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset, void *value) { if (!vector) { return false; } ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t count = vector->count; if (!count) { return false; } void *elem = ECS_OFFSET(vector, offset + (count - 1) * elem_size); if (value) { ecs_os_memcpy(value, elem, elem_size); } ecs_vector_remove_last(vector); return true; } int32_t _ecs_vector_remove( ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset, int32_t index) { ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t count = vector->count; void *buffer = ECS_OFFSET(vector, offset); void *elem = ECS_OFFSET(buffer, index * elem_size); ecs_assert(index < count, ECS_INVALID_PARAMETER, NULL); count --; if (index != count) { void *last_elem = ECS_OFFSET(buffer, elem_size * count); ecs_os_memcpy(elem, last_elem, elem_size); } vector->count = count; return count; } void _ecs_vector_reclaim( ecs_vector_t **array_inout, ecs_size_t elem_size, int16_t offset) { ecs_vector_t *vector = *array_inout; if (!vector) { return; } ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t size = vector->size; int32_t count = vector->count; if (count < size) { if (count) { size = count; vector = flecs_vector_resize(vector, offset, size * elem_size); vector->size = size; *array_inout = vector; } else { ecs_vector_free(vector); *array_inout = NULL; } } } int32_t ecs_vector_count( const ecs_vector_t *vector) { if (!vector) { return 0; } return vector->count; } int32_t ecs_vector_size( const ecs_vector_t *vector) { if (!vector) { return 0; } return vector->size; } int32_t _ecs_vector_set_size( ecs_vector_t **array_inout, ecs_size_t elem_size, int16_t offset, int32_t elem_count) { ecs_vector_t *vector = *array_inout; if (!vector) { *array_inout = _ecs_vector_new(elem_size, offset, elem_count); return elem_count; } else { ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t result = vector->size; if (elem_count < vector->count) { elem_count = vector->count; } if (result < elem_count) { elem_count = flecs_next_pow_of_2(elem_count); vector = flecs_vector_resize(vector, offset, elem_count * elem_size); vector->size = elem_count; *array_inout = vector; result = elem_count; } return result; } } int32_t _ecs_vector_grow( ecs_vector_t **array_inout, ecs_size_t elem_size, int16_t offset, int32_t elem_count) { int32_t current = ecs_vector_count(*array_inout); return _ecs_vector_set_size(array_inout, elem_size, offset, current + elem_count); } int32_t _ecs_vector_set_count( ecs_vector_t **array_inout, ecs_size_t elem_size, int16_t offset, int32_t elem_count) { if (!*array_inout) { *array_inout = _ecs_vector_new(elem_size, offset, elem_count); } ecs_dbg_assert((*array_inout)->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); (*array_inout)->count = elem_count; ecs_size_t size = _ecs_vector_set_size(array_inout, elem_size, offset, elem_count); return size; } void* _ecs_vector_first( const ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset) { (void)elem_size; ecs_dbg_assert(!vector || vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); if (vector && vector->size) { return ECS_OFFSET(vector, offset); } else { return NULL; } } void* _ecs_vector_get( const ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset, int32_t index) { ecs_assert(vector != NULL, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(index < vector->count, ECS_INTERNAL_ERROR, NULL); return ECS_OFFSET(vector, offset + elem_size * index); } void* _ecs_vector_last( const ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset) { if (vector) { ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t count = vector->count; if (!count) { return NULL; } else { return ECS_OFFSET(vector, offset + elem_size * (count - 1)); } } else { return NULL; } } int32_t _ecs_vector_set_min_size( ecs_vector_t **vector_inout, ecs_size_t elem_size, int16_t offset, int32_t elem_count) { if (!*vector_inout || (*vector_inout)->size < elem_count) { return _ecs_vector_set_size(vector_inout, elem_size, offset, elem_count); } else { return (*vector_inout)->size; } } int32_t _ecs_vector_set_min_count( ecs_vector_t **vector_inout, ecs_size_t elem_size, int16_t offset, int32_t elem_count) { _ecs_vector_set_min_size(vector_inout, elem_size, offset, elem_count); ecs_vector_t *v = *vector_inout; if (v && v->count < elem_count) { v->count = elem_count; } return v->count; } void _ecs_vector_sort( ecs_vector_t *vector, ecs_size_t elem_size, int16_t offset, ecs_comparator_t compare_action) { if (!vector) { return; } ecs_dbg_assert(vector->elem_size == elem_size, ECS_INTERNAL_ERROR, NULL); int32_t count = vector->count; void *buffer = ECS_OFFSET(vector, offset); if (count > 1) { qsort(buffer, (size_t)count, (size_t)elem_size, compare_action); } } ecs_vector_t* _ecs_vector_copy( const ecs_vector_t *src, ecs_size_t elem_size, int16_t offset) { if (!src) { return NULL; } ecs_vector_t *dst = _ecs_vector_new(elem_size, offset, src->size); ecs_os_memcpy(dst, src, offset + elem_size * src->count); return dst; } /** * @file datastructures/sparse.c * @brief Sparse set data structure. * * The sparse set data structure allows for fast lookups by 64bit key with * variable payload size. Lookup operations are guaranteed to be O(1), in * contrast to ecs_map_t which has O(1) lookup time on average. At a high level * the sparse set works with two arrays: * * dense [ - ][ 3 ][ 1 ][ 4 ] * sparse [ ][ 2 ][ ][ 1 ][ 3 ] * * Indices in the dense array point to the sparse array, and vice versa. The * dense array is guaranteed to contain no holes. By iterating the dense array, * all populated elements in the sparse set can be found without having to skip * empty elements. * * The sparse array is paged, which means that it is split up in memory blocks * (pages, not to be confused with OS pages) of equal size. The page size is * set to 4096 elements. Paging prevents the sparse array from having to grow * to N elements, where N is the largest key used in the set. It also ensures * that payload pointers are stable. * * The sparse set recycles deleted keys. It does this by moving not alive keys * to the end of the dense array, and using a count member that indicates the * last alive member in the dense array. This approach makes it possible to * recycle keys in bulk, by increasing the alive count. * * When a key is deleted, the sparse set increases its generation count. This * generation count is used to test whether a key passed to the sparse set is * still valid, or whether it has been deleted. * * The sparse set is used in a number of places, like for retrieving entity * administration, tables and allocators. */ /** Compute the page index from an id by stripping the first 12 bits */ #define PAGE(index) ((int32_t)((uint32_t)index >> 12)) /** This computes the offset of an index inside a page */ #define OFFSET(index) ((int32_t)index & 0xFFF) /* 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_CHUNK_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_CHUNK_SIZE) : ecs_os_calloc(sparse->size * FLECS_SPARSE_CHUNK_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_CHUNK_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[0]); } static uint64_t flecs_sparse_get_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(sparse->max_id != NULL, ECS_INTERNAL_ERROR, NULL); return sparse->max_id[0]; } 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[0] = 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) { ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); 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_local = UINT64_MAX; result->max_id = &result->max_id_local; 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_set_id_source( ecs_sparse_t * sparse, uint64_t * id_source) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); sparse->max_id = id_source; } 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_CHUNK_SIZE); } } ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); sparse->count = 1; sparse->max_id_local = 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); } const uint64_t* flecs_sparse_new_ids( ecs_sparse_t *sparse, int32_t new_count) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t dense_count = ecs_vec_count(&sparse->dense); int32_t count = sparse->count; int32_t recyclable = dense_count - count; int32_t i, to_create = new_count - recyclable; if (to_create > 0) { flecs_sparse_set_size(sparse, dense_count + to_create); for (i = 0; i < to_create; i ++) { flecs_sparse_create_id(sparse, count + recyclable + i); } } sparse->count += new_count; return ecs_vec_get_t(&sparse->dense, uint64_t, count); } 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 the next unused element in the array, simply increase * the count to make it part of the alive set. */ sparse->count ++; } else 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_set_generation( ecs_sparse_t *sparse, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); uint64_t index_w_gen = index; flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Increase generation */ ecs_vec_get_t(&sparse->dense, uint64_t, dense)[0] = index_w_gen; } else { /* Element is not paired and thus not alive, nothing to be done */ } } bool flecs_sparse_exists( const ecs_sparse_t *sparse, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return false; } flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; return dense != 0; } bool flecs_sparse_is_valid( const ecs_sparse_t *sparse, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return true; /* Doesn't exist yet, so is valid */ } flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense) { return true; /* Doesn't exist yet, so is valid */ } /* If the id exists, it must be alive */ return dense < sparse->count; } 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; } uint64_t flecs_sparse_get_current( const ecs_sparse_t *sparse, uint64_t index) { ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return 0; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); /* If dense is 0 (tombstone) this will return 0 */ return dense_array[dense]; } 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; } int32_t flecs_sparse_not_alive_count( const ecs_sparse_t *sparse) { if (!sparse) { return 0; } return ecs_vec_count(&sparse->dense) - sparse->count; } int32_t flecs_sparse_size( const ecs_sparse_t *sparse) { if (!sparse) { return 0; } return ecs_vec_count(&sparse->dense) - 1; } const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); if (sparse->dense.array) { return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); } else { return NULL; } } void flecs_sparse_set_size( ecs_sparse_t *sparse, int32_t elem_count) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_vec_set_size_t(sparse->allocator, &sparse->dense, uint64_t, elem_count); } static void flecs_sparse_copy_intern( ecs_sparse_t * dst, const ecs_sparse_t * src) { flecs_sparse_set_size(dst, flecs_sparse_size(src)); const uint64_t *indices = flecs_sparse_ids(src); ecs_size_t size = src->size; int32_t i, count = src->count; for (i = 0; i < count - 1; i ++) { uint64_t index = indices[i]; void *src_ptr = flecs_sparse_get(src, size, index); void *dst_ptr = flecs_sparse_ensure(dst, size, index); flecs_sparse_set_generation(dst, index); ecs_os_memcpy(dst_ptr, src_ptr, size); } flecs_sparse_set_id(dst, flecs_sparse_get_id(src)); ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); } void flecs_sparse_copy( ecs_sparse_t *dst, const ecs_sparse_t *src) { if (!src) { return; } flecs_sparse_init(dst, src->allocator, src->page_allocator, src->size); flecs_sparse_copy_intern(dst, src); } void flecs_sparse_restore( ecs_sparse_t * dst, const ecs_sparse_t * src) { ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); dst->count = 1; if (src) { flecs_sparse_copy_intern(dst, src); } } 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/switch_list.c * @brief Interleaved linked list for storing mutually exclusive values. * * Datastructure 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 datastructure 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_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/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); } 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 = (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 = (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/hash.c * @brief Functions for hasing byte arrays to 64bit value. */ #ifdef ECS_TARGET_GNU #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #endif /* See explanation below. The hashing function may read beyond the memory passed * into the hashing function, but only at word boundaries. This should be safe, * but trips up address sanitizers and valgrind. * This ensures clean valgrind logs in debug mode & the best perf in release */ #if !defined(FLECS_NDEBUG) || defined(ADDRESS_SANITIZER) #ifndef VALGRIND #define VALGRIND #endif #endif /* ------------------------------------------------------------------------------- lookup3.c, by Bob Jenkins, May 2006, Public Domain. http://burtleburtle.net/bob/c/lookup3.c ------------------------------------------------------------------------------- */ #ifdef ECS_TARGET_POSIX #include /* attempt to define endianness */ #endif #ifdef ECS_TARGET_LINUX #include /* attempt to define endianness */ #endif /* * My best guess at if you are big-endian or little-endian. This may * need adjustment. */ #if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ __BYTE_ORDER == __LITTLE_ENDIAN) || \ (defined(i386) || defined(__i386__) || defined(__i486__) || \ defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) # define HASH_LITTLE_ENDIAN 1 #elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ __BYTE_ORDER == __BIG_ENDIAN) || \ (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) # define HASH_LITTLE_ENDIAN 0 #else # define HASH_LITTLE_ENDIAN 0 #endif #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) /* ------------------------------------------------------------------------------- mix -- mix 3 32-bit values reversibly. This is reversible, so any information in (a,b,c) before mix() is still in (a,b,c) after mix(). If four pairs of (a,b,c) inputs are run through mix(), or through mix() in reverse, there are at least 32 bits of the output that are sometimes the same for one pair and different for another pair. This was tested for: * pairs that differed by one bit, by two bits, in any combination of top bits of (a,b,c), or in any combination of bottom bits of (a,b,c). * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed the output delta to a Gray code (a^(a>>1)) so a string of 1's (as is commonly produced by subtraction) look like a single 1-bit difference. * the base values were pseudorandom, all zero but one bit set, or all zero plus a counter that starts at zero. Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that satisfy this are 4 6 8 16 19 4 9 15 3 18 27 15 14 9 3 7 17 3 Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing for "differ" defined as + with a one-bit base and a two-bit delta. I used http://burtleburtle.net/bob/hash/avalanche.html to choose the operations, constants, and arrangements of the variables. This does not achieve avalanche. There are input bits of (a,b,c) that fail to affect some output bits of (a,b,c), especially of a. The most thoroughly mixed value is c, but it doesn't really even achieve avalanche in c. This allows some parallelism. Read-after-writes are good at doubling the number of bits affected, so the goal of mixing pulls in the opposite direction as the goal of parallelism. I did what I could. Rotates seem to cost as much as shifts on every machine I could lay my hands on, and rotates are much kinder to the top and bottom bits, so I used rotates. ------------------------------------------------------------------------------- */ #define mix(a,b,c) \ { \ a -= c; a ^= rot(c, 4); c += b; \ b -= a; b ^= rot(a, 6); a += c; \ c -= b; c ^= rot(b, 8); b += a; \ a -= c; a ^= rot(c,16); c += b; \ b -= a; b ^= rot(a,19); a += c; \ c -= b; c ^= rot(b, 4); b += a; \ } /* ------------------------------------------------------------------------------- final -- final mixing of 3 32-bit values (a,b,c) into c Pairs of (a,b,c) values differing in only a few bits will usually produce values of c that look totally different. This was tested for * pairs that differed by one bit, by two bits, in any combination of top bits of (a,b,c), or in any combination of bottom bits of (a,b,c). * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed the output delta to a Gray code (a^(a>>1)) so a string of 1's (as is commonly produced by subtraction) look like a single 1-bit difference. * the base values were pseudorandom, all zero but one bit set, or all zero plus a counter that starts at zero. These constants passed: 14 11 25 16 4 14 24 12 14 25 16 4 14 24 and these came close: 4 8 15 26 3 22 24 10 8 15 26 3 22 24 11 8 15 26 3 22 24 ------------------------------------------------------------------------------- */ #define final(a,b,c) \ { \ c ^= b; c -= rot(b,14); \ a ^= c; a -= rot(c,11); \ b ^= a; b -= rot(a,25); \ c ^= b; c -= rot(b,16); \ a ^= c; a -= rot(c,4); \ b ^= a; b -= rot(a,14); \ c ^= b; c -= rot(b,24); \ } /* * hashlittle2: return 2 32-bit hash values * * This is identical to hashlittle(), except it returns two 32-bit hash * values instead of just one. This is good enough for hash table * lookup with 2^^64 buckets, or if you want a second hash if you're not * happy with the first, or if you want a probably-unique 64-bit ID for * the key. *pc is better mixed than *pb, so use *pc first. If you want * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". */ static void hashlittle2( const void *key, /* the key to hash */ size_t length, /* length of the key */ uint32_t *pc, /* IN: primary initval, OUT: primary hash */ uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ { uint32_t a,b,c; /* internal state */ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ /* Set up the internal state */ a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; c += *pb; u.ptr = key; if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ const uint8_t *k8; (void)k8; /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; b += k[1]; c += k[2]; mix(a,b,c); length -= 12; k += 3; } /*----------------------------- handle the last (probably partial) block */ /* * "k[2]&0xffffff" actually reads beyond the end of the string, but * then masks off the part it's not allowed to read. Because the * string is aligned, the masked-off tail is in the same word as the * rest of the string. Every machine with memory protection I've seen * does it on word boundaries, so is OK with this. But VALGRIND will * still catch it and complain. The masking trick does make the hash * noticably faster for short strings (like English words). */ #ifndef VALGRIND switch(length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=k[1]&0xffffff; a+=k[0]; break; case 6 : b+=k[1]&0xffff; a+=k[0]; break; case 5 : b+=k[1]&0xff; a+=k[0]; break; case 4 : a+=k[0]; break; case 3 : a+=k[0]&0xffffff; break; case 2 : a+=k[0]&0xffff; break; case 1 : a+=k[0]&0xff; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } #else /* make valgrind happy */ k8 = (const uint8_t *)k; switch(length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ case 9 : c+=k8[8]; /* fall through */ case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ case 5 : b+=k8[4]; /* fall through */ case 4 : a+=k[0]; break; case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ case 1 : a+=k8[0]; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } #endif /* !valgrind */ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ const uint8_t *k8; /*--------------- all but last block: aligned reads and different mixing */ while (length > 12) { a += k[0] + (((uint32_t)k[1])<<16); b += k[2] + (((uint32_t)k[3])<<16); c += k[4] + (((uint32_t)k[5])<<16); mix(a,b,c); length -= 12; k += 6; } /*----------------------------- handle the last (probably partial) block */ k8 = (const uint8_t *)k; switch(length) { case 12: c+=k[4]+(((uint32_t)k[5])<<16); b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ case 10: c+=k[4]; b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 9 : c+=k8[8]; /* fall through */ case 8 : b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ case 6 : b+=k[2]; a+=k[0]+(((uint32_t)k[1])<<16); break; case 5 : b+=k8[4]; /* fall through */ case 4 : a+=k[0]+(((uint32_t)k[1])<<16); break; case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=k[0]; break; case 1 : a+=k8[0]; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } } else { /* need to read the key one byte at a time */ const uint8_t *k = (const uint8_t *)key; /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; a += ((uint32_t)k[1])<<8; a += ((uint32_t)k[2])<<16; a += ((uint32_t)k[3])<<24; b += k[4]; b += ((uint32_t)k[5])<<8; b += ((uint32_t)k[6])<<16; b += ((uint32_t)k[7])<<24; c += k[8]; c += ((uint32_t)k[9])<<8; c += ((uint32_t)k[10])<<16; c += ((uint32_t)k[11])<<24; mix(a,b,c); length -= 12; k += 12; } /*-------------------------------- last block: affect all 32 bits of (c) */ switch(length) /* all the case statements fall through */ { case 12: c+=((uint32_t)k[11])<<24; case 11: c+=((uint32_t)k[10])<<16; case 10: c+=((uint32_t)k[9])<<8; case 9 : c+=k[8]; case 8 : b+=((uint32_t)k[7])<<24; case 7 : b+=((uint32_t)k[6])<<16; case 6 : b+=((uint32_t)k[5])<<8; case 5 : b+=k[4]; case 4 : a+=((uint32_t)k[3])<<24; case 3 : a+=((uint32_t)k[2])<<16; case 2 : a+=((uint32_t)k[1])<<8; case 1 : a+=k[0]; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } } final(a,b,c); *pc=c; *pb=b; } uint64_t flecs_hash( const void *data, ecs_size_t length) { uint32_t h_1 = 0; uint32_t h_2 = 0; hashlittle2( data, flecs_ito(size_t, length), &h_1, &h_2); #ifndef __clang_analyzer__ uint64_t h_2_shift = (uint64_t)h_2 << 32; #else uint64_t h_2_shift = 0; #endif return h_1 | h_2_shift; } /** * @file datastructures/qsort.c * @brief Quicksort implementation. */ void ecs_qsort( void *base, ecs_size_t nitems, ecs_size_t size, int (*compar)(const void *, const void*)) { void *tmp = ecs_os_alloca(size); /* For swap */ #define LESS(i, j) \ compar(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j)) < 0 #define SWAP(i, j) \ ecs_os_memcpy(tmp, ECS_ELEM(base, size, i), size),\ ecs_os_memcpy(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j), size),\ ecs_os_memcpy(ECS_ELEM(base, size, j), tmp, size) QSORT(nitems, LESS, SWAP); } /** * @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); 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/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 int 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 (isnan(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "NaN"); return ecs_strbuf_appendch(out, nan_delim); } else { return ecs_strbuf_appendlit(out, "NaN"); } } if (isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "Inf"); return ecs_strbuf_appendch(out, nan_delim); } else { return ecs_strbuf_appendlit(out, "Inf"); } } 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]; 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'; } return 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) { /* Allocate new element */ ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); b->size += b->current->pos; b->current->next = (ecs_strbuf_element*)e; b->current = (ecs_strbuf_element*)e; b->elementCount ++; e->super.buffer_embedded = true; e->super.buf = e->buf; e->super.pos = 0; e->super.next = NULL; } /* Add an extra dynamic element */ static void flecs_strbuf_grow_str( ecs_strbuf_t *b, char *str, char *alloc_str, int32_t size) { /* Allocate new element */ ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); b->size += b->current->pos; b->current->next = (ecs_strbuf_element*)e; b->current = (ecs_strbuf_element*)e; b->elementCount ++; e->super.buffer_embedded = false; e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); e->super.next = NULL; e->super.buf = str; e->alloc_str = alloc_str; } static char* flecs_strbuf_ptr( ecs_strbuf_t *b) { if (b->buf) { return &b->buf[b->current->pos]; } else { return &b->current->buf[b->current->pos]; } } /* Compute the amount of space left in the current element */ static int32_t flecs_strbuf_memLeftInCurrentElement( ecs_strbuf_t *b) { if (b->current->buffer_embedded) { return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; } else { return 0; } } /* Compute the amount of space left */ static int32_t flecs_strbuf_memLeft( ecs_strbuf_t *b) { if (b->max) { return b->max - b->size - b->current->pos; } else { return INT_MAX; } } static void flecs_strbuf_init( ecs_strbuf_t *b) { /* Initialize buffer structure only once */ if (!b->elementCount) { b->size = 0; b->firstElement.super.next = NULL; b->firstElement.super.pos = 0; b->firstElement.super.buffer_embedded = true; b->firstElement.super.buf = b->firstElement.buf; b->elementCount ++; b->current = (ecs_strbuf_element*)&b->firstElement; } } /* Append a format string to a buffer */ static bool flecs_strbuf_vappend( ecs_strbuf_t *b, const char* str, va_list args) { bool result = true; va_list arg_cpy; if (!str) { return result; } flecs_strbuf_init(b); int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); int32_t memLeft = flecs_strbuf_memLeft(b); if (!memLeft) { return false; } /* 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 max_copy = b->buf ? memLeft : memLeftInElement; int32_t memRequired; va_copy(arg_cpy, args); memRequired = vsnprintf( flecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); if (memRequired <= memLeftInElement) { /* Element was large enough to fit string */ b->current->pos += memRequired; } else if ((memRequired - memLeftInElement) < memLeft) { /* If string is a format string, a new buffer of size memRequired is * needed to re-evaluate the format string and only use the part that * wasn't already copied to the previous element */ if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { /* Resulting string fits in standard-size buffer. Note that the * entire string needs to fit, not just the remainder, as the * format string cannot be partially evaluated */ flecs_strbuf_grow(b); /* Copy entire string to new buffer */ ecs_os_vsprintf(flecs_strbuf_ptr(b), str, arg_cpy); /* Ignore the part of the string that was copied into the * previous buffer. The string copied into the new buffer could * be memmoved so that only the remainder is left, but that is * most likely more expensive than just keeping the entire * string. */ /* Update position in buffer */ b->current->pos += memRequired; } else { /* Resulting string does not fit in standard-size buffer. * Allocate a new buffer that can hold the entire string. */ char *dst = ecs_os_malloc(memRequired + 1); ecs_os_vsprintf(dst, str, arg_cpy); flecs_strbuf_grow_str(b, dst, dst, memRequired); } } va_end(arg_cpy); return flecs_strbuf_memLeft(b) > 0; } static bool flecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str, int n) { flecs_strbuf_init(b); int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); int32_t memLeft = flecs_strbuf_memLeft(b); if (memLeft <= 0) { return false; } /* Never write more than what the buffer can store */ if (n > memLeft) { n = memLeft; } if (n <= memLeftInElement) { /* Element was large enough to fit string */ ecs_os_strncpy(flecs_strbuf_ptr(b), str, n); b->current->pos += n; } else if ((n - memLeftInElement) < memLeft) { ecs_os_strncpy(flecs_strbuf_ptr(b), str, memLeftInElement); /* Element was not large enough, but buffer still has space */ b->current->pos += memLeftInElement; n -= memLeftInElement; /* Current element was too small, copy remainder into new element */ if (n < ECS_STRBUF_ELEMENT_SIZE) { /* A standard-size buffer is large enough for the new string */ flecs_strbuf_grow(b); /* Copy the remainder to the new buffer */ if (n) { /* If a max number of characters to write is set, only a * subset of the string should be copied to the buffer */ ecs_os_strncpy( flecs_strbuf_ptr(b), str + memLeftInElement, (size_t)n); } else { ecs_os_strcpy(flecs_strbuf_ptr(b), str + memLeftInElement); } /* Update to number of characters copied to new buffer */ b->current->pos += n; } else { /* String doesn't fit in a single element, strdup */ char *remainder = ecs_os_strdup(str + memLeftInElement); flecs_strbuf_grow_str(b, remainder, remainder, n); } } else { /* Buffer max has been reached */ return false; } return flecs_strbuf_memLeft(b) > 0; } static bool flecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { flecs_strbuf_init(b); int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); int32_t memLeft = flecs_strbuf_memLeft(b); if (memLeft <= 0) { return false; } if (memLeftInElement) { /* Element was large enough to fit string */ flecs_strbuf_ptr(b)[0] = ch; b->current->pos ++; } else { flecs_strbuf_grow(b); flecs_strbuf_ptr(b)[0] = ch; b->current->pos ++; } return flecs_strbuf_memLeft(b) > 0; } bool 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); return flecs_strbuf_vappend(b, fmt, args); } bool 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); bool result = flecs_strbuf_vappend(b, fmt, args); va_end(args); return result; } bool 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); return flecs_strbuf_appendstr(b, str, len); } bool ecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_appendch(b, ch); } bool 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); return ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } bool ecs_strbuf_appendflt( ecs_strbuf_t *b, double flt, char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_ftoa(b, flt, 10, nan_delim); } bool ecs_strbuf_appendstr_zerocpy( ecs_strbuf_t *b, char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_init(b); flecs_strbuf_grow_str(b, str, str, 0); return true; } bool ecs_strbuf_appendstr_zerocpyn( ecs_strbuf_t *b, char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_init(b); flecs_strbuf_grow_str(b, str, str, n); return true; } bool ecs_strbuf_appendstr_zerocpy_const( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); /* Removes const modifier, but logic prevents changing / delete string */ flecs_strbuf_init(b); flecs_strbuf_grow_str(b, (char*)str, NULL, 0); return true; } bool ecs_strbuf_appendstr_zerocpyn_const( 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); /* Removes const modifier, but logic prevents changing / delete string */ flecs_strbuf_init(b); flecs_strbuf_grow_str(b, (char*)str, NULL, n); return true; } bool 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); return flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } bool ecs_strbuf_mergebuff( ecs_strbuf_t *dst_buffer, ecs_strbuf_t *src_buffer) { if (src_buffer->elementCount) { if (src_buffer->buf) { return ecs_strbuf_appendstrn( dst_buffer, src_buffer->buf, src_buffer->length); } else { ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; /* Copy first element as it is inlined in the src buffer */ ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); while ((e = e->next)) { dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); *dst_buffer->current->next = *e; } } *src_buffer = ECS_STRBUF_INIT; } return true; } char* ecs_strbuf_get( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char* result = NULL; if (b->elementCount) { if (b->buf) { b->buf[b->current->pos] = '\0'; result = ecs_os_strdup(b->buf); } else { void *next = NULL; int32_t len = b->size + b->current->pos + 1; ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; result = ecs_os_malloc(len); char* ptr = result; do { ecs_os_memcpy(ptr, e->buf, e->pos); ptr += e->pos; next = e->next; if (e != &b->firstElement.super) { if (!e->buffer_embedded) { ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); } ecs_os_free(e); } } while ((e = next)); result[len - 1] = '\0'; b->length = len; } } else { result = NULL; } b->elementCount = 0; b->content = result; return result; } char *ecs_strbuf_get_small( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t written = ecs_strbuf_written(b); ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); char *buf = b->firstElement.buf; buf[written] = '\0'; return buf; } void ecs_strbuf_reset( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->elementCount && !b->buf) { void *next = NULL; ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; do { next = e->next; if (e != (ecs_strbuf_element*)&b->firstElement) { ecs_os_free(e); } } while ((e = next)); } *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); b->list_sp ++; 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); 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 ++; } bool ecs_strbuf_list_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); return flecs_strbuf_appendch(b, ch); } bool 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); bool result = flecs_strbuf_vappend(b, fmt, args); va_end(args); return result; } bool 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); return ecs_strbuf_appendstr(b, str); } bool 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); return ecs_strbuf_appendstrn(b, str, n); } int32_t ecs_strbuf_written( const ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->current) { return b->size + b->current->pos; } else { return 0; } } /** * @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_DEBUG v->elem_size = size; #endif return v; } void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (v->array) { ecs_dbg_assert(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_dbg_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, ecs_vec_t *v, ecs_size_t size) { ecs_dbg_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_DEBUG , .elem_size = size #endif }; } void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_dbg_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_dbg_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_count( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_dbg_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_dbg_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_dbg_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_dbg_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_dbg_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_dbg_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; } /** * @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*)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, 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)elem; return elem; } else { return (void*)*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*)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/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_USE_OS_ALLOC // #define FLECS_MEMSET_UNINITIALIZED // #endif int64_t ecs_block_allocator_alloc_count = 0; int64_t ecs_block_allocator_free_count = 0; 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; } 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, NULL); #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); #endif if (!ba) return NULL; void *result = flecs_balloc(ba); ecs_os_memset(result, 0, ba->data_size); return result; } void flecs_bfree( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC ecs_os_free(memory); return; #endif 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"); } void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) { void *result; #ifdef FLECS_USE_OS_ALLOC 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; } #endif void *result = flecs_balloc(ba); if (result) { ecs_os_memcpy(result, memory, ba->data_size); } return result; } /** * @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/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->cur; 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->cur = 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) { return (ecs_stack_cursor_t){ .cur = stack->cur, .sp = stack->cur->sp }; } void flecs_stack_restore_cursor( ecs_stack_t *stack, const ecs_stack_cursor_t *cursor) { ecs_stack_page_t *cur = cursor->cur; if (!cur) { return; } if (cur == stack->cur) { if (cursor->sp > stack->cur->sp) { return; } } else if (cur->id > stack->cur->id) { return; } stack->cur = cursor->cur; stack->cur->sp = cursor->sp; } void flecs_stack_reset( ecs_stack_t *stack) { stack->cur = &stack->first; stack->first.sp = 0; } void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); stack->cur = &stack->first; stack->first.data = NULL; } void flecs_stack_fini( ecs_stack_t *stack) { ecs_stack_page_t *next, *cur = &stack->first; ecs_assert(stack->cur == &stack->first, ECS_LEAK_DETECTED, NULL); ecs_assert(stack->cur->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 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) { 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 */ ecs_data_t *data = &table->data; sw = sparse_column->sw_column = &data->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->data.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 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_vec_t *sw_terms = &entity_filter->sw_terms; ecs_vec_t *bs_terms = &entity_filter->bs_terms; ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t); ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t); ecs_term_t *terms = filter->terms; int32_t i, term_count = filter->term_count; entity_filter->has_filter = false; /* 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(id); el->sw_column = NULL; ids[field] = id; entity_filter->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_search(world, table, bs_id, 0); 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; entity_filter->has_filter = true; } } } } void flecs_entity_filter_fini( ecs_world_t *world, ecs_entity_filter_t *entity_filter) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &entity_filter->sw_terms, flecs_switch_term_t); ecs_vec_fini_t(a, &entity_filter->bs_terms, flecs_bitset_term_t); } 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_table_range_t *range = &it->range; bool found = false, next_table = true; 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 { found = true; next_table = false; } } if (sw_terms) { if (flecs_entity_filter_switch_next(table, it, found) == -1) { /* No more elements in sparse column */ if (found) { /* Try again */ next_table = true; found = false; } else { /* Nothing found */ it->bs_offset = 0; break; } } else { found = true; next_table = false; it->bs_offset = range->offset + range->count; } } } while (!found); if (!found) { return 1; } else if (next_table) { return 0; } else { return -1; } } /** * @file addons/log.c * @brief Log addon. */ #ifdef FLECS_LOG #include void ecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf) { 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); ecs_colorize_buf(msg_nocolor, ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); ecs_os_free(msg_nocolor); char *msg = ecs_strbuf_get(&msg_buf); if (msg) { ecs_os_api.log_(level, file, line, msg); ecs_os_free(msg); } else { ecs_os_api.log_(level, file, line, ""); } } void _ecs_print( int level, const char *file, int32_t line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ecs_printv(level, file, line, fmt, args); va_end(args); } void _ecs_logv( int level, const char *file, int32_t line, const char *fmt, va_list args) { if (level > ecs_os_api.log_level_) { return; } _ecs_printv(level, file, line, fmt, args); } void _ecs_log( int level, const char *file, int32_t line, const char *fmt, ...) { if (level > ecs_os_api.log_level_) { return; } va_list args; va_start(args, fmt); _ecs_printv(level, file, line, fmt, args); va_end(args); } void _ecs_log_push( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ ++; } } void _ecs_log_pop( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ --; ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); } } void _ecs_parser_errorv( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the * function is called with (expr - ptr), and expr is NULL. */ column_arg = 0; } int32_t column = flecs_itoi32(column_arg); if (ecs_os_api.log_level_ >= -2) { ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; ecs_strbuf_vappend(&msg_buf, fmt, args); if (expr) { ecs_strbuf_appendch(&msg_buf, '\n'); /* Find start of line by taking column and looking for the * last occurring newline */ if (column != -1) { const char *ptr = &expr[column]; while (ptr[0] != '\n' && ptr > expr) { ptr --; } if (ptr == expr) { /* ptr is already at start of line */ } else { column -= (int32_t)(ptr - expr + 1); expr = ptr + 1; } } /* Strip newlines from current statement, if any */ char *newline_ptr = strchr(expr, '\n'); if (newline_ptr) { /* Strip newline from expr */ ecs_strbuf_appendstrn(&msg_buf, expr, (int32_t)(newline_ptr - expr)); } else { ecs_strbuf_appendstr(&msg_buf, expr); } ecs_strbuf_appendch(&msg_buf, '\n'); if (column != -1) { int32_t c; for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } ecs_strbuf_appendch(&msg_buf, '^'); } } char *msg = ecs_strbuf_get(&msg_buf); ecs_os_err(name, 0, msg); ecs_os_free(msg); } } void _ecs_parser_error( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); _ecs_parser_errorv(name, expr, column, fmt, args); va_end(args); } } void _ecs_abort( int32_t err, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = ecs_vasprintf(fmt, args); va_end(args); _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); ecs_os_free(msg); } else { _ecs_fatal(file, line, "%s", ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } bool _ecs_assert( bool condition, int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (!condition) { if (fmt) { va_list args; va_start(args, fmt); char *msg = ecs_vasprintf(fmt, args); va_end(args); _ecs_fatal(file, line, "assert: %s %s (%s)", cond_str, msg, ecs_strerror(err)); ecs_os_free(msg); } else { _ecs_fatal(file, line, "assert: %s %s", cond_str, ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } return condition; } void _ecs_deprecated( const char *file, int32_t line, const char *msg) { _ecs_err(file, line, "%s", msg); } bool ecs_should_log(int32_t level) { # if !defined(FLECS_LOG_3) if (level == 3) { return false; } # endif # if !defined(FLECS_LOG_2) if (level == 2) { return false; } # endif # if !defined(FLECS_LOG_1) if (level == 1) { return false; } # endif return level <= ecs_os_api.log_level_; } #define ECS_ERR_STR(code) case code: return &(#code[4]) const char* ecs_strerror( int32_t error_code) { switch (error_code) { ECS_ERR_STR(ECS_INVALID_PARAMETER); ECS_ERR_STR(ECS_NOT_A_COMPONENT); ECS_ERR_STR(ECS_INTERNAL_ERROR); ECS_ERR_STR(ECS_ALREADY_DEFINED); ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); ECS_ERR_STR(ECS_NAME_IN_USE); ECS_ERR_STR(ECS_OUT_OF_MEMORY); ECS_ERR_STR(ECS_OPERATION_FAILED); ECS_ERR_STR(ECS_INVALID_CONVERSION); ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_LEAK_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); ECS_ERR_STR(ECS_INVALID_FROM_WORKER); ECS_ERR_STR(ECS_OUT_OF_RANGE); ECS_ERR_STR(ECS_MISSING_OS_API); ECS_ERR_STR(ECS_UNSUPPORTED); ECS_ERR_STR(ECS_ACCESS_VIOLATION); ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); ECS_ERR_STR(ECS_INCONSISTENT_NAME); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); ECS_ERR_STR(ECS_INVALID_OPERATION); ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); ECS_ERR_STR(ECS_LOCKED_STORAGE); ECS_ERR_STR(ECS_ID_IN_USE); } return "unknown error code"; } #else /* Empty bodies for when logging is disabled */ void _ecs_log( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void _ecs_parser_error( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void _ecs_parser_errorv( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void _ecs_abort( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } bool _ecs_assert( bool condition, int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)condition; (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; return true; } #endif int ecs_log_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/pipeline/worker.c * @brief Functions for running pipelines on one or more threads. */ /** * @file addons/system/system.c * @brief Internal types and functions for system addon. */ #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #ifdef FLECS_SYSTEM #define ecs_system_t_magic (0x65637383) #define ecs_system_t_tag EcsSystem extern ecs_mixins_t ecs_system_t_mixins; typedef struct ecs_system_t { ecs_header_t hdr; ecs_run_action_t run; /* See ecs_system_desc_t */ ecs_iter_action_t action; /* See ecs_system_desc_t */ ecs_query_t *query; /* System query */ ecs_entity_t query_entity; /* Entity associated with query */ ecs_entity_t tick_source; /* Tick source associated with system */ /* Schedule parameters */ bool multi_threaded; bool no_readonly; int64_t invoke_count; /* Number of times system is invoked */ ecs_ftime_t time_spent; /* Time spent on running system */ ecs_ftime_t time_passed; /* Time passed since last invocation */ int64_t last_frame; /* Last frame for which the system was considered */ void *ctx; /* Userdata for system */ void *binding_ctx; /* Optional language binding context */ ecs_ctx_free_t ctx_free; ecs_ctx_free_t binding_ctx_free; /* Mixins */ ecs_world_t *world; ecs_entity_t entity; ecs_poly_dtor_t dtor; } ecs_system_t; /* Invoked when system becomes active / inactive */ void ecs_system_activate( ecs_world_t *world, ecs_entity_t system, bool activate, const ecs_system_t *system_data); /* Internal function to run a system */ ecs_entity_t ecs_run_intern( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_current, int32_t stage_count, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param); #endif #endif #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 */ bool multi_threaded; /* Whether systems can be ran multi threaded */ bool no_readonly; /* Whether systems are staged or not */ } ecs_pipeline_op_t; typedef 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 */ } ecs_pipeline_state_t; 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); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// bool flecs_worker_begin( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, bool start_of_frame); void flecs_worker_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_worker_sync( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, ecs_pipeline_op_t **cur_op, int32_t *cur_i); void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); #endif typedef struct ecs_worker_state_t { ecs_stage_t *stage; ecs_pipeline_state_t *pq; } ecs_worker_state_t; /* Worker thread */ static void* flecs_worker(void *arg) { ecs_worker_state_t *state = arg; ecs_stage_t *stage = state->stage; ecs_pipeline_state_t *pq = state->pq; 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((ecs_world_t*)stage, pq, world->info.delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } 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); ecs_os_free(state); return NULL; } static bool flecs_is_multithreaded( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); return ecs_get_stage_count(world) > 1; } static bool flecs_is_main_thread( ecs_stage_t *stage) { return !stage->id; } /* Start threads */ 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); int32_t i; for (i = 0; i < threads - 1; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i + 1); ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(stage, ecs_stage_t); ecs_entity_t pipeline = world->pipeline; ecs_assert(pipeline != 0, ECS_INVALID_OPERATION, NULL); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); ecs_assert(pqc != NULL, ECS_INVALID_OPERATION, NULL); ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_worker_state_t *state = ecs_os_calloc_t(ecs_worker_state_t); state->stage = stage; state->pq = pq; stage->thread = ecs_os_thread_new(flecs_worker, state); ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); } } /* 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 */ static 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"); } /* 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); } /* Signal workers that they can start/resume work */ static 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); } /** Stop workers */ static bool ecs_stop_threads( ecs_world_t *world) { 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; } stage->thread = 0; }; /* If no threads are active, just return */ if (!threads_active) { return false; } /* Make sure all threads are running, to ensure they catch the signal */ flecs_wait_for_workers(world); /* Signal threads should quit */ world->flags |= EcsWorldQuitWorkers; flecs_signal_workers(world); /* Join all threads with main */ for (i = 1; i < count; i ++) { ecs_os_thread_join(stages[i].thread); stages[i].thread = 0; } world->flags &= ~EcsWorldQuitWorkers; ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); /* Deinitialize stages */ ecs_set_stage_count(world, 1); return true; } /* -- Private functions -- */ bool flecs_worker_begin( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, bool start_of_frame) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); bool main_thread = flecs_is_main_thread(stage); bool multi_threaded = flecs_is_multithreaded(world); if (main_thread) { if (ecs_stage_is_readonly(world)) { ecs_assert(!pq->no_readonly, ECS_INTERNAL_ERROR, NULL); ecs_readonly_end(world); pq->no_readonly = false; } flecs_pipeline_update(world, pq, start_of_frame); } ecs_pipeline_op_t *cur_op = pq->cur_op; if (main_thread && (cur_op != NULL)) { pq->no_readonly = cur_op->no_readonly; if (!cur_op->no_readonly) { ecs_readonly_begin(world); } ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, cur_op->multi_threaded); ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); } if (main_thread && multi_threaded) { flecs_signal_workers(world); } return pq->cur_op != NULL; } void flecs_worker_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); if (flecs_is_multithreaded(world)) { if (flecs_is_main_thread(stage)) { flecs_wait_for_sync(world); } else { flecs_sync_worker(world); } } if (flecs_is_main_thread(stage)) { if (ecs_stage_is_readonly(world)) { ecs_readonly_end(world); } } } bool flecs_worker_sync( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, ecs_pipeline_op_t **cur_op, int32_t *cur_i) { ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL); bool main_thread = flecs_is_main_thread(stage); /* Synchronize workers */ flecs_worker_end(world, stage); /* Store the current state of the schedule after we synchronized the * threads, to avoid race conditions. */ if (main_thread) { pq->cur_op = *cur_op; pq->cur_i = *cur_i; } /* Prepare state for running the next part of the schedule */ bool result = flecs_worker_begin(world, stage, pq, false); *cur_op = pq->cur_op; *cur_i = pq->cur_i; return result; } 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); } /* -- Public functions -- */ void ecs_set_threads( ecs_world_t *world, int32_t threads) { ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); int32_t stage_count = ecs_get_stage_count(world); if (stage_count != threads) { /* Stop existing threads */ if (stage_count > 1) { if (ecs_stop_threads(world)) { ecs_os_cond_free(world->worker_cond); ecs_os_cond_free(world->sync_cond); ecs_os_mutex_free(world->sync_mutex); } } /* 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); } } } #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; default: break; } switch(inout) { case EcsIn: case EcsInOut: if (ws == WriteStateToStage) { /* If a system does a get/get_mut, the component is fetched from * the main store so it must be merged first */ return true; } default: 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_search(it->real_world, it->table, ecs_poly_id(EcsSystem), 0); 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_index(world, it.table, EcsEmpty) == -1; int32_t i; for (i = 0; i < it.count; i ++) { ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); 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; } /* 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_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t); #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; } EcsPipeline *pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); flecs_pipeline_update(world, pq->state, true); flecs_run_pipeline((ecs_world_t*)flecs_stage_from_world(&world), pq->state, delta_time); } 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); if (!flecs_worker_begin(world, stage, pq, true)) { return; } ecs_time_t st = {0}; bool main_thread = !stage_index; bool measure_time = main_thread && (world->flags & EcsWorldMeasureSystemTime); ecs_pipeline_op_t *op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); int32_t i = 0; do { 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; if (i == count) { break; } if (measure_time) { ecs_time_measure(&st); } for (; i < count; i ++) { /* Run system if: * - this is the main thread, or if * - the system is multithreaded */ if (main_thread || op->multi_threaded) { ecs_entity_t system = systems[i]; const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t); /* 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; } } if (measure_time) { /* Don't include merge time in system time */ world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } /* Synchronize workers, rebuild pipeline if necessary. Pass current op * and system index to function, so we know where to resume from. */ } while (flecs_worker_sync(world, stage, pq, &op, &i)); if (measure_time) { world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } flecs_worker_end(world, stage); return; } 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); } 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); 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"); int32_t thread_count = ecs_get_stage_count(world); if (thread_count > 1) { ecs_set_threads(world, 1); } world->pipeline = pipeline; if (thread_count > 1) { ecs_set_threads(world, thread_count); } 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_assert(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_assert(query->filter.terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, NULL); 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; } /* -- Module implementation -- */ static void FlecsPipelineFini( ecs_world_t *world, void *ctx) { (void)ctx; if (ecs_get_stage_count(world)) { ecs_set_threads(world, 0); } ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } #define flecs_bootstrap_phase(world, phase, depends_on)\ flecs_bootstrap_tag(world, phase);\ _flecs_bootstrap_phase(world, phase, depends_on) static void _flecs_bootstrap_phase( ecs_world_t *world, ecs_entity_t phase, ecs_entity_t depends_on) { ecs_add_id(world, phase, EcsPhase); if (depends_on) { ecs_add_pair(world, phase, EcsDependsOn, depends_on); } } void FlecsPipelineImport( ecs_world_t *world) { ECS_MODULE(world, FlecsPipeline); ECS_IMPORT(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsPipeline); flecs_bootstrap_tag(world, EcsPhase); /* Create anonymous phases to which the builtin phases will have DependsOn * relationships. This ensures that, for example, EcsOnUpdate doesn't have a * direct DependsOn relationship on EcsPreUpdate, which ensures that when * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ ecs_entity_t phase_0 = ecs_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_init(world, &(ecs_pipeline_desc_t){ .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/monitor.c * @brief Monitor addon. */ #ifdef FLECS_MONITOR ECS_COMPONENT_DECLARE(FlecsMonitor); ECS_COMPONENT_DECLARE(EcsWorldStats); 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 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_init(world, &(ecs_system_desc_t){ .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_init(world, &(ecs_system_desc_t){ .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_init(world, &(ecs_system_desc_t){ .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_init(world, &(ecs_system_desc_t){ .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_init(world, &(ecs_system_desc_t){ .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_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"); 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/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; 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); timer = ecs_set(world, timer, EcsTimer, { .timeout = interval, .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_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_get_mut(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_get_mut(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ptr->active = false; 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; } void FlecsTimerImport( ecs_world_t *world) { ECS_MODULE(world, FlecsTimer); ECS_IMPORT(world, FlecsPipeline); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTimer); flecs_bootstrap_component(world, EcsRateFilter); /* Add EcsTickSource to timers and rate filters */ ecs_system_init(world, &(ecs_system_desc_t){ .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 = EcsOr, .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} }, .callback = AddTickSource }); /* Timer handling */ ecs_system_init(world, &(ecs_system_desc_t){ .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_init(world, &(ecs_system_desc_t){ .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_init(world, &(ecs_system_desc_t){ .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/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) { memcpy(type_name, func_name + ECS_FUNC_NAME_FRONT(const char*, type_name), 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* cpp_func_rchr( const char *func_name, ecs_size_t func_name_len, char ch) { const char *r = strrchr(func_name, ch); if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))) { return NULL; } return r; } static const char* 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) { ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); const char *start = cpp_func_rchr(func_name, f_len, ' '); start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ')')); start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ':')); start = cpp_func_max(start, cpp_func_rchr(func_name, f_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) - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))); ecs_os_memcpy_n(constant_name, start, char, len); constant_name[len] = '\0'; return constant_name; } // Names returned from the name_helper class do not start with :: // but are relative to the root. If the namespace of the type // overlaps with the namespace of the current module, strip it from // the implicit identifier. // This allows for registration of component types that are not in the // module namespace to still be registered under the module scope. const char* ecs_cpp_trim_module( ecs_world_t *world, const char *type_name) { ecs_entity_t scope = ecs_get_scope(world); if (!scope) { return type_name; } char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); if (path) { const char *ptr = strrchr(type_name, ':'); ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); if (ptr) { ptr --; ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); if (name_path_len <= ecs_os_strlen(path)) { if (!ecs_os_strncmp(type_name, path, name_path_len)) { type_name = &type_name[name_path_len + 2]; } } } } ecs_os_free(path); return type_name; } // Validate registered component void ecs_cpp_component_validate( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, size_t size, size_t alignment, bool implicit_name) { /* If entity has a name check if it matches */ if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { if (!implicit_name && id >= EcsFirstUserComponentId) { #ifndef FLECS_NDEBUG char *path = ecs_get_path_w_sep( world, 0, id, "::", NULL); if (ecs_os_strcmp(path, name)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' already registered with name '%s'", name, path); } ecs_os_free(path); #endif } if (symbol) { const char *existing_symbol = ecs_get_symbol(world, id); if (existing_symbol) { if (ecs_os_strcmp(symbol, existing_symbol)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' with symbol '%s' already registered with symbol '%s'", name, symbol, existing_symbol); } } } } else { /* Ensure that the entity id valid */ if (!ecs_is_alive(world, id)) { ecs_ensure(world, id); } /* Register name with entity, so that when the entity is created the * correct id will be resolved from the name. Only do this when the * entity is empty. */ ecs_add_path_w_sep(world, id, 0, name, "::", "::"); } /* If a component was already registered with this id but with a * different size, the ecs_component_init function will fail. */ /* We need to explicitly call ecs_component_init here again. Even though * the component was already registered, it may have been registered * with a different world. This ensures that the component is registered * with the same id for the current world. * If the component was registered already, nothing will change. */ ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ .entity = id, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); (void)ent; ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); } ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, ecs_size_t size, ecs_size_t alignment, bool implicit_name, bool *existing_out) { (void)size; (void)alignment; /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ bool existing = false; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t ent; if (id) { ent = id; } else { ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); existing = ent != 0; } ecs_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); 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); 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) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_add_id(world, id, EcsExclusive); ecs_add_id(world, id, EcsOneOf); ecs_add_id(world, id, EcsTag); flecs_resume_readonly(world, &readonly_state); } 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_init(world, &(ecs_entity_desc_t){ .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 ecs_set_id(world, id, parent, sizeof(int), &value); 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; } #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 static ecs_os_thread_t win_thread_new( ecs_os_thread_callback_t callback, void *arg) { HANDLE *thread = ecs_os_malloc_t(HANDLE); *thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL); return (ecs_os_thread_t)(uintptr_t)thread; } static void* win_thread_join( ecs_os_thread_t thr) { HANDLE *thread = (HANDLE*)(uintptr_t)thr; DWORD r = WaitForSingleObject(*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(count); } static int32_t win_adec( int32_t *count) { return InterlockedDecrement(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 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 double win_time_freq; static ULONG win_current_resolution; static void win_enable_high_timer_resolution(bool enable) { HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); if (!hntdll) { return; } LONG (__stdcall *pNtSetTimerResolution)( ULONG desired, BOOLEAN set, ULONG * current); pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) GetProcAddress(hntdll, "NtSetTimerResolution"); if(!pNtSetTimerResolution) { return; } ULONG current, resolution = 10000; /* 1 ms */ if (!enable && win_current_resolution) { pNtSetTimerResolution(win_current_resolution, 0, ¤t); win_current_resolution = 0; return; } else if (!enable) { return; } if (resolution == win_current_resolution) { return; } if (win_current_resolution) { pNtSetTimerResolution(win_current_resolution, 0, ¤t); } if (pNtSetTimerResolution(resolution, 1, ¤t)) { /* Try setting a lower resolution */ resolution *= 2; if(pNtSetTimerResolution(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)(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.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 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 /* Unsupported */ abort(); #endif } static int32_t posix_adec( int32_t *count) { int32_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #endif } static int64_t posix_lainc( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #endif } static int64_t posix_ladec( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #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.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/plecs.c * @brief Plecs addon. */ #ifdef FLECS_PLECS #include #define TOK_NEWLINE '\n' #define TOK_WITH "with" #define TOK_USING "using" #define TOK_CONST "const" #define STACK_MAX_SIZE (64) 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]; int32_t using_frames[STACK_MAX_SIZE]; int32_t sp; int32_t with_frame; int32_t using_frame; char *annot[STACK_MAX_SIZE]; int32_t annot_count; #ifdef FLECS_EXPR ecs_vars_t vars; char var_name[256]; #endif bool with_stmt; bool scope_assign_stmt; bool using_stmt; bool assign_stmt; bool isa_stmt; bool decl_stmt; bool decl_type; bool const_stmt; int32_t errors; } plecs_state_t; 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 */ #ifdef FLECS_EXPR 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); } #endif 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, "_"); if (is_anonymous) { path = NULL; e = ecs_new_id(world); } if (!e) { e = plecs_lookup(world, path, state, rel, is_subject); } if (!e) { 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; } if (!is_subject) { /* If this is not a subject create an existing empty id, which * ensures that scope & with are not applied */ e = ecs_new_id(world); } e = ecs_add_path(world, e, 0, path); ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); } 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); } } } return e; } 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->using_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 = (char*)ecs_parse_whitespace(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 *pred_name = term->first.name; const char *subj_name = term->src.name; const char *obj_name = term->second.name; if (!subj_name) { subj_name = plecs_set_mask_to_name(term->src.flags); } if (!obj_name) { obj_name = plecs_set_mask_to_name(term->second.flags); } if (!ecs_term_id_is_set(&term->first)) { ecs_parser_error(name, expr, column, "missing predicate 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 pred = plecs_ensure_entity(world, state, pred_name, 0, pred_as_subj); ecs_entity_t subj = plecs_ensure_entity(world, state, subj_name, pred, true); ecs_entity_t obj = 0; if (ecs_term_id_is_set(&term->second)) { obj = plecs_ensure_entity(world, state, obj_name, pred, state->assign_stmt == false); 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->using_stmt && (obj || subj)) { ecs_parser_error(name, expr, column, "invalid predicate/object in using statement"); return -1; } if (state->isa_stmt) { pred = ecs_pair(EcsIsA, pred); } if (subj) { if (!obj) { ecs_add_id(world, subj, pred); state->last_assign_id = pred; } else { ecs_add_pair(world, subj, pred, obj); state->last_object = obj; state->last_assign_id = ecs_pair(pred, obj); } state->last_predicate = pred; state->last_subject = subj; pred_as_subj = false; } else { if (!obj) { /* If no subject or object were provided, use predicate as subj * unless the expression explictly excluded the subject */ if (pred_as_subj) { state->last_subject = pred; subj = pred; } else { state->last_predicate = pred; pred_as_subj = false; } } else { state->last_predicate = pred; state->last_object = obj; pred_as_subj = false; } } /* If this is a with clause (the list of entities between 'with' and scope * open), add subject to the array of with frames */ if (state->with_stmt) { ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); ecs_id_t id; if (obj) { id = ecs_pair(pred, obj); } else { id = pred; } state->with[state->with_frame ++] = id; } else if (state->using_stmt) { ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(obj == 0, ECS_INTERNAL_ERROR, NULL); state->using[state->using_frame ++] = pred; state->using_frames[state->sp] = state->using_frame; /* If this is not a with/using clause, add with frames to subject */ } else { if (subj) { int32_t i, frame_count = state->with_frames[state->sp]; for (i = 0; i < frame_count; i ++) { ecs_add_id(world, subj, state->with[i]); } } } /* 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; } #ifdef FLECS_EXPR 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_value_t value = {0}; ecs_expr_var_t *var = NULL; if (state->last_assign_id) { value.type = state->last_assign_id; value.ptr = ecs_value_new(world, state->last_assign_id); 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) { return NULL; } if (var) { 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 { var = ecs_vars_declare_w_value( &state->vars, state->var_name, &value); if (!var) { return NULL; } } return ptr; } #endif static const char* plecs_parse_assign_expr( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { (void)world; if (state->const_stmt) { #ifdef FLECS_EXPR return plecs_parse_assign_var_expr(world, name, expr, ptr, state); #else ecs_parser_error(name, expr, ptr - expr, "variables not supported, missing FLECS_EXPR addon"); #endif } 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; } #ifndef FLECS_EXPR ecs_parser_error(name, expr, ptr - expr, "cannot parse value, missing FLECS_EXPR addon"); return NULL; #else 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; } void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); ptr = ecs_parse_expr(world, ptr, &(ecs_value_t){type, value_ptr}, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { return NULL; } ecs_modified_id(world, assign_to, assign_id); #endif 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_using_stmt( 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; } /* Add following expressions to using list */ state->using_stmt = true; return ptr + 5; } 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; } #ifdef FLECS_EXPR static const char* plecs_parse_const_stmt( 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 || ptr[0] != '=') { return NULL; } state->const_stmt = true; return ptr + 1; } #endif 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; 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; } else { state->scope[state->sp] = state->scope[state->sp - 1]; state->default_scope_type[state->sp] = state->default_scope_type[state->sp - 1]; } state->using_frames[state->sp] = state->using_frame; state->with_frames[state->sp] = state->with_frame; state->with_stmt = false; #ifdef FLECS_EXPR ecs_vars_push(&state->vars); #endif return ptr; } 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; } 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); } state->with_frame = state->with_frames[state->sp]; state->using_frame = state->using_frames[state->sp]; state->last_subject = 0; state->assign_stmt = false; #ifdef FLECS_EXPR ecs_vars_pop(&state->vars); #endif return ptr; } static const char *plecs_parse_plecs_term( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_term_t term = {0}; ecs_entity_t decl_id = 0; if (state->decl_stmt) { decl_id = state->last_predicate; } ptr = ecs_parse_term(world, name, expr, ptr, &term); if (!ptr) { return NULL; } if (flecs_isident(ptr[0])) { state->decl_type = true; } if (!ecs_term_is_initialized(&term)) { ecs_parser_error(name, expr, ptr - expr, "expected identifier"); return NULL; /* No term found */ } if (plecs_create_term(world, &term, name, expr, (ptr - expr), state)) { ecs_term_fini(&term); return NULL; /* Failed to create term */ } if (decl_id && state->last_subject) { ecs_add_id(world, state->last_subject, decl_id); } state->decl_type = false; ecs_term_fini(&term); return ptr; } static const char* plecs_parse_annotation( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { do { if(state->annot_count >= STACK_MAX_SIZE) { ecs_parser_error(name, expr, ptr - expr, "max number of annotations reached"); return NULL; } char ch; const char *start = ptr; for (; (ch = *ptr) && ch != '\n'; ptr ++) { } int32_t len = (int32_t)(ptr - start); char *annot = ecs_os_malloc_n(char, len + 1); ecs_os_memcpy_n(annot, start, char, len); annot[len] = '\0'; state->annot[state->annot_count] = annot; state->annot_count ++; ptr = ecs_parse_fluff(ptr, NULL); } 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->using_stmt = false; state->decl_stmt = false; state->const_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 = ecs_parse_fluff(ptr, NULL); char ch = ptr[0]; if (!ch) { goto done; } else if (ch == '{') { ptr = ecs_parse_fluff(ptr + 1, NULL); goto scope_open; } else if (ch == '}') { ptr = ecs_parse_fluff(ptr + 1, NULL); goto scope_close; } else if (ch == '-') { ptr = ecs_parse_fluff(ptr + 1, NULL); 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(name, expr, ptr, state); if (!ptr) goto error; goto term_expr; } 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; #ifdef FLECS_EXPR } else if (!ecs_os_strncmp(ptr, TOK_CONST " ", 6)) { ptr = plecs_parse_const_stmt(name, expr, ptr, state); if (!ptr) goto error; goto assign_expr; #endif } else { goto term_expr; } term_expr: if (!ptr[0]) { goto done; } if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { goto error; } const char *tptr = ecs_parse_whitespace(ptr); if (flecs_isident(tptr[0])) { if (state->decl_stmt) { ecs_parser_error(name, expr, (ptr - expr), "unexpected ' ' in declaration statement"); } ptr = tptr; goto decl_stmt; } ptr = ecs_parse_fluff(ptr, NULL); if (!state->using_stmt) { if (ptr[0] == ':' && ptr[1] == '-') { ptr = ecs_parse_fluff(ptr + 2, NULL); goto assign_stmt; } else if (ptr[0] == ':') { ptr = ecs_parse_fluff(ptr + 1, NULL); goto inherit_stmt; } else if (ptr[0] == ',') { ptr = ecs_parse_fluff(ptr + 1, NULL); goto term_expr; } else if (ptr[0] == '{') { if (state->assign_stmt) { goto assign_expr; } else { ptr = ecs_parse_fluff(ptr + 1, NULL); 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 = ecs_parse_fluff(ptr, NULL); /* Assignment without a preceding component */ if (ptr[0] == '{') { goto assign_expr; } /* Expect component identifiers */ goto term_expr; assign_expr: ptr = plecs_parse_assign_expr(world, name, expr, ptr, state); if (!ptr) goto error; ptr = ecs_parse_fluff(ptr, NULL); if (ptr[0] == ',') { ptr ++; goto term_expr; } else if (ptr[0] == '{') { if (state->const_stmt) { #ifdef FLECS_EXPR const 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); goto done; } #else ecs_parser_error(name, expr, (ptr - expr), "variables not supported, missing FLECS_EXPR addon"); #endif } ecs_parser_error(name, expr, (ptr - expr), "unexpected '{' after assignment"); 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; } int ecs_plecs_from_str( ecs_world_t *world, const char *name, const char *expr) { 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); #ifdef FLECS_EXPR ecs_vars_init(world, &state.vars); #endif 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.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; } #ifdef FLECS_EXPR ecs_vars_fini(&state.vars); #endif return 0; error: #ifdef FLECS_EXPR ecs_vars_fini(&state.vars); #endif ecs_set_scope(world, state.scope[0]); ecs_set_with(world, prev_with); ecs_term_fini(&term); return -1; } int ecs_plecs_from_file( ecs_world_t *world, const char *filename) { FILE* file; char* content = NULL; int32_t bytes; size_t size; /* Open file for reading */ ecs_os_fopen(&file, filename, "r"); if (!file) { ecs_err("%s (%s)", ecs_os_strerror(errno), filename); goto error; } /* Determine file size */ fseek(file, 0 , SEEK_END); bytes = (int32_t)ftell(file); if (bytes == -1) { goto error; } rewind(file); /* Load contents in memory */ content = ecs_os_malloc(bytes + 1); size = (size_t)bytes; if (!(size = fread(content, 1, size, file)) && bytes) { ecs_err("%s: read zero bytes instead of %d", filename, size); ecs_os_free(content); content = NULL; goto error; } else { content[size] = '\0'; } fclose(file); int result = ecs_plecs_from_str(world, filename, content); ecs_os_free(content); return result; error: ecs_os_free(content); return -1; } #endif /** * @file addons/journal.c * @brief Journal addon. */ #ifdef FLECS_JOURNAL static char* flecs_journal_entitystr( ecs_world_t *world, ecs_entity_t entity) { char *path; const char *_path = ecs_get_symbol(world, entity); if (_path && !strchr(_path, '.')) { path = ecs_asprintf("#[blue]%s", _path); } else { uint32_t gen = entity >> 32; if (gen) { path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); } else { path = ecs_asprintf("#[normal]_%u", (uint32_t)entity); } } return path; } static char* flecs_journal_idstr( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *second_path = flecs_journal_entitystr(world, ecs_pair_second(world, id)); char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", first_path, second_path); ecs_os_free(first_path); ecs_os_free(second_path); return result; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_journal_entitystr(world, id); } else { return ecs_id_str(world, id); } } static int flecs_journal_sp = 0; void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove) { flecs_journal_sp ++; if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { return; } char *path = NULL; char *var_id = NULL; if (entity) { path = ecs_get_fullpath(world, entity); var_id = flecs_journal_entitystr(world, entity); } if (kind == EcsJournalNew) { ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); ecs_print(4, "#[green]ecs_entity_t %s;", var_id); ecs_print(4, "#[magenta]#endif"); ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " "#[grey] // %s = new()", var_id, path); } if (add) { for (int i = 0; i < add->count; i ++) { char *jidstr = flecs_journal_idstr(world, add->array[i]); char *idstr = ecs_id_str(world, add->array[i]); ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " "#[grey] // add(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (remove) { for (int i = 0; i < remove->count; i ++) { char *jidstr = flecs_journal_idstr(world, remove->array[i]); char *idstr = ecs_id_str(world, remove->array[i]); ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " "#[grey] // remove(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (kind == EcsJournalClear) { ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " "#[grey] // clear(%s)", var_id, path); } else if (kind == EcsJournalDelete) { ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " "#[grey] // delete(%s)", var_id, path); } else if (kind == EcsJournalDeleteWith) { ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " "#[grey] // delete_with(%s)", var_id, path); } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); } else if (kind == EcsJournalTableEvents) { ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); ecs_log_push(); } void flecs_journal_end(void) { flecs_journal_sp --; ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } #endif /** * @file addons/rules.c * @brief Rules addon. */ #ifdef FLECS_RULES #define ECS_RULE_MAX_VAR_COUNT (32) #define RULE_PAIR_PREDICATE (1) #define RULE_PAIR_OBJECT (2) /* A rule pair contains a predicate and object that can be stored in a register. */ typedef struct ecs_rule_pair_t { union { int32_t reg; ecs_entity_t id; } first; union { int32_t reg; ecs_entity_t id; } second; int32_t reg_mask; /* bit 1 = predicate, bit 2 = object */ bool transitive; /* Is predicate transitive */ bool final; /* Is predicate final */ bool reflexive; /* Is predicate reflexive */ bool acyclic; /* Is predicate acyclic */ bool second_0; } ecs_rule_pair_t; /* Filter for evaluating & reifing types and variables. Filters are created ad- * hoc from pairs, and take into account all variables that had been resolved * up to that point. */ typedef struct ecs_rule_filter_t { ecs_id_t mask; /* Mask with wildcard in place of variables */ bool wildcard; /* Does the filter contain wildcards */ bool first_wildcard; /* Is predicate a wildcard */ bool second_wildcard; /* Is object a wildcard */ bool same_var; /* True if first & second are both the same variable */ int32_t hi_var; /* If hi part should be stored in var, this is the var id */ int32_t lo_var; /* If lo part should be stored in var, this is the var id */ } ecs_rule_filter_t; /* A rule register stores temporary values for rule variables */ typedef enum ecs_rule_var_kind_t { EcsRuleVarKindTable, /* Used for sorting, must be smallest */ EcsRuleVarKindEntity, EcsRuleVarKindUnknown } ecs_rule_var_kind_t; /* Operations describe how the rule should be evaluated */ typedef enum ecs_rule_op_kind_t { EcsRuleInput, /* Input placeholder, first instruction in every rule */ EcsRuleSelect, /* Selects all ables for a given predicate */ EcsRuleWith, /* Applies a filter to a table or entity */ EcsRuleSubSet, /* Finds all subsets for transitive relationship */ EcsRuleSuperSet, /* Finds all supersets for a transitive relationship */ EcsRuleStore, /* Store entity in table or entity variable */ EcsRuleEach, /* Forwards each entity in a table */ EcsRuleSetJmp, /* Set label for jump operation to one of two values */ EcsRuleJump, /* Jump to an operation label */ EcsRuleNot, /* Invert result of an operation */ EcsRuleInTable, /* Test if entity (subject) is in table (r_in) */ EcsRuleEq, /* Test if entity in (subject) and (r_in) are equal */ EcsRuleYield /* Yield result */ } ecs_rule_op_kind_t; /* Single operation */ typedef struct ecs_rule_op_t { ecs_rule_op_kind_t kind; /* What kind of operation is it */ ecs_rule_pair_t filter; /* Parameter that contains optional filter */ ecs_entity_t subject; /* If set, operation has a constant subject */ int32_t on_pass; /* Jump location when match succeeds */ int32_t on_fail; /* Jump location when match fails */ int32_t frame; /* Register frame */ int32_t term; /* Corresponding term index in signature */ int32_t r_in; /* Optional In/Out registers */ int32_t r_out; bool has_in, has_out; /* Keep track of whether operation uses input * and/or output registers. This helps with * debugging rule programs. */ } ecs_rule_op_t; /* With context. Shared with select. */ typedef struct ecs_rule_with_ctx_t { ecs_id_record_t *idr; /* Currently evaluated table set */ ecs_table_cache_iter_t it; int32_t column; } ecs_rule_with_ctx_t; /* Subset context */ typedef struct ecs_rule_subset_frame_t { ecs_rule_with_ctx_t with_ctx; ecs_table_t *table; int32_t row; int32_t column; } ecs_rule_subset_frame_t; typedef struct ecs_rule_subset_ctx_t { ecs_rule_subset_frame_t storage[16]; /* Alloc-free array for small trees */ ecs_rule_subset_frame_t *stack; int32_t sp; } ecs_rule_subset_ctx_t; /* Superset context */ typedef struct ecs_rule_superset_frame_t { ecs_table_t *table; int32_t column; } ecs_rule_superset_frame_t; typedef struct ecs_rule_superset_ctx_t { ecs_rule_superset_frame_t storage[16]; /* Alloc-free array for small trees */ ecs_rule_superset_frame_t *stack; ecs_id_record_t *idr; int32_t sp; } ecs_rule_superset_ctx_t; /* Each context */ typedef struct ecs_rule_each_ctx_t { int32_t row; /* Currently evaluated row in evaluated table */ } ecs_rule_each_ctx_t; /* Jump context */ typedef struct ecs_rule_setjmp_ctx_t { int32_t label; /* Operation label to jump to */ } ecs_rule_setjmp_ctx_t; /* Operation context. This is a per-operation, per-iterator structure that * stores information for stateful operations. */ typedef struct ecs_rule_op_ctx_t { union { ecs_rule_subset_ctx_t subset; ecs_rule_superset_ctx_t superset; ecs_rule_with_ctx_t with; ecs_rule_each_ctx_t each; ecs_rule_setjmp_ctx_t setjmp; } is; } ecs_rule_op_ctx_t; /* Rule variables allow for the rule to be parameterized */ typedef struct ecs_rule_var_t { ecs_rule_var_kind_t kind; char *name; /* Variable name */ int32_t id; /* Unique variable id */ int32_t other; /* Id to table variable (-1 if none exists) */ int32_t occurs; /* Number of occurrences (used for operation ordering) */ int32_t depth; /* Depth in dependency tree (used for operation ordering) */ bool marked; /* Used for cycle detection */ } ecs_rule_var_t; /* Variable ids per term */ typedef struct ecs_rule_term_vars_t { int32_t first; int32_t src; int32_t second; } ecs_rule_term_vars_t; /* Top-level rule datastructure */ struct ecs_rule_t { ecs_header_t hdr; ecs_rule_op_t *operations; /* Operations array */ ecs_filter_t filter; /* Filter of rule */ /* Variable array */ ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; /* Passed to iterator */ char *var_names[ECS_RULE_MAX_VAR_COUNT]; /* Variable ids used in terms */ ecs_rule_term_vars_t term_vars[ECS_RULE_MAX_VAR_COUNT]; /* Variable evaluation order */ int32_t var_eval_order[ECS_RULE_MAX_VAR_COUNT]; int32_t var_count; /* Number of variables in signature */ int32_t subj_var_count; int32_t frame_count; /* Number of register frames */ int32_t operation_count; /* Number of operations in rule */ ecs_iterable_t iterable; /* Iterable mixin */ ecs_poly_dtor_t dtor; }; /* ecs_rule_t mixins */ 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) } }; static void rule_error( const ecs_rule_t *rule, const char *fmt, ...) { char *fstr = ecs_filter_str(rule->filter.world, &rule->filter); va_list valist; va_start(valist, fmt); const char *name = NULL; if (rule->filter.entity) { name = ecs_get_name(rule->filter.world, rule->filter.entity); } ecs_parser_errorv(name, fstr, -1, fmt, valist); va_end(valist); ecs_os_free(fstr); } static bool subj_is_set( ecs_term_t *term) { return ecs_term_id_is_set(&term->src); } static bool obj_is_set( ecs_term_t *term) { return ecs_term_id_is_set(&term->second) || ECS_HAS_ID_FLAG(term->id_flags, PAIR); } static ecs_rule_op_t* create_operation( ecs_rule_t *rule) { int32_t cur = rule->operation_count ++; rule->operations = ecs_os_realloc( rule->operations, (cur + 1) * ECS_SIZEOF(ecs_rule_op_t)); ecs_rule_op_t *result = &rule->operations[cur]; ecs_os_memset_t(result, 0, ecs_rule_op_t); return result; } static const char* get_var_name(const char *name) { if (name && !ecs_os_strcmp(name, "This")) { /* Make sure that both This and . resolve to the same variable */ name = "."; } return name; } static ecs_rule_var_t* create_variable( ecs_rule_t *rule, ecs_rule_var_kind_t kind, const char *name) { int32_t cur = ++ rule->var_count; name = get_var_name(name); if (name && !ecs_os_strcmp(name, "*")) { /* Wildcards are treated as anonymous variables */ name = NULL; } ecs_rule_var_t *var = &rule->vars[cur - 1]; if (name) { var->name = ecs_os_strdup(name); } else { /* Anonymous register */ char name_buff[32]; ecs_os_sprintf(name_buff, "_%u", cur - 1); var->name = ecs_os_strdup(name_buff); } var->kind = kind; /* The variable id is the location in the variable array and also points to * the register element that corresponds with the variable. */ var->id = cur - 1; /* Depth is used to calculate how far the variable is from the root, where * the root is the variable with 0 dependencies. */ var->depth = UINT8_MAX; var->marked = false; var->occurs = 0; return var; } static ecs_rule_var_t* create_anonymous_variable( ecs_rule_t *rule, ecs_rule_var_kind_t kind) { return create_variable(rule, kind, NULL); } /* Find variable with specified name and type. If Unknown is provided as type, * the function will return any variable with the provided name. The root * variable can occur both as a table and entity variable, as some rules * require that each entity in a table is iterated. In this case, there are two * variables, one for the table and one for the entities in the table, that both * have the same name. */ static ecs_rule_var_t* find_variable( const ecs_rule_t *rule, ecs_rule_var_kind_t kind, const char *name) { ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); name = get_var_name(name); const ecs_rule_var_t *variables = rule->vars; int32_t i, count = rule->var_count; for (i = 0; i < count; i ++) { const ecs_rule_var_t *variable = &variables[i]; if (!ecs_os_strcmp(name, variable->name)) { if (kind == EcsRuleVarKindUnknown || kind == variable->kind) { return (ecs_rule_var_t*)variable; } } } return NULL; } /* Ensure variable with specified name and type exists. If an existing variable * is found with an unknown type, its type will be overwritten with the * specified type. During the variable ordering phase it is not yet clear which * variable is the root. Which variable is the root determines its type, which * is why during this phase variables are still untyped. */ static ecs_rule_var_t* ensure_variable( ecs_rule_t *rule, ecs_rule_var_kind_t kind, const char *name) { ecs_rule_var_t *var = find_variable(rule, kind, name); if (!var) { var = create_variable(rule, kind, name); } else { if (var->kind == EcsRuleVarKindUnknown) { var->kind = kind; } } return var; } static const char *term_id_var_name( ecs_term_id_t *term_id) { if (term_id->flags & EcsIsVariable) { if (term_id->name) { return term_id->name; } else if (term_id->id == EcsThis) { return "."; } else if (term_id->id == EcsWildcard) { return "*"; } else if (term_id->id == EcsAny) { return "_"; } else if (term_id->id == EcsVariable) { return "$"; } else { ecs_check(term_id->name != NULL, ECS_INVALID_PARAMETER, NULL); } } error: return NULL; } static int ensure_term_id_variable( ecs_rule_t *rule, ecs_term_t *term, ecs_term_id_t *term_id) { if (term_id->flags & EcsIsVariable) { if (term_id->id == EcsAny) { /* Any variables aren't translated to rule variables since their * result isn't stored. */ return 0; } const char *name = term_id_var_name(term_id); /* If this is a Not term, it should not introduce new variables. It may * however create entity variables if there already was an existing * table variable */ if (term->oper == EcsNot) { if (!find_variable(rule, EcsRuleVarKindUnknown, name)) { rule_error(rule, "variable in '%s' only appears in Not term", name); return -1; } } ecs_rule_var_t *var = ensure_variable(rule, EcsRuleVarKindEntity, name); ecs_os_strset(&term_id->name, var->name); return 0; } return 0; } static bool term_id_is_variable( ecs_term_id_t *term_id) { return term_id->flags & EcsIsVariable; } /* Get variable from a term identifier */ static ecs_rule_var_t* term_id_to_var( ecs_rule_t *rule, ecs_term_id_t *id) { if (id->flags & EcsIsVariable) { return find_variable(rule, EcsRuleVarKindUnknown, term_id_var_name(id)); } return NULL; } /* Get variable from a term predicate */ static ecs_rule_var_t* term_pred( ecs_rule_t *rule, ecs_term_t *term) { return term_id_to_var(rule, &term->first); } /* Get variable from a term subject */ static ecs_rule_var_t* term_subj( ecs_rule_t *rule, ecs_term_t *term) { return term_id_to_var(rule, &term->src); } /* Get variable from a term object */ static ecs_rule_var_t* term_obj( ecs_rule_t *rule, ecs_term_t *term) { if (obj_is_set(term)) { return term_id_to_var(rule, &term->second); } else { return NULL; } } /* Return predicate variable from pair */ static ecs_rule_var_t* pair_pred( ecs_rule_t *rule, const ecs_rule_pair_t *pair) { if (pair->reg_mask & RULE_PAIR_PREDICATE) { return &rule->vars[pair->first.reg]; } else { return NULL; } } /* Return object variable from pair */ static ecs_rule_var_t* pair_obj( ecs_rule_t *rule, const ecs_rule_pair_t *pair) { if (pair->reg_mask & RULE_PAIR_OBJECT) { return &rule->vars[pair->second.reg]; } else { return NULL; } } /* Create new frame for storing register values. Each operation that yields data * gets its own register frame, which contains all variables reified up to that * point. The preceding frame always contains the reified variables from the * previous operation. Operations that do not yield data (such as control flow) * do not have their own frames. */ static int32_t push_frame( ecs_rule_t *rule) { return rule->frame_count ++; } /* Get register array for current stack frame. The stack frame is determined by * the current operation that is evaluated. The register array contains the * values for the reified variables. If a variable hasn't been reified yet, its * register will store a wildcard. */ static ecs_var_t* get_register_frame( const ecs_rule_iter_t *it, int32_t frame) { if (it->registers) { return &it->registers[frame * it->rule->var_count]; } else { return NULL; } } /* Get register array for current stack frame. The stack frame is determined by * the current operation that is evaluated. The register array contains the * values for the reified variables. If a variable hasn't been reified yet, its * register will store a wildcard. */ static ecs_var_t* get_registers( const ecs_rule_iter_t *it, ecs_rule_op_t *op) { return get_register_frame(it, op->frame); } /* Get columns array. Columns store, for each matched column in a table, the * index at which it occurs. This reduces the amount of searching that * operations need to do in a type, since select/with already provide it. */ static int32_t* rule_get_columns_frame( ecs_rule_iter_t *it, int32_t frame) { return &it->columns[frame * it->rule->filter.term_count]; } static int32_t* rule_get_columns( ecs_rule_iter_t *it, ecs_rule_op_t *op) { return rule_get_columns_frame(it, op->frame); } static void entity_reg_set( const ecs_rule_t *rule, ecs_var_t *regs, int32_t r, ecs_entity_t entity) { (void)rule; ecs_assert(rule->vars[r].kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_valid(rule->filter.world, entity), ECS_INVALID_PARAMETER, NULL); regs[r].entity = entity; error: return; } static ecs_entity_t entity_reg_get( const ecs_rule_t *rule, ecs_var_t *regs, int32_t r) { (void)rule; ecs_entity_t e = regs[r].entity; if (!e) { return EcsWildcard; } ecs_check(ecs_is_valid(rule->filter.world, e), ECS_INVALID_PARAMETER, NULL); return e; error: return 0; } static void table_reg_set( const ecs_rule_t *rule, ecs_var_t *regs, int32_t r, ecs_table_t *table) { (void)rule; ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); regs[r].range.table = table; regs[r].range.offset = 0; regs[r].range.count = 0; regs[r].entity = 0; } static ecs_table_range_t table_reg_get( const ecs_rule_t *rule, ecs_var_t *regs, int32_t r) { (void)rule; ecs_assert(rule->vars[r].kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); return regs[r].range; } static ecs_entity_t reg_get_entity( const ecs_rule_t *rule, ecs_rule_op_t *op, ecs_var_t *regs, int32_t r) { if (r == UINT8_MAX) { ecs_assert(op->subject != 0, ECS_INTERNAL_ERROR, NULL); /* The subject is referenced from the query string by string identifier. * If subject entity is not valid, it could have been deletd by the * application after the rule was created */ ecs_check(ecs_is_valid(rule->filter.world, op->subject), ECS_INVALID_PARAMETER, NULL); return op->subject; } if (rule->vars[r].kind == EcsRuleVarKindTable) { int32_t offset = regs[r].range.offset; ecs_assert(regs[r].range.count == 1, ECS_INTERNAL_ERROR, NULL); ecs_data_t *data = &table_reg_get(rule, regs, r).table->data; ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = ecs_vec_first(&data->entities); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(offset < ecs_vec_count(&data->entities), ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_valid(rule->filter.world, entities[offset]), ECS_INVALID_PARAMETER, NULL); return entities[offset]; } if (rule->vars[r].kind == EcsRuleVarKindEntity) { return entity_reg_get(rule, regs, r); } /* Must return an entity */ ecs_assert(false, ECS_INTERNAL_ERROR, NULL); error: return 0; } static ecs_table_range_t table_from_entity( const ecs_world_t *world, ecs_entity_t entity) { ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); entity = ecs_get_alive(world, entity); ecs_table_range_t slice = {0}; ecs_record_t *record = flecs_entities_get(world, entity); if (record) { slice.table = record->table; slice.offset = ECS_RECORD_TO_ROW(record->row); slice.count = 1; } return slice; } static ecs_table_range_t reg_get_range( const ecs_rule_t *rule, ecs_rule_op_t *op, ecs_var_t *regs, int32_t r) { if (r == UINT8_MAX) { ecs_check(ecs_is_valid(rule->filter.world, op->subject), ECS_INVALID_PARAMETER, NULL); return table_from_entity(rule->filter.world, op->subject); } if (rule->vars[r].kind == EcsRuleVarKindTable) { return table_reg_get(rule, regs, r); } if (rule->vars[r].kind == EcsRuleVarKindEntity) { return table_from_entity(rule->filter.world, entity_reg_get(rule, regs, r)); } error: return (ecs_table_range_t){0}; } static void reg_set_entity( const ecs_rule_t *rule, ecs_var_t *regs, int32_t r, ecs_entity_t entity) { if (rule->vars[r].kind == EcsRuleVarKindTable) { ecs_world_t *world = rule->filter.world; ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); regs[r].range = table_from_entity(world, entity); regs[r].entity = entity; } else { entity_reg_set(rule, regs, r, entity); } error: return; } static void reg_set_range( const ecs_rule_t *rule, ecs_var_t *regs, int32_t r, const ecs_table_range_t *range) { if (rule->vars[r].kind == EcsRuleVarKindEntity) { ecs_check(range->count == 1, ECS_INTERNAL_ERROR, NULL); regs[r].range = *range; regs[r].entity = ecs_vec_get_t(&range->table->data.entities, ecs_entity_t, range->offset)[0]; } else { regs[r].range = *range; regs[r].entity = 0; } error: return; } /* This encodes a column expression into a pair. A pair stores information about * the variable(s) associated with the column. Pairs are used by operations to * apply filters, and when there is a match, to reify variables. */ static ecs_rule_pair_t term_to_pair( ecs_rule_t *rule, ecs_term_t *term) { ecs_rule_pair_t result = {0}; /* Terms must always have at least one argument (the subject) */ ecs_assert(subj_is_set(term), ECS_INTERNAL_ERROR, NULL); /* If the predicate id is a variable, find the variable and encode its id * in the pair so the operation can find it later. */ if (term->first.flags & EcsIsVariable) { if (term->first.id != EcsAny) { /* Always lookup var as an entity, as pairs never refer to tables */ const ecs_rule_var_t *var = find_variable( rule, EcsRuleVarKindEntity, term_id_var_name(&term->first)); /* Variables should have been declared */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); result.first.reg = var->id; /* Set flag so the operation can see the predicate is a variable */ result.reg_mask |= RULE_PAIR_PREDICATE; result.final = true; } else { result.first.id = EcsWildcard; result.final = true; } } else { /* If the predicate is not a variable, simply store its id. */ ecs_entity_t pred_id = term->first.id; result.first.id = pred_id; /* Test if predicate is transitive. When evaluating the predicate, this * will also take into account transitive relationships */ if (ecs_has_id(rule->filter.world, pred_id, EcsTransitive)) { /* Transitive queries must have an object */ if (obj_is_set(term)) { if (term->second.flags & (EcsUp|EcsTraverseAll)) { result.transitive = true; } } } if (ecs_has_id(rule->filter.world, pred_id, EcsFinal)) { result.final = true; } if (ecs_has_id(rule->filter.world, pred_id, EcsReflexive)) { result.reflexive = true; } if (ecs_has_id(rule->filter.world, pred_id, EcsAcyclic)) { result.acyclic = true; } } /* The pair doesn't do anything with the subject (sources are the things that * are matched against pairs) so if the column does not have a object, * there is nothing left to do. */ if (!obj_is_set(term)) { return result; } /* If arguments is higher than 2 this is not a pair but a nested rule */ ecs_assert(obj_is_set(term), ECS_INTERNAL_ERROR, NULL); /* Same as above, if the object is a variable, store it and flag it */ if (term->second.flags & EcsIsVariable) { if (term->second.id != EcsAny) { const ecs_rule_var_t *var = find_variable( rule, EcsRuleVarKindEntity, term_id_var_name(&term->second)); /* Variables should have been declared */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, term_id_var_name(&term->second)); ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, term_id_var_name(&term->second)); result.second.reg = var->id; result.reg_mask |= RULE_PAIR_OBJECT; } else { result.second.id = EcsWildcard; } } else { /* If the object is not a variable, simply store its id */ result.second.id = term->second.id; if (!result.second.id) { result.second_0 = true; } } return result; } /* When an operation has a pair, it is used to filter its input. This function * translates a pair back into an entity id, and in the process substitutes the * variables that have already been filled out. It's one of the most important * functions, as a lot of the filtering logic depends on having an entity that * has all of the reified variables correctly filled out. */ static ecs_rule_filter_t pair_to_filter( ecs_rule_iter_t *it, ecs_rule_op_t *op, ecs_rule_pair_t pair) { ecs_entity_t first = pair.first.id; ecs_entity_t second = pair.second.id; ecs_rule_filter_t result = { .lo_var = -1, .hi_var = -1 }; /* Get registers in case we need to resolve ids from registers. Get them * from the previous, not the current stack frame as the current operation * hasn't reified its variables yet. */ ecs_var_t *regs = get_register_frame(it, op->frame - 1); if (pair.reg_mask & RULE_PAIR_OBJECT) { second = entity_reg_get(it->rule, regs, pair.second.reg); second = ecs_entity_t_lo(second); /* Filters don't have generations */ if (second == EcsWildcard) { result.wildcard = true; result.second_wildcard = true; result.lo_var = pair.second.reg; } } if (pair.reg_mask & RULE_PAIR_PREDICATE) { first = entity_reg_get(it->rule, regs, pair.first.reg); first = ecs_entity_t_lo(first); /* Filters don't have generations */ if (first == EcsWildcard) { if (result.wildcard) { result.same_var = pair.first.reg == pair.second.reg; } result.wildcard = true; result.first_wildcard = true; if (second) { result.hi_var = pair.first.reg; } else { result.lo_var = pair.first.reg; } } } if (!second && !pair.second_0) { result.mask = first; } else { result.mask = ecs_pair(first, second); } return result; } /* This function is responsible for reifying the variables (filling them out * with their actual values as soon as they are known). It uses the pair * expression returned by pair_get_most_specific_var, and attempts to fill out each of the * wildcards in the pair. If a variable isn't reified yet, the pair expression * will still contain one or more wildcards, which is harmless as the respective * registers will also point to a wildcard. */ static void reify_variables( ecs_rule_iter_t *it, ecs_rule_op_t *op, ecs_rule_filter_t *filter, ecs_type_t type, int32_t column) { const ecs_rule_t *rule = it->rule; const ecs_rule_var_t *vars = rule->vars; (void)vars; ecs_var_t *regs = get_registers(it, op); ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *elem = &type.array[column]; int32_t obj_var = filter->lo_var; int32_t pred_var = filter->hi_var; if (obj_var != -1) { ecs_assert(vars[obj_var].kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); entity_reg_set(rule, regs, obj_var, ecs_get_alive(rule->filter.world, ECS_PAIR_SECOND(*elem))); } if (pred_var != -1) { ecs_assert(vars[pred_var].kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); entity_reg_set(rule, regs, pred_var, ecs_get_alive(rule->filter.world, ECS_PAIR_FIRST(*elem))); } } /* Returns whether variable is a subject */ static bool is_subject( ecs_rule_t *rule, ecs_rule_var_t *var) { ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); if (!var) { return false; } if (var->id < rule->subj_var_count) { return true; } return false; } static bool skip_term(ecs_term_t *term) { if (ecs_term_match_0(term)) { return true; } return false; } static int32_t get_variable_depth( ecs_rule_t *rule, ecs_rule_var_t *var, ecs_rule_var_t *root, int recur); static int32_t crawl_variable( ecs_rule_t *rule, ecs_rule_var_t *var, ecs_rule_var_t *root, int recur) { ecs_term_t *terms = rule->filter.terms; int32_t i, count = rule->filter.term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (skip_term(term)) { continue; } ecs_rule_var_t *first = term_pred(rule, term), *src = term_subj(rule, term), *second = term_obj(rule, term); /* Variable must at least appear once in term */ if (var != first && var != src && var != second) { continue; } if (first && first != var && !first->marked) { get_variable_depth(rule, first, root, recur + 1); } if (src && src != var && !src->marked) { get_variable_depth(rule, src, root, recur + 1); } if (second && second != var && !second->marked) { get_variable_depth(rule, second, root, recur + 1); } } return 0; } static int32_t get_depth_from_var( ecs_rule_t *rule, ecs_rule_var_t *var, ecs_rule_var_t *root, int recur) { /* If variable is the root or if depth has been set, return depth + 1 */ if (var == root || var->depth != UINT8_MAX) { return var->depth + 1; } /* Variable is already being evaluated, so this indicates a cycle. Stop */ if (var->marked) { return 0; } /* Variable is not yet being evaluated and depth has not yet been set. * Calculate depth. */ int32_t depth = get_variable_depth(rule, var, root, recur + 1); if (depth == UINT8_MAX) { return depth; } else { return depth + 1; } } static int32_t get_depth_from_term( ecs_rule_t *rule, ecs_rule_var_t *cur, ecs_rule_var_t *first, ecs_rule_var_t *second, ecs_rule_var_t *root, int recur) { int32_t result = UINT8_MAX; /* If neither of the other parts of the terms are variables, this * variable is guaranteed to have no dependencies. */ if (!first && !second) { result = 0; } else { /* If this is a variable that is not the same as the current, * we can use it to determine dependency depth. */ if (first && cur != first) { int32_t depth = get_depth_from_var(rule, first, root, recur); if (depth == UINT8_MAX) { return UINT8_MAX; } /* If the found depth is lower than the depth found, overwrite it */ if (depth < result) { result = depth; } } /* Same for second */ if (second && cur != second) { int32_t depth = get_depth_from_var(rule, second, root, recur); if (depth == UINT8_MAX) { return UINT8_MAX; } if (depth < result) { result = depth; } } } return result; } /* Find the depth of the dependency tree from the variable to the root */ static int32_t get_variable_depth( ecs_rule_t *rule, ecs_rule_var_t *var, ecs_rule_var_t *root, int recur) { var->marked = true; /* Iterate columns, find all instances where 'var' is not used as subject. * If the subject of that column is either the root or a variable for which * the depth is known, the depth for this variable can be determined. */ ecs_term_t *terms = rule->filter.terms; int32_t i, count = rule->filter.term_count; int32_t result = UINT8_MAX; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (skip_term(term)) { continue; } ecs_rule_var_t *first = term_pred(rule, term), *src = term_subj(rule, term), *second = term_obj(rule, term); if (src != var) { continue; } if (!is_subject(rule, first)) { first = NULL; } if (!is_subject(rule, second)) { second = NULL; } int32_t depth = get_depth_from_term(rule, var, first, second, root, recur); if (depth < result) { result = depth; } } if (result == UINT8_MAX) { result = 0; } var->depth = result; /* Dependencies are calculated from subject to (first, second). If there were * sources that are only related by object (like (X, Y), (Z, Y)) it is * possible that those have not yet been found yet. To make sure those * variables are found, loop again & follow predicate & object links */ for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (skip_term(term)) { continue; } ecs_rule_var_t *src = term_subj(rule, term), *first = term_pred(rule, term), *second = term_obj(rule, term); /* Only evaluate first & second for current subject. This ensures that we * won't evaluate variables that are unreachable from the root. This * must be detected as unconstrained variables are not allowed. */ if (src != var) { continue; } crawl_variable(rule, src, root, recur); if (first && first != var) { crawl_variable(rule, first, root, recur); } if (second && second != var) { crawl_variable(rule, second, root, recur); } } return var->depth; } /* Compare function used for qsort. It ensures that variables are first ordered * by depth, followed by how often they occur. */ static int compare_variable( const void* ptr1, const void *ptr2) { const ecs_rule_var_t *v1 = ptr1; const ecs_rule_var_t *v2 = ptr2; if (v1->kind < v2->kind) { return -1; } else if (v1->kind > v2->kind) { return 1; } if (v1->depth < v2->depth) { return -1; } else if (v1->depth > v2->depth) { return 1; } if (v1->occurs < v2->occurs) { return 1; } else { return -1; } return (v1->id < v2->id) - (v1->id > v2->id); } /* After all subject variables have been found, inserted and sorted, the * remaining variables (predicate & object) still need to be inserted. This * function serves two purposes. The first purpose is to ensure that all * variables are known before operations are emitted. This ensures that the * variables array won't be reallocated while emitting, which simplifies code. * The second purpose of the function is to ensure that if the root variable * (which, if it exists has now been created with a table type) is also inserted * with an entity type if required. This is used later to decide whether the * rule needs to insert an each instruction. */ static int ensure_all_variables( ecs_rule_t *rule) { ecs_term_t *terms = rule->filter.terms; int32_t i, count = rule->filter.term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (skip_term(term)) { continue; } /* If predicate is a variable, make sure it has been registered */ if (term->first.flags & EcsIsVariable) { if (ensure_term_id_variable(rule, term, &term->first) != 0) { return -1; } } /* If subject is a variable and it is not This, make sure it is * registered as an entity variable. This ensures that the program will * correctly return all permutations */ if (!ecs_term_match_this(term)) { if (ensure_term_id_variable(rule, term, &term->src) != 0) { return -1; } } /* If object is a variable, make sure it has been registered */ if (obj_is_set(term) && (term->second.flags & EcsIsVariable)) { if (ensure_term_id_variable(rule, term, &term->second) != 0) { return -1; } } } return 0; } /* Scan for variables, put them in optimal dependency order. */ static int scan_variables( ecs_rule_t *rule) { /* Objects found in rule. One will be elected root */ int32_t subject_count = 0; /* If this (.) is found, it always takes precedence in root election */ int32_t this_var = UINT8_MAX; /* Keep track of the subject variable that occurs the most. In the absence of * this (.) the variable with the most occurrences will be elected root. */ int32_t max_occur = 0; int32_t max_occur_var = UINT8_MAX; /* Step 1: find all possible roots */ ecs_term_t *terms = rule->filter.terms; int32_t i, term_count = rule->filter.term_count; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; /* Evaluate the subject. The predicate and object are not evaluated, * since they never can be elected as root. */ if (term_id_is_variable(&term->src)) { const char *subj_name = term_id_var_name(&term->src); ecs_rule_var_t *src = find_variable( rule, EcsRuleVarKindTable, subj_name); if (!src) { src = create_variable(rule, EcsRuleVarKindTable, subj_name); if (subject_count >= ECS_RULE_MAX_VAR_COUNT) { rule_error(rule, "too many variables in rule"); goto error; } /* Make sure that variable name in term array matches with the * rule name. */ if (term->src.id != EcsThis && term->src.id != EcsAny) { ecs_os_strset(&term->src.name, src->name); term->src.id = 0; } } if (++ src->occurs > max_occur) { max_occur = src->occurs; max_occur_var = src->id; } } } rule->subj_var_count = rule->var_count; if (ensure_all_variables(rule) != 0) { goto error; } /* Variables in a term with a literal subject have depth 0 */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (!(term->src.flags & EcsIsVariable)) { ecs_rule_var_t *first = term_pred(rule, term), *second = term_obj(rule, term); if (first) { first->depth = 0; } if (second) { second->depth = 0; } } } /* Elect a root. This is either this (.) or the variable with the most * occurrences. */ int32_t root_var = this_var; if (root_var == UINT8_MAX) { root_var = max_occur_var; if (root_var == UINT8_MAX) { /* If no subject variables have been found, the rule expression only * operates on a fixed set of entities, in which case no root * election is required. */ goto done; } } ecs_rule_var_t *root = &rule->vars[root_var]; root->depth = get_variable_depth(rule, root, root, 0); /* Verify that there are no unconstrained variables. Unconstrained variables * are variables that are unreachable from the root. */ for (i = 0; i < rule->subj_var_count; i ++) { if (rule->vars[i].depth == UINT8_MAX) { rule_error(rule, "unconstrained variable '%s'", rule->vars[i].name); goto error; } } /* For each Not term, verify that variables are known */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper != EcsNot) { continue; } ecs_rule_var_t *first = term_pred(rule, term), *second = term_obj(rule, term); if (!first && term_id_is_variable(&term->first) && term->first.id != EcsAny) { rule_error(rule, "missing predicate variable '%s'", term_id_var_name(&term->first)); goto error; } if (!second && term_id_is_variable(&term->second) && term->second.id != EcsAny) { rule_error(rule, "missing object variable '%s'", term_id_var_name(&term->second)); goto error; } } /* Order variables by depth, followed by occurrence. The variable * array will later be used to lead the iteration over the terms, and * determine which operations get inserted first. */ int32_t var_count = rule->var_count; ecs_rule_var_t vars[ECS_RULE_MAX_VAR_COUNT]; ecs_os_memcpy_n(vars, rule->vars, ecs_rule_var_t, var_count); ecs_qsort_t(&vars, var_count, ecs_rule_var_t, compare_variable); for (i = 0; i < var_count; i ++) { rule->var_eval_order[i] = vars[i].id; } done: return 0; error: return -1; } /* Get entity variable from table variable */ static ecs_rule_var_t* to_entity( ecs_rule_t *rule, ecs_rule_var_t *var) { if (!var) { return NULL; } ecs_rule_var_t *evar = NULL; if (var->kind == EcsRuleVarKindTable) { evar = find_variable(rule, EcsRuleVarKindEntity, var->name); } else { evar = var; } return evar; } /* Ensure that if a table variable has been written, the corresponding entity * variable is populated. The function will return the most specific, populated * variable. */ static ecs_rule_var_t* most_specific_var( ecs_rule_t *rule, ecs_rule_var_t *var, bool *written, bool create) { if (!var) { return NULL; } ecs_rule_var_t *tvar, *evar = to_entity(rule, var); if (!evar) { return var; } if (var->kind == EcsRuleVarKindTable) { tvar = var; } else { tvar = find_variable(rule, EcsRuleVarKindTable, var->name); } /* If variable is used as predicate or object, it should have been * registered as an entity. */ ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); /* Usually table variables are resolved before they are used as a predicate * or object, but in the case of cyclic dependencies this is not guaranteed. * Only insert an each instruction of the table variable has been written */ if (tvar && written[tvar->id]) { /* If the variable has been written as a table but not yet * as an entity, insert an each operation that yields each * entity in the table. */ if (evar) { if (written[evar->id]) { return evar; } else if (create) { ecs_rule_op_t *op = create_operation(rule); op->kind = EcsRuleEach; op->on_pass = rule->operation_count; op->on_fail = rule->operation_count - 2; op->frame = rule->frame_count; op->has_in = true; op->has_out = true; op->r_in = tvar->id; op->r_out = evar->id; /* Entity will either be written or has been written */ written[evar->id] = true; push_frame(rule); return evar; } else { return tvar; } } } else if (evar && written[evar->id]) { return evar; } return var; } /* Get most specific known variable */ static ecs_rule_var_t *get_most_specific_var( ecs_rule_t *rule, ecs_rule_var_t *var, bool *written) { return most_specific_var(rule, var, written, false); } /* Get or create most specific known variable. This will populate an entity * variable if a table variable is known but the entity variable isn't. */ static ecs_rule_var_t *ensure_most_specific_var( ecs_rule_t *rule, ecs_rule_var_t *var, bool *written) { return most_specific_var(rule, var, written, true); } /* Ensure that an entity variable is written before using it */ static ecs_rule_var_t* ensure_entity_written( ecs_rule_t *rule, ecs_rule_var_t *var, bool *written) { if (!var) { return NULL; } /* Ensure we're working with the most specific version of src we can get */ ecs_rule_var_t *evar = ensure_most_specific_var(rule, var, written); /* The post condition of this function is that there is an entity variable, * and that it is written. Make sure that the result is an entity */ ecs_assert(evar != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(evar->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); /* Make sure the variable has been written */ ecs_assert(written[evar->id] == true, ECS_INTERNAL_ERROR, NULL); return evar; } static ecs_rule_op_t* insert_operation( ecs_rule_t *rule, int32_t term_index, bool *written) { ecs_rule_pair_t pair = {0}; /* Parse the term's type into a pair. A pair extracts the ids from * the term, and replaces variables with wildcards which can then * be matched against actual relationships. A pair retains the * information about the variables, so that when a match happens, * the pair can be used to reify the variable. */ if (term_index != -1) { ecs_term_t *term = &rule->filter.terms[term_index]; pair = term_to_pair(rule, term); /* If the pair contains entity variables that have not yet been written, * insert each instructions in case their tables are known. Variables in * a pair that are truly unknown will be populated by the operation, * but an operation should never overwrite an entity variable if the * corresponding table variable has already been resolved. */ if (pair.reg_mask & RULE_PAIR_PREDICATE) { ecs_rule_var_t *first = &rule->vars[pair.first.reg]; first = get_most_specific_var(rule, first, written); pair.first.reg = first->id; } if (pair.reg_mask & RULE_PAIR_OBJECT) { ecs_rule_var_t *second = &rule->vars[pair.second.reg]; second = get_most_specific_var(rule, second, written); pair.second.reg = second->id; } } else { /* Not all operations have a filter (like Each) */ } ecs_rule_op_t *op = create_operation(rule); op->on_pass = rule->operation_count; op->on_fail = rule->operation_count - 2; op->frame = rule->frame_count; op->filter = pair; /* Store corresponding signature term so we can correlate and * store the table columns with signature columns. */ op->term = term_index; return op; } /* Insert first operation, which is always Input. This creates an entry in * the register stack for the initial state. */ static void insert_input( ecs_rule_t *rule) { ecs_rule_op_t *op = create_operation(rule); op->kind = EcsRuleInput; /* The first time Input is evaluated it goes to the next/first operation */ op->on_pass = 1; /* When Input is evaluated with redo = true it will return false, which will * finish the program as op becomes -1. */ op->on_fail = -1; push_frame(rule); } /* Insert last operation, which is always Yield. When the program hits Yield, * data is returned to the application. */ static void insert_yield( ecs_rule_t *rule) { ecs_rule_op_t *op = create_operation(rule); op->kind = EcsRuleYield; op->has_in = true; op->on_fail = rule->operation_count - 2; /* Yield can only "fail" since it is the end of the program */ /* Find variable associated with this. It is possible that the variable * exists both as a table and as an entity. This can happen when a rule * first selects a table for this, but then subsequently needs to evaluate * each entity in that table. In that case the yield instruction should * return the entity, so look for that first. */ ecs_rule_var_t *var = find_variable(rule, EcsRuleVarKindEntity, "."); if (!var) { var = find_variable(rule, EcsRuleVarKindTable, "."); } /* If there is no this, there is nothing to yield. In that case the rule * simply returns true or false. */ if (!var) { op->r_in = UINT8_MAX; } else { op->r_in = var->id; } op->frame = push_frame(rule); } /* Return superset/subset including the root */ static void insert_reflexive_set( ecs_rule_t *rule, ecs_rule_op_kind_t op_kind, ecs_rule_var_t *out, const ecs_rule_pair_t pair, int32_t c, bool *written, bool reflexive) { ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *first = pair_pred(rule, &pair); ecs_rule_var_t *second = pair_obj(rule, &pair); int32_t setjmp_lbl = rule->operation_count; int32_t store_lbl = setjmp_lbl + 1; int32_t set_lbl = setjmp_lbl + 2; int32_t next_op = setjmp_lbl + 4; int32_t prev_op = setjmp_lbl - 1; /* Insert 4 operations at once, so we don't have to worry about how * the instruction array reallocs. If operation is not reflexive, we only * need to insert the set operation. */ if (reflexive) { insert_operation(rule, -1, written); insert_operation(rule, -1, written); insert_operation(rule, -1, written); } ecs_rule_op_t *op = insert_operation(rule, -1, written); ecs_rule_op_t *setjmp = &rule->operations[setjmp_lbl]; ecs_rule_op_t *store = &rule->operations[store_lbl]; ecs_rule_op_t *set = &rule->operations[set_lbl]; ecs_rule_op_t *jump = op; if (!reflexive) { set_lbl = setjmp_lbl; set = op; setjmp = NULL; store = NULL; jump = NULL; next_op = set_lbl + 1; prev_op = set_lbl - 1; } /* The SetJmp operation stores a conditional jump label that either * points to the Store or *Set operation */ if (reflexive) { setjmp->kind = EcsRuleSetJmp; setjmp->on_pass = store_lbl; setjmp->on_fail = set_lbl; } /* The Store operation yields the root of the subtree. After yielding, * this operation will fail and return to SetJmp, which will cause it * to switch to the *Set operation. */ if (reflexive) { store->kind = EcsRuleStore; store->on_pass = next_op; store->on_fail = setjmp_lbl; store->has_in = true; store->has_out = true; store->r_out = out->id; store->term = c; if (!first) { store->filter.first = pair.first; } else { store->filter.first.reg = first->id; store->filter.reg_mask |= RULE_PAIR_PREDICATE; } /* If the object of the filter is not a variable, store literal */ if (!second) { store->r_in = UINT8_MAX; store->subject = ecs_get_alive(rule->filter.world, pair.second.id); store->filter.second = pair.second; } else { store->r_in = second->id; store->filter.second.reg = second->id; store->filter.reg_mask |= RULE_PAIR_OBJECT; } } /* This is either a SubSet or SuperSet operation */ set->kind = op_kind; set->on_pass = next_op; set->on_fail = prev_op; set->has_out = true; set->r_out = out->id; set->term = c; /* Predicate can be a variable if it's non-final */ if (!first) { set->filter.first = pair.first; } else { set->filter.first.reg = first->id; set->filter.reg_mask |= RULE_PAIR_PREDICATE; } if (!second) { set->filter.second = pair.second; } else { set->filter.second.reg = second->id; set->filter.reg_mask |= RULE_PAIR_OBJECT; } if (reflexive) { /* The jump operation jumps to either the store or subset operation, * depending on whether the store operation already yielded. The * operation is inserted last, so that the on_fail label of the next * operation will point to it */ jump->kind = EcsRuleJump; /* The pass/fail labels of the Jump operation are not used, since it * jumps to a variable location. Instead, the pass label is (ab)used to * store the label of the SetJmp operation, so that the jump can access * the label it needs to jump to from the setjmp op_ctx. */ jump->on_pass = setjmp_lbl; jump->on_fail = -1; } written[out->id] = true; } static ecs_rule_var_t* store_reflexive_set( ecs_rule_t *rule, ecs_rule_op_kind_t op_kind, ecs_rule_pair_t *pair, bool *written, bool reflexive, bool as_entity) { /* Ensure we're using the most specific version of second */ ecs_rule_var_t *second = pair_obj(rule, pair); if (second) { pair->second.reg = second->id; } ecs_rule_var_kind_t var_kind = EcsRuleVarKindTable; /* Create anonymous variable for storing the set */ ecs_rule_var_t *av = create_anonymous_variable(rule, var_kind); int32_t ave_id = 0, av_id = av->id; /* If the variable kind is a table, also create an entity variable as the * result of the set operation should be returned as an entity */ if (var_kind == EcsRuleVarKindTable && as_entity) { create_variable(rule, EcsRuleVarKindEntity, av->name); av = &rule->vars[av_id]; ave_id = av_id + 1; } /* Generate the operations */ insert_reflexive_set(rule, op_kind, av, *pair, -1, written, reflexive); /* Make sure to return entity variable, and that it is populated */ if (as_entity) { return ensure_entity_written(rule, &rule->vars[ave_id], written); } else { return &rule->vars[av_id]; } } static bool is_known( ecs_rule_var_t *var, bool *written) { if (!var) { return true; } else { return written[var->id]; } } static bool is_pair_known( ecs_rule_t *rule, ecs_rule_pair_t *pair, bool *written) { ecs_rule_var_t *pred_var = pair_pred(rule, pair); if (!is_known(pred_var, written) || pair->first.id == EcsWildcard) { return false; } ecs_rule_var_t *obj_var = pair_obj(rule, pair); if (!is_known(obj_var, written) || pair->second.id == EcsWildcard) { return false; } return true; } static void set_input_to_subj( ecs_rule_t *rule, ecs_rule_op_t *op, ecs_term_t *term, ecs_rule_var_t *var) { (void)rule; op->has_in = true; if (!var) { op->r_in = UINT8_MAX; op->subject = term->src.id; /* Invalid entities should have been caught during parsing */ ecs_assert(ecs_is_valid(rule->filter.world, op->subject), ECS_INTERNAL_ERROR, NULL); } else { op->r_in = var->id; } } static void set_output_to_subj( ecs_rule_t *rule, ecs_rule_op_t *op, ecs_term_t *term, ecs_rule_var_t *var) { (void)rule; op->has_out = true; if (!var) { op->r_out = UINT8_MAX; op->subject = term->src.id; /* Invalid entities should have been caught during parsing */ ecs_assert(ecs_is_valid(rule->filter.world, op->subject), ECS_INTERNAL_ERROR, NULL); } else { op->r_out = var->id; } } static void insert_select_or_with( ecs_rule_t *rule, int32_t c, ecs_term_t *term, ecs_rule_var_t *src, ecs_rule_pair_t *pair, bool *written) { ecs_rule_op_t *op; bool eval_subject_supersets = false; /* Find any entity and/or table variables for subject */ ecs_rule_var_t *tvar = NULL, *evar = to_entity(rule, src), *var = evar; if (src && src->kind == EcsRuleVarKindTable) { tvar = src; if (!evar) { var = tvar; } } int32_t lbl_start = rule->operation_count; ecs_rule_pair_t filter; if (pair) { filter = *pair; } else { filter = term_to_pair(rule, term); } /* Only insert implicit IsA if filter isn't already an IsA */ if ((!filter.transitive || filter.first.id != EcsIsA) && term->oper != EcsNot) { if (!var) { ecs_rule_pair_t isa_pair = { .first.id = EcsIsA, .second.id = term->src.id }; evar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, written, true, true); tvar = NULL; eval_subject_supersets = true; } else if (ecs_id_is_wildcard(term->id) && ECS_PAIR_FIRST(term->id) != EcsThis && ECS_PAIR_SECOND(term->id) != EcsThis) { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); op = insert_operation(rule, -1, written); if (!is_known(src, written)) { op->kind = EcsRuleSelect; set_output_to_subj(rule, op, term, src); written[src->id] = true; } else { op->kind = EcsRuleWith; set_input_to_subj(rule, op, term, src); } ecs_rule_pair_t isa_pair = { .first.id = EcsIsA, .second.reg = src->id, .reg_mask = RULE_PAIR_OBJECT }; op->filter = filter; if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { op->filter.first.id = EcsWildcard; } if (op->filter.reg_mask & RULE_PAIR_OBJECT) { op->filter.second.id = EcsWildcard; } op->filter.reg_mask = 0; push_frame(rule); tvar = src = store_reflexive_set(rule, EcsRuleSuperSet, &isa_pair, written, true, false); evar = NULL; } } /* If no pair is provided, create operation from specified term */ if (!pair) { op = insert_operation(rule, c, written); /* If an explicit pair is provided, override the default one from the * term. This allows for using a predicate or object variable different * from what is in the term. One application of this is to substitute a * predicate with its subsets, if it is non final */ } else { op = insert_operation(rule, -1, written); op->filter = *pair; /* Assign the term id, so that the operation will still be correctly * associated with the correct expression term. */ op->term = c; } /* If entity variable is known and resolved, create with for it */ if (evar && is_known(evar, written)) { op->kind = EcsRuleWith; op->r_in = evar->id; set_input_to_subj(rule, op, term, src); /* If table variable is known and resolved, create with for it */ } else if (tvar && is_known(tvar, written)) { op->kind = EcsRuleWith; op->r_in = tvar->id; set_input_to_subj(rule, op, term, src); /* If subject is neither table nor entitiy, with operates on literal */ } else if (!tvar && !evar) { op->kind = EcsRuleWith; set_input_to_subj(rule, op, term, src); /* If subject is table or entity but not known, use select */ } else { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); op->kind = EcsRuleSelect; set_output_to_subj(rule, op, term, src); written[src->id] = true; } /* If supersets of subject are being evaluated, and we're looking for a * specific filter, stop as soon as the filter has been matched. */ if (eval_subject_supersets && is_pair_known(rule, &op->filter, written)) { op = insert_operation(rule, -1, written); /* When the next operation returns, it will first hit SetJmp with a redo * which will switch the jump label to the previous operation */ op->kind = EcsRuleSetJmp; op->on_pass = rule->operation_count; op->on_fail = lbl_start - 1; } if (op->filter.reg_mask & RULE_PAIR_PREDICATE) { written[op->filter.first.reg] = true; } if (op->filter.reg_mask & RULE_PAIR_OBJECT) { written[op->filter.second.reg] = true; } } static void prepare_predicate( ecs_rule_t *rule, ecs_rule_pair_t *pair, int32_t term, bool *written) { /* If pair is not final, resolve term for all IsA relationships of the * predicate. Note that if the pair has final set to true, it is guaranteed * that the predicate can be used in an IsA query */ if (!pair->final) { ecs_rule_pair_t isa_pair = { .first.id = EcsIsA, .second.id = pair->first.id }; ecs_rule_var_t *first = store_reflexive_set(rule, EcsRuleSubSet, &isa_pair, written, true, true); pair->first.reg = first->id; pair->reg_mask |= RULE_PAIR_PREDICATE; if (term != -1) { rule->term_vars[term].first = first->id; } } } static void insert_term_2( ecs_rule_t *rule, ecs_term_t *term, ecs_rule_pair_t *filter, int32_t c, bool *written) { int32_t subj_id = -1, obj_id = -1; ecs_rule_var_t *src = term_subj(rule, term); if ((src = get_most_specific_var(rule, src, written))) { subj_id = src->id; } ecs_rule_var_t *second = term_obj(rule, term); if ((second = get_most_specific_var(rule, second, written))) { obj_id = second->id; } bool subj_known = is_known(src, written); bool same_obj_subj = false; if (src && second) { same_obj_subj = !ecs_os_strcmp(src->name, second->name); } if (!filter->transitive) { insert_select_or_with(rule, c, term, src, filter, written); if (src) src = &rule->vars[subj_id]; if (second) second = &rule->vars[obj_id]; } else if (filter->transitive) { if (subj_known) { if (is_known(second, written)) { if (filter->second.id != EcsWildcard) { ecs_rule_var_t *obj_subsets = store_reflexive_set( rule, EcsRuleSubSet, filter, written, true, true); if (src) { src = &rule->vars[subj_id]; } rule->term_vars[c].second = obj_subsets->id; ecs_rule_pair_t pair = *filter; pair.second.reg = obj_subsets->id; pair.reg_mask |= RULE_PAIR_OBJECT; insert_select_or_with(rule, c, term, src, &pair, written); } else { insert_select_or_with(rule, c, term, src, filter, written); } } else { ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL); /* If subject is literal, find supersets for subject */ if (src == NULL || src->kind == EcsRuleVarKindEntity) { second = to_entity(rule, second); ecs_rule_pair_t set_pair = *filter; set_pair.reg_mask &= RULE_PAIR_PREDICATE; if (src) { set_pair.second.reg = src->id; set_pair.reg_mask |= RULE_PAIR_OBJECT; } else { set_pair.second.id = term->src.id; } insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair, c, written, filter->reflexive); /* If subject is variable, first find matching pair for the * evaluated entity(s) and return supersets */ } else { ecs_rule_var_t *av = create_anonymous_variable( rule, EcsRuleVarKindEntity); src = &rule->vars[subj_id]; second = &rule->vars[obj_id]; second = to_entity(rule, second); ecs_rule_pair_t set_pair = *filter; set_pair.second.reg = av->id; set_pair.reg_mask |= RULE_PAIR_OBJECT; /* Insert with to find initial object for relationship */ insert_select_or_with( rule, c, term, src, &set_pair, written); push_frame(rule); /* Find supersets for returned initial object. Make sure * this is always reflexive since it needs to return the * object from the pair that the entity has itself. */ insert_reflexive_set(rule, EcsRuleSuperSet, second, set_pair, c, written, true); } } /* src is not known */ } else { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); if (is_known(second, written)) { ecs_rule_pair_t set_pair = *filter; set_pair.reg_mask &= RULE_PAIR_PREDICATE; /* clear object mask */ if (second) { set_pair.second.reg = second->id; set_pair.reg_mask |= RULE_PAIR_OBJECT; } else { set_pair.second.id = term->second.id; } if (second) { rule->term_vars[c].second = second->id; } else { ecs_rule_var_t *av = create_anonymous_variable(rule, EcsRuleVarKindEntity); rule->term_vars[c].second = av->id; written[av->id] = true; } insert_reflexive_set(rule, EcsRuleSubSet, src, set_pair, c, written, filter->reflexive); } else if (src == second) { insert_select_or_with(rule, c, term, src, filter, written); } else { ecs_assert(second != NULL, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *av = NULL; if (!filter->reflexive) { av = create_anonymous_variable(rule, EcsRuleVarKindEntity); } src = &rule->vars[subj_id]; second = &rule->vars[obj_id]; second = to_entity(rule, second); /* Insert instruction to find all sources and objects */ ecs_rule_op_t *op = insert_operation(rule, -1, written); op->kind = EcsRuleSelect; set_output_to_subj(rule, op, term, src); op->filter.first = filter->first; if (filter->reflexive) { op->filter.second.id = EcsWildcard; op->filter.reg_mask = filter->reg_mask & RULE_PAIR_PREDICATE; } else { op->filter.second.reg = av->id; op->filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; written[av->id] = true; } written[src->id] = true; /* Create new frame for operations that create reflexive set */ push_frame(rule); /* Insert superset instruction to find all supersets */ if (filter->reflexive) { src = ensure_most_specific_var(rule, src, written); ecs_assert(src->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); ecs_assert(written[src->id] == true, ECS_INTERNAL_ERROR, NULL); ecs_rule_pair_t super_filter = {0}; super_filter.first = filter->first; super_filter.second.reg = src->id; super_filter.reg_mask = filter->reg_mask | RULE_PAIR_OBJECT; insert_reflexive_set(rule, EcsRuleSuperSet, second, super_filter, c, written, true); } else { insert_reflexive_set(rule, EcsRuleSuperSet, second, op->filter, c, written, true); } } } } if (same_obj_subj) { /* Can't have relationship with same variables that is acyclic and not * reflexive, this should've been caught earlier. */ ecs_assert(!filter->acyclic || filter->reflexive, ECS_INTERNAL_ERROR, NULL); /* If relationship is reflexive and entity has an instance of R, no checks * are needed because R(X, X) is always true. */ if (!filter->reflexive) { push_frame(rule); /* Insert check if the (R, X) pair that was found matches with one * of the entities in the table with the pair. */ ecs_rule_op_t *op = insert_operation(rule, -1, written); second = get_most_specific_var(rule, second, written); ecs_assert(second->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); ecs_assert(written[src->id] == true, ECS_INTERNAL_ERROR, NULL); ecs_assert(written[second->id] == true, ECS_INTERNAL_ERROR, NULL); set_input_to_subj(rule, op, term, src); op->filter.second.reg = second->id; op->filter.reg_mask = RULE_PAIR_OBJECT; if (src->kind == EcsRuleVarKindTable) { op->kind = EcsRuleInTable; } else { op->kind = EcsRuleEq; } } } } static void insert_term_1( ecs_rule_t *rule, ecs_term_t *term, ecs_rule_pair_t *filter, int32_t c, bool *written) { ecs_rule_var_t *src = term_subj(rule, term); src = get_most_specific_var(rule, src, written); insert_select_or_with(rule, c, term, src, filter, written); } static void insert_term( ecs_rule_t *rule, ecs_term_t *term, int32_t c, bool *written) { bool obj_set = obj_is_set(term); ensure_most_specific_var(rule, term_pred(rule, term), written); if (obj_set) { ensure_most_specific_var(rule, term_obj(rule, term), written); } /* If term has Not operator, prepend Not which turns a fail into a pass */ int32_t prev = rule->operation_count; ecs_rule_op_t *not_pre; if (term->oper == EcsNot) { not_pre = insert_operation(rule, -1, written); not_pre->kind = EcsRuleNot; not_pre->has_in = false; not_pre->has_out = false; } ecs_rule_pair_t filter = term_to_pair(rule, term); prepare_predicate(rule, &filter, c, written); if (subj_is_set(term) && !obj_set) { insert_term_1(rule, term, &filter, c, written); } else if (obj_set) { insert_term_2(rule, term, &filter, c, written); } /* If term has Not operator, append Not which turns a pass into a fail */ if (term->oper == EcsNot) { ecs_rule_op_t *not_post = insert_operation(rule, -1, written); not_post->kind = EcsRuleNot; not_post->has_in = false; not_post->has_out = false; not_post->on_pass = prev - 1; not_post->on_fail = prev - 1; not_pre = &rule->operations[prev]; not_pre->on_fail = rule->operation_count; } if (term->oper == EcsOptional) { /* Insert Not instruction that ensures that the optional term is only * executed once */ ecs_rule_op_t *jump = insert_operation(rule, -1, written); jump->kind = EcsRuleNot; jump->has_in = false; jump->has_out = false; jump->on_pass = rule->operation_count; jump->on_fail = prev - 1; /* Find exit instruction for optional term, and make the fail label * point to the Not operation, so that even when the operation fails, * it won't discard the result */ int i, min_fail = -1, exit_op = -1; for (i = prev; i < rule->operation_count; i ++) { ecs_rule_op_t *op = &rule->operations[i]; if (min_fail == -1 || (op->on_fail >= 0 && op->on_fail < min_fail)){ min_fail = op->on_fail; exit_op = i; } } ecs_assert(exit_op != -1, ECS_INTERNAL_ERROR, NULL); ecs_rule_op_t *op = &rule->operations[exit_op]; op->on_fail = rule->operation_count - 1; } push_frame(rule); } /* Create program from operations that will execute the query */ static void compile_program( ecs_rule_t *rule) { /* Trace which variables have been written while inserting instructions. * This determines which instruction needs to be inserted */ bool written[ECS_RULE_MAX_VAR_COUNT] = { false }; ecs_term_t *terms = rule->filter.terms; int32_t v, c, term_count = rule->filter.term_count; ecs_rule_op_t *op; /* Insert input, which is always the first instruction */ insert_input(rule); /* First insert all instructions that do not have a variable subject. Such * instructions iterate the type of an entity literal and are usually good * candidates for quickly narrowing down the set of potential results. */ for (c = 0; c < term_count; c ++) { ecs_term_t *term = &terms[c]; if (skip_term(term)) { continue; } if (term->oper == EcsOptional || term->oper == EcsNot) { continue; } ecs_rule_var_t* src = term_subj(rule, term); if (src) { continue; } insert_term(rule, term, c, written); } /* Insert variables based on dependency order */ for (v = 0; v < rule->subj_var_count; v ++) { int32_t var_id = rule->var_eval_order[v]; ecs_rule_var_t *var = &rule->vars[var_id]; ecs_assert(var->kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); for (c = 0; c < term_count; c ++) { ecs_term_t *term = &terms[c]; if (skip_term(term)) { continue; } if (term->oper == EcsOptional || term->oper == EcsNot) { continue; } /* Only process columns for which variable is subject */ ecs_rule_var_t* src = term_subj(rule, term); if (src != var) { continue; } insert_term(rule, term, c, written); var = &rule->vars[var_id]; } } /* Insert terms with Not operators */ for (c = 0; c < term_count; c ++) { ecs_term_t *term = &terms[c]; if (term->oper != EcsNot) { continue; } insert_term(rule, term, c, written); } /* Insert terms with Optional operators last, as optional terms cannot * eliminate results, and would just add overhead to evaluation of * non-matching entities. */ for (c = 0; c < term_count; c ++) { ecs_term_t *term = &terms[c]; if (term->oper != EcsOptional) { continue; } insert_term(rule, term, c, written); } /* Verify all subject variables have been written. Source variables are of * the table type, and a select/subset should have been inserted for each */ for (v = 0; v < rule->subj_var_count; v ++) { if (!written[v]) { /* If the table variable hasn't been written, this can only happen * if an instruction wrote the variable before a select/subset could * have been inserted for it. Make sure that this is the case by * testing if an entity variable exists and whether it has been * written. */ ecs_rule_var_t *var = find_variable( rule, EcsRuleVarKindEntity, rule->vars[v].name); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(written[var->id], ECS_INTERNAL_ERROR, var->name); (void)var; } } /* Make sure that all entity variables are written. With the exception of * the this variable, which can be returned as a table, other variables need * to be available as entities. This ensures that all permutations for all * variables are correctly returned by the iterator. When an entity variable * hasn't been written yet at this point, it is because it only constrained * through a common predicate or object. */ for (; v < rule->var_count; v ++) { if (!written[v]) { ecs_rule_var_t *var = &rule->vars[v]; ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *table_var = find_variable( rule, EcsRuleVarKindTable, var->name); /* A table variable must exist if the variable hasn't been resolved * yet. If there doesn't exist one, this could indicate an * unconstrained variable which should have been caught earlier */ ecs_assert(table_var != NULL, ECS_INTERNAL_ERROR, var->name); /* Insert each operation that takes the table variable as input, and * yields each entity in the table */ op = insert_operation(rule, -1, written); op->kind = EcsRuleEach; op->r_in = table_var->id; op->r_out = var->id; op->frame = rule->frame_count; op->has_in = true; op->has_out = true; written[var->id] = true; push_frame(rule); } } /* Insert yield, which is always the last operation */ insert_yield(rule); } static void create_variable_name_array( ecs_rule_t *rule) { if (rule->var_count) { int i; for (i = 0; i < rule->var_count; i ++) { ecs_rule_var_t *var = &rule->vars[i]; if (var->kind != EcsRuleVarKindEntity) { /* Table variables are hidden for applications. */ rule->var_names[var->id] = NULL; } else { rule->var_names[var->id] = var->name; } } } } static void create_variable_cross_references( ecs_rule_t *rule) { if (rule->var_count) { int i; for (i = 0; i < rule->var_count; i ++) { ecs_rule_var_t *var = &rule->vars[i]; if (var->kind == EcsRuleVarKindEntity) { ecs_rule_var_t *tvar = find_variable( rule, EcsRuleVarKindTable, var->name); if (tvar) { var->other = tvar->id; } else { var->other = -1; } } else { ecs_rule_var_t *evar = find_variable( rule, EcsRuleVarKindEntity, var->name); if (evar) { var->other = evar->id; } else { var->other = -1; } } } } } /* Implementation for iterable mixin */ static void rule_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_rule_t); if (filter) { iter[1] = ecs_rule_iter(world, (ecs_rule_t*)poly); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_rule_iter(world, (ecs_rule_t*)poly); } } static int32_t find_term_var_id( ecs_rule_t *rule, ecs_term_id_t *term_id) { if (term_id_is_variable(term_id)) { const char *var_name = term_id_var_name(term_id); ecs_rule_var_t *var = find_variable( rule, EcsRuleVarKindEntity, var_name); if (var) { return var->id; } else { /* If this is Any look for table variable. Since Any is only * required to return a single result, there is no need to * insert an each instruction for a matching table. */ if (term_id->id == EcsAny) { var = find_variable( rule, EcsRuleVarKindTable, var_name); if (var) { return var->id; } } } } return -1; } static void flecs_rule_fini( ecs_rule_t *rule) { int32_t i; for (i = 0; i < rule->var_count; i ++) { ecs_os_free(rule->vars[i].name); } ecs_filter_fini(&rule->filter); ecs_os_free(rule->operations); ecs_os_free(rule); } 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); /* 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; } /* Rule has no terms */ if (!result->filter.term_count) { rule_error(result, "rule has no terms"); goto error; } ecs_term_t *terms = result->filter.terms; int32_t i, term_count = result->filter.term_count; /* Make sure rule doesn't just have Not terms */ for (i = 0; i < term_count; i++) { ecs_term_t *term = &terms[i]; if (term->oper != EcsNot) { break; } } if (i == term_count) { rule_error(result, "rule cannot only have terms with Not operator"); goto error; } /* Translate terms with a Not operator and Wildcard to Any, as we don't need * implicit variables for those */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper == EcsNot) { if (term->first.id == EcsWildcard) { term->first.id = EcsAny; } if (term->src.id == EcsWildcard) { term->src.id = EcsAny; } if (term->second.id == EcsWildcard) { term->second.id = EcsAny; } } } /* Find all variables & resolve dependencies */ if (scan_variables(result) != 0) { goto error; } /* Create lookup array for subject variables */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ecs_rule_term_vars_t *vars = &result->term_vars[i]; vars->first = find_term_var_id(result, &term->first); vars->src = find_term_var_id(result, &term->src); vars->second = find_term_var_id(result, &term->second); } /* Generate the opcode array */ compile_program(result); /* Create array with variable names so this can be easily accessed by * iterators without requiring access to the ecs_rule_t */ create_variable_name_array(result); /* Create cross-references between variables so it's easy to go from entity * to table variable and vice versa */ create_variable_cross_references(result); result->iterable.init = rule_iter_init; 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; } const ecs_filter_t* ecs_rule_get_filter( const ecs_rule_t *rule) { return &rule->filter; } /* Quick convenience function to get a variable from an id */ static ecs_rule_var_t* get_variable( const ecs_rule_t *rule, int32_t var_id) { if (var_id == UINT8_MAX) { return NULL; } return (ecs_rule_var_t*)&rule->vars[var_id]; } /* Convert the program to a string. This can be useful to analyze how a rule is * being evaluated. */ char* ecs_rule_str( ecs_rule_t *rule) { ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); ecs_world_t *world = rule->filter.world; ecs_strbuf_t buf = ECS_STRBUF_INIT; char filter_expr[256]; int32_t i, count = rule->operation_count; for (i = 1; i < count; i ++) { ecs_rule_op_t *op = &rule->operations[i]; ecs_rule_pair_t pair = op->filter; ecs_entity_t first = pair.first.id; ecs_entity_t second = pair.second.id; const char *pred_name = NULL, *obj_name = NULL; char *pred_name_alloc = NULL, *obj_name_alloc = NULL; if (pair.reg_mask & RULE_PAIR_PREDICATE) { ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *type_var = &rule->vars[pair.first.reg]; pred_name = type_var->name; } else if (first) { pred_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, first)); pred_name = pred_name_alloc; } if (pair.reg_mask & RULE_PAIR_OBJECT) { ecs_assert(rule->vars != NULL, ECS_INTERNAL_ERROR, NULL); ecs_rule_var_t *obj_var = &rule->vars[pair.second.reg]; obj_name = obj_var->name; } else if (second) { obj_name_alloc = ecs_get_fullpath(world, ecs_get_alive(world, second)); obj_name = obj_name_alloc; } else if (pair.second_0) { obj_name = "0"; } ecs_strbuf_append(&buf, "%2d: [S:%2d, P:%2d, F:%2d, T:%2d] ", i, op->frame, op->on_pass, op->on_fail, op->term); bool has_filter = false; switch(op->kind) { case EcsRuleSelect: ecs_strbuf_append(&buf, "select "); has_filter = true; break; case EcsRuleWith: ecs_strbuf_append(&buf, "with "); has_filter = true; break; case EcsRuleStore: ecs_strbuf_append(&buf, "store "); break; case EcsRuleSuperSet: ecs_strbuf_append(&buf, "superset "); has_filter = true; break; case EcsRuleSubSet: ecs_strbuf_append(&buf, "subset "); has_filter = true; break; case EcsRuleEach: ecs_strbuf_append(&buf, "each "); break; case EcsRuleSetJmp: ecs_strbuf_append(&buf, "setjmp "); break; case EcsRuleJump: ecs_strbuf_append(&buf, "jump "); break; case EcsRuleNot: ecs_strbuf_append(&buf, "not "); break; case EcsRuleInTable: ecs_strbuf_append(&buf, "intable "); has_filter = true; break; case EcsRuleEq: ecs_strbuf_append(&buf, "eq "); has_filter = true; break; case EcsRuleYield: ecs_strbuf_append(&buf, "yield "); break; default: continue; } if (op->has_out) { ecs_rule_var_t *r_out = get_variable(rule, op->r_out); if (r_out) { ecs_strbuf_append(&buf, "O:%s%s ", r_out->kind == EcsRuleVarKindTable ? "t" : "", r_out->name); } else if (op->subject) { char *subj_path = ecs_get_fullpath(world, op->subject); ecs_strbuf_append(&buf, "O:%s ", subj_path); ecs_os_free(subj_path); } } if (op->has_in) { ecs_rule_var_t *r_in = get_variable(rule, op->r_in); if (r_in) { ecs_strbuf_append(&buf, "I:%s%s ", r_in->kind == EcsRuleVarKindTable ? "t" : "", r_in->name); } if (op->subject) { char *subj_path = ecs_get_fullpath(world, op->subject); ecs_strbuf_append(&buf, "I:%s ", subj_path); ecs_os_free(subj_path); } } if (has_filter) { if (!pred_name) { pred_name = "-"; } if (!obj_name && !pair.second_0) { ecs_os_sprintf(filter_expr, "(%s)", pred_name); } else { ecs_os_sprintf(filter_expr, "(%s, %s)", pred_name, obj_name); } ecs_strbuf_append(&buf, "F:%s", filter_expr); } ecs_strbuf_appendch(&buf, '\n'); ecs_os_free(pred_name_alloc); ecs_os_free(obj_name_alloc); } return ecs_strbuf_get(&buf); error: return NULL; } /* Public function that returns number of variables. This enables an application * to iterate the variables and obtain their values. */ int32_t ecs_rule_var_count( const ecs_rule_t *rule) { ecs_assert(rule != NULL, ECS_INTERNAL_ERROR, NULL); return rule->var_count; } /* Public function to find a variable by name */ int32_t ecs_rule_find_var( const ecs_rule_t *rule, const char *name) { ecs_rule_var_t *v = find_variable(rule, EcsRuleVarKindEntity, name); if (v) { return v->id; } else { return -1; } } /* Public function to get the name of a variable. */ const char* ecs_rule_var_name( const ecs_rule_t *rule, int32_t var_id) { return rule->vars[var_id].name; } /* Public function to get the type of a variable. */ bool ecs_rule_var_is_entity( const ecs_rule_t *rule, int32_t var_id) { return rule->vars[var_id].kind == EcsRuleVarKindEntity; } static void ecs_rule_iter_free( ecs_iter_t *iter) { ecs_rule_iter_t *it = &iter->priv.iter.rule; ecs_os_free(it->registers); ecs_os_free(it->columns); ecs_os_free(it->op_ctx); iter->columns = NULL; it->registers = NULL; it->columns = NULL; it->op_ctx = NULL; } /* Create rule iterator */ ecs_iter_t ecs_rule_iter( const ecs_world_t *world, const ecs_rule_t *rule) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(rule != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = {0}; int i; result.world = (ecs_world_t*)world; result.real_world = (ecs_world_t*)ecs_get_world(rule->filter.world); flecs_process_pending_tables(result.real_world); ecs_rule_iter_t *it = &result.priv.iter.rule; it->rule = rule; if (rule->operation_count) { if (rule->var_count) { it->registers = ecs_os_malloc_n(ecs_var_t, rule->operation_count * rule->var_count); } it->op_ctx = ecs_os_calloc_n(ecs_rule_op_ctx_t, rule->operation_count); if (rule->filter.term_count) { it->columns = ecs_os_malloc_n(int32_t, rule->operation_count * rule->filter.term_count); } for (i = 0; i < rule->filter.term_count; i ++) { it->columns[i] = -1; } } it->op = 0; for (i = 0; i < rule->var_count; i ++) { if (rule->vars[i].kind == EcsRuleVarKindEntity) { entity_reg_set(rule, it->registers, i, EcsWildcard); } else { table_reg_set(rule, it->registers, i, NULL); } } result.variable_names = (char**)rule->var_names; result.variable_count = rule->var_count; result.field_count = rule->filter.term_count; result.terms = rule->filter.terms; result.next = ecs_rule_next; result.fini = ecs_rule_iter_free; ECS_BIT_COND(result.flags, EcsIterIsFilter, ECS_BIT_IS_SET(rule->filter.flags, EcsFilterNoData)); flecs_iter_init(world, &result, flecs_iter_cache_ids | /* flecs_iter_cache_columns | provided by rule iterator */ flecs_iter_cache_sources | flecs_iter_cache_sizes | flecs_iter_cache_ptrs | /* flecs_iter_cache_match_indices | not necessary for iteration */ flecs_iter_cache_variables); result.columns = it->columns; /* prevent alloc */ return result; } /* Edge case: if the filter has the same variable for both predicate and * object, they are both resolved at the same time but at the time of * evaluating the filter they're still wildcards which would match columns * that have different predicates/objects. Do an additional scan to make * sure the column we're returning actually matches. */ static int32_t find_next_same_var( ecs_type_t type, int32_t column, ecs_id_t pattern) { /* If same_var is true, this has to be a wildcard pair. We cannot have * the same variable in a pair, and one part of a pair resolved with * another part unresolved. */ ecs_assert(pattern == ecs_pair(EcsWildcard, EcsWildcard), ECS_INTERNAL_ERROR, NULL); (void)pattern; /* Keep scanning for an id where rel and second are the same */ ecs_id_t *ids = type.array; int32_t i, count = type.count; for (i = column + 1; i < count; i ++) { ecs_id_t id = ids[i]; if (!ECS_HAS_ID_FLAG(id, PAIR)) { /* If id is not a pair, this will definitely not match, and we * will find no further matches. */ return -1; } if (ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id)) { /* Found a match! */ return i; } } /* No pairs found with same rel/second */ return -1; } static int32_t find_next_column( const ecs_world_t *world, const ecs_table_t *table, int32_t column, ecs_rule_filter_t *filter) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t pattern = filter->mask; ecs_type_t type = table->type; if (column == -1) { ecs_table_record_t *tr = flecs_table_record_get(world, table, pattern); if (!tr) { return -1; } column = tr->column; } else { column = ecs_search_offset(world, table, column + 1, filter->mask, 0); if (column == -1) { return -1; } } if (filter->same_var) { column = find_next_same_var(type, column - 1, filter->mask); } return column; } /* This function finds the next table in a table set, and is used by the select * operation. The function automatically skips empty tables, so that subsequent * operations don't waste a lot of processing for nothing. */ static ecs_table_record_t find_next_table( ecs_rule_filter_t *filter, ecs_rule_with_ctx_t *op_ctx) { ecs_table_cache_iter_t *it = &op_ctx->it; ecs_table_t *table = NULL; int32_t column = -1; const ecs_table_record_t *tr; while ((column == -1) && (tr = flecs_table_cache_next(it, ecs_table_record_t))) { table = tr->hdr.table; /* Should only iterate non-empty tables */ ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); column = tr->column; if (filter->same_var) { column = find_next_same_var(table->type, column - 1, filter->mask); } } if (column == -1) { table = NULL; } return (ecs_table_record_t){.hdr.table = table, .column = column}; } static ecs_id_record_t* find_tables( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr || !flecs_table_cache_count(&idr->cache)) { /* Skip ids that don't have (non-empty) tables */ return NULL; } return idr; } static ecs_id_t rule_get_column( ecs_type_t type, int32_t column) { ecs_assert(column < type.count, ECS_INTERNAL_ERROR, NULL); ecs_id_t *comp = &type.array[column]; return *comp; } static void set_source( ecs_iter_t *it, ecs_rule_op_t *op, ecs_var_t *regs, int32_t r) { if (op->term == -1) { /* If operation is not associated with a term, don't set anything */ return; } ecs_assert(op->term >= 0, ECS_INTERNAL_ERROR, NULL); const ecs_rule_t *rule = it->priv.iter.rule.rule; if ((r != UINT8_MAX) && rule->vars[r].kind == EcsRuleVarKindEntity) { it->sources[op->term] = reg_get_entity(rule, op, regs, r); } else { it->sources[op->term] = 0; } } static void set_term_vars( const ecs_rule_t *rule, ecs_var_t *regs, int32_t term, ecs_id_t id) { if (term != -1) { ecs_world_t *world = rule->filter.world; const ecs_rule_term_vars_t *vars = &rule->term_vars[term]; if (vars->first != -1) { regs[vars->first].entity = ecs_pair_first(world, id); ecs_assert(ecs_is_valid(world, regs[vars->first].entity), ECS_INTERNAL_ERROR, NULL); } if (vars->second != -1) { regs[vars->second].entity = ecs_pair_second(world, id); ecs_assert(ecs_is_valid(world, regs[vars->second].entity), ECS_INTERNAL_ERROR, NULL); } } } /* Input operation. The input operation acts as a placeholder for the start of * the program, and creates an entry in the register array that can serve to * store variables passed to an iterator. */ static bool eval_input( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { (void)it; (void)op; (void)op_index; if (!redo) { /* First operation executed by the iterator. Always return true. */ return true; } else { /* When Input is asked to redo, it means that all other operations have * exhausted their results. Input itself does not yield anything, so * return false. This will terminate rule execution. */ return false; } } static bool eval_superset( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; ecs_world_t *world = rule->filter.world; ecs_rule_superset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.superset; ecs_rule_superset_frame_t *frame = NULL; ecs_var_t *regs = get_registers(iter, op); /* Get register indices for output */ int32_t sp; int32_t r = op->r_out; /* Register cannot be a literal, since we need to store things in it */ ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); /* Get queried for id, fill out potential variables */ ecs_rule_pair_t pair = op->filter; ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); ecs_entity_t rel = ECS_PAIR_FIRST(filter.mask); ecs_rule_filter_t super_filter = { .mask = ecs_pair(rel, EcsWildcard) }; ecs_table_t *table = NULL; /* Check if input register is constrained */ ecs_entity_t result = iter->registers[r].entity; bool output_is_input = ecs_iter_var_is_constrained(it, r); if (output_is_input && !redo) { ecs_assert(regs[r].entity == iter->registers[r].entity, ECS_INTERNAL_ERROR, NULL); } if (!redo) { op_ctx->stack = op_ctx->storage; sp = op_ctx->sp = 0; frame = &op_ctx->stack[sp]; /* Get table of object for which to get supersets */ ecs_entity_t second = ECS_PAIR_SECOND(filter.mask); if (second == EcsWildcard) { ecs_assert(pair.reg_mask & RULE_PAIR_OBJECT, ECS_INTERNAL_ERROR, NULL); table = regs[pair.second.reg].range.table; } else { table = table_from_entity(world, second).table; } int32_t column; /* If output variable is already set, check if it matches */ if (output_is_input) { ecs_id_t id = ecs_pair(rel, result); ecs_entity_t src = 0; column = ecs_search_relation(world, table, 0, id, rel, 0, &src, 0, 0); if (column != -1) { if (src != 0) { table = ecs_get_table(world, src); } } } else { column = find_next_column(world, table, -1, &super_filter); } /* If no matching column was found, there are no supersets */ if (column == -1) { return false; } ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t col_id = rule_get_column(table->type, column); ecs_assert(ECS_HAS_ID_FLAG(col_id, PAIR), ECS_INTERNAL_ERROR, NULL); ecs_entity_t col_obj = ecs_pair_second(world, col_id); reg_set_entity(rule, regs, r, col_obj); frame->table = table; frame->column = column; return true; } else if (output_is_input) { return false; } sp = op_ctx->sp; frame = &op_ctx->stack[sp]; table = frame->table; int32_t column = frame->column; ecs_id_t col_id = rule_get_column(table->type, column); ecs_entity_t col_obj = ecs_pair_second(world, col_id); ecs_table_t *next_table = table_from_entity(world, col_obj).table; if (next_table) { sp ++; frame = &op_ctx->stack[sp]; frame->table = next_table; frame->column = -1; } do { frame = &op_ctx->stack[sp]; table = frame->table; column = frame->column; column = find_next_column(world, table, column, &super_filter); if (column != -1) { op_ctx->sp = sp; frame->column = column; col_id = rule_get_column(table->type, column); col_obj = ecs_pair_second(world, col_id); reg_set_entity(rule, regs, r, col_obj); return true; } sp --; } while (sp >= 0); return false; } static bool eval_subset( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; ecs_world_t *world = rule->filter.world; ecs_rule_subset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.subset; ecs_rule_subset_frame_t *frame = NULL; ecs_table_record_t table_record; ecs_var_t *regs = get_registers(iter, op); /* Get register indices for output */ int32_t sp, row; int32_t r = op->r_out; ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); /* Get queried for id, fill out potential variables */ ecs_rule_pair_t pair = op->filter; ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); ecs_id_record_t *idr; ecs_table_t *table = NULL; if (!redo) { op_ctx->stack = op_ctx->storage; sp = op_ctx->sp = 0; frame = &op_ctx->stack[sp]; idr = frame->with_ctx.idr = find_tables(world, filter.mask); if (!idr) { return false; } flecs_table_cache_iter(&idr->cache, &frame->with_ctx.it); table_record = find_next_table(&filter, &frame->with_ctx); /* If first table set has no non-empty table, yield nothing */ if (!table_record.hdr.table) { return false; } frame->row = 0; frame->column = table_record.column; table_reg_set(rule, regs, r, (frame->table = table_record.hdr.table)); goto yield; } do { sp = op_ctx->sp; frame = &op_ctx->stack[sp]; table = frame->table; row = frame->row; /* If row exceeds number of elements in table, find next table in frame that * still has entities */ while ((sp >= 0) && (row >= ecs_table_count(table))) { table_record = find_next_table(&filter, &frame->with_ctx); if (table_record.hdr.table) { table = frame->table = table_record.hdr.table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); frame->row = 0; frame->column = table_record.column; table_reg_set(rule, regs, r, table); goto yield; } else { sp = -- op_ctx->sp; if (sp < 0) { /* If none of the frames yielded anything, no more data */ return false; } frame = &op_ctx->stack[sp]; table = frame->table; idr = frame->with_ctx.idr; row = ++ frame->row; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); } } int32_t row_count = ecs_table_count(table); /* Table must have at least row elements */ ecs_assert(row_count > row, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); /* The entity used to find the next table set */ do { ecs_entity_t e = entities[row]; /* Create look_for expression with the resolved entity as object */ pair.reg_mask &= ~RULE_PAIR_OBJECT; /* turn of bit because it's not a reg */ pair.second.id = e; filter = pair_to_filter(iter, op, pair); /* Find table set for expression */ table = NULL; idr = find_tables(world, filter.mask); /* If table set is found, find first non-empty table */ if (idr) { ecs_rule_subset_frame_t *new_frame = &op_ctx->stack[sp + 1]; new_frame->with_ctx.idr = idr; flecs_table_cache_iter(&idr->cache, &new_frame->with_ctx.it); table_record = find_next_table(&filter, &new_frame->with_ctx); /* If set contains non-empty table, push it to stack */ if (table_record.hdr.table) { table = table_record.hdr.table; op_ctx->sp ++; new_frame->table = table; new_frame->row = 0; new_frame->column = table_record.column; frame = new_frame; } } /* If no table was found for the current entity, advance row */ if (!table) { row = ++ frame->row; } } while (!table && row < row_count); } while (!table); table_reg_set(rule, regs, r, table); yield: set_term_vars(rule, regs, op->term, frame->table->type.array[frame->column]); return true; } /* Select operation. The select operation finds and iterates a table set that * corresponds to its pair expression. */ static bool eval_select( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; ecs_world_t *world = rule->filter.world; ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; ecs_table_record_t table_record; ecs_var_t *regs = get_registers(iter, op); /* Get register indices for output */ int32_t r = op->r_out; ecs_assert(r != UINT8_MAX, ECS_INTERNAL_ERROR, NULL); /* Get queried for id, fill out potential variables */ ecs_rule_pair_t pair = op->filter; ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); ecs_entity_t pattern = filter.mask; int32_t *columns = rule_get_columns(iter, op); int32_t column = -1; ecs_table_t *table = NULL; ecs_id_record_t *idr; if (!redo && op->term != -1) { columns[op->term] = -1; } /* If this is a redo, we already looked up the table set */ if (redo) { idr = op_ctx->idr; /* If this is not a redo lookup the table set. Even though this may not be * the first time the operation is evaluated, variables may have changed * since last time, which could change the table set to lookup. */ } else { /* A table set is a set of tables that all contain at least the * requested look_for expression. What is returned is a table record, * which in addition to the table also stores the first occurrance at * which the requested expression occurs in the table. This reduces (and * in most cases eliminates) any searching that needs to occur in a * table type. Tables are also registered under wildcards, which is why * this operation can simply use the look_for variable directly */ idr = op_ctx->idr = find_tables(world, pattern); } /* If no table set was found for queried for entity, there are no results */ if (!idr) { return false; } /* If the input register is not NULL, this is a variable that's been set by * the application. */ table = iter->registers[r].range.table; bool output_is_input = table != NULL; if (output_is_input && !redo) { ecs_assert(regs[r].range.table == iter->registers[r].range.table, ECS_INTERNAL_ERROR, NULL); table = iter->registers[r].range.table; /* Check if table can be found in the id record. If not, the provided * table does not match with the query. */ ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); if (!tr) { return false; } column = op_ctx->column = tr->column; } /* If this is not a redo, start at the beginning */ if (!redo) { if (!table) { flecs_table_cache_iter(&idr->cache, &op_ctx->it); /* Return the first table_record in the table set. */ table_record = find_next_table(&filter, op_ctx); /* If no table record was found, there are no results. */ if (!table_record.hdr.table) { return false; } table = table_record.hdr.table; /* Set current column to first occurrence of queried for entity */ column = op_ctx->column = table_record.column; /* Store table in register */ table_reg_set(rule, regs, r, table); } /* If this is a redo, progress to the next match */ } else { /* First test if there are any more matches for the current table, in * case we're looking for a wildcard. */ if (filter.wildcard) { table = table_reg_get(rule, regs, r).table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); column = op_ctx->column; column = find_next_column(world, table, column, &filter); op_ctx->column = column; } /* If no next match was found for this table, move to next table */ if (column == -1) { if (output_is_input) { return false; } table_record = find_next_table(&filter, op_ctx); if (!table_record.hdr.table) { return false; } /* Assign new table to table register */ table_reg_set(rule, regs, r, (table = table_record.hdr.table)); /* Assign first matching column */ column = op_ctx->column = table_record.column; } } /* If we got here, we found a match. Table and column must be set */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); if (op->term != -1) { columns[op->term] = column; } /* If this is a wildcard query, fill out the variable registers */ if (filter.wildcard) { reify_variables(iter, op, &filter, table->type, column); } return true; } /* With operation. The With operation always comes after either the Select or * another With operation, and applies additional filters to the table. */ static bool eval_with( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; ecs_world_t *world = rule->filter.world; ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; ecs_var_t *regs = get_registers(iter, op); /* Get register indices for input */ int32_t r = op->r_in; /* Get queried for id, fill out potential variables */ ecs_rule_pair_t pair = op->filter; ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); int32_t *columns = rule_get_columns(iter, op); /* If looked for entity is not a wildcard (meaning there are no unknown/ * unconstrained variables) and this is a redo, nothing more to yield. */ if (redo && !filter.wildcard) { return false; } int32_t column = -1; ecs_table_t *table = NULL; ecs_id_record_t *idr; if (op->term != -1) { columns[op->term] = -1; } /* If this is a redo, we already looked up the table set */ if (redo) { idr = op_ctx->idr; /* If this is not a redo lookup the table set. Even though this may not be * the first time the operation is evaluated, variables may have changed * since last time, which could change the table set to lookup. */ } else { /* Predicates can be reflexive, which means that if we have a * transitive predicate which is provided with the same subject and * object, it should return true. By default with will not return true * as the subject likely does not have itself as a relationship, which * is why this is a special case. * * TODO: might want to move this code to a separate with_reflexive * instruction to limit branches for non-transitive queries (and to keep * code more readable). */ if (pair.transitive && pair.reflexive) { ecs_entity_t src = 0, second = 0; if (r == UINT8_MAX) { src = op->subject; } else { const ecs_rule_var_t *v_subj = &rule->vars[r]; if (v_subj->kind == EcsRuleVarKindEntity) { src = entity_reg_get(rule, regs, r); /* This is the input for the op, so should always be set */ ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); } } /* If src is set, it means that it is an entity. Try to also * resolve the object. */ if (src) { /* If the object is not a wildcard, it has been reified. Get the * value from either the register or as a literal */ if (!filter.second_wildcard) { second = ECS_PAIR_SECOND(filter.mask); if (ecs_strip_generation(src) == second) { return true; } } } } /* The With operation finds the table set that belongs to its pair * filter. The table set is a sparse set that provides an O(1) operation * to check whether the current table has the required expression. */ idr = op_ctx->idr = find_tables(world, filter.mask); } /* If no table set was found for queried for entity, there are no results. * If this result is a transitive query, the table we're evaluating may not * be in the returned table set. Regardless, if the filter that contains a * transitive predicate does not have any tables associated with it, there * can be no transitive matches for the filter. */ if (!idr) { return false; } table = reg_get_range(rule, op, regs, r).table; if (!table) { return false; } /* If this is not a redo, start at the beginning */ if (!redo) { column = op_ctx->column = find_next_column(world, table, -1, &filter); /* If this is a redo, progress to the next match */ } else { if (!filter.wildcard) { return false; } /* Find the next match for the expression in the column. The columns * array keeps track of the state for each With operation, so that * even after redoing a With, the search doesn't have to start from * the beginning. */ column = find_next_column(world, table, op_ctx->column, &filter); op_ctx->column = column; } /* If no next match was found for this table, no more data */ if (column == -1) { return false; } if (op->term != -1) { columns[op->term] = column; } /* If we got here, we found a match. Table and column must be set */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); /* If this is a wildcard query, fill out the variable registers */ if (filter.wildcard) { reify_variables(iter, op, &filter, table->type, column); } set_source(it, op, regs, r); return true; } /* Each operation. The each operation is a simple operation that takes a table * as input, and outputs each of the entities in a table. This operation is * useful for rules that match a table, and where the entities of the table are * used as predicate or object. If a rule contains an each operation, an * iterator is guaranteed to yield an entity instead of a table. The input for * an each operation can only be the root variable. */ static bool eval_each( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { ecs_rule_iter_t *iter = &it->priv.iter.rule; ecs_rule_each_ctx_t *op_ctx = &iter->op_ctx[op_index].is.each; ecs_var_t *regs = get_registers(iter, op); int32_t r_in = op->r_in; int32_t r_out = op->r_out; ecs_entity_t e; /* Make sure in/out registers are of the correct kind */ ecs_assert(iter->rule->vars[r_in].kind == EcsRuleVarKindTable, ECS_INTERNAL_ERROR, NULL); ecs_assert(iter->rule->vars[r_out].kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); /* Get table, make sure that it contains data. The select operation should * ensure that empty tables are never forwarded. */ ecs_table_range_t slice = table_reg_get(iter->rule, regs, r_in); ecs_table_t *table = slice.table; if (table) { int32_t row, count = slice.count; int32_t offset = slice.offset; if (!count) { count = ecs_table_count(table); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); } else { count += offset; } ecs_entity_t *entities = ecs_vec_first(&table->data.entities); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); /* If this is is not a redo, start from row 0, otherwise go to the * next entity. */ if (!redo) { row = op_ctx->row = offset; } else { row = ++ op_ctx->row; } /* If row exceeds number of entities in table, return false */ if (row >= count) { return false; } /* Skip builtin entities that could confuse operations */ e = entities[row]; while (e == EcsWildcard || e == EcsThis || e == EcsAny) { row ++; if (row == count) { return false; } e = entities[row]; } } else { if (!redo) { e = entity_reg_get(iter->rule, regs, r_in); } else { return false; } } /* Assign entity */ entity_reg_set(iter->rule, regs, r_out, e); return true; } /* Store operation. Stores entity in register. This can either be an entity * literal or an entity variable that will be stored in a table register. The * latter facilitates scenarios where an iterator only need to return a single * entity but where the Yield returns tables. */ static bool eval_store( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { (void)op_index; if (redo) { /* Only ever return result once */ return false; } ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; ecs_world_t *world = rule->filter.world; ecs_var_t *regs = get_registers(iter, op); int32_t r_in = op->r_in; int32_t r_out = op->r_out; (void)world; const ecs_rule_var_t *var_out = &rule->vars[r_out]; if (var_out->kind == EcsRuleVarKindEntity) { ecs_entity_t out, in = reg_get_entity(rule, op, regs, r_in); ecs_assert(in != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_is_valid(world, in), ECS_INTERNAL_ERROR, NULL); out = iter->registers[r_out].entity; bool output_is_input = ecs_iter_var_is_constrained(it, r_out); if (output_is_input && !redo) { ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, ECS_INTERNAL_ERROR, NULL); if (out != in) { /* If output variable is set it must match the input */ return false; } } reg_set_entity(rule, regs, r_out, in); } else { ecs_table_range_t out, in = reg_get_range(rule, op, regs, r_in); out = iter->registers[r_out].range; bool output_is_input = out.table != NULL; if (output_is_input && !redo) { ecs_assert(regs[r_out].entity == iter->registers[r_out].entity, ECS_INTERNAL_ERROR, NULL); if (ecs_os_memcmp_t(&out, &in, ecs_table_range_t)) { /* If output variable is set it must match the input */ return false; } } reg_set_range(rule, regs, r_out, &in); /* Ensure that if the input was an empty entity, information is not * lost */ if (!regs[r_out].range.table) { regs[r_out].entity = reg_get_entity(rule, op, regs, r_in); ecs_assert(ecs_is_valid(world, regs[r_out].entity), ECS_INTERNAL_ERROR, NULL); } } ecs_rule_filter_t filter = pair_to_filter(iter, op, op->filter); set_term_vars(rule, regs, op->term, filter.mask); return true; } /* A setjmp operation sets the jump label for a subsequent jump label. When the * operation is first evaluated (redo=false) it sets the label to the on_pass * label, and returns true. When the operation is evaluated again (redo=true) * the label is set to on_fail and the operation returns false. */ static bool eval_setjmp( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { ecs_rule_iter_t *iter = &it->priv.iter.rule; ecs_rule_setjmp_ctx_t *ctx = &iter->op_ctx[op_index].is.setjmp; if (!redo) { ctx->label = op->on_pass; return true; } else { ctx->label = op->on_fail; return false; } } /* The jump operation jumps to an operation label. The operation always returns * true. Since the operation modifies the control flow of the program directly, * the dispatcher does not look at the on_pass or on_fail labels of the jump * instruction. Instead, the on_pass label is used to store the label of the * operation that contains the label to jump to. */ static bool eval_jump( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { (void)it; (void)op; (void)op_index; /* Passthrough, result is not used for control flow */ return !redo; } /* The not operation reverts the result of the operation it embeds */ static bool eval_not( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { (void)it; (void)op; (void)op_index; return !redo; } /* Check if entity is stored in table */ static bool eval_intable( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { (void)op_index; if (redo) { return false; } ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; ecs_world_t *world = rule->filter.world; ecs_var_t *regs = get_registers(iter, op); ecs_table_t *table = table_reg_get(rule, regs, op->r_in).table; ecs_rule_pair_t pair = op->filter; ecs_rule_filter_t filter = pair_to_filter(iter, op, pair); ecs_entity_t second = ECS_PAIR_SECOND(filter.mask); ecs_assert(second != 0 && second != EcsWildcard, ECS_INTERNAL_ERROR, NULL); second = ecs_get_alive(world, second); ecs_assert(second != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_t *obj_table = ecs_get_table(world, second); return obj_table == table; } /* Yield operation. This is the simplest operation, as all it does is return * false. This will move the solver back to the previous instruction which * forces redo's on previous operations, for as long as there are matching * results. */ static bool eval_yield( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { (void)it; (void)op; (void)op_index; (void)redo; /* Yield always returns false, because there are never any operations after * a yield. */ return false; } /* Dispatcher for operations */ static bool eval_op( ecs_iter_t *it, ecs_rule_op_t *op, int32_t op_index, bool redo) { switch(op->kind) { case EcsRuleInput: return eval_input(it, op, op_index, redo); case EcsRuleSelect: return eval_select(it, op, op_index, redo); case EcsRuleWith: return eval_with(it, op, op_index, redo); case EcsRuleSubSet: return eval_subset(it, op, op_index, redo); case EcsRuleSuperSet: return eval_superset(it, op, op_index, redo); case EcsRuleEach: return eval_each(it, op, op_index, redo); case EcsRuleStore: return eval_store(it, op, op_index, redo); case EcsRuleSetJmp: return eval_setjmp(it, op, op_index, redo); case EcsRuleJump: return eval_jump(it, op, op_index, redo); case EcsRuleNot: return eval_not(it, op, op_index, redo); case EcsRuleInTable: return eval_intable(it, op, op_index, redo); case EcsRuleYield: return eval_yield(it, op, op_index, redo); default: return false; } } /* Utility to copy all registers to the next frame. Keeping track of register * values for each operation is necessary, because if an operation is asked to * redo matching, it must to be able to pick up from where it left of */ static void push_registers( ecs_rule_iter_t *it, int32_t cur, int32_t next) { if (!it->rule->var_count) { return; } ecs_var_t *src_regs = get_register_frame(it, cur); ecs_var_t *dst_regs = get_register_frame(it, next); ecs_os_memcpy_n(dst_regs, src_regs, ecs_var_t, it->rule->var_count); } /* Utility to copy all columns to the next frame. Columns keep track of which * columns are currently being evaluated for a table, and are populated by the * Select and With operations. The columns array is important, as it is used * to tell the application where to find component data. */ static void push_columns( ecs_rule_iter_t *it, int32_t cur, int32_t next) { if (!it->rule->filter.term_count) { return; } int32_t *src_cols = rule_get_columns_frame(it, cur); int32_t *dst_cols = rule_get_columns_frame(it, next); ecs_os_memcpy_n(dst_cols, src_cols, int32_t, it->rule->filter.term_count); } /* Populate iterator with data before yielding to application */ static void populate_iterator( const ecs_rule_t *rule, ecs_iter_t *iter, ecs_rule_iter_t *it, ecs_rule_op_t *op) { ecs_world_t *world = rule->filter.world; int32_t r = op->r_in; ecs_var_t *regs = get_register_frame(it, op->frame); ecs_table_t *table = NULL; int32_t count = 0; int32_t offset = 0; /* If the input register for the yield does not point to a variable, * the rule doesn't contain a this (.) variable. In that case, the * iterator doesn't contain any data, and this function will simply * return true or false. An application will still be able to obtain * the variables that were resolved. */ if (r != UINT8_MAX) { const ecs_rule_var_t *var = &rule->vars[r]; ecs_var_t *reg = ®s[r]; if (var->kind == EcsRuleVarKindTable) { ecs_table_range_t slice = table_reg_get(rule, regs, r); table = slice.table; count = slice.count; offset = slice.offset; if (!count && table) { count = ecs_table_count(table); } } else { /* If a single entity is returned, simply return the * iterator with count 1 and a pointer to the entity id */ ecs_assert(var->kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = reg->entity; ecs_assert(ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_get(world, e); offset = ECS_RECORD_TO_ROW(record->row); /* If an entity is not stored in a table, it could not have * been matched by anything */ ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); table = record->table; count = 1; } } int32_t i, var_count = rule->var_count; int32_t term_count = rule->filter.term_count; for (i = 0; i < var_count; i ++) { iter->variables[i] = regs[i]; } for (i = 0; i < term_count; i ++) { int32_t v = rule->term_vars[i].src; if (v != -1) { const ecs_rule_var_t *var = &rule->vars[v]; if (var->name[0] != '.') { if (var->kind == EcsRuleVarKindEntity) { iter->sources[i] = regs[var->id].entity; } else { /* This can happen for Any variables, where the actual * content of the variable is not of interest to the query. * Just pick the first entity from the table, so that the * column can be correctly resolved */ ecs_table_t *t = regs[var->id].range.table; if (t) { iter->sources[i] = ecs_vec_first_t( &t->data.entities, ecs_entity_t)[0]; } else { /* Can happen if term is optional */ iter->sources[i] = 0; } } } } } /* Iterator expects column indices to start at 1 */ iter->columns = rule_get_columns_frame(it, op->frame); for (i = 0; i < term_count; i ++) { ecs_assert(iter->sources != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t src = iter->sources[i]; int32_t c = ++ iter->columns[i]; if (!src) { src = iter->terms[i].src.id; if (src != EcsThis && src != EcsAny) { iter->columns[i] = 0; } } else if (c) { iter->columns[i] = -1; } } /* Set iterator ids */ for (i = 0; i < term_count; i ++) { const ecs_rule_term_vars_t *vars = &rule->term_vars[i]; ecs_term_t *term = &rule->filter.terms[i]; if (term->oper == EcsOptional || term->oper == EcsNot) { if (iter->columns[i] == 0) { iter->ids[i] = term->id; continue; } } ecs_id_t id = term->id; ecs_entity_t first = 0; ecs_entity_t second = 0; bool is_pair = ECS_HAS_ID_FLAG(id, PAIR); if (!is_pair) { first = id; } else { first = ECS_PAIR_FIRST(id); second = ECS_PAIR_SECOND(id); } if (vars->first != -1) { first = regs[vars->first].entity; } if (vars->second != -1) { ecs_assert(is_pair, ECS_INTERNAL_ERROR, NULL); second = regs[vars->second].entity; } if (!is_pair) { id = first; } else { id = ecs_pair(first, second); } iter->ids[i] = id; } flecs_iter_populate_data(world, iter, table, offset, count, iter->ptrs, iter->sizes); } static bool is_control_flow( ecs_rule_op_t *op) { switch(op->kind) { case EcsRuleSetJmp: case EcsRuleJump: return true; default: return false; } } static void rule_iter_set_initial_state( ecs_iter_t *it, ecs_rule_iter_t *iter, const ecs_rule_t *rule) { int32_t i; /* Make sure that if there are any terms with literal sources, they're * initialized in the sources array */ const ecs_filter_t *filter = &rule->filter; int32_t term_count = filter->term_count; for (i = 0; i < term_count; i ++) { ecs_term_t *t = &filter->terms[i]; ecs_term_id_t *src = &t->src; ecs_assert(src->flags & EcsIsVariable || src->id != EcsThis, ECS_INTERNAL_ERROR, NULL); if (!(src->flags & EcsIsVariable)) { it->sources[i] = src->id; } } /* Initialize registers of constrained variables */ if (it->constrained_vars) { ecs_var_t *regs = get_register_frame(iter, 0); for (i = 0; i < it->variable_count; i ++) { if (ecs_iter_var_is_constrained(it, i)) { const ecs_rule_var_t *var = &rule->vars[i]; ecs_assert(var->id == i, ECS_INTERNAL_ERROR, NULL); int32_t other_var = var->other; ecs_var_t *it_var = &it->variables[i]; ecs_entity_t e = it_var->entity; if (e) { ecs_assert(ecs_is_valid(it->world, e), ECS_INTERNAL_ERROR, NULL); reg_set_entity(rule, regs, i, e); if (other_var != -1) { reg_set_entity(rule, regs, other_var, e); } } else { ecs_assert(it_var->range.table != NULL, ECS_INVALID_PARAMETER, NULL); reg_set_range(rule, regs, i, &it_var->range); if (other_var != -1) { reg_set_range(rule, regs, other_var, &it_var->range); } } } } } } 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; } /* Iterator next function. This evaluates the program until it reaches a Yield * operation, and returns the intermediate result(s) to the application. An * iterator can, depending on the program, either return a table, entity, or * just true/false, in case a rule doesn't contain the this variable. */ bool ecs_rule_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_rule_next, ECS_INVALID_PARAMETER, NULL); ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; bool redo = iter->redo; int32_t last_frame = -1; bool first_time = !ECS_BIT_IS_SET(it->flags, EcsIterIsValid); ecs_poly_assert(rule, ecs_rule_t); /* Mark iterator as valid & ensure iterator resources are up to date */ flecs_iter_validate(it); /* Can't iterate an iterator that's already depleted */ ecs_check(iter->op != -1, ECS_INVALID_PARAMETER, NULL); /* If this is the first time the iterator is iterated, set initial state */ if (first_time) { ecs_assert(redo == false, ECS_INTERNAL_ERROR, NULL); rule_iter_set_initial_state(it, iter, rule); } do { /* Evaluate an operation. The result of an operation determines the * flow of the program. If an operation returns true, the program * continues to the operation pointed to by 'on_pass'. If the operation * returns false, the program continues to the operation pointed to by * 'on_fail'. * * In most scenarios, on_pass points to the next operation, and on_fail * points to the previous operation. * * When an operation fails, the previous operation will be invoked with * redo=true. This will cause the operation to continue its search from * where it left off. When the operation succeeds, the next operation * will be invoked with redo=false. This causes the operation to start * from the beginning, which is necessary since it just received a new * input. */ int32_t op_index = iter->op; ecs_rule_op_t *op = &rule->operations[op_index]; int32_t cur = op->frame; /* If this is not the first operation and is also not a control flow * operation, push a new frame on the stack for the next operation */ if (!redo && !is_control_flow(op) && cur && cur != last_frame) { int32_t prev = cur - 1; push_registers(iter, prev, cur); push_columns(iter, prev, cur); } /* Dispatch the operation */ bool result = eval_op(it, op, op_index, redo); iter->op = result ? op->on_pass : op->on_fail; /* If the current operation is yield, return results */ if (op->kind == EcsRuleYield) { populate_iterator(rule, it, iter, op); iter->redo = true; return true; } /* If the current operation is a jump, goto stored label */ if (op->kind == EcsRuleJump) { /* Label is stored in setjmp context */ iter->op = iter->op_ctx[op->on_pass].is.setjmp.label; } /* If jumping backwards, it's a redo */ redo = iter->op <= op_index; if (!is_control_flow(op)) { last_frame = op->frame; } } while (iter->op != -1); ecs_iter_fini(it); error: return false; } #endif /** * @file addons/module.c * @brief Module addon. */ #ifdef FLECS_MODULE #include char* ecs_module_path_from_c( const char *c_name) { ecs_strbuf_t str = ECS_STRBUF_INIT; const char *ptr; char ch; for (ptr = c_name; (ch = *ptr); ptr++) { if (isupper(ch)) { ch = flecs_ito(char, tolower(ch)); if (ptr != c_name) { ecs_strbuf_appendstrn(&str, ".", 1); } } ecs_strbuf_appendstrn(&str, &ch, 1); } return ecs_strbuf_get(&str); } ecs_entity_t ecs_import( ecs_world_t *world, ecs_module_action_t module, const char *module_name) { ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = ecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup_fullpath(world, path); ecs_os_free(path); if (!e) { ecs_trace("#[magenta]import#[reset] %s", module_name); ecs_log_push(); /* Load module */ module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup_fullpath(world, module_name); ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); ecs_log_pop(); } /* Restore to previous state */ ecs_set_scope(world, old_scope); world->info.name_prefix = old_name_prefix; return e; error: return 0; } ecs_entity_t ecs_import_c( ecs_world_t *world, ecs_module_action_t module, const char *c_name) { char *name = ecs_module_path_from_c(c_name); ecs_entity_t e = ecs_import(world, module, name); ecs_os_free(name); return e; } ecs_entity_t ecs_import_from_library( ecs_world_t *world, const char *library_name, const char *module_name) { ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); char *import_func = (char*)module_name; /* safe */ char *module = (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); } 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 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); bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data); #endif #endif #ifdef FLECS_META ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsPrimitive, { desc->kind }); return t; } ecs_entity_t ecs_enum_init( ecs_world_t *world, const ecs_enum_desc_t *desc) { 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_init(world, &(ecs_entity_desc_t){ .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); 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_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_init(world, &(ecs_entity_desc_t){ .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); 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_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsArray, { .type = desc->type, .count = desc->count }); return t; } ecs_entity_t ecs_vector_init( ecs_world_t *world, const ecs_vector_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsVector, { .type = desc->type }); return t; } ecs_entity_t ecs_struct_init( ecs_world_t *world, const ecs_struct_desc_t *desc) { 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)); ecs_delete(world, t); return 0; } ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){ .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 }); } ecs_set_scope(world, old_scope); if (i == 0) { ecs_err("struct '%s' has no members", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } if (!ecs_has(world, t, EcsStruct)) { /* Invalid members */ ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_opaque_init( ecs_world_t *world, const ecs_opaque_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->as_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->serialize != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsOpaque, { .as_type = desc->as_type, .serialize = desc->serialize }); return t; } ecs_entity_t ecs_unit_init( ecs_world_t *world, const ecs_unit_desc_t *desc) { 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_get_mut(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); return t; error: if (t) { ecs_delete(world, t); } return 0; } ecs_entity_t ecs_unit_prefix_init( ecs_world_t *world, const ecs_unit_prefix_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsUnitPrefix, { .symbol = (char*)desc->symbol, .translation = desc->translation }); return t; } ecs_entity_t ecs_quantity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_entity_t t = ecs_entity_init(world, desc); if (!t) { return 0; } ecs_add_id(world, t, EcsQuantity); return t; } #endif /** * @file meta/serialized.c * @brief Serialize type into flat operations array to speed up deserialization. */ #ifdef FLECS_META static ecs_vector_t* serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops); static ecs_meta_type_op_kind_t primitive_to_op_kind(ecs_primitive_kind_t kind) { return EcsOpPrimitive + kind; } static ecs_size_t 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* ops_add(ecs_vector_t **ops, ecs_meta_type_op_kind_t kind) { ecs_meta_type_op_t *op = ecs_vector_add(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->unit = 0; return op; } static ecs_meta_type_op_t* ops_get(ecs_vector_t *ops, int32_t index) { ecs_meta_type_op_t* op = ecs_vector_get(ops, ecs_meta_type_op_t, index); ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); return op; } static ecs_vector_t* serialize_primitive( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *op = ops_add(&ops, primitive_to_op_kind(ptr->kind)); op->offset = offset, op->type = type; op->size = type_size(world, type); return ops; } static ecs_vector_t* serialize_enum( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { (void)world; ecs_meta_type_op_t *op = ops_add(&ops, EcsOpEnum); op->offset = offset, op->type = type; op->size = ECS_SIZEOF(ecs_i32_t); return ops; } static ecs_vector_t* serialize_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { (void)world; ecs_meta_type_op_t *op = ops_add(&ops, EcsOpBitmask); op->offset = offset, op->type = type; op->size = ECS_SIZEOF(ecs_u32_t); return ops; } static ecs_vector_t* serialize_array( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { (void)world; ecs_meta_type_op_t *op = ops_add(&ops, EcsOpArray); op->offset = offset; op->type = type; op->size = type_size(world, type); return ops; } static ecs_vector_t* serialize_array_component( ecs_world_t *world, ecs_entity_t type) { const EcsArray *ptr = ecs_get(world, type, EcsArray); if (!ptr) { return NULL; /* Should never happen, will trigger internal error */ } ecs_vector_t *ops = serialize_type(world, ptr->type, 0, NULL); ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *first = ecs_vector_first(ops, ecs_meta_type_op_t); first->count = ptr->count; return ops; } static ecs_vector_t* serialize_vector( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { (void)world; ecs_meta_type_op_t *op = ops_add(&ops, EcsOpVector); op->offset = offset; op->type = type; op->size = type_size(world, type); return ops; } static ecs_vector_t* serialize_custom_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { (void)world; ecs_meta_type_op_t *op = ops_add(&ops, EcsOpOpaque); op->offset = offset; op->type = type; op->size = type_size(world, type); return ops; } static ecs_vector_t* serialize_struct( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_t *ops) { const EcsStruct *ptr = ecs_get(world, type, EcsStruct); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur, first = ecs_vector_count(ops); ecs_meta_type_op_t *op = ops_add(&ops, EcsOpPush); op->offset = offset; op->type = type; op->size = type_size(world, type); ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); int32_t i, count = ecs_vector_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_vector_count(ops); ops = serialize_type(world, member->type, offset + member->offset, ops); op = 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->unit = member->unit; op->op_count = ecs_vector_count(ops) - cur; flecs_name_index_ensure( member_index, flecs_ito(uint64_t, cur - first - 1), member_name, 0, 0); } ops_add(&ops, EcsOpPop); ops_get(ops, first)->op_count = ecs_vector_count(ops) - first; return ops; } static ecs_vector_t* serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vector_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 NULL; } switch(ptr->kind) { case EcsPrimitiveType: ops = serialize_primitive(world, type, offset, ops); break; case EcsEnumType: ops = serialize_enum(world, type, offset, ops); break; case EcsBitmaskType: ops = serialize_bitmask(world, type, offset, ops); break; case EcsStructType: ops = serialize_struct(world, type, offset, ops); break; case EcsArrayType: ops = serialize_array(world, type, offset, ops); break; case EcsVectorType: ops = serialize_vector(world, type, offset, ops); break; case EcsOpaqueType: ops = serialize_custom_type(world, type, offset, ops); break; } return ops; } static ecs_vector_t* serialize_component( ecs_world_t *world, ecs_entity_t type) { 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 NULL; } ecs_vector_t *ops = NULL; switch(ptr->kind) { case EcsArrayType: ops = serialize_array_component(world, type); break; default: ops = serialize_type(world, type, 0, NULL); break; } return 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_vector_t *ops = serialize_component(world, e); ecs_assert(ops != NULL, ECS_INTERNAL_ERROR, NULL); EcsMetaTypeSerialized *ptr = ecs_get_mut( world, e, EcsMetaTypeSerialized); if (ptr->ops) { ecs_meta_dtor_serialized(ptr); } ptr->ops = ops; } } #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(*(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_vector_count(ptr->ops); ecs_meta_type_op_t *ops = ecs_vector_first(ptr->ops, ecs_meta_type_op_t); for (i = 0; i < count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op->members) { flecs_name_index_free(op->members); } } ecs_vector_free(ptr->ops); } static ECS_COPY(EcsMetaTypeSerialized, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = ecs_vector_copy(src->ops, ecs_meta_type_op_t); int32_t o, count = ecs_vector_count(dst->ops); ecs_meta_type_op_t *ops = ecs_vector_first(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 = NULL; }) static ECS_DTOR(EcsMetaTypeSerialized, ptr, { ecs_meta_dtor_serialized(ptr); }) /* EcsStruct lifecycle */ static void flecs_struct_dtor( EcsStruct *ptr) { ecs_member_t *members = ecs_vector_first(ptr->members, ecs_member_t); int32_t i, count = ecs_vector_count(ptr->members); for (i = 0; i < count; i ++) { ecs_os_free((char*)members[i].name); } ecs_vector_free(ptr->members); } static ECS_COPY(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = ecs_vector_copy(src->members, ecs_member_t); ecs_member_t *members = ecs_vector_first(dst->members, ecs_member_t); int32_t m, count = ecs_vector_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 = NULL; }) 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((char*)c->name); ecs_os_free(c); } ecs_map_fini(constants); } static void flecs_constants_copy( ecs_map_t *dst, 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 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_get_mut(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 with different kind", ecs_get_name(world, type)); return -1; } } if (!meta_type->existing) { EcsComponent *comp = ecs_get_mut(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) { 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); return -1; } meta_type->partial = comp->size != size; } meta_type->kind = kind; meta_type->size = size; meta_type->alignment = alignment; 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) { member->member = entity; member->type = type; member->count = count; member->unit = unit; member->offset = offset; if (!count) { member->count = 1; } ecs_os_strset((char**)&member->name, name); } static int flecs_add_member_to_struct( ecs_world_t *world, ecs_entity_t type, ecs_entity_t member, EcsMember *m) { 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_get_mut(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_vector_first(s->members, ecs_member_t); int32_t i, count = ecs_vector_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); break; } } /* If member wasn't added yet, add a new element to vector */ if (i == count) { ecs_member_t *elem = ecs_vector_add(&s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member, name, m->type, m->count, m->offset, unit); /* Reobtain members array in case it was reallocated */ members = ecs_vector_first(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, member); 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, member); ecs_err("member '%s' has 0 size/alignment"); ecs_os_free(path); return -1; } member_size *= elem->count; size = ECS_ALIGN(size, member_alignment); elem->size = member_size; elem->offset = size; 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. Grab size of just added member instead. It * doesn't matter if the size doesn't correspond to the actual struct * size. The flecs_init_type function compares computed size with actual * (component) size to determine if the type is partial. */ const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); size = cptr->size; alignment = cptr->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_get_mut(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_get_mut(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((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 for '%s' (other is '%s')", 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_get_mut_pair_object( world, e, EcsConstant, ecs_i32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); 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_get_mut(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((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_get_mut_pair_object( world, e, EcsConstant, ecs_u32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); 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; } } } static void flecs_set_member(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMember *member = ecs_field(it, EcsMember, 1); 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]); } } 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_vector_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_set_hooks_id(world, it->entities[i], &(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); 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, 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, 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_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_primitive }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_enum }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_constant }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_add_constant }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_array }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_vector }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_custom_type }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_unit }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_serialized_init }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_init_default_ctor }); ecs_observer_init(world, &(ecs_observer_desc_t){ .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); #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); /* Initialize reflection data for meta components */ 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 = (char*)"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"} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsPrimitive), .members = { {.name = (char*)"kind", .type = primitive_kind} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMember), .members = { {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, {.name = (char*)"unit", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"offset", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsVector), .members = { {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsOpaque), .members = { { .name = (char*)"as_type", .type = ecs_id(ecs_entity_t) }, { .name = (char*)"serialize", .type = ecs_id(ecs_uptr_t) } } }); ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "unit_translation" }), .members = { {.name = (char*)"factor", .type = ecs_id(ecs_i32_t)}, {.name = (char*)"power", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnit), .members = { {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, {.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"base", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"over", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"translation", .type = ut} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnitPrefix), .members = { {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, {.name = (char*)"translation", .type = ut} } }); } #endif /** * @file meta/api.c * @brief API for assigning values of runtime types with reflection. */ #include #ifdef FLECS_META 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 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"; 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_meta_scope_t*)&cursor->scope[cursor->depth]; error: return NULL; } /* Restore scope, if dotmember was used */ static void 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; } /* Get previous scope */ static ecs_meta_scope_t* get_prev_scope( ecs_meta_cursor_t *cursor) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(cursor->depth > 0, ECS_INVALID_PARAMETER, NULL); return &cursor->scope[cursor->depth - 1]; error: return NULL; } /* 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; } /* Get alignment for type in current scope */ static ecs_size_t get_alignment( const ecs_world_t *world, ecs_meta_scope_t *scope) { return get_ecs_component(world, scope)->alignment; } static int32_t get_elem_count( ecs_meta_scope_t *scope) { if (scope->vector) { return ecs_vector_count(*(scope->vector)); } 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); if (scope->vector) { ecs_size_t align = get_alignment(world, scope); ecs_vector_set_min_count_t( scope->vector, size, align, scope->elem_cur + 1); scope->ptr = ecs_vector_first_t(*(scope->vector), size, align); } 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 entity '%s' which is not a type", str); ecs_os_free(str); return -1; } scope[0] = (ecs_meta_scope_t) { .type = type, .ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t), .op_count = ecs_vector_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_check(ptr != NULL, 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); 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->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 *prev_scope = get_prev_scope(cursor); ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *push_op = flecs_meta_cursor_get_op(prev_scope); const ecs_world_t *world = cursor->world; ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); if (!push_op->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(push_op->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]); return 0; } int ecs_meta_dotmember( ecs_meta_cursor_t *cursor, const char *name) { #ifdef FLECS_PARSER 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 ++; } ecs_meta_scope_t *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) { 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; } switch(op->kind) { case EcsOpPush: 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 }; break; 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; } 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; default: { char *path = ecs_get_fullpath(world, scope->type); ecs_err("invalid push for type '%s'", path); ecs_os_free(path); goto error; } } if (scope->is_collection) { next_scope[0].ptr = ECS_OFFSET(next_scope[0].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); 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 { /* 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); } } else { /* Make sure that this was an inline array */ ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); } flecs_meta_cursor_restore_scope(cursor, next_scope); 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_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->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; } /* Utilities for type conversions and bounds checking */ 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}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, INT32_MAX} }; 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}, [EcsOpEnum] = {0, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; 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}, [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(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 conversion_error( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, const char *from) { 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_unsigned(ptr, value, ecs_meta_bounds_unsigned); default: conversion_error(cursor, op, "bool"); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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); default: conversion_error(cursor, op, "char"); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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); default: { if(!value) return ecs_meta_set_null(cursor); conversion_error(cursor, op, "int"); return -1; } } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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); default: if(!value) return ecs_meta_set_null(cursor); conversion_error(cursor, op, "uint"); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); default: conversion_error(cursor, op, "float"); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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); 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); conversion_error(cursor, op, type_str); ecs_os_free(type_str); goto error; } flecs_meta_cursor_restore_scope(cursor, scope); return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); } error: return -1; } static int 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 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 (add_bitmask_constant(cursor, op, out, token) != 0) { return -1; } ptr ++; prev = ptr; } if (add_bitmask_constant(cursor, op, out, prev) != 0) { 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); return -1; } break; case EcsOpI8: case EcsOpU8: case EcsOpChar: case EcsOpByte: set_T(ecs_i8_t, ptr, atol(value)); 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, atol(value)); break; case EcsOpIPtr: case EcsOpUPtr: set_T(ecs_iptr_t, ptr, atol(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); return -1; } 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); return -1; } set_T(ecs_i32_t, ptr, v[0]); break; } case EcsOpBitmask: if (parse_bitmask(cursor, op, ptr, value) != 0) { return -1; } break; case EcsOpEntity: { ecs_entity_t e = 0; if (ecs_os_strcmp(value, "0")) { if (cursor->lookup_action) { e = cursor->lookup_action( cursor->world, value, cursor->lookup_ctx); } else { e = ecs_lookup_path(cursor->world, 0, value); } if (!e) { ecs_err("unresolved entity identifier '%s'", value); return -1; } } set_T(ecs_entity_t, ptr, e); break; } case EcsOpPop: ecs_err("excess element '%s' in scope", value); return -1; default: ecs_err("unsupported conversion from string '%s' to '%s'", value, flecs_meta_op_kind_str(op->kind)); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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); return -1; } switch(op->kind) { case EcsOpChar: set_T(ecs_char_t, ptr, value[1]); break; default: case EcsOpEntity: case EcsOpString: 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; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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; default: conversion_error(cursor, op, "entity"); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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; default: conversion_error(cursor, op, "null"); return -1; } flecs_meta_cursor_restore_scope(cursor, scope); return 0; } 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_f32_t*)ptr != 0; case EcsOpF64: return *(ecs_f64_t*)ptr != 0; 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; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); } 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; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); } 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 *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr; case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return *(ecs_char_t*)ptr; case EcsOpByte: return *(ecs_u8_t*)ptr; case EcsOpI16: return *(ecs_i16_t*)ptr; case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return *(ecs_i32_t*)ptr; case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return *(ecs_i64_t*)ptr; case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr); case EcsOpIPtr: return *(ecs_iptr_t*)ptr; case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr); case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr; case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr; case EcsOpString: return atoi(*(const char**)ptr); case EcsOpEnum: return *(ecs_i32_t*)ptr; case EcsOpBitmask: return *(ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to int"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); } 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, *(ecs_i8_t*)ptr); case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr); case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr); case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr); case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); case EcsOpU64: return *(ecs_u64_t*)ptr; case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); case EcsOpUPtr: return *(ecs_uptr_t*)ptr; case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr); case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr); case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); case EcsOpBitmask: return *(ecs_u32_t*)ptr; case EcsOpEntity: return *(ecs_entity_t*)ptr; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); } 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); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr; case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return *(ecs_char_t*)ptr; case EcsOpByte: return *(ecs_u8_t*)ptr; case EcsOpI16: return *(ecs_i16_t*)ptr; case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return *(ecs_i32_t*)ptr; case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return (double)*(ecs_i64_t*)ptr; case EcsOpU64: return (double)*(ecs_u64_t*)ptr; case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr; case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr; case EcsOpF32: return (double)*(ecs_f32_t*)ptr; case EcsOpF64: return *(ecs_f64_t*)ptr; case EcsOpString: return atof(*(const char**)ptr); case EcsOpEnum: return *(ecs_i32_t*)ptr; case EcsOpBitmask: return *(ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to float"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); } error: return 0; } 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; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); } error: return 0; } ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor) { 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; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); } error: return 0; } #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, ecs_vector_t *ser, const void *base, ecs_strbuf_t *str); 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); 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); 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) { switch(kind) { case EcsBool: if (*(bool*)base) { ecs_strbuf_appendlit(str, "true"); } else { ecs_strbuf_appendlit(str, "false"); } break; case EcsChar: { char chbuf[3]; char ch = *(char*)base; if (ch) { ecs_chresc(chbuf, *(char*)base, '"'); ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, chbuf); ecs_strbuf_appendch(str, '"'); } else { ecs_strbuf_appendch(str, '0'); } break; } case EcsByte: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base)); break; case EcsU8: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base)); break; case EcsU16: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint16_t*)base)); break; case EcsU32: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint32_t*)base)); break; case EcsU64: ecs_strbuf_append(str, "%llu", *(uint64_t*)base); break; case EcsI8: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int8_t*)base)); break; case EcsI16: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int16_t*)base)); break; case EcsI32: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int32_t*)base)); break; case EcsI64: ecs_strbuf_appendint(str, *(int64_t*)base); break; case EcsF32: ecs_strbuf_appendflt(str, (double)*(float*)base, 0); break; case EcsF64: ecs_strbuf_appendflt(str, *(double*)base, 0); break; case EcsIPtr: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(intptr_t*)base)); break; case EcsUPtr: ecs_strbuf_append(str, "%u", *(uintptr_t*)base); break; case EcsString: { char *value = *(char**)base; if (value) { 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_zerocpy(str, out); } } else { ecs_strbuf_appendlit(str, "null"); } break; } case EcsEntity: { ecs_entity_t e = *(ecs_entity_t*)base; if (!e) { ecs_strbuf_appendch(str, '0'); } else { ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, 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 = *(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 = *(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) { 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, 1)) { 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) { 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_vector_first(ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vector_count(ser->ops); return expr_ser_elements( world, ops, op_count, base, elem_count, comp->size, str); } /* 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); } /* 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) { ecs_vector_t *value = *(ecs_vector_t**)base; if (!value) { ecs_strbuf_appendlit(str, "null"); return 0; } const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vector_count(value); void *array = ecs_vector_first_t(value, comp->size, comp->alignment); /* Serialize contiguous buffer of vector */ return expr_ser_type_elements(world, v->type, array, count, str); } /* 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) { 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; default: if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str)) { /* Unknown operation */ ecs_err("unknown serializer operation kind (%d)", op->kind); goto error; } break; } 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) { 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)) { 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; default: if (flecs_expr_ser_type_op(world, op, base, str)) { goto error; } break; } } return 0; error: return -1; } /* Iterate over the type ops of a type */ static int flecs_expr_ser_type( const ecs_world_t *world, ecs_vector_t *v_ops, const void *base, ecs_strbuf_t *str) { ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vector_count(v_ops); return flecs_expr_ser_type_ops(world, ops, count, base, str, 0); } 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)) { 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_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); } #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); 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); } } 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); 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); 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( ecs_vars_t *vars, const char *name) { return flecs_vars_scope_lookup(vars->cur, name); } #endif /** * @file expr/strutil.c * @brief String parsing utilities. */ #ifdef FLECS_EXPR 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; 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; } #endif /** * @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) { const char *start = ptr; char *token_ptr = token; 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[ptr - start]; token_ptr[0] = '|'; token_ptr[1] = '\0'; token_ptr ++; ptr ++; start = 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; } /* 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( 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_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); default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } /** 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)); default: return false; } } /** 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; default: 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; default: break; } /* 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) { ecs_parser_error(name, expr, ptr - expr, "invalid non-primitive type in binary expression"); 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; } /* 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_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) { return -1; } if (!flecs_oper_valid_for_type(type, op)) { ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); return -1; } 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_OP(lvalue, rvalue, storage, ==); break; case EcsCondNeq: ECS_BINARY_COND_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; default: ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); return -1; } 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; } 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_fluff(ptr, NULL); 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_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; expr = expr ? expr : ptr; ptr = ecs_parse_fluff(ptr, NULL); /* Check for postfix operators */ ecs_expr_oper_t unary_op = EcsExprOperUnknown; if (ptr[0] == '-' && !isdigit(ptr[1])) { unary_op = EcsMin; ptr = ecs_parse_fluff(ptr + 1, NULL); } /* 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( 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 (!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_fluff(ptr + 1, NULL); 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, "expected '{'"); 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; } const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return NULL; } ecs_meta_set_value(&cur, &var->value); is_lvalue = true; } else { const char *tptr = ecs_parse_fluff(ptr, NULL); for (; ptr != tptr; ptr ++) { if (ptr[0] == '\n') { newline = true; } } if (ptr[0] == ':') { /* Member assignment */ ptr ++; if (ecs_meta_member(&cur, token) != 0) { 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_fluff(ptr, NULL); } 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 addons/stats.c * @brief Stats addon. */ #ifdef FLECS_SYSTEM #endif #ifdef FLECS_PIPELINE #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 (delta_world_time != 0 && delta_frame_count != 0) { 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_sparse_count(ecs_eis(world))); ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_sparse_not_alive_count(ecs_eis(world))); ECS_GAUGE_RECORD(&s->ids.count, t, world->info.id_count); ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count); ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count); ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count); ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count); ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info)); ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->ids.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, 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_GAUGE_RECORD(&s->tables.tag_only_count, t, world->info.tag_table_count); ECS_GAUGE_RECORD(&s->tables.trivial_only_count, t, world->info.trivial_table_count); ECS_GAUGE_RECORD(&s->tables.storage_count, t, world->info.table_storage_count); ECS_GAUGE_RECORD(&s->tables.record_count, t, world->info.table_record_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.get_mut_count, t, world->info.cmd.get_mut_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_REST ECS_COUNTER_RECORD(&s->rest.request_count, t, ecs_rest_request_count); ECS_COUNTER_RECORD(&s->rest.entity_count, t, ecs_rest_entity_count); ECS_COUNTER_RECORD(&s->rest.entity_error_count, t, ecs_rest_entity_error_count); ECS_COUNTER_RECORD(&s->rest.query_count, t, ecs_rest_query_count); ECS_COUNTER_RECORD(&s->rest.query_error_count, t, ecs_rest_query_error_count); ECS_COUNTER_RECORD(&s->rest.query_name_count, t, ecs_rest_query_name_count); ECS_COUNTER_RECORD(&s->rest.query_name_error_count, t, ecs_rest_query_name_error_count); ECS_COUNTER_RECORD(&s->rest.query_name_from_cache_count, t, ecs_rest_query_name_from_cache_count); ECS_COUNTER_RECORD(&s->rest.enable_count, t, ecs_rest_enable_count); ECS_COUNTER_RECORD(&s->rest.enable_error_count, t, ecs_rest_enable_error_count); ECS_COUNTER_RECORD(&s->rest.world_stats_count, t, ecs_rest_world_stats_count); ECS_COUNTER_RECORD(&s->rest.pipeline_stats_count, t, ecs_rest_pipeline_stats_count); ECS_COUNTER_RECORD(&s->rest.stats_error_count, t, ecs_rest_stats_error_count); #endif #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); } 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); ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsEmpty)); ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); 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); /* Make sure vector is large enough to store all systems & sync points */ ecs_entity_t *systems = NULL; if (pip_count) { ecs_vector_set_count(&s->systems, ecs_entity_t, pip_count); systems = ecs_vector_first(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_vector_free(s->systems); s->systems = NULL; } /* 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)) { int 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_vector_free(stats->systems); } void ecs_pipeline_stats_reduce( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t system_count = ecs_vector_count(src->systems); ecs_vector_set_count(&dst->systems, ecs_entity_t, system_count); ecs_entity_t *dst_systems = ecs_vector_first(dst->systems, ecs_entity_t); ecs_entity_t *src_systems = ecs_vector_first(src->systems, ecs_entity_t); ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); 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) { 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) { 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) { 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", world->info.target_fps); flecs_metric_print("time scale", 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("id count", t, &s->ids.count); flecs_gauge_print("tag id count", t, &s->ids.tag_count); flecs_gauge_print("component id count", t, &s->ids.component_count); flecs_gauge_print("pair id count", t, &s->ids.pair_count); flecs_gauge_print("wildcard id count", t, &s->ids.wildcard_count); flecs_gauge_print("type count", t, &s->ids.type_count); flecs_counter_print("id create count", t, &s->ids.create_count); flecs_counter_print("id delete count", t, &s->ids.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_gauge_print("tag table count", t, &s->tables.tag_only_count); flecs_gauge_print("trivial table count", t, &s->tables.trivial_only_count); flecs_gauge_print("table storage count", t, &s->tables.storage_count); flecs_gauge_print("table cache record count", t, &s->tables.record_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("get_mut commands", t, &s->commands.get_mut_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/units.c * @brief Units addon. */ #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); void FlecsUnitsImport( ecs_world_t *world) { ECS_MODULE(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t){ .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 addons/snapshot.c * @brief Snapshot addon. */ #ifdef FLECS_SNAPSHOT /* World snapshot */ struct ecs_snapshot_t { ecs_world_t *world; ecs_sparse_t entity_index; ecs_vector_t *tables; ecs_entity_t last_id; }; /** Small footprint data structure for storing data associated with a table. */ typedef struct ecs_table_leaf_t { ecs_table_t *table; ecs_type_t type; ecs_data_t *data; } ecs_table_leaf_t; static ecs_data_t* flecs_duplicate_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *main_data) { if (!ecs_table_count(table)) { return NULL; } ecs_data_t *result = ecs_os_calloc_t(ecs_data_t); int32_t i, column_count = table->storage_count; result->columns = flecs_wdup_n(world, ecs_vec_t, column_count, main_data->columns); /* Copy entities and records */ ecs_allocator_t *a = &world->allocator; result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t); result->records = ecs_vec_copy_t(a, &main_data->records, ecs_record_t*); /* Copy each column */ for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &result->columns[i]; ecs_type_info_t *ti = table->type_info[i]; int32_t size = ti->size; ecs_copy_t copy = ti->hooks.copy; if (copy) { ecs_vec_t dst = ecs_vec_copy(a, column, size); int32_t count = ecs_vec_count(column); void *dst_ptr = ecs_vec_first(&dst); void *src_ptr = ecs_vec_first(column); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(dst_ptr, count, ti); } copy(dst_ptr, src_ptr, count, ti); *column = dst; } else { *column = ecs_vec_copy(a, column, 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_vector_get( 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_world_t*)world, &table->type); l->data = flecs_duplicate_data((ecs_world_t*)world, table, &table->data); } static ecs_snapshot_t* snapshot_create( const ecs_world_t *world, const ecs_sparse_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_world_t*)world, 0); result->world = (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_sparse_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; result->tables = ecs_vector_new(ecs_table_leaf_t, (int32_t)table_count); ecs_vector_set_count(&result->tables, ecs_table_leaf_t, (int32_t)table_count); ecs_table_leaf_t *arr = ecs_vector_first(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 = 0; 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 = world->info.last_id; 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 = world->info.last_id; 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_sparse_restore(ecs_eis(world), &snapshot->entity_index); flecs_sparse_fini(&snapshot->entity_index); world->info.last_id = snapshot->last_id; ecs_table_leaf_t *leafs = ecs_vector_first( snapshot->tables, ecs_table_leaf_t); int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); int32_t snapshot_count = ecs_vector_count(snapshot->tables); for (i = 0; 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) { flecs_notify_on_set(world, table, 0, tcount, NULL, 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_vector_first( snapshot->tables, ecs_table_leaf_t); int32_t l = 0, snapshot_count = ecs_vector_count(snapshot->tables); for (l = 0; l < snapshot_count; l ++) { ecs_table_leaf_t *snapshot_table = &leafs[l]; ecs_table_t *table = snapshot_table->table; if (!table) { continue; } ecs_data_t *data = snapshot_table->data; if (!data) { flecs_type_free(world, &snapshot_table->type); continue; } /* Delete entity from storage first, so that when we restore it to the * current table we can be sure that there won't be any duplicates */ int32_t i, entity_count = ecs_vec_count(&data->entities); ecs_entity_t *entities = ecs_vec_first( &snapshot_table->data->entities); for (i = 0; i < entity_count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *r = flecs_entities_try(world, e); if (r && r->table) { flecs_table_delete(world, r->table, ECS_RECORD_TO_ROW(r->row), true); } else { /* Make sure that the entity has the same generation count */ flecs_entities_set_generation(world, e); } } /* Merge data from snapshot table with world table */ int32_t old_count = ecs_table_count(snapshot_table->table); int32_t new_count = flecs_table_data_count(snapshot_table->data); flecs_table_merge(world, table, table, &table->data, snapshot_table->data); /* Run OnSet systems for merged entities */ if (new_count) { flecs_notify_on_set( world, table, old_count, new_count, NULL, true); } flecs_wfree_n(world, ecs_vec_t, table->storage_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_sparse_count(&snapshot->entity_index)) { /* 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_vector_free(snapshot->tables); 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_vector_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_vector_first(iter->tables, ecs_table_leaf_t); int32_t count = ecs_vector_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_sparse_fini(&snapshot->entity_index); ecs_table_leaf_t *tables = ecs_vector_first(snapshot->tables, ecs_table_leaf_t); int32_t i, count = ecs_vector_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_vector_free(snapshot->tables); ecs_os_free(snapshot); } #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_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); } if (measure_time) { system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); } system_data->invoke_count ++; 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_get_system_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_get_system_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); } 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; if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { #ifdef FLECS_TIMER if (desc->interval != 0) { ecs_set_interval(world, entity, desc->interval); } if (desc->rate) { ecs_set_rate(world, entity, desc->rate, desc->tick_source); } else if (desc->tick_source) { ecs_set_tick_source(world, entity, desc->tick_source); } #else ecs_abort(ECS_UNSUPPORTED, "timer module not available"); #endif } if (ecs_get_name(world, entity)) { ecs_trace("#[green]system#[reset] %s created", ecs_get_name(world, entity)); } ecs_defer_end(world); } else { ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t); if (desc->run) { system->run = desc->run; } if (desc->callback) { system->action = desc->callback; } if (desc->ctx) { system->ctx = desc->ctx; } if (desc->binding_ctx) { system->binding_ctx = desc->binding_ctx; } 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; } } ecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } void FlecsSystemImport( ecs_world_t *world) { ECS_MODULE(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_tag(world, EcsSystem); flecs_bootstrap_component(world, EcsTickSource); /* 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 /** * @file json/json.c * @brief JSON serializer utilities. */ /** * @file json/json.h * @brief Internal functions for JSON addon. */ #ifdef FLECS_JSON void flecs_json_next( ecs_strbuf_t *buf); void flecs_json_number( ecs_strbuf_t *buf, double 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); ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind); #endif #ifdef FLECS_JSON 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_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_zerocpy(buf, 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, '"'); } void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { const char *lbl = NULL; #ifdef FLECS_DOC lbl = ecs_doc_get_name(world, e); #else lbl = ecs_get_name(world, e); #endif if (lbl) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, lbl); ecs_strbuf_appendch(buf, '"'); } 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, '"'); ecs_id_str_buf(world, id, buf); ecs_strbuf_appendch(buf, '"'); } 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 json_ser_type( const ecs_world_t *world, ecs_vector_t *ser, const void *base, ecs_strbuf_t *str); static int 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 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 = *(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) { 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 = *(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 */ 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) { flecs_json_array_push(str); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (json_ser_type_ops(world, ops, op_count, ptr, str, 1)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } flecs_json_array_pop(str); return 0; } static int json_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str) { 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_vector_first(ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vector_count(ser->ops); return json_ser_elements( world, ops, op_count, base, elem_count, comp->size, str); } /* 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 json_ser_type_elements( world, a->type, ptr, a->count, str); } /* 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) { ecs_vector_t *value = *(ecs_vector_t**)base; if (!value) { ecs_strbuf_appendlit(str, "null"); return 0; } const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, v->type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vector_count(value); void *array = ecs_vector_first_t(value, comp->size, comp->alignment); /* Serialize contiguous buffer of vector */ return json_ser_type_elements(world, v->type, array, count, str); } 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_meta_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_meta_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); 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_meta_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 json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *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)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); break; case EcsOpF64: ecs_strbuf_appendflt(str, *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); break; case EcsOpEnum: if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpBitmask: if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpArray: if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpVector: if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpOpaque: if (json_ser_custom_type(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = *(ecs_entity_t*)ECS_OFFSET(ptr, op->offset); if (!e) { ecs_strbuf_appendch(str, '0'); } else { flecs_json_path(str, world, e); } break; } default: if (ecs_primitive_to_expr_buf(world, flecs_json_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str)) { /* Unknown operation */ ecs_throw(ECS_INTERNAL_ERROR, NULL); return -1; } break; } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int 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)) { 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; default: if (json_ser_type_op(world, op, base, str)) { goto error; } break; } } return 0; error: return -1; } /* Iterate over the type ops of a type */ static int json_ser_type( const ecs_world_t *world, ecs_vector_t *v_ops, const void *base, ecs_strbuf_t *str) { ecs_meta_type_op_t *ops = ecs_vector_first(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vector_count(v_ops); return 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 (json_ser_type(world, ser->ops, ptr, buf)) { return -1; } ptr = ECS_OFFSET(ptr, size); } while (-- count); flecs_json_array_pop(buf); } else { if (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 (!desc || !desc->serialize_meta_ids) { if (pred == EcsIsA || pred == EcsChildOf || pred == ecs_id(EcsIdentifier)) { return true; } #ifdef FLECS_DOC if (pred == ecs_id(EcsDocDescription)) { return true; } #endif } 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 (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; } 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; } 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; } 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); } static bool flecs_json_skip_variable( const char *name) { if (!name || name[0] == '_' || name[0] == '.') { 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_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_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; } 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 = ecs_get_typeid(world, it->terms[i].id); if (typeid) { flecs_json_serialize_id(world, typeid, buf); ecs_strbuf_appendch(buf, ':'); ecs_type_info_to_json_buf(world, typeid, buf); } else { flecs_json_serialize_id(world, it->terms[i].id, buf); ecs_strbuf_appendlit(buf, ":0"); } } 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 = 0; 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_table_type( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->table) { return; } 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 ++) { flecs_json_next(buf); flecs_json_serialize_id(world, type->array[i], 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); } static void flecs_json_serialize_iter_result_is_set( const ecs_iter_t *it, ecs_strbuf_t *buf) { 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); } 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 = 0; 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 = 0; 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 = 0; 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_number(buf, (double)variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_entities( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t count = it->count; if (!it->count) { return; } flecs_json_memberl(buf, "entities"); flecs_json_array_push(buf); ecs_entity_t *entities = it->entities; for (int i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_path(buf, world, entities[i]); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_entity_labels( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t count = it->count; if (!it->count) { return; } flecs_json_memberl(buf, "entity_labels"); flecs_json_array_push(buf); ecs_entity_t *entities = it->entities; for (int i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_label(buf, world, entities[i]); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_entity_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t count = it->count; if (!it->count) { return; } flecs_json_memberl(buf, "entity_ids"); flecs_json_array_push(buf); ecs_entity_t *entities = it->entities; for (int i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_number(buf, (double)entities[i]); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_colors( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t count = it->count; if (!it->count) { return; } flecs_json_memberl(buf, "colors"); flecs_json_array_push(buf); ecs_entity_t *entities = it->entities; for (int i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_color(buf, world, entities[i]); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_values( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { 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)) { ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_array_push(buf); flecs_json_array_pop(buf); continue; } if (ecs_field_is_self(it, i + 1)) { int32_t count = it->count; array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } else { array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser); } } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_columns( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_table_t *table = it->table; if (!table || !table->storage_table) { return; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); ecs_type_t *type = &table->type; int32_t *storage_map = table->storage_map; ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < type->count; i ++) { int32_t storage_column = -1; if (storage_map) { storage_column = storage_map[i]; } ecs_strbuf_list_next(buf); if (storage_column == -1) { ecs_strbuf_appendch(buf, '0'); continue; } ecs_entity_t typeid = table->type_info[storage_column]->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]); array_to_json_buf_w_type_data(world, ptr, it->count, buf, comp, ser); } flecs_json_array_pop(buf); } static void 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) { 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); } else { if (!desc || desc->serialize_ids) { flecs_json_serialize_iter_result_ids(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 || desc->serialize_table) { flecs_json_serialize_iter_result_entities(world, it, buf); } /* Write labels for entities */ if (desc && desc->serialize_entity_labels) { flecs_json_serialize_iter_result_entity_labels(world, it, buf); } /* Write ids for entities */ if (desc && desc->serialize_entity_ids) { flecs_json_serialize_iter_result_entity_ids(it, buf); } /* Write colors for entities */ if (desc && desc->serialize_colors) { flecs_json_serialize_iter_result_colors(world, it, buf); } /* Serialize component values */ if (desc && desc->serialize_table) { flecs_json_serialize_iter_result_columns(world, it, buf); } else { if (!desc || desc->serialize_values) { flecs_json_serialize_iter_result_values(world, it, buf); } } flecs_json_object_pop(buf); } 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); } flecs_json_object_push(buf); /* Serialize component ids of the terms (usually provided by query) */ if (!desc || desc->serialize_term_ids) { flecs_json_serialize_iter_ids(world, it, buf); } /* Serialize type info if enabled */ if (desc && desc->serialize_type_info) { flecs_json_serialize_type_info(world, it, buf); } /* Serialize variable names, if iterator has any */ flecs_json_serialize_iter_variables(it, buf); /* 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, EcsIterIsFilter); } ecs_iter_next_action_t next = it->next; while (next(it)) { flecs_json_serialize_iter_result(world, it, buf, desc); } flecs_json_array_pop(buf); 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) { ecs_filter_t f = ECS_FILTER_INIT; ecs_filter(world, { .terms = {{ .id = EcsAny }}, .storage = &f }); ecs_iter_t it = ecs_filter_iter(world, &f); ecs_iter_to_json_desc_t json_desc = { .serialize_table = true }; return ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); } char* ecs_world_to_json( ecs_world_t *world) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_world_to_json_buf(world, &buf)) { 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 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; 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; } /* 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) { 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_abort(ECS_INTERNAL_ERROR, NULL); break; default: if (json_typeinfo_ser_primitive( flecs_json_op_to_primitive_kind(op->kind), str)) { /* Unknown operation */ ecs_throw(ECS_INTERNAL_ERROR, NULL); return -1; } break; } ecs_entity_t unit = op->unit; if (unit) { flecs_json_next(str); flecs_json_next(str); flecs_json_object_push(str); json_typeinfo_ser_unit(world, str, unit); 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) { 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); break; case EcsOpPop: flecs_json_object_pop(str); break; default: if (json_typeinfo_ser_type_op(world, op, str)) { goto error; } break; } } 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; } ecs_meta_type_op_t *ops = ecs_vector_first(ser->ops, ecs_meta_type_op_t); int32_t count = ecs_vector_count(ser->ops); return json_typeinfo_ser_type_ops(world, ops, count, buf); } 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 json/deserialize.c * @brief Deserialize JSON strings into (component) values. */ #ifdef FLECS_JSON const char* ecs_parse_json( const ecs_world_t *world, const char *ptr, ecs_entity_t type, void *data_out, const ecs_parse_json_desc_t *desc) { char token[ECS_MAX_TOKEN_SIZE]; int depth = 0; const char *name = NULL; const char *expr = NULL; ptr = ecs_parse_fluff(ptr, NULL); ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, data_out); if (cur.valid == false) { return NULL; } if (desc) { name = desc->name; expr = desc->expr; } while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { ptr = ecs_parse_fluff(ptr, NULL); if (!ecs_os_strcmp(token, "{")) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected '['"); 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, "[")) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected '{'"); 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 (ecs_meta_next(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "null")) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token[0] == '\"') { if (ptr[0] == ':') { /* Member assignment */ ptr ++; /* Strip trailing " */ ecs_size_t len = ecs_os_strlen(token); if (token[len - 1] != '"') { ecs_parser_error(name, expr, ptr - expr, "expected \""); return NULL; } else { token[len - 1] = '\0'; } if (ecs_meta_dotmember(&cur, token + 1) != 0) { goto error; } } else { if (ecs_meta_set_string_literal(&cur, token) != 0) { goto error; } } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } if (!depth) { break; } } return ptr; error: return NULL; } static const char* flecs_parse_json_path( const ecs_world_t *world, const char *name, const char *expr, const char *ptr, char *token, ecs_entity_t *out) { /* Start of id. Ids can have two elements, in case of pairs. */ if (ptr[0] != '"') { ecs_parser_error(name, expr, ptr - expr, "expected starting '\"'"); goto error; } const char *token_start = ptr; ptr = ecs_parse_expr_token(name, expr, ptr, token); if (!ptr) { goto error; } ecs_size_t len = ecs_os_strlen(token); if (token_start[len - 1] != '"') { ecs_parser_error(name, expr, ptr - expr, "missing closing '\"'"); goto error; } token[len - 1] = '\0'; ecs_entity_t result = ecs_lookup_fullpath(world, token + 1); if (!result) { ecs_parser_error(name, expr, ptr - expr, "unresolved identifier '%s'", token); goto error; } *out = result; return ecs_parse_fluff(ptr, NULL); error: return NULL; } const char* ecs_parse_json_values( ecs_world_t *world, ecs_entity_t e, const char *ptr, const ecs_parse_json_desc_t *desc) { char token[ECS_MAX_TOKEN_SIZE]; const char *name = NULL; const char *expr = ptr; if (desc) { name = desc->name; expr = desc->expr; } const char *ids = NULL, *values = NULL; ptr = ecs_parse_fluff(ptr, NULL); if (ptr[0] != '{') { ecs_parser_error(name, expr, ptr - expr, "expected '{'"); goto error; } /* Find start of ids array */ ptr = ecs_parse_fluff(ptr + 1, NULL); if (ecs_os_strncmp(ptr, "\"ids\"", 5)) { ecs_parser_error(name, expr, ptr - expr, "expected '\"ids\"'"); goto error; } ptr = ecs_parse_fluff(ptr + 5, NULL); if (ptr[0] != ':') { ecs_parser_error(name, expr, ptr - expr, "expected ':'"); goto error; } ptr = ecs_parse_fluff(ptr + 1, NULL); if (ptr[0] != '[') { ecs_parser_error(name, expr, ptr - expr, "expected '['"); goto error; } ids = ecs_parse_fluff(ptr + 1, NULL); /* Find start of values array */ const char *vptr = ptr; int32_t bracket_depth = 0; while (vptr[0]) { if (vptr[0] == '[') { bracket_depth ++; } if (vptr[0] == ']') { bracket_depth --; if (!bracket_depth) { break; } } vptr ++; } if (!vptr[0]) { ecs_parser_error(name, expr, ptr - expr, "found end of string while looking for values"); goto error; } ptr = ecs_parse_fluff(vptr + 1, NULL); if (ptr[0] == '}') { /* String doesn't contain values, which is valid */ return ptr + 1; } if (ptr[0] != ',') { ecs_parser_error(name, expr, ptr - expr, "expected ','"); goto error; } ptr = ecs_parse_fluff(ptr + 1, NULL); if (ecs_os_strncmp(ptr, "\"values\"", 8)) { ecs_parser_error(name, expr, ptr - expr, "expected '\"values\"'"); goto error; } ptr = ecs_parse_fluff(ptr + 8, NULL); if (ptr[0] != ':') { ecs_parser_error(name, expr, ptr - expr, "expected ':'"); goto error; } ptr = ecs_parse_fluff(ptr + 1, NULL); if (ptr[0] != '[') { ecs_parser_error(name, expr, ptr - expr, "expected '['"); goto error; } values = ptr; /* Parse ids & values */ while (ids[0]) { ecs_entity_t first = 0, second = 0, type_id = 0; ecs_id_t id; if (ids[0] == ']') { /* Last id found */ if (values[0] != ']') { ecs_parser_error(name, expr, values - expr, "expected ']'"); } ptr = values; break; } else if (ids[0] == '[') { ids = ecs_parse_fluff(ids + 1, NULL); ids = flecs_parse_json_path(world, name, expr, ids, token, &first); if (!ids) { goto error; } if (ids[0] == ',') { /* Id is a pair*/ ids = ecs_parse_fluff(ids + 1, NULL); ids = flecs_parse_json_path(world, name, expr, ids, token, &second); if (!ids) { goto error; } } if (ids[0] == ']') { /* End of id array */ } else { ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); goto error; } ids = ecs_parse_fluff(ids + 1, NULL); if (ids[0] == ',') { /* Next id */ ids = ecs_parse_fluff(ids + 1, NULL); } else if (ids[0] != ']') { /* End of ids array */ ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); goto error; } } else { ecs_parser_error(name, expr, ids - 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_get_mut_id(world, e, id); if (!comp_ptr) { char *idstr = ecs_id_str(world, id); ecs_parser_error(name, expr, ptr - expr, "id '%s' is not a valid component", idstr); ecs_os_free(idstr); goto error; } ecs_parse_json_desc_t parse_desc = { .name = name, .expr = expr, }; values = ecs_parse_json(world, values + 1, type_id, comp_ptr, &parse_desc); if (!values) { goto error; } if (values[0] != ']' && values[0] != ',') { ecs_parser_error(name, expr, ptr - expr, "expected ',' or ']'"); goto error; } ecs_modified_id(world, e, id); } ptr = ecs_parse_fluff(ptr + 1, NULL); if (ptr[0] != '}') { ecs_parser_error(name, expr, ptr - expr, "expected '}'"); } return ptr + 1; error: return NULL; } #endif /** * @file addons/rest.c * @brief Rest addon. */ #ifdef FLECS_REST /* Time interval used to determine when to return a result from the cache. Use * a short interval so that clients still get realtime data, but multiple * clients requesting for the same data can reuse the same result. */ #define FLECS_REST_CACHE_TIMEOUT ((ecs_ftime_t)0.5) typedef struct { char *content; int32_t content_length; ecs_ftime_t time; } ecs_rest_cached_t; typedef struct { ecs_world_t *world; ecs_entity_t entity; ecs_http_server_t *srv; ecs_map_t reply_cache; int32_t rc; ecs_ftime_t time; } ecs_rest_ctx_t; /* Global statistics */ int64_t ecs_rest_request_count = 0; int64_t ecs_rest_entity_count = 0; int64_t ecs_rest_entity_error_count = 0; int64_t ecs_rest_query_count = 0; int64_t ecs_rest_query_error_count = 0; int64_t ecs_rest_query_name_count = 0; int64_t ecs_rest_query_name_error_count = 0; int64_t ecs_rest_query_name_from_cache_count = 0; int64_t ecs_rest_enable_count = 0; int64_t ecs_rest_enable_error_count = 0; int64_t ecs_rest_set_count = 0; int64_t ecs_rest_set_error_count = 0; int64_t ecs_rest_delete_count = 0; int64_t ecs_rest_delete_error_count = 0; int64_t ecs_rest_world_stats_count = 0; int64_t ecs_rest_pipeline_stats_count = 0; int64_t ecs_rest_stats_error_count = 0; static void flecs_rest_free_reply_cache(ecs_map_t *reply_cache) { if (ecs_map_is_init(reply_cache)) { ecs_map_iter_t it = ecs_map_iter(reply_cache); while (ecs_map_next(&it)) { ecs_rest_cached_t *reply = ecs_map_ptr(&it); ecs_os_free(reply->content); ecs_os_free(reply); } ecs_map_fini(reply_cache); } } static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_http_server_fini(impl->srv); flecs_rest_free_reply_cache(&impl->reply_cache); ecs_os_free(impl); } } ecs_os_free(ptr->ipaddr); }) static char *rest_last_err; static void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; 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 = (char*)value; } } static void flecs_rest_parse_json_ser_entity_params( 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, "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); } 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, "ids", &desc->serialize_ids); 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, "entities", &desc->serialize_entities); flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); 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, "serialize_table", &desc->serialize_table); } static bool flecs_rest_reply_entity( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *path = &req->path[7]; ecs_dbg_2("rest: request entity '%s'", path); ecs_os_linc(&ecs_rest_entity_count); ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { ecs_dbg_2("rest: entity '%s' not found", path); flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; ecs_os_linc(&ecs_rest_entity_error_count); return true; } ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; flecs_rest_parse_json_ser_entity_params(&desc, req); ecs_entity_to_json_buf(world, e, &reply->body, &desc); 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; ecs_world_to_json_buf(world, &reply->body); 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_os_linc(&ecs_rest_set_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_set_error_count); return true; } const char *data = ecs_http_get_param(req, "data"); ecs_parse_json_desc_t desc = {0}; desc.expr = data; desc.name = path; if (ecs_parse_json_values(world, e, data, &desc) == NULL) { flecs_reply_error(reply, "invalid request"); reply->code = 400; ecs_os_linc(&ecs_rest_set_error_count); return true; } return true; } static bool flecs_rest_delete( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_os_linc(&ecs_rest_set_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_delete_error_count); return true; } ecs_delete(world, e); return true; } static bool flecs_rest_enable( ecs_world_t *world, ecs_http_reply_t *reply, const char *path, bool enable) { ecs_os_linc(&ecs_rest_enable_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_enable_error_count); return true; } ecs_enable(world, e, enable); return true; } static void flecs_rest_iter_to_reply( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; flecs_rest_parse_json_ser_iter_params(&desc, req); int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); ecs_iter_t pit = ecs_page_iter(it, offset, limit); ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); } static bool flecs_rest_reply_existing_query( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *name) { ecs_os_linc(&ecs_rest_query_name_count); ecs_entity_t q = ecs_lookup_fullpath(world, name); if (!q) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } ecs_map_init_if(&impl->reply_cache, NULL); ecs_rest_cached_t *cached = ecs_map_get_deref(&impl->reply_cache, ecs_rest_cached_t, q); if (cached) { if ((impl->time - cached->time) > FLECS_REST_CACHE_TIMEOUT) { ecs_os_free(cached->content); } else { /* Cache hit */ ecs_strbuf_appendstr_zerocpyn_const( &reply->body, cached->content, cached->content_length); ecs_os_linc(&ecs_rest_query_name_from_cache_count); return true; } } else { cached = ecs_map_insert_alloc_t( &impl->reply_cache, ecs_rest_cached_t, q); } /* Cache miss */ const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); if (!poly) { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } ecs_iter_t it; ecs_iter_poly(world, poly->poly, &it, NULL); flecs_rest_iter_to_reply(world, req, reply, &it); cached->content_length = ecs_strbuf_written(&reply->body); cached->content = ecs_strbuf_get(&reply->body); ecs_strbuf_reset(&reply->body); ecs_strbuf_appendstr_zerocpyn_const( &reply->body, cached->content, cached->content_length); cached->time = impl->time; return true; } static bool flecs_rest_reply_query( ecs_world_t *world, ecs_rest_ctx_t *impl, 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, impl, req, reply, q_name); } ecs_os_linc(&ecs_rest_query_count); const char *q = ecs_http_get_param(req, "q"); if (!q) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); reply->code = 400; /* bad request */ ecs_os_linc(&ecs_rest_query_error_count); return true; } ecs_dbg_2("rest: request query '%s'", q); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t 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) { char *err = flecs_rest_get_captured_log(); char *escaped_err = ecs_astresc('"', err); flecs_reply_error(reply, escaped_err); ecs_os_linc(&ecs_rest_query_error_count); reply->code = 400; /* bad request */ ecs_os_free(escaped_err); ecs_os_free(err); } else { ecs_iter_t it = ecs_rule_iter(world, r); flecs_rest_iter_to_reply(world, req, reply, &it); ecs_rule_fini(r); } ecs_os_api.log_ = prev_log_; ecs_log_enable_colors(prev_color); return true; } #ifdef FLECS_MONITOR static void _flecs_rest_array_append( ecs_strbuf_t *reply, const char *field, int32_t field_len, const ecs_float_t *values, int32_t t) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "[", ","); int32_t i; for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { int32_t index = i % ECS_STAT_WINDOW; ecs_strbuf_list_next(reply); ecs_strbuf_appendflt(reply, (double)values[index], '"'); } ecs_strbuf_list_pop(reply, "]"); } #define flecs_rest_array_append(reply, field, values, t)\ _flecs_rest_array_append(reply, field, sizeof(field) - 1, values, t) static void flecs_rest_gauge_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "{", ","); flecs_rest_array_append(reply, "avg", m->gauge.avg, t); flecs_rest_array_append(reply, "min", m->gauge.min, t); flecs_rest_array_append(reply, "max", m->gauge.max, t); if (brief) { ecs_strbuf_list_appendlit(reply, "\"brief\":\""); ecs_strbuf_appendstrn(reply, brief, brief_len); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_counter_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); } #define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_GAUGE_APPEND(reply, s, field, brief)\ ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) #define ECS_COUNTER_APPEND(reply, s, field, brief)\ ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) static void flecs_world_stats_to_json( ecs_strbuf_t *reply, const EcsWorldStats *monitor_stats) { const ecs_world_stats_t *stats = &monitor_stats->stats; ecs_strbuf_list_push(reply, "{", ","); ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.get_mut_count, "Get_mut commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); ECS_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags"); ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)"); ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices"); ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables"); ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, ids.count, "Component, tag and pair ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, ids.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, rest.request_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache"); ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests"); ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests"); ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); ecs_strbuf_appendch(reply, '"'); if (!stats->task) { ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); } ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); ecs_strbuf_list_pop(reply, "}"); } static void flecs_pipeline_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, const EcsPipelineStats *stats) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = ecs_vector_count(stats->stats.systems); ecs_entity_t *ids = ecs_vector_first(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_strbuf_list_pop(reply, "}"); } } ecs_strbuf_list_pop(reply, "]"); } static bool flecs_rest_reply_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *period_str = NULL; flecs_rest_string_param(req, "period", &period_str); char *category = &req->path[6]; ecs_entity_t period = EcsPeriod1s; if (period_str) { char *period_name = ecs_asprintf("Period%s", period_str); period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); ecs_os_free(period_name); if (!period) { flecs_reply_error(reply, "bad request (invalid period string)"); reply->code = 400; ecs_os_linc(&ecs_rest_stats_error_count); return false; } } if (!ecs_os_strcmp(category, "world")) { const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, EcsWorldStats, period); flecs_world_stats_to_json(&reply->body, stats); ecs_os_linc(&ecs_rest_world_stats_count); return true; } else if (!ecs_os_strcmp(category, "pipeline")) { const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); flecs_pipeline_stats_to_json(world, &reply->body, stats); ecs_os_linc(&ecs_rest_pipeline_stats_count); return true; } else { flecs_reply_error(reply, "bad request (unsupported category)"); reply->code = 400; ecs_os_linc(&ecs_rest_stats_error_count); return false; } return true; } #else static bool flecs_rest_reply_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; return false; } #endif static void flecs_rest_reply_table_append_type( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = table->type.count; ecs_id_t *ids = table->type.array; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(reply); ecs_strbuf_appendch(reply, '"'); ecs_id_str_buf(world, ids[i], reply); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "]"); } static void flecs_rest_reply_table_append_memory( ecs_strbuf_t *reply, const ecs_table_t *table) { int32_t used = 0, allocated = 0; used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*); int32_t i, storage_count = table->storage_count; ecs_type_info_t **ti = table->type_info; ecs_vec_t *storages = table->data.columns; for (i = 0; i < storage_count; i ++) { used += storages[i].count * ti[i]->size; allocated += storages[i].size * ti[i]->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_append(reply, "\"refcount\":%d", table->refcount); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_reply_tables( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_rest_reply_table_append(world, &reply->body, table); } ecs_strbuf_list_pop(&reply->body, "]"); return true; } static bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) { ecs_rest_ctx_t *impl = ctx; ecs_world_t *world = impl->world; ecs_os_linc(&ecs_rest_request_count); if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } if (req->method == EcsHttpGet) { /* Entity endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_reply_entity(world, req, reply); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_reply_query(world, impl, req, reply); /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_reply_world(world, req, reply); /* Stats endpoint */ } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { return flecs_rest_reply_stats(world, req, reply); /* Tables endpoint */ } else if (!ecs_os_strncmp(req->path, "tables", 6)) { return flecs_rest_reply_tables(world, req, reply); } } else if (req->method == EcsHttpPut) { /* Set endpoint */ if (!ecs_os_strncmp(req->path, "set/", 4)) { return flecs_rest_set(world, req, reply, &req->path[4]); /* Delete endpoint */ } else if (!ecs_os_strncmp(req->path, "delete/", 7)) { return flecs_rest_delete(world, reply, &req->path[7]); /* Enable endpoint */ } else if (!ecs_os_strncmp(req->path, "enable/", 7)) { return flecs_rest_enable(world, reply, &req->path[7], true); /* Disable endpoint */ } else if (!ecs_os_strncmp(req->path, "disable/", 8)) { return flecs_rest_enable(world, reply, &req->path[8], false); } } return false; } 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_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port, .callback = flecs_rest_reply, .ctx = srv_ctx }); 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); ecs_os_free(srv_ctx); continue; } srv_ctx->world = it->world; srv_ctx->entity = it->entities[i]; srv_ctx->srv = srv; srv_ctx->rc = 1; rest[i].impl = srv_ctx; ecs_http_server_start(srv_ctx->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) { ctx->time += it->delta_time; ecs_http_server_dequeue(ctx->srv, it->delta_time); } } } static void DisableRest(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_id(EcsRest), .src.flags = EcsSelf }); if (it->event == EcsOnAdd) { /* REST module was disabled */ while (ecs_term_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 1); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_stop(ctx->srv); } } } else if (it->event == EcsOnRemove) { /* REST module was enabled */ while (ecs_term_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 1); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_start(ctx->srv); } } } } void FlecsRestImport( ecs_world_t *world) { ECS_MODULE(world, FlecsRest); ecs_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_observer(world, { .filter = { .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} }, .events = {EcsOnAdd, EcsOnRemove}, .callback = DisableRest }); } #endif /** * @file addons/coredoc.c * @brief Core doc addon. */ #ifdef FLECS_COREDOC #define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.html/" void FlecsCoreDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsCoreDoc); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsDoc); ecs_set_name_prefix(world, "Ecs"); /* Initialize reflection data for core components */ ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsComponent), .members = { {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsDocDescription), .members = { {.name = "value", .type = ecs_id(ecs_string_t)} } }); /* Initialize documentation data for core components */ ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components"); ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); ecs_doc_set_brief(world, EcsWith, "With relationship property"); ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); /* Initialize documentation for meta components */ ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); /* Initialize documentation for doc components */ ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); } #endif /** * @file addons/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 #if defined(ECS_TARGET_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #pragma comment(lib, "Ws2_32.lib") #include #include #include typedef SOCKET ecs_http_socket_t; #else #include #include #include #include #include #include #include typedef int ecs_http_socket_t; #endif #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL (0) #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) /* 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) /* Max length of headers in reply */ #define ECS_HTTP_REPLY_HEADER_SIZE (1024) /* 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 cur; int32_t count; ecs_os_thread_t thread; int32_t wait_ms; } ecs_http_send_queue_t; /* HTTP server struct */ struct ecs_http_server_t { bool should_run; bool running; ecs_http_socket_t sock; ecs_os_mutex_t lock; ecs_os_thread_t thread; ecs_http_reply_action_t callback; void *ctx; ecs_sparse_t connections; /* sparse */ ecs_sparse_t requests; /* sparse */ bool initialized; uint16_t port; const char *ipaddr; double dequeue_timeout; /* used to not lock request queue too often */ double stats_timeout; /* used for periodic reporting of statistics */ double request_time; /* time spent on requests in last stats interval */ double request_time_total; /* total time spent on requests */ int32_t requests_processed; /* requests processed in last stats interval */ int32_t requests_processed_total; /* total requests processed */ int32_t dequeue_count; /* number of dequeues in last stats interval */ ecs_http_send_queue_t send_queue; }; /** 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 */ void *res; } 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 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); return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len, port, (uint32_t)port_len, flags); } 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); return bind(sock, addr, (uint32_t)addr_len); } 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_free(ecs_http_reply_t* response) { ecs_assert(response != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_free(response->body.content); } static void http_request_free(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 void 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 { char *res = ecs_strbuf_get(&frag->buf); if (res) { ecs_http_request_impl_t *req = flecs_sparse_add_t( &srv->requests, ecs_http_request_impl_t); req->pub.id = flecs_sparse_last_id(&srv->requests); req->conn_id = conn->pub.id; req->pub.conn = (ecs_http_connection_t*)conn; 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]]; http_decode_url_str((char*)req->pub.params[i].value); } req->pub.header_count = frag->header_count; req->pub.param_count = frag->param_count; req->res = res; ecs_os_linc(&ecs_http_request_received_count); } } ecs_os_mutex_unlock(srv->lock); } static bool http_parse_request( ecs_http_fragment_t *frag, const char* req_frag, ecs_size_t req_frag_len) { int32_t i; for (i = 0; i < req_frag_len; i++) { char c = req_frag[i]; switch (frag->state) { case HttpFragStateBegin: ecs_os_memset_t(frag, 0, ecs_http_fragment_t); frag->buf.max = ECS_HTTP_METHOD_LEN_MAX; frag->state = HttpFragStateMethod; frag->header_buf_ptr = frag->header_buf; /* fallthrough */ case HttpFragStateMethod: if (c == ' ') { http_parse_method(frag); frag->state = HttpFragStatePath; frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; } else { ecs_strbuf_appendch(&frag->buf, c); } break; case HttpFragStatePath: if (c == ' ') { frag->state = HttpFragStateVersion; ecs_strbuf_appendch(&frag->buf, '\0'); } else { if (c == '?' || c == '=' || c == '&') { ecs_strbuf_appendch(&frag->buf, '\0'); int32_t offset = ecs_strbuf_written(&frag->buf); if (c == '?' || c == '&') { frag->param_offsets[frag->param_count] = offset; } else { frag->param_value_offsets[frag->param_count] = offset; frag->param_count ++; } } else { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateVersion: if (c == '\r') { frag->state = HttpFragStateCR; } /* version is not stored */ break; case HttpFragStateHeaderStart: if (http_header_writable(frag)) { frag->header_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } http_header_buf_reset(frag); frag->state = HttpFragStateHeaderName; /* fallthrough */ 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; } /* fallthrough */ 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; ecs_assert(sq->count <= ECS_HTTP_SEND_QUEUE_MAX, ECS_INTERNAL_ERROR, NULL); if (sq->count == ECS_HTTP_SEND_QUEUE_MAX) { 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 */ return &sq->requests[(sq->cur + sq->count ++) % ECS_HTTP_SEND_QUEUE_MAX]; } static ecs_http_send_request_t* http_send_queue_get( ecs_http_server_t *srv) { ecs_http_send_request_t *result = NULL; ecs_os_mutex_lock(srv->lock); ecs_http_send_queue_t *sq = &srv->send_queue; if (sq->count) { result = &sq->requests[sq->cur]; sq->cur = (sq->cur + 1) % ECS_HTTP_SEND_QUEUE_MAX; sq->count --; } 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.count) { 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); } } 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; /* 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); } 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_appendlit(hdrs, "Server: flecs\r\n"); ecs_strbuf_mergebuff(hdrs, extra_headers); ecs_strbuf_appendlit(hdrs, "\r\n"); } static void http_send_reply( ecs_http_connection_impl_t* conn, ecs_http_reply_t* reply, bool preflight) { ecs_strbuf_t hdrs = ECS_STRBUF_INIT; char *content = ecs_strbuf_get(&reply->body); int32_t content_length = reply->body.length - 1; /* Use asynchronous send queue for outgoing data so send operations won't * hold up main thread */ ecs_http_send_request_t *req = NULL; if (!preflight) { req = http_send_queue_post(conn->pub.server); if (!req) { reply->code = 503; /* queue full, server is busy */ ecs_os_linc(&ecs_http_busy_count); } } http_append_send_headers(&hdrs, reply->code, reply->status, reply->content_type, &reply->headers, content_length, preflight); char *headers = ecs_strbuf_get(&hdrs); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* Second, enqueue send request for response body */ req->sock = conn->sock; req->headers = headers; req->header_length = headers_length; req->content = content; req->content_length = content_length; /* Take ownership of values */ reply->body.content = NULL; conn->sock = HTTP_SOCKET_INVALID; } static void http_recv_request( 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}; while ((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 */ return; } 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 { http_enqueue_request(conn, conn_id, &frag); } return; } else { ecs_os_linc(&ecs_http_request_invalid_count); } } /* Partial request received, cleanup resources */ ecs_strbuf_reset(&frag.buf); /* No request was enqueued, flag connection so it'll get purged */ ecs_os_mutex_lock(srv->lock); if (conn->pub.id == conn_id) { /* Only flag connection if it was still alive */ conn->dequeue_timeout = ECS_HTTP_CONNECTION_PURGE_TIMEOUT; conn->dequeue_retries = ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT; } ecs_os_mutex_unlock(srv->lock); } static void 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); /* 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); http_recv_request(srv, conn, conn_id, sock_conn); ecs_dbg_2("http: request received from '%s:%s'", remote_host, remote_port); } 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 (SOCKET_ERROR == 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; int 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); ecs_http_socket_t sock_conn; struct sockaddr_storage remote_addr; ecs_size_t remote_addr_len; while (srv->should_run) { remote_addr_len = ECS_SIZEOF(remote_addr); 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_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); } 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_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); } } 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_free(&reply); http_request_free(req); http_connection_free(conn); } 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); } } ecs_os_mutex_unlock(srv->lock); return request_count - 1; } const char* ecs_http_get_header( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->header_count; i++) { if (!ecs_os_strcmp(req->headers[i].key, name)) { return req->headers[i].value; } } return NULL; } const char* ecs_http_get_param( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->param_count; i++) { if (!ecs_os_strcmp(req->params[i].key, name)) { return req->params[i].value; } } return NULL; } ecs_http_server_t* ecs_http_server_init( const ecs_http_server_desc_t *desc) { ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); srv->lock = ecs_os_mutex_new(); srv->sock = HTTP_SOCKET_INVALID; srv->should_run = false; srv->initialized = true; srv->callback = desc->callback; srv->ctx = desc->ctx; srv->port = desc->port; srv->ipaddr = desc->ipaddr; srv->send_queue.wait_ms = desc->send_queue_wait_ms; if (!srv->send_queue.wait_ms) { srv->send_queue.wait_ms = 100 * 1000 * 1000; } 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); #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); flecs_sparse_fini(&srv->connections); flecs_sparse_fini(&srv->requests); 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_free(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; } #endif /** * @file addons/doc.c * @brief Doc addon. */ #ifdef FLECS_DOC static ECS_COPY(EcsDocDescription, dst, src, { ecs_os_strset((char**)&dst->value, src->value); }) static ECS_MOVE(EcsDocDescription, dst, src, { ecs_os_free((char*)dst->value); dst->value = src->value; src->value = NULL; }) static ECS_DTOR(EcsDocDescription, ptr, { ecs_os_free((char*)ptr->value); }) void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { ecs_set_pair(world, entity, EcsDocDescription, EcsName, { .value = (char*)name }); } void ecs_doc_set_brief( ecs_world_t *world, ecs_entity_t entity, const char *description) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocBrief, { .value = (char*)description }); } void ecs_doc_set_detail( ecs_world_t *world, ecs_entity_t entity, const char *description) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocDetail, { .value = (char*)description }); } void ecs_doc_set_link( ecs_world_t *world, ecs_entity_t entity, const char *link) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocLink, { .value = (char*)link }); } void ecs_doc_set_color( ecs_world_t *world, ecs_entity_t entity, const char *color) { ecs_set_pair(world, entity, EcsDocDescription, EcsDocColor, { .value = (char*)color }); } const char* ecs_doc_get_name( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsName); if (ptr) { return ptr->value; } else { return ecs_get_name(world, entity); } } const char* ecs_doc_get_brief( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocBrief); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_detail( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocDetail); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_link( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocLink); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_color( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocColor); if (ptr) { return ptr->value; } else { return NULL; } } void FlecsDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsDoc); ecs_set_name_prefix(world, "EcsDoc"); flecs_bootstrap_component(world, EcsDocDescription); flecs_bootstrap_tag(world, EcsDocBrief); flecs_bootstrap_tag(world, EcsDocDetail); flecs_bootstrap_tag(world, EcsDocLink); flecs_bootstrap_tag(world, EcsDocColor); ecs_set_hooks(world, EcsDocDescription, { .ctor = ecs_default_ctor, .move = ecs_move(EcsDocDescription), .copy = ecs_copy(EcsDocDescription), .dtor = ecs_dtor(EcsDocDescription) }); ecs_add_id(world, ecs_id(EcsDocDescription), EcsDontInherit); } #endif /** * @file addons/parser.c * @brief Parser addon. */ #ifdef FLECS_PARSER #include #define ECS_ANNOTATION_LENGTH_MAX (16) #define TOK_NEWLINE '\n' #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_WILDCARD '*' #define TOK_VARIABLE '$' #define TOK_PAREN_OPEN '(' #define TOK_PAREN_CLOSE ')' #define TOK_SELF "self" #define TOK_UP "up" #define TOK_DOWN "down" #define TOK_CASCADE "cascade" #define TOK_PARENT "parent" #define TOK_OVERRIDE "OVERRIDE" #define TOK_ROLE_AND "AND" #define TOK_ROLE_OR "OR" #define TOK_ROLE_NOT "NOT" #define TOK_ROLE_TOGGLE "TOGGLE" #define TOK_IN "in" #define TOK_OUT "out" #define TOK_INOUT "inout" #define TOK_INOUT_NONE "none" static const ecs_id_t ECS_OR = (1ull << 59); static const ecs_id_t ECS_NOT = (1ull << 58); #define ECS_MAX_TOKEN_SIZE (256) typedef char ecs_token_t[ECS_MAX_TOKEN_SIZE]; const char* ecs_parse_eol_and_whitespace( const char *ptr) { while (isspace(*ptr)) { ptr ++; } return ptr; } /** Skip spaces when parsing signature */ const char* ecs_parse_whitespace( 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)) { break; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; return ptr; } static bool flecs_is_newline_comment( const char *ptr) { if (ptr[0] == '/' && ptr[1] == '/') { return true; } return false; } const char* ecs_parse_fluff( const char *ptr, char **last_comment) { const char *last_comment_start = NULL; do { /* Skip whitespaces before checking for a comment */ ptr = ecs_parse_whitespace(ptr); /* Newline comment, skip until newline character */ if (flecs_is_newline_comment(ptr)) { ptr += 2; last_comment_start = ptr; 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]) || flecs_is_newline_comment(ptr)); if (last_comment) { *last_comment = (char*)last_comment_start; } 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_whitespace(ptr); char *tptr = token_out, ch = ptr[0]; if (!flecs_valid_token_start_char(ch)) { if (ch == '\0' || ch == '\n') { ecs_parser_error(name, expr, column, "unexpected end of expression"); } else { ecs_parser_error(name, expr, column, "invalid start of token '%s'", ptr); } return NULL; } tptr[0] = ch; tptr ++; ptr ++; if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { tptr[0] = 0; return ptr; } int tmpl_nesting = 0; bool in_str = ch == '"'; for (; (ch = *ptr); ptr ++) { if (ch == '<') { tmpl_nesting ++; } else if (ch == '>') { if (!tmpl_nesting) { break; } tmpl_nesting --; } else if (ch == '"') { in_str = !in_str; } else if (!flecs_valid_token_char(ch) && !in_str) { break; } if (delim && (ch == delim)) { break; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; if (tmpl_nesting != 0) { ecs_parser_error(name, expr, column, "identifier '%s' has mismatching < > pairs", ptr); return NULL; } const char *next_ptr = ecs_parse_whitespace(ptr); if (next_ptr[0] == ':' && next_ptr != ptr) { /* Whitespace between token and : is significant */ ptr = next_ptr - 1; } else { ptr = next_ptr; } return ptr; } static 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])) { 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 ++; } out->name = ecs_os_strdup(tptr); 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_abort(ECS_INTERNAL_ERROR, NULL); } } 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_whitespace(ptr); if (ptr[0] != TOK_BRACKET_CLOSE) { ecs_parser_error(name, sig, column, "expected ]"); return NULL; } return ptr + 1; } static uint8_t flecs_parse_set_token( const char *token) { if (!ecs_os_strcmp(token, TOK_SELF)) { return EcsSelf; } else if (!ecs_os_strcmp(token, TOK_UP)) { return EcsUp; } else if (!ecs_os_strcmp(token, TOK_DOWN)) { return EcsDown; } else if (!ecs_os_strcmp(token, TOK_CASCADE)) { return EcsCascade; } else if (!ecs_os_strcmp(token, TOK_PARENT)) { return EcsParent; } else { return 0; } } static const char* flecs_parse_term_flags( const ecs_world_t *world, const char *name, const char *expr, int64_t column, const char *ptr, char *token, ecs_term_id_t *id, char tok_end) { char token_buf[ECS_MAX_TOKEN_SIZE] = {0}; if (!token) { token = token_buf; ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } } do { uint8_t tok = flecs_parse_set_token(token); if (!tok) { ecs_parser_error(name, expr, column, "invalid set token '%s'", token); return NULL; } if (id->flags & tok) { ecs_parser_error(name, expr, column, "duplicate set token '%s'", token); return NULL; } id->flags |= tok; if (ptr[0] == TOK_PAREN_OPEN) { ptr ++; /* Relationship (overrides IsA default) */ if (!isdigit(ptr[0]) && flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } id->trav = ecs_lookup_fullpath(world, token); if (!id->trav) { ecs_parser_error(name, expr, column, "unresolved identifier '%s'", token); return NULL; } if (ptr[0] == TOK_AND) { ptr = ecs_parse_whitespace(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_whitespace(ptr + 1); if (ptr[0] != tok_end && ptr[0] != TOK_AND && ptr[0] != 0) { ecs_parser_error(name, expr, column, "expected end of set expr"); return NULL; } } } /* Next token in set expression */ if (ptr[0] == TOK_BITWISE_OR) { ptr ++; if (flecs_valid_token_start_char(ptr[0])) { ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } } /* End of set expression */ } else if (ptr[0] == tok_end || ptr[0] == TOK_AND || !ptr[0]) { break; } } while (true); return ptr; } static const char* flecs_parse_arguments( const ecs_world_t *world, const char *name, const char *expr, int64_t column, const char *ptr, char *token, ecs_term_t *term) { (void)column; int32_t arg = 0; do { if (flecs_valid_token_start_char(ptr[0])) { if (arg == 2) { ecs_parser_error(name, expr, (ptr - expr), "too many arguments in term"); return NULL; } ptr = ecs_parse_identifier(name, expr, ptr, token); if (!ptr) { return NULL; } ecs_term_id_t *term_id = NULL; if (arg == 0) { term_id = &term->src; } else if (arg == 1) { term_id = &term->second; } /* If token is a colon, the token is an identifier followed by a * set expression. */ if (ptr[0] == TOK_COLON) { if (flecs_parse_identifier(token, term_id)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); return NULL; } ptr = ecs_parse_whitespace(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, term_id, TOK_PAREN_CLOSE); if (!ptr) { return NULL; } /* Check for term flags */ } else if (!ecs_os_strcmp(token, TOK_CASCADE) || !ecs_os_strcmp(token, TOK_SELF) || !ecs_os_strcmp(token, TOK_UP) || !ecs_os_strcmp(token, TOK_DOWN) || !(ecs_os_strcmp(token, TOK_PARENT))) { ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, token, term_id, TOK_PAREN_CLOSE); if (!ptr) { return NULL; } /* Regular identifier */ } else if (flecs_parse_identifier(token, term_id)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); return NULL; } if (ptr[0] == TOK_AND) { ptr = ecs_parse_whitespace(ptr + 1); term->id_flags = ECS_PAIR; } else if (ptr[0] == TOK_PAREN_CLOSE) { ptr = ecs_parse_whitespace(ptr + 1); break; } else { ecs_parser_error(name, expr, (ptr - expr), "expected ',' or ')'"); return NULL; } } else { ecs_parser_error(name, expr, (ptr - expr), "expected identifier or set expression"); return NULL; } arg ++; } while (true); return ptr; } static void flecs_parser_unexpected_char( const char *name, const char *expr, const char *ptr, char ch) { if (ch && (ch != '\n')) { ecs_parser_error(name, expr, (ptr - expr), "unexpected character '%c'", ch); } else { ecs_parser_error(name, expr, (ptr - expr), "unexpected end of term"); } } static const char* flecs_parse_term( const ecs_world_t *world, const char *name, const char *expr, ecs_term_t *term_out) { const char *ptr = expr; char token[ECS_MAX_TOKEN_SIZE] = {0}; ecs_term_t term = { .move = true /* parser never owns resources */ }; ptr = ecs_parse_whitespace(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_whitespace(ptr); } if (flecs_valid_operator_char(ptr[0])) { term.oper = flecs_parse_operator(ptr[0]); ptr = ecs_parse_whitespace(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_token_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; /* 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_whitespace(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_whitespace(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_whitespace(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_whitespace(ptr + 1); } else { ptr = ecs_parse_whitespace(ptr); } if (ptr[0] == TOK_PAREN_OPEN) { ptr ++; if (ptr[0] == TOK_PAREN_CLOSE) { term.src.flags = EcsIsEntity; term.src.id = 0; ptr ++; ptr = ecs_parse_whitespace(ptr); } else { ptr = flecs_parse_arguments( world, name, expr, (ptr - expr), ptr, token, &term); } goto parse_done; } 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_whitespace(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 ++; 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_whitespace(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_whitespace(ptr + 1); ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, NULL, &term.second, TOK_PAREN_CLOSE); if (!ptr) { goto error; } } if (ptr[0] == TOK_PAREN_CLOSE) { ptr ++; goto parse_pair_object; } else { flecs_parser_unexpected_char(name, expr, ptr, ptr[0]); goto error; } } else if (ptr[0] == TOK_PAREN_CLOSE) { /* No object */ ptr ++; goto parse_done; } else { ecs_parser_error(name, expr, (ptr - expr), "expected pair object or ')'"); goto error; } parse_pair_object: if (flecs_parse_identifier(token, &term.second)) { ecs_parser_error(name, expr, (ptr - expr), "invalid identifier '%s'", token); goto error; } if (term.id_flags != 0) { if (!ECS_HAS_ID_FLAG(term.id_flags, PAIR)) { ecs_parser_error(name, expr, (ptr - expr), "invalid combination of role '%s' with pair", ecs_id_flag_str(term.id_flags)); goto error; } } else { term.id_flags = ECS_PAIR; } ptr = ecs_parse_whitespace(ptr); goto parse_done; parse_done: *term_out = term; return ptr; error: ecs_term_fini(&term); *term_out = (ecs_term_t){0}; return NULL; } static bool flecs_is_valid_end_of_term( const char *ptr) { if ((ptr[0] == TOK_AND) || /* another term with And operator */ (ptr[0] == TOK_OR[0]) || /* another term with Or operator */ (ptr[0] == '\n') || /* newlines are valid */ (ptr[0] == '\0') || /* end of string */ (ptr[0] == '/') || /* comment (in plecs) */ (ptr[0] == '{') || /* scope (in plecs) */ (ptr[0] == '}') || (ptr[0] == ':') || /* inheritance (in plecs) */ (ptr[0] == '=')) /* assignment (in plecs) */ { return true; } return false; } char* ecs_parse_term( const ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_term_t *term) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); ecs_term_id_t *src = &term->src; bool prev_or = false; if (ptr != expr) { if (ptr[0]) { if (ptr[0] == ',') { ptr ++; } else if (ptr[0] == '|') { ptr += 2; prev_or = true; } else { ecs_parser_error(name, expr, (ptr - expr), "invalid preceding token"); } } } ptr = ecs_parse_eol_and_whitespace(ptr); if (!ptr[0]) { *term = (ecs_term_t){0}; return (char*)ptr; } if (ptr == expr && !strcmp(expr, "0")) { return (char*)&ptr[1]; } /* Parse next element */ ptr = flecs_parse_term(world, name, ptr, term); if (!ptr) { goto error; } /* Check for $() notation */ if (!ecs_os_strcmp(term->first.name, "$")) { if (term->src.name) { ecs_os_free(term->first.name); term->first = term->src; if (term->second.name) { term->src = term->second; } else { term->src.id = EcsThis; term->src.name = NULL; term->src.flags |= EcsIsVariable; } term->second.name = ecs_os_strdup(term->first.name); term->second.flags |= EcsIsVariable; } } /* Post-parse consistency checks */ /* If next token is OR, term is part of an OR expression */ if (!ecs_os_strncmp(ptr, TOK_OR, 2) || prev_or) { /* 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 (!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; ecs_os_free(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)) { ecs_parser_error(name, expr, (ptr - expr), "invalid operator for empty source"); goto error; } /* Verify consistency of OR expression */ if (prev_or && term->oper == EcsOr) { term->oper = EcsOr; } /* Automatically assign This if entity is not assigned and the set is * nothing */ if (!(src->flags & EcsIsEntity)) { if (!src->name) { if (!src->id) { src->id = EcsThis; src->flags |= EcsIsVariable; } } } if (src->name && !ecs_os_strcmp(src->name, "0")) { src->id = 0; src->flags = EcsIsEntity; } /* Process role */ if (term->id_flags == ECS_AND) { term->oper = EcsAndFrom; term->id_flags = 0; } else if (term->id_flags == ECS_OR) { term->oper = EcsOrFrom; term->id_flags = 0; } else if (term->id_flags == ECS_NOT) { term->oper = EcsNotFrom; term->id_flags = 0; } ptr = ecs_parse_whitespace(ptr); return (char*)ptr; error: if (term) { ecs_term_fini(term); } return NULL; } #endif /** * @file addons/meta.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_eol_and_whitespace(ptr); ptr = ecs_parse_digit(ptr, token); if (!ptr) { goto error; } *value_out = strtol(token, NULL, 0); return ecs_parse_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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_eol_and_whitespace(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; } /* 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_eol_and_whitespace(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 == '[') { array_start = (char*)ptr; /* safe, will not be modified */ } } 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_eol_and_whitespace(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_eol_and_whitespace(ptr); /* If next token is a ',' the first type was a key type */ if (*ptr == ',') { ptr = ecs_parse_eol_and_whitespace(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_eol_and_whitespace(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); if (!element_type) { ecs_meta_error(ctx, params_decl, "unknown element type '%s'", params.type.type); } if (!e) { e = ecs_new_id(world); } ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); return ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); error: return 0; } static ecs_entity_t meta_lookup_vector( ecs_world_t *world, ecs_entity_t e, const char *params_decl, meta_parse_ctx_t *ctx) { meta_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; meta_params_t params; if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { goto error; } if (params.is_key_value) { ecs_meta_error(ctx, params_decl, "unexpected key value parameters for vector"); goto error; } ecs_entity_t element_type = meta_lookup( world, ¶ms.type, params_decl, 1, ¶m_ctx); if (!e) { e = ecs_new_id(world); } return ecs_set(world, e, EcsVector, { element_type }); error: return 0; } static ecs_entity_t meta_lookup_bitmask( ecs_world_t *world, ecs_entity_t e, const char *params_decl, meta_parse_ctx_t *ctx) { (void)e; meta_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; meta_params_t params; if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { goto error; } if (params.is_key_value) { ecs_meta_error(ctx, params_decl, "unexpected key value parameters for bitmask"); goto error; } if (params.is_fixed_size) { ecs_meta_error(ctx, params_decl, "unexpected size for bitmask"); goto error; } ecs_entity_t bitmask_type = meta_lookup( world, ¶ms.type, params_decl, 1, ¶m_ctx); ecs_check(bitmask_type != 0, ECS_INVALID_PARAMETER, NULL); #ifndef FLECS_NDEBUG /* Make sure this is a bitmask type */ const EcsMetaType *type_ptr = ecs_get(world, bitmask_type, EcsMetaType); ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); #endif return bitmask_type; error: return 0; } static ecs_entity_t meta_lookup( ecs_world_t *world, meta_type_t *token, const char *ptr, int64_t count, meta_parse_ctx_t *ctx) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); const char *typename = token->type; ecs_entity_t type = 0; /* Parse vector type */ if (!token->is_ptr) { if (!ecs_os_strcmp(typename, "ecs_array")) { type = meta_lookup_array(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "ecs_vector") || !ecs_os_strcmp(typename, "flecs::vector")) { type = meta_lookup_vector(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { type = meta_lookup_bitmask(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::byte")) { type = ecs_id(ecs_byte_t); } else if (!ecs_os_strcmp(typename, "char")) { type = ecs_id(ecs_char_t); } else if (!ecs_os_strcmp(typename, "bool") || !ecs_os_strcmp(typename, "_Bool")) { type = ecs_id(ecs_bool_t); } else if (!ecs_os_strcmp(typename, "int8_t")) { type = ecs_id(ecs_i8_t); } else if (!ecs_os_strcmp(typename, "int16_t")) { type = ecs_id(ecs_i16_t); } else if (!ecs_os_strcmp(typename, "int32_t")) { type = ecs_id(ecs_i32_t); } else if (!ecs_os_strcmp(typename, "int64_t")) { type = ecs_id(ecs_i64_t); } else if (!ecs_os_strcmp(typename, "uint8_t")) { type = ecs_id(ecs_u8_t); } else if (!ecs_os_strcmp(typename, "uint16_t")) { type = ecs_id(ecs_u16_t); } else if (!ecs_os_strcmp(typename, "uint32_t")) { type = ecs_id(ecs_u32_t); } else if (!ecs_os_strcmp(typename, "uint64_t")) { type = ecs_id(ecs_u64_t); } else if (!ecs_os_strcmp(typename, "float")) { type = ecs_id(ecs_f32_t); } else if (!ecs_os_strcmp(typename, "double")) { type = ecs_id(ecs_f64_t); } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { type = ecs_id(ecs_entity_t); } else if (!ecs_os_strcmp(typename, "char*")) { type = ecs_id(ecs_string_t); } else { type = ecs_lookup_symbol(world, typename, true); } } 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); } 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_init(world, &(ecs_entity_desc_t){ .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); 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; } ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ .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; default: break; } return 0; error: return -1; } #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) { } } 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; 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_app_desc.target_fps != 0) { 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 ecs_set(world, EcsWorld, EcsRest, {.port = 0}); #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) { 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) { ecs_err("frame action already set"); return -1; } frame_action = callback; return 0; } #endif /** * @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 = ECS_HI_COMPONENT_ID + 0; const ecs_entity_t EcsFlecs = ECS_HI_COMPONENT_ID + 1; const ecs_entity_t EcsFlecsCore = ECS_HI_COMPONENT_ID + 2; const ecs_entity_t EcsFlecsInternals = ECS_HI_COMPONENT_ID + 3; const ecs_entity_t EcsModule = ECS_HI_COMPONENT_ID + 4; const ecs_entity_t EcsPrivate = ECS_HI_COMPONENT_ID + 5; const ecs_entity_t EcsPrefab = ECS_HI_COMPONENT_ID + 6; const ecs_entity_t EcsDisabled = ECS_HI_COMPONENT_ID + 7; const ecs_entity_t EcsSlotOf = ECS_HI_COMPONENT_ID + 8; const ecs_entity_t EcsFlag = ECS_HI_COMPONENT_ID + 9; /* Relationship properties */ const ecs_entity_t EcsWildcard = ECS_HI_COMPONENT_ID + 10; const ecs_entity_t EcsAny = ECS_HI_COMPONENT_ID + 11; const ecs_entity_t EcsThis = ECS_HI_COMPONENT_ID + 12; const ecs_entity_t EcsVariable = ECS_HI_COMPONENT_ID + 13; const ecs_entity_t EcsTransitive = ECS_HI_COMPONENT_ID + 14; const ecs_entity_t EcsReflexive = ECS_HI_COMPONENT_ID + 15; const ecs_entity_t EcsSymmetric = ECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsFinal = ECS_HI_COMPONENT_ID + 17; const ecs_entity_t EcsDontInherit = ECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsTag = ECS_HI_COMPONENT_ID + 19; const ecs_entity_t EcsUnion = ECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsExclusive = ECS_HI_COMPONENT_ID + 21; const ecs_entity_t EcsAcyclic = ECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsWith = ECS_HI_COMPONENT_ID + 23; const ecs_entity_t EcsOneOf = ECS_HI_COMPONENT_ID + 24; /* Builtin relationships */ const ecs_entity_t EcsChildOf = ECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsIsA = ECS_HI_COMPONENT_ID + 26; const ecs_entity_t EcsDependsOn = ECS_HI_COMPONENT_ID + 27; /* Identifier tags */ const ecs_entity_t EcsName = ECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsSymbol = ECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsAlias = ECS_HI_COMPONENT_ID + 32; /* Events */ const ecs_entity_t EcsOnAdd = ECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsOnRemove = ECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsOnSet = ECS_HI_COMPONENT_ID + 35; const ecs_entity_t EcsUnSet = ECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsOnDelete = ECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsOnTableCreate = ECS_HI_COMPONENT_ID + 38; const ecs_entity_t EcsOnTableDelete = ECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnTableEmpty = ECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnTableFill = ECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnCreateTrigger = ECS_HI_COMPONENT_ID + 42; const ecs_entity_t EcsOnDeleteTrigger = ECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsOnDeleteObservable = ECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOnComponentHooks = ECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsOnDeleteTarget = ECS_HI_COMPONENT_ID + 46; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = ECS_HI_COMPONENT_ID + 47; const ecs_entity_t ecs_id(EcsTimer) = ECS_HI_COMPONENT_ID + 48; const ecs_entity_t ecs_id(EcsRateFilter) = ECS_HI_COMPONENT_ID + 49; /* Actions */ const ecs_entity_t EcsRemove = ECS_HI_COMPONENT_ID + 50; const ecs_entity_t EcsDelete = ECS_HI_COMPONENT_ID + 51; const ecs_entity_t EcsPanic = ECS_HI_COMPONENT_ID + 52; /* Misc */ const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; /* Systems */ const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; const ecs_entity_t EcsEmpty = ECS_HI_COMPONENT_ID + 62; const ecs_entity_t ecs_id(EcsPipeline) = ECS_HI_COMPONENT_ID + 63; const ecs_entity_t EcsOnStart = ECS_HI_COMPONENT_ID + 64; const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; const ecs_entity_t EcsPreUpdate = ECS_HI_COMPONENT_ID + 68; const ecs_entity_t EcsOnUpdate = ECS_HI_COMPONENT_ID + 69; const ecs_entity_t EcsOnValidate = ECS_HI_COMPONENT_ID + 70; const ecs_entity_t EcsPostUpdate = ECS_HI_COMPONENT_ID + 71; const ecs_entity_t EcsPreStore = ECS_HI_COMPONENT_ID + 72; const ecs_entity_t EcsOnStore = ECS_HI_COMPONENT_ID + 73; const ecs_entity_t EcsPostFrame = ECS_HI_COMPONENT_ID + 74; const ecs_entity_t EcsPhase = ECS_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) = ECS_HI_COMPONENT_ID + 80; const ecs_entity_t ecs_id(ecs_char_t) = ECS_HI_COMPONENT_ID + 81; const ecs_entity_t ecs_id(ecs_byte_t) = ECS_HI_COMPONENT_ID + 82; const ecs_entity_t ecs_id(ecs_u8_t) = ECS_HI_COMPONENT_ID + 83; const ecs_entity_t ecs_id(ecs_u16_t) = ECS_HI_COMPONENT_ID + 84; const ecs_entity_t ecs_id(ecs_u32_t) = ECS_HI_COMPONENT_ID + 85; const ecs_entity_t ecs_id(ecs_u64_t) = ECS_HI_COMPONENT_ID + 86; const ecs_entity_t ecs_id(ecs_uptr_t) = ECS_HI_COMPONENT_ID + 87; const ecs_entity_t ecs_id(ecs_i8_t) = ECS_HI_COMPONENT_ID + 88; const ecs_entity_t ecs_id(ecs_i16_t) = ECS_HI_COMPONENT_ID + 89; const ecs_entity_t ecs_id(ecs_i32_t) = ECS_HI_COMPONENT_ID + 90; const ecs_entity_t ecs_id(ecs_i64_t) = ECS_HI_COMPONENT_ID + 91; const ecs_entity_t ecs_id(ecs_iptr_t) = ECS_HI_COMPONENT_ID + 92; const ecs_entity_t ecs_id(ecs_f32_t) = ECS_HI_COMPONENT_ID + 93; const ecs_entity_t ecs_id(ecs_f64_t) = ECS_HI_COMPONENT_ID + 94; const ecs_entity_t ecs_id(ecs_string_t) = ECS_HI_COMPONENT_ID + 95; const ecs_entity_t ecs_id(ecs_entity_t) = ECS_HI_COMPONENT_ID + 96; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsMetaType) = ECS_HI_COMPONENT_ID + 97; const ecs_entity_t ecs_id(EcsMetaTypeSerialized) = ECS_HI_COMPONENT_ID + 98; const ecs_entity_t ecs_id(EcsPrimitive) = ECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsEnum) = ECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsBitmask) = ECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsMember) = ECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsStruct) = ECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsArray) = ECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsVector) = ECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsOpaque) = ECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsUnit) = ECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsUnitPrefix) = ECS_HI_COMPONENT_ID + 108; const ecs_entity_t EcsConstant = ECS_HI_COMPONENT_ID + 109; const ecs_entity_t EcsQuantity = ECS_HI_COMPONENT_ID + 110; /* Doc module components */ const ecs_entity_t ecs_id(EcsDocDescription) = ECS_HI_COMPONENT_ID + 111; const ecs_entity_t EcsDocBrief = ECS_HI_COMPONENT_ID + 112; const ecs_entity_t EcsDocDetail = ECS_HI_COMPONENT_ID + 113; const ecs_entity_t EcsDocLink = ECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocColor = ECS_HI_COMPONENT_ID + 115; /* REST module components */ const ecs_entity_t ecs_id(EcsRest) = ECS_HI_COMPONENT_ID + 116; /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; /* -- 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_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_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_world_t*)ecs_get_world(stage_world); ecs_poly_assert(world, ecs_world_t); bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); bool is_deferred = ecs_is_deferred(world); if (!is_readonly && !is_deferred) { state->is_readonly = false; state->is_deferred = false; return world; } ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = is_deferred; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; /* Hack around safety checks (this ought to look ugly) */ ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); state->defer_count = stage->defer; state->commands = stage->commands; state->defer_stack = stage->defer_stack; flecs_stack_init(&stage->defer_stack); state->scope = stage->scope; state->with = stage->with; stage->defer = 0; ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); return world; } void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state) { ecs_poly_assert(world, ecs_world_t); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (state->is_readonly || state->is_deferred) { ecs_dbg_3("resuming readonly mode"); ecs_run_aperiodic(world, 0); /* Restore readonly state / defer count */ ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); stage->defer = state->defer_count; ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); stage->commands = state->commands; flecs_stack_fini(&stage->defer_stack); stage->defer_stack = state->defer_stack; stage->scope = state->scope; stage->with = state->with; } } /* Evaluate component monitor. If a monitored entity changed it will have set a * flag in one of the world's component monitors. Queries can register * themselves with component monitors to determine whether they need to rematch * with tables. */ static void flecs_eval_component_monitor( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); if (!world->monitors.is_dirty) { return; } world->monitors.is_dirty = false; ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); while (ecs_map_next(&it)) { ecs_monitor_t *m = ecs_map_ptr(&it); if (!m->is_dirty) { continue; } m->is_dirty = false; ecs_vector_each(m->queries, ecs_query_t*, q_ptr, { flecs_query_notify(world, *q_ptr, &(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_query_t **q = ecs_vector_add(&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_vector_count(m->queries); ecs_query_t **queries = ecs_vector_first(m->queries, ecs_query_t*); for (i = 0; i < count; i ++) { if (queries[i] == query) { ecs_vector_remove(m->queries, ecs_query_t*, i); count --; break; } } if (!count) { ecs_vector_free(m->queries); 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)); /* Initialize entity index */ flecs_sparse_init_t(&world->store.entity_index, &world->allocator, &world->allocators.sparse_chunk, ecs_record_t); flecs_sparse_set_id_source(&world->store.entity_index, &world->info.last_id); /* Initialize root table */ flecs_sparse_init_t(&world->store.tables, &world->allocator, &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); ecs_assert(first->id == 0, ECS_INTERNAL_ERROR, NULL); (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_release(world, t); } /* Free table types separately so that if application destructors rely on * a type it's still valid. */ for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free_type(world, t); } /* Clear the root table */ if (count) { flecs_table_reset(world, &world->store.root); } } static void flecs_fini_roots(ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_table_cache_iter_t it; bool has_roots = flecs_table_cache_iter(&idr->cache, &it); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, &world->stages[0]); const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableHasBuiltins) { continue; /* Filter out modules */ } int32_t i, count = table->data.entities.count; ecs_entity_t *entities = table->data.entities.array; /* Count backwards so that we're always deleting the last entity in the * table which reduces moving components around */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_flags32_t flags = ECS_RECORD_TO_ROW_FLAGS(r->row); if (!(flags & EcsEntityObservedTarget)) { continue; /* Filter out entities that aren't objects */ } ecs_delete(world, entities[i]); } } flecs_defer_end(world, &world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_release(world, &world->store.root); flecs_sparse_clear(&world->store.entity_index); flecs_hashmap_fini(&world->store.table_map); ecs_vector_free(world->store.records); ecs_vector_free(world->store.marked_ids); } /* 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; ecs_sparse_t *entity_index = &world->store.entity_index; it->entities = (ecs_entity_t*)flecs_sparse_ids(entity_index); it->count = flecs_sparse_count(entity_index); 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_world_t*)world, .real_world = (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, ECS_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_CHUNK_SIZE); flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_CHUNK_SIZE); flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); flecs_table_diff_builder_init(world, &world->allocators.diff_builder); } static void flecs_world_allocators_fini( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; ecs_map_params_fini(&a->ptr); ecs_map_params_fini(&a->query_table_list); flecs_ballocator_fini(&a->query_table); flecs_ballocator_fini(&a->query_table_match); flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->id_record); flecs_ballocator_fini(&a->id_record_chunk); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); flecs_ballocator_fini(&a->hashmap); flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); flecs_allocator_fini(&world->allocator); } static void flecs_log_addons(void) { ecs_trace("addons included in build:"); ecs_log_push(); #ifdef FLECS_CPP ecs_trace("FLECS_CPP"); #endif #ifdef FLECS_MODULE ecs_trace("FLECS_MODULE"); #endif #ifdef FLECS_PARSER ecs_trace("FLECS_PARSER"); #endif #ifdef FLECS_PLECS ecs_trace("FLECS_PLECS"); #endif #ifdef FLECS_RULES ecs_trace("FLECS_RULES"); #endif #ifdef FLECS_SNAPSHOT ecs_trace("FLECS_SNAPSHOT"); #endif #ifdef FLECS_STATS ecs_trace("FLECS_STATS"); #endif #ifdef FLECS_MONITOR ecs_trace("FLECS_MONITOR"); #endif #ifdef FLECS_SYSTEM ecs_trace("FLECS_SYSTEM"); #endif #ifdef FLECS_PIPELINE ecs_trace("FLECS_PIPELINE"); #endif #ifdef FLECS_TIMER ecs_trace("FLECS_TIMER"); #endif #ifdef FLECS_META ecs_trace("FLECS_META"); #endif #ifdef FLECS_META_C ecs_trace("FLECS_META_C"); #endif #ifdef FLECS_UNITS ecs_trace("FLECS_UNITS"); #endif #ifdef FLECS_EXPR ecs_trace("FLECS_EXPR"); #endif #ifdef FLECS_JSON ecs_trace("FLECS_JSON"); #endif #ifdef FLECS_DOC ecs_trace("FLECS_DOC"); #endif #ifdef FLECS_COREDOC ecs_trace("FLECS_COREDOC"); #endif #ifdef FLECS_LOG ecs_trace("FLECS_LOG"); #endif #ifdef FLECS_JOURNAL ecs_trace("FLECS_JOURNAL"); #endif #ifdef FLECS_APP ecs_trace("FLECS_APP"); #endif #ifdef FLECS_OS_API_IMPL ecs_trace("FLECS_OS_API_IMPL"); #endif #ifdef FLECS_HTTP ecs_trace("FLECS_HTTP"); #endif #ifdef FLECS_REST ecs_trace("FLECS_REST"); #endif ecs_log_pop(); } /* -- Public functions -- */ ecs_world_t *ecs_mini(void) { #ifdef FLECS_OS_API_IMPL ecs_set_os_api_impl(); #endif ecs_os_init(); ecs_trace("#[bold]bootstrapping world"); ecs_log_push(); ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); if (!ecs_os_has_heap()) { ecs_abort(ECS_MISSING_OS_API, NULL); } if (!ecs_os_has_threading()) { ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } if (!ecs_os_has_time()) { ecs_trace("time management not available"); } flecs_log_addons(); #ifdef FLECS_SANITIZE ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); #elif defined(FLECS_DEBUG) ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for improved " "performance"); #else ecs_trace("#[green]release#[reset] build"); #endif #ifdef __clang__ ecs_trace("compiled with clang %s", __clang_version__); #elif defined(__GNUC__) ecs_trace("compiled with gcc %d.%d", __GNUC__, __GNUC_MINOR__); #elif defined (_MSC_VER) ecs_trace("compiled with msvc %d", _MSC_VER); #endif ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_poly_init(world, ecs_world_t); flecs_world_allocators_init(world); world->self = world; flecs_sparse_init_t(&world->type_info, &world->allocator, &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, ECS_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, &world->allocator, &world->allocators.sparse_chunk, ecs_table_t*); world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_buffer, &world->allocator, &world->allocators.sparse_chunk, ecs_table_t*); flecs_name_index_init(&world->aliases, &world->allocator); flecs_name_index_init(&world->symbols, &world->allocator); 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); ecs_trace("world ready!"); ecs_log_pop(); return world; } ecs_world_t *ecs_init(void) { ecs_world_t *world = ecs_mini(); #ifdef FLECS_MODULE_H ecs_trace("#[bold]import addons"); ecs_log_push(); ecs_trace("use ecs_mini to create world without importing addons"); #ifdef FLECS_SYSTEM ECS_IMPORT(world, FlecsSystem); #endif #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); #endif #ifdef FLECS_TIMER ECS_IMPORT(world, FlecsTimer); #endif #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif #ifdef FLECS_COREDOC ECS_IMPORT(world, FlecsCoreDoc); #endif #ifdef FLECS_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; } #define ARG(short, long, action)\ if (i < argc) {\ if (argv[i][0] == '-') {\ if (argv[i][1] == '-') {\ if (long && !strcmp(&argv[i][2], long ? long : "")) {\ action;\ parsed = true;\ }\ } else {\ if (short && argv[i][1] == short) {\ action;\ parsed = true;\ }\ }\ }\ } 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; } } } 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_vector_add(&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_vector_add(&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) { ecs_vector_each(world->fini_actions, ecs_action_elem_t, elem, { elem->action(world, elem->ctx); }); ecs_vector_free(world->fini_actions); } /* 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. After this point no more * user code is executed. */ 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); ecs_dbg_1("#[bold]cleanup world datastructures"); ecs_log_push_1(); flecs_sparse_fini(&world->store.entity_index); 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 + ECS_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 (world->info.target_fps == (ecs_ftime_t)0 || 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_context( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->context; error: return NULL; } void ecs_set_context( ecs_world_t *world, void *context) { ecs_poly_assert(world, ecs_world_t); world->context = context; } 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 > world->info.last_id, ECS_INVALID_PARAMETER, NULL); if (world->info.last_id < id_start) { world->info.last_id = id_start - 1; } world->info.min_id = id_start; world->info.max_id = 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; } void ecs_set_entity_generation( ecs_world_t *world, ecs_entity_t entity_with_generation) { flecs_sparse_set_generation( &world->store.entity_index, 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; const char *sym = ecs_get_symbol(world, component); if (sym) { ti_mut->name = ecs_os_strdup(sym); } } else { ti_mut = (ecs_type_info_t*)ti; } 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((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 (world->info.target_fps == (ecs_ftime_t)0.0) { 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 (sleep_time != 0) { 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) || (user_delta_time == 0)) { 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 (world->info.target_fps != 0) { delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; } else { /* Best guess */ delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; } } /* Keep trying while delta_time is zero */ } while (delta_time == 0); 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(user_delta_time != 0 || 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 (user_delta_time == 0) { user_delta_time = delta_time; } world->info.delta_time_raw = user_delta_time; world->info.delta_time = user_delta_time * world->info.time_scale; /* Keep track of total scaled time passed in world */ world->info.world_time_total += world->info.delta_time; ecs_run_aperiodic(world, 0); return world->info.delta_time; error: return (ecs_ftime_t)0; } void ecs_frame_end( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); world->info.frame_count_total ++; ecs_stage_t *stages = world->stages; int32_t i, count = world->stage_count; for (i = 0; i < count; i ++) { flecs_stage_merge_post_frame(world, &stages[i]); } flecs_stop_measure_frame(world); error: return; } const ecs_world_info_t* ecs_get_world_info( const ecs_world_t *world) { world = ecs_get_world(world); return &world->info; } void flecs_delete_table( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); flecs_table_release(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_world_t*)world_r; /* If pending buffer is NULL there already is a stackframe that's iterating * the table list. This can happen when an observer for a table event results * in a mutation that causes another table to change state. A typical * example of this is a system that becomes active/inactive as the result of * a query (and as a result, its matched tables) becoming empty/non empty */ if (!world->pending_buffer) { return; } /* Swap buffer. The logic could in theory have been implemented with a * single sparse set, but that would've complicated (and slowed down) the * iteration. Additionally, by using a double buffer approach we can still * keep most of the original ordering of events intact, which is desirable * as it means that the ordering of tables in the internal datastructures is * more predictable. */ int32_t i, count = flecs_sparse_count(world->pending_tables); if (!count) { return; } flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); do { ecs_sparse_t *pending_tables = world->pending_tables; world->pending_tables = world->pending_buffer; world->pending_buffer = NULL; /* Make sure that any ECS operations that occur while delivering the * events does not cause inconsistencies, like sending an Empty * notification for a table that just became non-empty. */ flecs_defer_begin(world, &world->stages[0]); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( pending_tables, ecs_table_t*, i)[0]; if (!table->id) { /* Table is being deleted, ignore empty events */ continue; } /* For each id in the table, add it to the empty/non empty list * based on its current state */ if (flecs_table_records_update_empty(table)) { /* 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. */ int32_t table_count = ecs_table_count(table); 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( 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; ecs_time_measure(&start); if (time_budget_seconds != 0) { 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->refcount > 1) { /* Don't delete claimed tables */ continue; } if (table->type.count < min_id_count) { continue; } uint16_t gen = ++ table->generation; if (delete_generation && (gen > delete_generation)) { if (flecs_table_release(world, table)) { delete_count ++; } } else if (clear_generation && (gen > clear_generation)) { if (flecs_table_shrink(world, table)) { clear_count ++; } } } } done: 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 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_event_record_t*)&o->on_add; else if (event == EcsOnRemove) return (ecs_event_record_t*)&o->on_remove; else if (event == EcsOnSet) return (ecs_event_record_t*)&o->on_set; else if (event == EcsUnSet) return (ecs_event_record_t*)&o->un_set; else if (event == EcsWildcard) return (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 ecs_event_record_t* flecs_event_record_get_if( const ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { if (ecs_map_is_init(&er->event_ids)) { return er; } if (er->any) { return er; } if (er->wildcard) { return er; } if (er->wildcard_pair) { return er; } } return NULL; } ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id) { if (!er) { return NULL; } if (id == EcsAny) return er->any; else if (id == EcsWildcard) return er->wildcard; else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; else { if (ecs_map_is_init(&er->event_ids)) { return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); } return NULL; } } static ecs_event_id_record_t* flecs_event_id_record_get_if( const ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (!ider) { return NULL; } if (ider->observer_count) { return ider; } return NULL; } ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (ider) { return ider; } ider = ecs_os_calloc_t(ecs_event_id_record_t); if (id == EcsAny) { return er->any = ider; } else if (id == EcsWildcard) { return er->wildcard = ider; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { return er->wildcard_pair = ider; } ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); ecs_map_insert_ptr(&er->event_ids, id, ider); return ider; } void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id) { if (id == EcsAny) { er->any = NULL; } else if (id == EcsWildcard) { er->wildcard = NULL; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { er->wildcard_pair = NULL; } else { ecs_map_remove(&er->event_ids, id); if (!ecs_map_count(&er->event_ids)) { ecs_map_fini(&er->event_ids); } } } static int32_t flecs_event_observers_get( const ecs_event_record_t *er, ecs_id_t id, ecs_event_id_record_t **iders) { if (!er) { return 0; } /* Populate array with observer sets matching the id */ int32_t count = 0; iders[0] = flecs_event_id_record_get_if(er, EcsAny); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id); count += iders[count] != 0; if (ECS_IS_PAIR(id)) { ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); iders[count] = flecs_event_id_record_get_if(er, id_fwc); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id_swc); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id_pwc); count += iders[count] != 0; } else { iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); count += iders[count] != 0; } return count; } bool flecs_check_observers_for_event( const ecs_poly_t *object, ecs_id_t id, ecs_entity_t event) { ecs_observable_t *observable = ecs_get_observable(object); ecs_event_record_t *er = flecs_event_record_get_if(observable, event); if (!er) { return false; } return flecs_event_id_record_get_if(er, id) != NULL; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *tgt_idr, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("propagate events/invalidate cache for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Propagate to records of acyclic relationships */ ecs_id_record_t *cur = tgt_idr; while ((cur = cur->acyclic.next)) { cur->reachable.generation ++; /* Invalidate cache */ ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { continue; } /* Get traversed relationship */ ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table)) { continue; } bool owned = flecs_id_record_get_table(idr, table); 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. */ 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->observed_count) { continue; } ecs_record_t **records = ecs_vec_first(&table->data.records); for (e = 0; e < entity_count; e ++) { ecs_id_record_t *idr_t = records[e]->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * acyclic relationships */ flecs_emit_propagate(world, it, idr, idr_t, iders, ider_count); } } } } ecs_log_pop_3(); } static void flecs_emit_propagate_invalidate_tables( ecs_world_t *world, ecs_id_record_t *tgt_idr) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("invalidate reachable cache for %s", idstr); ecs_os_free(idstr); } /* Invalidate records of acyclic relationships */ ecs_id_record_t *cur = tgt_idr; while ((cur = cur->acyclic.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->observed_count) { continue; } int32_t e, entity_count = ecs_table_count(table); ecs_record_t **records = ecs_vec_first(&table->data.records); for (e = 0; e < entity_count; e ++) { ecs_id_record_t *idr_t = records[e]->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * acyclic relationships */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } } } void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count) { ecs_record_t **recs = ecs_vec_get_t(&table->data.records, ecs_record_t*, offset); int32_t i; for (i = 0; i < count; i ++) { ecs_record_t *record = recs[i]; if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Event is used as target in acyclic relationship, propagate */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } static void flecs_override_copy( const ecs_type_info_t *ti, void *dst, const void *src, int32_t count) { 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(dst, src, count, ti); dst = ECS_OFFSET(dst, size); } } else { for (i = 0; i < count; i ++) { ecs_os_memcpy(dst, src, size); dst = ECS_OFFSET(dst, size); } } } static void* flecs_override( ecs_iter_t *it, const ecs_type_t *emit_ids, ecs_id_t id, ecs_table_t *table, ecs_id_record_t *idr) { if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { return NULL; } 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. */ const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { continue; } int32_t column = tr->column; column = ecs_table_type_to_storage_index(table, column); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = idr->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_size_t size = ti->size; ecs_vec_t *vec = &table->data.columns[column]; return ecs_vec_get(vec, size, it->offset); } } return NULL; } static void flecs_emit_forward_up( ecs_world_t *world, ecs_event_record_t *er, 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, ecs_event_record_t *er, 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_storage_index(tgt_table, column); if (storage_i != -1) { ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *vec = &tgt_table->data.columns[storage_i]; ecs_size_t size = idr->type_info->size; it->ptrs[0] = ecs_vec_get(vec, size, offset); it->sizes[0] = size; } const 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, ecs_event_record_t *er, 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, ecs_event_record_t *er, 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->column, 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, ecs_event_record_t *er, 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_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); } if (it->event == EcsOnAdd || it->event == EcsOnRemove) { /* Only OnAdd/OnRemove events can validate a cache */ 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, ecs_event_record_t *er, 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_current(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, ecs_event_record_t *er, 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 a cache */ 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->column, offset, trav); } } } /* 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; int32_t offset = desc->offset; int32_t i, r, count = desc->count; ecs_flags32_t table_flags = table->flags; /* Table events are emitted for internal table operations only, and do not * provide component data and/or entity ids. */ bool table_event = desc->flags & EcsEventTableOnly; if (!count && !table_event) { /* If no count is provided, forward event for all entities in table */ count = ecs_table_count(table) - offset; } /* When the NoOnSet flag is provided, no OnSet/UnSet events should be * generated when new components are inherited. */ bool no_on_set = desc->flags & EcsEventNoOnSet; ecs_id_t ids_cache = 0; void *ptrs_cache = NULL; ecs_size_t sizes_cache = 0; int32_t columns_cache = 0; ecs_entity_t sources_cache = 0; ecs_iter_t it = { .world = stage, .real_world = world, .event = event, .table = table, .field_count = 1, .ids = &ids_cache, .ptrs = &ptrs_cache, .sizes = &sizes_cache, .columns = &columns_cache, .sources = &sources_cache, .other_table = desc->other_table, .offset = offset, .count = count, .param = (void*)desc->param, .flags = desc->flags | EcsIterIsValid }; /* The world event id is used to determine if an observer has already been * triggered for an event. Observers for multiple components are split up * into multiple observers for a single component, and this counter is used * to make sure a multi observer only triggers once, even if multiple of its * single-component observers trigger. */ world->event_id ++; ecs_observable_t *observable = ecs_get_observable(desc->observable); ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); /* Event records contain all observers for a specific event. In addition to * the emitted event, also request data for the Wildcard event (for * observers subscribing to the wildcard event), OnSet and UnSet events. The * latter to are used for automatically emitting OnSet/UnSet events for * inherited components, for example when an IsA relationship is added to an * entity. This doesn't add much overhead, as fetching records is cheap for * builtin event types. */ ecs_event_record_t *er = flecs_event_record_get_if(observable, event); ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet); ecs_data_t *storage = NULL; ecs_vec_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 (acyclic) relationship is added (emitting an OnAdd/OnRemove * event) this will cause the components of the target entity to be * propagated to the source entity. This makes it possible for observers to * get notified of any new reachable components though the relationship. */ bool can_forward = event != EcsOnSet; /* Set if event has been propagated */ bool propagated = false; /* Does table has observed entities */ bool has_observed = table_flags & EcsTableHasObserved; /* When a relationship is removed, the events reachable through that * relationship should emit UnSet events. This is part of the behavior that * allows observers to be agnostic of whether a component is inherited. */ bool can_unset = count && (event == EcsOnRemove) && !no_on_set; ecs_event_id_record_t *iders[5] = {0}; int32_t unset_count = 0; repeat_event: /* This is the core event logic, which is executed for each event. By * default this is just the event kind from the ecs_event_desc_t struct, but * can also include the Wildcard and UnSet events. The latter is emitted as * counterpart to OnSet, for any removed ids associated with data. */ for (i = 0; i < id_count; i ++) { /* Emit event for each id passed to the function. In most cases this * will just be one id, like a component that was added, removed or set. * In some cases events are emitted for multiple ids. * * One example is when an id was added with a "With" property, or * inheriting from a prefab with overrides. In these cases an entity is * moved directly to the archetype with the additional components. */ ecs_id_record_t *idr = NULL; const ecs_type_info_t *ti = NULL; ecs_id_t id = id_array[i]; int32_t ider_i, ider_count = 0; bool is_pair = ECS_IS_PAIR(id); void *override_ptr = NULL; ecs_entity_t base = 0; /* Check if this id is a pair of an acyclic 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 & EcsIdAcyclic)) { 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); } 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 = ecs_table_type_to_storage_index( base_table, 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]; 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); const 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->column, storage_i = -1; 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 = ecs_table_type_to_storage_index(table, column); if (storage_i != -1) { /* If this is a component, fetch pointer & size */ ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *vec = &columns[storage_i]; ecs_size_t size = idr->type_info->size; void *ptr = ecs_vec_get(vec, 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(ti, ptr, override_ptr, 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); flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA); } 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); flecs_observers_invoke(world, &ider->self_up, &it, table, 0); } if (!ider_count || !count || !has_observed) { continue; } /* If event is propagated, we don't have to manually invalidate entities * lower in the tree(s). */ propagated = true; /* The table->observed_count value indicates if the table contains any * entities that are used as targets of acyclic relationships. If the * entity/entities for which the event was generated is used as such a * target, events must be propagated downwards. */ ecs_entity_t *entities = it.entities; it.entities = NULL; ecs_record_t **recs = ecs_vec_get_t(&storage->records, ecs_record_t*, offset); for (r = 0; r < count; r ++) { ecs_record_t *record = recs[r]; if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Entity is used as target in acyclic pairs, propagate */ ecs_entity_t e = entities[r]; it.sources[0] = e; flecs_emit_propagate(world, &it, idr, idr_t, iders, ider_count); } } it.table = table; it.entities = entities; it.count = count; it.sources[0] = 0; } if (count && can_forward && has_observed && !propagated) { flecs_emit_propagate_invalidate(world, table, offset, count); } can_override = false; /* Don't override twice */ can_unset = false; /* Don't unset twice */ can_forward = false; /* Don't forward twice */ if (unset_count && er_unset && (er != er_unset)) { /* Repeat event loop for UnSet event */ unset_count = 0; er = er_unset; it.event = EcsUnSet; goto repeat_event; } if (wcer && er != wcer) { /* Repeat event loop for Wildcard event */ er = wcer; it.event = event; goto repeat_event; } error: if (measure_time) { world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); } return; } void ecs_emit( ecs_world_t *stage, ecs_event_desc_t *desc) { ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); if (desc->entity) { ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, desc->entity); ecs_table_t *table; if (!r || !(table = r->table)) { /* Empty entities can't trigger observers */ return; } desc->table = table; desc->offset = ECS_RECORD_TO_ROW(r->row); desc->count = 1; } if (!desc->observable) { desc->observable = world; } flecs_emit(world, stage, desc); } /** * @file filter.c * @brief Uncached query implementation. * * Uncached queries (filters) are stateless objects that do not cache their * results. This file contains the creation and validation of uncached queries * and code for query iteration. * * There file contains the implementation for term queries and filters. Term * queries are uncached queries that only apply to a single term. Filters are * uncached queries that support multiple terms. Filters are built on top of * term queries: before iteration a filter will first find a "pivot" term (the * term with the smallest number of elements), and create a term iterator for * it. The output of that term iterator is then evaluated against the rest of * the terms of the filter. * * Cached queries and observers are built using filters. */ #include ecs_filter_t ECS_FILTER_INIT = { .hdr = { .magic = ecs_filter_t_magic }}; /* Helper type for passing around context required for error messages */ typedef struct { const ecs_world_t *world; ecs_filter_t *filter; ecs_term_t *term; int32_t term_index; } ecs_filter_finalize_ctx_t; static char* flecs_filter_str( const ecs_world_t *world, const ecs_filter_t *filter, const ecs_filter_finalize_ctx_t *ctx, int32_t *term_start_out); static void flecs_filter_error( const ecs_filter_finalize_ctx_t *ctx, const char *fmt, ...) { va_list args; va_start(args, fmt); int32_t term_start = 0; char *expr = NULL; if (ctx->filter) { expr = flecs_filter_str(ctx->world, ctx->filter, ctx, &term_start); } else { expr = ecs_term_str(ctx->world, ctx->term); } const char *name = NULL; if (ctx->filter && ctx->filter->entity) { name = ecs_get_name(ctx->filter->world, ctx->filter->entity); } ecs_parser_errorv(name, expr, term_start, fmt, args); ecs_os_free(expr); va_end(args); } static int flecs_term_id_finalize_flags( ecs_term_id_t *term_id, ecs_filter_finalize_ctx_t *ctx) { if ((term_id->flags & EcsIsEntity) && (term_id->flags & EcsIsVariable)) { flecs_filter_error(ctx, "cannot set both IsEntity and IsVariable"); return -1; } if (!(term_id->flags & EcsIsEntity) && !(term_id->flags & EcsIsVariable)) { 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) { char *name = term_id->name; if (!name) { return 0; } if (term_id->flags & EcsIsVariable) { if (!ecs_os_strcmp(name, "This")) { term_id->id = EcsThis; if (free_name) { ecs_os_free(term_id->name); } term_id->name = NULL; } return 0; } ecs_assert(term_id->flags & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); if (ecs_identifier_is_0(name)) { if (term_id->id) { flecs_filter_error(ctx, "name '0' does not match entity id"); return -1; } return 0; } ecs_entity_t e = ecs_lookup_symbol(world, name, true); if (scope && !e) { e = ecs_lookup_child(world, scope, name); } if (!e) { 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, "$") || !ecs_os_strcmp(name, ".")) { term_id->flags &= ~EcsIsEntity; term_id->flags |= EcsIsVariable; } /* Check if looked up id is alive (relevant for numerical ids) */ 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) { ecs_os_free(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 | 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; } 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_filter_finalize_ctx_t *ctx) { 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) { role = term->id_flags = ECS_PAIR; } if (!second && !ECS_HAS_ID_FLAG(role, PAIR)) { term->id = first | role; } else { if (!ECS_HAS_ID_FLAG(role, PAIR)) { flecs_filter_error(ctx, "invalid role for pair"); return -1; } term->id = ecs_pair(first, second); } 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 (term_first != 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 (ecs_entity_t_lo(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( 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 (first->flags & EcsIsEntity) { first_id = first->id; } if (second->flags & EcsIsEntity) { second_id = second->id; } 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) && !ecs_has_id(world, first_id, EcsReflexive)) { 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, EcsAcyclic)) { char *r_str = ecs_get_fullpath(world, term->src.trav); flecs_filter_error(ctx, "cannot traverse non-Acyclic 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 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); } if (!term->id) { if (flecs_term_populate_id(term, ctx)) { return -1; } } ecs_entity_t first_id = 0; if (term->first.flags & EcsIsEntity) { first_id = term->first.id; } if (first_id) { /* If component id is final, don't attempt component inheritance */ if (ecs_has_id(world, first_id, EcsFinal)) { if (first_flags & EcsDown) { flecs_filter_error(ctx, "final id cannot be traversed down"); return -1; } first->flags &= ~EcsDown; first->trav = 0; } /* Don't traverse ids that cannot be inherited */ if (ecs_has_id(world, first_id, EcsDontInherit)) { 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; } /* Add traversal flags for transitive relationships */ if (!(second_flags & EcsTraverseFlags)) { if (ecs_has_id(world, first_id, EcsTransitive)) { second->flags |= EcsSelf|EcsUp|EcsTraverseAll; second->trav = first_id; } } } if (first->id == EcsVariable) { flecs_filter_error(ctx, "invalid $ for term.first"); return -1; } if (term->id_flags & ECS_AND) { term->oper = EcsAndFrom; term->id &= ECS_COMPONENT_MASK; term->id_flags = 0; } if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { flecs_filter_error(ctx, "invalid inout value for AndFrom/OrFrom/NotFrom term"); return -1; } } if (flecs_term_verify(world, term, ctx)) { return -1; } term->idr = flecs_query_id_record_get(world, term->id); 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; } world = ecs_get_world(world); const ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr && idr->flags & EcsIdMarkedForDelete) { 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) { ecs_os_free(term->first.name); ecs_os_free(term->src.name); ecs_os_free(term->second.name); ecs_os_free(term->name); term->first.name = NULL; term->src.name = NULL; term->second.name = NULL; term->name = 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; bool is_or = false, prev_or = false; ecs_entity_t prev_src_id = 0; int32_t filter_terms = 0; ecs_filter_finalize_ctx_t ctx = {0}; ctx.world = world; ctx.filter = f; f->flags |= EcsFilterMatchOnlyThis; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ctx.term_index = i; if (flecs_term_finalize(world, term, &ctx)) { return -1; } is_or = term->oper == EcsOr; field_count += !(is_or && prev_or); term->field_index = field_count - 1; if (prev_or && is_or) { if (prev_src_id != term->src.id) { flecs_filter_error(&ctx, "mismatching src.id for OR terms"); return -1; } } prev_src_id = term->src.id; prev_or = is_or; 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->inout == EcsInOutNone) { filter_terms ++; } 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 ++; } } } f->field_count = field_count; if (filter_terms == term_count) { ECS_BIT_SET(f->flags, EcsFilterNoData); } 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_filter_t*)poly); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_filter_iter(world, (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 (ecs_os_has_threading()) { ecs_os_adec(&term->idr->keep_alive); } else { term->idr->keep_alive --; } } ecs_term_fini(&filter->terms[i]); } if (filter->terms_owned) { ecs_os_free(filter->terms); } } filter->terms = NULL; if (filter->owned) { ecs_os_free(filter); } } void ecs_filter_fini( ecs_filter_t *filter) { if (filter->owned && filter->entity) { /* If filter is associated with entity, use poly dtor path */ ecs_delete(filter->world, filter->entity); } else { flecs_filter_fini(filter); } } ecs_filter_t* ecs_filter_init( ecs_world_t *world, const ecs_filter_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_filter_t *f = desc->storage; int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; const ecs_term_t *terms = desc->terms_buffer; ecs_term_t *storage_terms = NULL, *expr_terms = NULL; if (f) { ecs_check(f->hdr.magic == ecs_filter_t_magic, ECS_INVALID_PARAMETER, NULL); storage_count = f->term_count; storage_terms = f->terms; ecs_poly_init(f, ecs_filter_t); } else { f = ecs_poly_new(ecs_filter_t); f->owned = true; } if (!storage_terms) { f->terms_owned = true; } ECS_BIT_COND(f->flags, EcsFilterIsInstanced, desc->instanced); ECS_BIT_SET(f->flags, EcsFilterMatchAnything); f->flags |= desc->flags; /* 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 < ECS_TERM_DESC_CACHE_SIZE; i ++) { if (!ecs_term_is_initialized(&terms[i])) { break; } term_count ++; } } else { ecs_check(term_count != 0, ECS_INVALID_PARAMETER, NULL); } /* If expr is set, parse query expression */ const char *expr = desc->expr; ecs_entity_t entity = desc->entity; if (expr) { #ifdef FLECS_PARSER const char *name = NULL; const char *ptr = desc->expr; ecs_term_t term = {0}; int32_t expr_size = 0; if (entity) { name = ecs_get_name(world, entity); } while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (expr_count == expr_size) { expr_size = expr_size ? expr_size * 2 : 8; expr_terms = ecs_os_realloc_n(expr_terms, ecs_term_t, expr_size); } expr_terms[expr_count ++] = term; if (ptr[0] == '\n') { break; } } if (!ptr) { /* Set terms in filter object to make sur they get cleaned up */ f->terms = expr_terms; f->term_count = expr_count; f->terms_owned = true; goto error; } #else (void)expr; ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } /* If storage is provided, make sure it's large enough */ ecs_check(!storage_terms || storage_count >= (term_count + expr_count), ECS_INVALID_PARAMETER, NULL); if (term_count || expr_count) { /* If no storage is provided, create it */ if (!storage_terms) { f->terms = ecs_os_calloc_n(ecs_term_t, term_count + expr_count); f->term_count = term_count + expr_count; ecs_assert(f->terms_owned == true, ECS_INTERNAL_ERROR, NULL); } else { f->terms = storage_terms; f->term_count = storage_count; } /* Copy terms to filter storage */ for (i = 0; i < term_count; i ++) { f->terms[i] = ecs_term_copy(&terms[i]); /* Allow freeing resources from expr parser during finalization */ f->terms[i].move = true; } /* Move expr terms to filter storage */ for (i = 0; i < expr_count; i ++) { f->terms[i + term_count] = ecs_term_move(&expr_terms[i]); /* Allow freeing resources from expr parser during finalization */ f->terms[i + term_count].move = true; } ecs_os_free(expr_terms); } /* Ensure all fields are consistent and properly filled out */ if (ecs_filter_finalize(world, f)) { goto error; } /* Any allocated resources remaining in terms are now owned by filter */ for (i = 0; i < f->term_count; i ++) { f->terms[i].move = false; } f->variable_names[0] = (char*)"."; f->iterable.init = flecs_filter_iter_init; f->dtor = (ecs_poly_dtor_t)flecs_filter_fini; f->world = world; f->entity = entity; if (entity && f->owned) { EcsPoly *poly = ecs_poly_bind(world, entity, ecs_filter_t); poly->poly = f; ecs_poly_modified(world, entity, ecs_filter_t); } return f; error: ecs_filter_fini(f); return NULL; } void ecs_filter_copy( ecs_filter_t *dst, const ecs_filter_t *src) { if (src == dst) { return; } if (src) { *dst = *src; int32_t i, term_count = src->term_count; dst->terms = ecs_os_malloc_n(ecs_term_t, term_count); dst->terms_owned = true; for (i = 0; i < term_count; i ++) { dst->terms[i] = ecs_term_copy(&src->terms[i]); } } else { ecs_os_memset_t(dst, 0, ecs_filter_t); } } void ecs_filter_move( ecs_filter_t *dst, ecs_filter_t *src) { if (src == dst) { return; } if (src) { *dst = *src; if (src->terms_owned) { dst->terms = src->terms; dst->terms_owned = true; } else { ecs_filter_copy(dst, src); } src->terms = 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_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) { 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|EcsDown; 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.flags & EcsIsEntity && term->first.id != 0) { if (ecs_has_id(world, term->first.id, EcsFinal)) { def_first_mask = EcsSelf; } 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); 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; int32_t or_count = 0; 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 + , */ } } } if (i) { if (terms[i - 1].oper == EcsOr && term->oper == EcsOr) { ecs_strbuf_appendlit(&buf, " || "); } else { ecs_strbuf_appendlit(&buf, ", "); } } if (term->oper != EcsOr) { or_count = 0; } if (or_count < 1) { 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) { ecs_strbuf_appendlit(&buf, "[none] "); } } if (term->oper == EcsOr) { or_count ++; } flecs_term_str_w_strbuf(world, term, &buf); } return ecs_strbuf_get(&buf); error: return NULL; } char* ecs_filter_str( const ecs_world_t *world, const ecs_filter_t *filter) { return flecs_filter_str(world, filter, NULL, NULL); } int32_t ecs_filter_find_this_var( const ecs_filter_t *filter) { ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { /* Filters currently only support the This variable at index 0. Only * return 0 if filter actually has terms for the This variable. */ return 0; } error: return -1; } /* Check if the id is a pair that has Any as first or second element. Any * pairs behave just like Wildcard pairs and reuses the same data structures, * with as only difference that the number of results returned for an Any pair * is never more than one. This function is used to tell the difference. */ static bool is_any_pair( ecs_id_t id) { if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } if (ECS_PAIR_FIRST(id) == EcsAny) { return true; } if (ECS_PAIR_SECOND(id) == EcsAny) { return true; } return false; } static bool flecs_n_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, ecs_entity_t type_id, ecs_oper_kind_t oper, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, int32_t *match_index_out, bool first, ecs_flags32_t iter_flags) { (void)column_out; const ecs_type_t *type = ecs_get_type(world, type_id); ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = type->array; int32_t i, count = type->count; ecs_term_t temp = *term; temp.oper = EcsAnd; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { continue; } bool result; if (ECS_HAS_ID_FLAG(id, AND)) { ecs_oper_kind_t id_oper = EcsAndFrom; result = flecs_n_term_match_table(world, term, table, id & ECS_COMPONENT_MASK, id_oper, id_out, column_out, subject_out, match_index_out, first, iter_flags); } else { temp.id = id; result = flecs_term_match_table(world, &temp, table, id_out, 0, subject_out, match_index_out, first, iter_flags); } if (!result && oper == EcsAndFrom) { return false; } else if (result && oper == EcsOrFrom) { return true; } } if (oper == EcsAndFrom) { if (id_out) { id_out[0] = type_id; } return true; } else if (oper == EcsOrFrom) { return false; } return false; } bool flecs_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, int32_t *match_index_out, bool first, ecs_flags32_t iter_flags) { const ecs_term_id_t *src = &term->src; ecs_oper_kind_t oper = term->oper; const ecs_table_t *match_table = table; ecs_id_t id = term->id; ecs_entity_t src_id = src->id; if (ecs_term_match_0(term)) { if (id_out) { id_out[0] = id; /* If no entity is matched, just set id */ } return true; } if (oper == EcsAndFrom || oper == EcsOrFrom) { return flecs_n_term_match_table(world, term, table, term->id, term->oper, id_out, column_out, subject_out, match_index_out, first, iter_flags); } /* If source is not This, search in table of source */ if (!ecs_term_match_this(term)) { if (iter_flags & EcsIterEntityOptional) { /* Treat entity terms as optional */ oper = EcsOptional; } match_table = ecs_get_table(world, src_id); if (match_table) { } else if (oper != EcsOptional) { return false; } } else { /* If filter contains This terms, a table must be provided */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } if (!match_table) { return false; } ecs_entity_t source = 0; /* If first = false, we're searching from an offset. This supports returning * multiple results when using wildcard filters. */ int32_t column = 0; if (!first && column_out && column_out[0] != 0) { column = column_out[0]; if (column < 0) { /* In case column is not from This, flip sign */ column = -column; } /* Remove base 1 offset */ column --; } /* Find location, source and id of match in table type */ ecs_table_record_t *tr = 0; bool is_any = is_any_pair(id); column = flecs_search_relation_w_idr(world, match_table, column, id, src->trav, src->flags, &source, id_out, &tr, term->idr); if (tr && match_index_out) { if (!is_any) { match_index_out[0] = tr->count; } else { match_index_out[0] = 1; } } bool result = column != -1; if (oper == EcsNot) { if (match_index_out) { match_index_out[0] = 1; } result = !result; } if (oper == EcsOptional) { result = true; } if (!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; bool is_or = false; bool or_result = false; int32_t match_count = 1; 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 == EcsOr) { is_or = true; or_result = true; } 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; if (!is_or && oper == EcsOr) { is_or = true; or_result = false; } else if (is_or && oper != EcsOr) { if (!or_result) { return false; } is_or = false; } ecs_entity_t src_id = src->id; if (!src_id) { if (ids) { ids[t_i] = term->id; } continue; } if (!ecs_term_match_this(term)) { match_table = ecs_get_table(world, src_id); } else { if (ECS_BIT_IS_SET(iter_flags, EcsIterIgnoreThis)) { or_result = true; continue; } /* If filter contains This terms, table must be provided */ ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); } int32_t match_index = 0; bool 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 (is_or) { or_result |= result; if (result) { /* If Or term matched, skip following Or terms */ for (; i < count && terms[i].oper == EcsOr; i ++) { } i -- ; } } else if (!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 !is_or || or_result; } 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( 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; } 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(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(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_world_t*)world, .world = (ecs_world_t*)stage, .field_count = 1, .next = ecs_term_next }; /* Term iter populates the iterator with arrays from its own cache, ensure * they don't get overwritten by flecs_iter_validate. * * Note: the reason the term iterator doesn't use the iterator cache itself * (which could easily accomodate a single term) is that the filter iterator * is built on top of the term iterator. The private cache of the term * iterator keeps the filter iterator code simple, as it doesn't need to * worry about the term iter overwriting the iterator fields. */ flecs_iter_init(stage, &it, 0); term_iter_init(world, term, &it.priv.iter.term, false); 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 = (ecs_world_t*)world, .world = chain_it->world, .terms = term, .field_count = 1, .chain_it = (ecs_iter_t*)chain_it, .next = ecs_term_next }; flecs_iter_init(chain_it->world, &it, flecs_iter_cache_all); term_iter_init(world, term, &it.priv.iter.term, false); return it; error: return (ecs_iter_t){ 0 }; } ecs_iter_t ecs_children( const ecs_world_t *world, ecs_entity_t parent) { return ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(parent) }); } bool ecs_children_next( ecs_iter_t *it) { return ecs_term_next(it); } static const ecs_table_record_t *flecs_term_iter_next_table( ecs_term_iter_t *iter) { ecs_id_record_t *idr = iter->cur; if (!idr) { return NULL; } return flecs_table_cache_next(&iter->it, ecs_table_record_t); } static bool flecs_term_iter_find_superset( ecs_world_t *world, ecs_table_t *table, ecs_term_t *term, ecs_entity_t *source, ecs_id_t *id, int32_t *column) { ecs_term_id_t *src = &term->src; /* Test if following the relationship finds the id */ int32_t index = flecs_search_relation_w_idr(world, table, 0, term->id, src->trav, src->flags, source, id, 0, term->idr); if (index == -1) { *source = 0; return false; } ecs_assert(*source != 0, ECS_INTERNAL_ERROR, NULL); *column = (index + 1) * -1; return true; } static bool flecs_term_iter_next( ecs_world_t *world, ecs_term_iter_t *iter, bool match_prefab, bool match_disabled) { ecs_table_t *table = iter->table; ecs_entity_t source = 0; const ecs_table_record_t *tr; ecs_term_t *term = &iter->term; do { if (table) { iter->cur_match ++; if (iter->cur_match >= iter->match_count) { table = NULL; } else { iter->last_column = ecs_search_offset( world, table, iter->last_column + 1, term->id, 0); iter->column = iter->last_column + 1; if (iter->last_column >= 0) { iter->id = table->type.array[iter->last_column]; } } } if (!table) { if (!(tr = flecs_term_iter_next_table(iter))) { if (iter->cur != iter->set_index && iter->set_index != NULL) { if (iter->observed_table_count != 0) { iter->cur = iter->set_index; if (iter->empty_tables) { flecs_table_cache_all_iter( &iter->set_index->cache, &iter->it); } else { flecs_table_cache_iter( &iter->set_index->cache, &iter->it); } iter->index = 0; tr = flecs_term_iter_next_table(iter); } } if (!tr) { return false; } } table = tr->hdr.table; if (table->observed_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->column; iter->column = tr->column + 1; iter->id = flecs_to_public_id(table->type.array[tr->column]); } 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->column; iter->column = tr->column + 1; iter->id = flecs_to_public_id(table->type.array[tr->column]); } } 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; if (term->inout != EcsInOutNone) { it->ptrs = &iter->ptr; } else { it->ptrs = NULL; } 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, false, false)) { 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, it->sizes); 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) { 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; } 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); const ecs_world_t *world = ecs_get_world(stage); if (!(flags & EcsIterMatchVar)) { flecs_process_pending_tables(world); } ecs_iter_t it = { .real_world = (ecs_world_t*)world, .world = (ecs_world_t*)stage, .terms = filter ? filter->terms : NULL, .next = ecs_filter_next, .flags = flags }; ecs_filter_iter_t *iter = &it.priv.iter.filter; iter->pivot_term = -1; flecs_init_filter_iter(&it, filter); ECS_BIT_COND(it.flags, EcsIterIsInstanced, ECS_BIT_IS_SET(filter->flags, EcsFilterIsInstanced)); /* 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, EcsIterIsFilter, 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 = (char**)filter->variable_names; } flecs_iter_init(stage, &it, flecs_iter_cache_all); return it; error: return (ecs_iter_t){ 0 }; } ecs_iter_t ecs_filter_iter( const ecs_world_t *stage, const ecs_filter_t *filter) { return flecs_filter_iter_w_flags(stage, filter, 0); } ecs_iter_t ecs_filter_chain_iter( const ecs_iter_t *chain_it, const ecs_filter_t *filter) { ecs_iter_t it = { .terms = filter->terms, .field_count = filter->field_count, .world = chain_it->world, .real_world = chain_it->real_world, .chain_it = (ecs_iter_t*)chain_it, .next = ecs_filter_next }; 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)) { ecs_iter_fini(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; } /* 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 { /* 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: it->offset = 0; flecs_iter_populate_data(world, it, table, 0, table ? ecs_table_count(table) : 0, it->ptrs, it->sizes); ECS_BIT_SET(it->flags, EcsIterIsValid); return true; } /** * @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->column; if (tr_out) tr_out[0] = tr; if (id_out) { if (ECS_PAIR_FIRST(search_id) == EcsUnion) { id_out[0] = ids[r]; } else { id_out[0] = flecs_to_public_id(ids[r]); } } return r; } return -1; } static int32_t flecs_type_offset_search( int32_t offset, ecs_id_t id, ecs_id_t *ids, int32_t count, ecs_id_t *id_out) { ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); while (offset < count) { ecs_id_t type_id = ids[offset ++]; if (ecs_id_match(type_id, id)) { if (id_out) { id_out[0] = flecs_to_public_id(type_id); } return offset - 1; } } return -1; } static bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_id_record_t *idr, ecs_id_t id) { ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (idr->flags & EcsIdDontInherit) { return false; } if (idr->flags & EcsIdExclusive) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t er = ECS_PAIR_FIRST(id); if (flecs_table_record_get( world, table, ecs_pair(er, EcsWildcard))) { return false; } } } return true; } static int32_t flecs_type_search_relation( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_id_record_t *idr, ecs_id_t rel, ecs_id_record_t *idr_r, bool self, ecs_entity_t *subject_out, ecs_id_t *id_out, ecs_table_record_t **tr_out) { ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t count = type.count; if (self) { if (offset) { int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); if (r != -1) { return r; } } else { int32_t r = flecs_type_search(table, id, idr, ids, id_out, tr_out); if (r != -1) { return r; } } } ecs_flags32_t flags = table->flags; if ((flags & EcsTableHasPairs) && rel) { bool is_a = rel == ecs_pair(EcsIsA, EcsWildcard); if (is_a) { if (!(flags & EcsTableHasIsA)) { return -1; } idr_r = world->idr_isa_wildcard; } if (!flecs_type_can_inherit_id(world, table, idr, id)) { return -1; } if (!idr_r) { idr_r = flecs_id_record_get(world, rel); if (!idr_r) { return -1; } } ecs_id_t id_r; int32_t r, r_column; if (offset) { r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r); } else { r_column = flecs_type_search(table, id, idr_r, ids, &id_r, 0); } while (r_column != -1) { ecs_entity_t obj = ECS_PAIR_SECOND(id_r); ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *rec = flecs_entities_get_any(world, obj); ecs_assert(rec != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *obj_table = rec->table; if (obj_table) { ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); r = flecs_type_search_relation(world, obj_table, 0, id, idr, rel, idr_r, true, subject_out, id_out, tr_out); if (r != -1) { if (subject_out && !subject_out[0]) { subject_out[0] = ecs_get_alive(world, obj); } return r_column; } if (!is_a) { r = flecs_type_search_relation(world, obj_table, 0, id, idr, ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, true, subject_out, id_out, tr_out); if (r != -1) { if (subject_out && !subject_out[0]) { subject_out[0] = ecs_get_alive(world, obj); } return r_column; } } } r_column = flecs_type_offset_search( r_column + 1, rel, ids, count, &id_r); } } return -1; } int32_t flecs_search_relation_w_idr( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, ecs_id_record_t *idr) { if (!table) return -1; ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); flags = flags ? flags : (EcsSelf|EcsUp); if (!idr) { idr = flecs_query_id_record_get(world, id); if (!idr) { return -1; } } if (subject_out) subject_out[0] = 0; if (!(flags & EcsUp)) { if (offset) { return ecs_search_offset(world, table, offset, id, id_out); } else { return flecs_type_search( table, id, idr, table->type.array, id_out, tr_out); } } int32_t result = flecs_type_search_relation(world, table, offset, id, idr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, id_out, tr_out); return result; } int32_t ecs_search_relation( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out) { if (!table) return -1; ecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); flags = flags ? flags : (EcsSelf|EcsUp); if (subject_out) subject_out[0] = 0; if (!(flags & EcsUp)) { return ecs_search_offset(world, table, offset, id, id_out); } ecs_id_record_t *idr = flecs_query_id_record_get(world, id); if (!idr) { return -1; } int32_t result = flecs_type_search_relation(world, table, offset, id, idr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, id_out, tr_out); return result; } int32_t 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) { return ecs_search(world, table, id, id_out); } if (!table) return -1; ecs_poly_assert(world, ecs_world_t); 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; const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return 0; } int32_t i = tr->column, 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; } return flecs_relation_depth_walk(world, idr, table, table); } /** * @file observer.h * @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; } return 0; } static void flecs_inc_observer_count( ecs_world_t *world, ecs_entity_t event, ecs_event_record_t *evt, ecs_id_t id, int32_t value) { ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); int32_t result = idt->observer_count += value; if (result == 1) { /* Notify framework that there are observers for the event/id. This * allows parts of the code to skip event evaluation early */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags |= flags; } } } else if (result == 0) { /* Ditto, but the reverse */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableNoTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags &= ~flags; } } flecs_event_id_record_remove(evt, id); ecs_os_free(idt); } } static void flecs_register_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer, size_t offset) { ecs_id_t term_id = observer->register_id; ecs_term_t *term = &observer->filter.terms[0]; ecs_entity_t trav = term->src.trav; int i; for (i = 0; i < observer->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, observer->events[i]); /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_ensure(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id for event */ ecs_event_id_record_t *idt = flecs_event_id_record_ensure( world, er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *observers = ECS_OFFSET(idt, offset); ecs_map_init_w_params_if(observers, &world->allocators.ptr); ecs_map_insert_ptr(observers, observer->filter.entity, observer); flecs_inc_observer_count(world, event, er, term_id, 1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), 1); } } } static void flecs_uni_observer_register( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer) { ecs_term_t *term = &observer->filter.terms[0]; ecs_flags32_t flags = term->src.flags; if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_register_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_register_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL); flecs_register_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, up)); } } static void flecs_unregister_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer, size_t offset) { ecs_id_t term_id = observer->register_id; ecs_term_t *term = &observer->filter.terms[0]; ecs_entity_t trav = term->src.trav; int i; for (i = 0; i < observer->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, observer->events[i]); /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_get(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id */ ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *id_observers = ECS_OFFSET(idt, offset); ecs_map_remove(id_observers, observer->filter.entity); if (!ecs_map_count(id_observers)) { ecs_map_fini(id_observers); } flecs_inc_observer_count(world, event, er, term_id, -1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), -1); } } } static void flecs_unregister_observer( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *observer) { ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); if (!observer->filter.terms) { ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); return; } ecs_term_t *term = &observer->filter.terms[0]; ecs_flags32_t flags = term->src.flags; if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_unregister_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_unregister_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { flecs_unregister_observer_for_id(world, observable, observer, offsetof(ecs_event_id_record_t, up)); } } static bool flecs_ignore_observer( ecs_world_t *world, ecs_observer_t *observer, ecs_table_t *table) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); 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] == world->event_id) { return true; } ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags; bool result = (table_flags & EcsTableIsPrefab) && !(filter_flags & EcsFilterMatchPrefab); result = result || ((table_flags & EcsTableIsDisabled) && !(filter_flags & EcsFilterMatchDisabled)); return result; } static bool flecs_is_simple_result( ecs_iter_t *it) { return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0); } static void flecs_observer_invoke( ecs_world_t *world, ecs_iter_t *it, ecs_observer_t *observer, ecs_iter_action_t callback, int32_t term_index, bool simple_result) { ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); if (ecs_should_log_3()) { char *path = ecs_get_fullpath(world, it->system); ecs_dbg_3("observer: invoke %s", path); ecs_os_free(path); } ecs_log_push_3(); world->info.observers_ran_frame ++; ecs_filter_t *filter = &observer->filter; ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL); ecs_term_t *term = &filter->terms[term_index]; if (term->oper != EcsNot) { ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), ECS_INTERNAL_ERROR, NULL); } bool instanced = filter->flags & EcsFilterIsInstanced; bool match_this = filter->flags & EcsFilterMatchThis; if (match_this && (simple_result || instanced)) { callback(it); } else { ecs_entity_t observer_src = term->src.id; if (observer_src && !(term->src.flags & EcsIsEntity)) { observer_src = 0; } ecs_entity_t *entities = it->entities; int32_t i, count = it->count; ecs_entity_t src = it->sources[0]; it->count = 1; for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; it->entities = &e; if (!observer_src) { callback(it); } else if (observer_src == e) { ecs_entity_t dummy = 0; it->entities = &dummy; if (!src) { it->sources[0] = e; } callback(it); it->sources[0] = src; break; } } it->entities = entities; it->count = count; } ecs_log_pop_3(); } static void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; it->ctx = o->ctx; it->callback = o->callback; if (ecs_should_log_3()) { char *path = ecs_get_fullpath(it->world, it->system); ecs_dbg_3("observer %s", path); ecs_os_free(path); } ecs_log_push_3(); flecs_observer_invoke(it->real_world, it, o, o->callback, 0, flecs_is_simple_result(it)); ecs_log_pop_3(); } static void flecs_uni_observer_invoke( ecs_world_t *world, ecs_observer_t *observer, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, bool simple_result) { ecs_filter_t *filter = &observer->filter; ecs_term_t *term = &filter->terms[0]; if (flecs_ignore_observer(world, observer, table)) { 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, EcsIterIsFilter, is_filter); it->system = observer->filter.entity; it->ctx = observer->ctx; it->binding_ctx = observer->binding_ctx; it->term_index = observer->term_index; it->terms = term; ecs_entity_t event = it->event; it->event = flecs_get_observer_event(term, event); if (observer->run) { it->next = flecs_default_observer_next_callback; it->callback = flecs_default_uni_observer_run_callback; it->ctx = observer; observer->run(it); } else { ecs_iter_action_t callback = observer->callback; it->callback = callback; flecs_observer_invoke(world, it, observer, callback, 0, simple_result); } it->event = event; } void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { 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] == world->event_id) { /* Already handled this event */ return false; } o->last_event_id[0] = world->event_id; ecs_iter_t user_it = *it; user_it.field_count = o->filter.field_count; user_it.terms = o->filter.terms; user_it.flags = 0; ECS_BIT_COND(user_it.flags, EcsIterIsFilter, 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); 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]; if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, user_it.columns, user_it.sources, NULL, NULL, false, pivot_term, user_it.flags)) { /* Monitor observers only invoke when the filter matches for the first * time with an entity */ if (o->is_monitor) { if (flecs_filter_match_table(world, &o->filter, prev_table, NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) { goto done; } } /* While filter matching needs to be reversed for a Not term, the * component data must be fetched from the table we got notified for. * Repeat the matching process for the non-matching table so we get the * correct column ids and sources, which we need for populate_data */ if (term->oper == EcsNot) { flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, user_it.columns, user_it.sources, NULL, NULL, false, -1, user_it.flags | EcsFilterPopulate); } flecs_iter_populate_data(world, &user_it, it->table, it->offset, it->count, user_it.ptrs, user_it.sizes); if (it->ptrs) { user_it.ptrs[pivot_term] = it->ptrs[0]; user_it.sizes[pivot_term] = it->sizes[0]; } user_it.ids[pivot_term] = it->event_id; user_it.system = o->filter.entity; user_it.term_index = pivot_term; user_it.ctx = o->ctx; user_it.binding_ctx = o->binding_ctx; user_it.field_count = o->filter.field_count; user_it.callback = o->callback; flecs_iter_validate(&user_it); ecs_table_lock(it->world, table); flecs_observer_invoke(world, &user_it, o, o->callback, pivot_term, flecs_is_simple_result(&user_it)); ecs_table_unlock(it->world, table); ecs_iter_fini(&user_it); return true; } done: ecs_iter_fini(&user_it); return false; } bool ecs_observer_default_run_action(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; if (o->is_multi) { return flecs_multi_observer_invoke(it); } else { it->ctx = o->ctx; ecs_table_lock(it->world, it->table); flecs_observer_invoke(it->real_world, it, o, o->callback, 0, flecs_is_simple_result(it)); ecs_table_unlock(it->world, it->table); return true; } } static void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { flecs_multi_observer_invoke(it); } /* For convenience, so applications can (in theory) use a single run callback * that uses ecs_iter_next to iterate results */ bool flecs_default_observer_next_callback(ecs_iter_t *it) { if (it->interrupted_by) { return false; } else { /* Use interrupted_by to signal the next iteration must return false */ it->interrupted_by = it->system; return true; } } /* Run action for children of multi observer */ static void flecs_multi_observer_builtin_run(ecs_iter_t *it) { ecs_observer_t *observer = it->ctx; ecs_run_action_t run = observer->run; if (run) { it->next = flecs_default_observer_next_callback; it->callback = flecs_default_multi_observer_run_callback; it->interrupted_by = 0; run(it); } else { flecs_multi_observer_invoke(it); } } static void flecs_uni_observer_trigger_existing( ecs_world_t *world, ecs_observer_t *observer) { ecs_iter_action_t callback = observer->callback; /* If yield existing is enabled, observer for each thing that matches * the event, if the event is iterable. */ int i, count = observer->event_count; for (i = 0; i < count; i ++) { ecs_entity_t evt = observer->events[i]; const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); if (!iterable) { continue; } ecs_iter_t it; iterable->init(world, world, &it, &observer->filter.terms[0]); it.system = observer->filter.entity; it.ctx = observer->ctx; it.binding_ctx = observer->binding_ctx; it.event = evt; ecs_iter_next_action_t next = it.next; ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); while (next(&it)) { it.event_id = it.ids[0]; callback(&it); } ecs_iter_fini(&it); } } 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); int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter); if (pivot_term < 0) { return; } /* If yield existing is enabled, invoke for each thing that matches * the event, if the event is iterable. */ int i, count = observer->event_count; for (i = 0; i < count; i ++) { ecs_entity_t evt = observer->events[i]; const EcsIterable *iterable = ecs_get(world, evt, EcsIterable); if (!iterable) { continue; } ecs_iter_t it; iterable->init(world, world, &it, &observer->filter.terms[pivot_term]); it.terms = observer->filter.terms; it.field_count = 1; it.term_index = pivot_term; it.system = observer->filter.entity; it.ctx = observer; it.binding_ctx = observer->binding_ctx; it.event = evt; ecs_iter_next_action_t next = it.next; ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL); while (next(&it)) { it.event_id = it.ids[0]; run(&it); world->event_id ++; } } } static int flecs_uni_observer_init( ecs_world_t *world, ecs_observer_t *observer, const ecs_observer_desc_t *desc) { ecs_term_t *term = &observer->filter.terms[0]; observer->last_event_id = desc->last_event_id; if (!observer->last_event_id) { observer->last_event_id = &observer->last_event_id_storage; } observer->register_id = flecs_from_public_id(world, term->id); term->field_index = desc->term_index; if (ecs_id_is_tag(world, term->id)) { /* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */ int32_t e, count = observer->event_count; for (e = 0; e < count; e ++) { if (observer->events[e] == EcsOnSet) { observer->events[e] = EcsOnAdd; } else if (observer->events[e] == EcsUnSet) { observer->events[e] = EcsOnRemove; } } } flecs_uni_observer_register(world, observer->observable, observer); if (desc->yield_existing) { flecs_uni_observer_trigger_existing(world, observer); } return 0; } static int flecs_multi_observer_init( ecs_world_t *world, ecs_observer_t *observer, const ecs_observer_desc_t *desc) { /* Create last event id for filtering out the same event that arrives from * more than one term */ observer->last_event_id = ecs_os_calloc_t(int32_t); /* Mark observer as multi observer */ observer->is_multi = true; /* Create a child observer for each term in the filter */ ecs_filter_t *filter = &observer->filter; ecs_observer_desc_t child_desc = *desc; child_desc.last_event_id = observer->last_event_id; child_desc.run = NULL; child_desc.callback = flecs_multi_observer_builtin_run; child_desc.ctx = observer; child_desc.ctx_free = NULL; child_desc.filter.expr = NULL; child_desc.filter.terms_buffer = NULL; child_desc.filter.terms_buffer_count = 0; child_desc.binding_ctx = NULL; child_desc.binding_ctx_free = NULL; child_desc.yield_existing = false; ecs_os_zeromem(&child_desc.entity); ecs_os_zeromem(&child_desc.filter.terms); ecs_os_memcpy_n(child_desc.events, observer->events, ecs_entity_t, observer->event_count); int i, term_count = filter->term_count; bool optional_only = filter->flags & EcsFilterMatchThis; for (i = 0; i < term_count; i ++) { if (filter->terms[i].oper != EcsOptional) { if (ecs_term_match_this(&filter->terms[i])) { optional_only = false; } } } if (filter->flags & EcsFilterMatchPrefab) { child_desc.filter.flags |= EcsFilterMatchPrefab; } if (filter->flags & EcsFilterMatchDisabled) { child_desc.filter.flags |= EcsFilterMatchDisabled; } /* Create observers as children of observer */ ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); for (i = 0; i < term_count; i ++) { if (filter->terms[i].src.flags & EcsFilter) { continue; } ecs_term_t *term = &child_desc.filter.terms[0]; child_desc.term_index = filter->terms[i].field_index; *term = filter->terms[i]; ecs_oper_kind_t oper = term->oper; ecs_id_t id = term->id; /* AndFrom & OrFrom terms insert multiple observers */ if (oper == EcsAndFrom || oper == EcsOrFrom) { const ecs_type_t *type = ecs_get_type(world, id); int32_t ti, ti_count = type->count; ecs_id_t *ti_ids = type->array; /* Correct operator will be applied when an event occurs, and * the observer is evaluated on the observer source */ term->oper = EcsAnd; for (ti = 0; ti < ti_count; ti ++) { ecs_id_t ti_id = ti_ids[ti]; ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); if (idr->flags & EcsIdDontInherit) { continue; } term->first.name = NULL; term->first.id = ti_ids[ti]; term->id = ti_ids[ti]; if (ecs_observer_init(world, &child_desc) == 0) { goto error; } } continue; } /* 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 < ECS_OBSERVER_DESC_EVENT_COUNT_MAX; i ++) { ecs_entity_t event = desc->events[i]; if (!event) { break; } if (event == EcsMonitor) { /* Monitor event must be first and last event */ ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL); observer->events[0] = EcsOnAdd; observer->events[1] = EcsOnRemove; observer->event_count ++; observer->is_monitor = true; } else { observer->events[i] = event; } observer->event_count ++; } /* Observer must have at least one event */ ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL); bool multi = false; if (filter->term_count == 1 && !desc->last_event_id) { ecs_term_t *term = &filter->terms[0]; /* If the filter has a single term but it is a *From operator, we * need to create a multi observer */ multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); /* An observer with only optional terms is a special case that is * only handled by multi observers */ multi |= term->oper == EcsOptional; } if (filter->term_count == 1 && !observer->is_monitor && !multi) { if (flecs_uni_observer_init(world, observer, desc)) { goto error; } } else { if (flecs_multi_observer_init(world, observer, desc)) { goto error; } } if (ecs_get_name(world, entity)) { ecs_trace("#[green]observer#[reset] %s created", ecs_get_name(world, entity)); } } else { /* If existing entity handle was provided, override existing params */ if (desc->callback) { ecs_poly(poly->poly, ecs_observer_t)->callback = desc->callback; } if (desc->ctx) { ecs_poly(poly->poly, ecs_observer_t)->ctx = desc->ctx; } if (desc->binding_ctx) { ecs_poly(poly->poly, ecs_observer_t)->binding_ctx = desc->binding_ctx; } } ecs_poly_modified(world, entity, ecs_observer_t); return entity; error: ecs_delete(world, entity); return 0; } void* ecs_get_observer_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_get_observer_binding_ctx( const ecs_world_t *world, ecs_entity_t observer) { const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t); if (o) { ecs_poly_assert(o->poly, ecs_observer_t); return ((ecs_observer_t*)o->poly)->binding_ctx; } else { return NULL; } } void flecs_observer_fini( ecs_observer_t *observer) { if (observer->is_multi) { ecs_os_free(observer->last_event_id); } else { if (observer->filter.term_count) { flecs_unregister_observer( observer->filter.world, observer->observable, observer); } else { /* Observer creation failed while creating filter */ } } /* Cleanup filters */ ecs_filter_fini(&observer->filter); /* Cleanup context */ if (observer->ctx_free) { observer->ctx_free(observer->ctx); } if (observer->binding_ctx_free) { observer->binding_ctx_free(observer->binding_ctx); } ecs_poly_free(observer, ecs_observer_t); } /** * @file 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_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 false; } } it->cur = next; it->next = next->next; return next; } /** * @file os_api.h * @brief Operating system abstraction API. * * The OS API implements an overridable interface for implementing functions * that are operating system specific, in addition to a number of hooks which * allow for customization by the user, like logging. */ #include #include void ecs_os_api_impl(ecs_os_api_t *api); static bool ecs_os_api_initialized = false; static bool ecs_os_api_initializing = false; static int ecs_os_api_init_count = 0; #ifndef __EMSCRIPTEN__ ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; #else /* Disable colors by default for emscripten */ ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; #endif int64_t ecs_os_api_malloc_count = 0; int64_t ecs_os_api_realloc_count = 0; int64_t ecs_os_api_calloc_count = 0; int64_t ecs_os_api_free_count = 0; void ecs_os_set_api( ecs_os_api_t *os_api) { if (!ecs_os_api_initialized) { ecs_os_api = *os_api; ecs_os_api_initialized = true; } } ecs_os_api_t ecs_os_get_api(void) { return ecs_os_api; } void ecs_os_init(void) { if (!ecs_os_api_initialized) { ecs_os_set_api_defaults(); } if (!(ecs_os_api_init_count ++)) { if (ecs_os_api.init_) { ecs_os_api.init_(); } } } void ecs_os_fini(void) { if (!--ecs_os_api_init_count) { if (ecs_os_api.fini_) { ecs_os_api.fini_(); } } } /* Assume every non-glibc Linux target has no execinfo. This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ #if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__) #define HAVE_EXECINFO 0 #elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) #define HAVE_EXECINFO 1 #else #define HAVE_EXECINFO 0 #endif #if HAVE_EXECINFO #include #define ECS_BT_BUF_SIZE 100 void flecs_dump_backtrace( FILE *stream) { int nptrs; void *buffer[ECS_BT_BUF_SIZE]; char **strings; nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { return; } for (int j = 1; j < nptrs; j++) { fprintf(stream, "%s\n", strings[j]); } free(strings); } #else void flecs_dump_backtrace( FILE *stream) { (void)stream; } #endif #undef HAVE_EXECINFO_H static void flecs_log_msg( int32_t level, const char *file, int32_t line, const char *msg) { FILE *stream; if (level >= 0) { stream = stdout; } else { stream = stderr; } bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; time_t now = 0; if (deltatime) { now = time(NULL); time_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_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 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->node.table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); match->node.group_id = flecs_query_get_group_id(query, table); } else { match->node.group_id = 0; } } static ecs_query_table_list_t* flecs_query_get_group( 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_node_t* flecs_query_find_group_insertion_node( ecs_query_t *query, uint64_t group_id) { /* Grouping must be enabled */ ecs_assert(query->group_by != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_iter_t it = ecs_map_iter(&query->groups); ecs_query_table_list_t *list, *closest_list = NULL; uint64_t id, closest_id = 0; /* Find closest smaller group id */ while (ecs_map_next(&it)) { id = ecs_map_key(&it); if (id >= group_id) { continue; } list = ecs_map_ptr(&it); if (!list->last) { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); continue; } if (!closest_list || ((group_id - id) < (group_id - closest_id))) { closest_id = id; closest_list = list; } } if (closest_list) { return closest_list->last; } else { return NULL; /* Group should be first in query */ } } /* Initialize group with first node */ static void flecs_query_create_group( ecs_query_t *query, ecs_query_table_node_t *node) { ecs_query_table_match_t *match = node->match; uint64_t group_id = match->node.group_id; /* If query has grouping enabled & this is a new/empty group, find * the insertion point for the group */ ecs_query_table_node_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_node_t *query_first = query->list.first; if (query_first) { /* If this is not the first match for the query, insert before it */ node->next = query_first; query_first->prev = node; query->list.first = node; } 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 = node; query->list.last = node; } } 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_node_t *insert_before = insert_after->next; node->prev = insert_after; insert_after->next = node; node->next = insert_before; if (insert_before) { insert_before->prev = node; } else { ecs_assert(query->list.last == insert_after, ECS_INTERNAL_ERROR, NULL); /* This group should appear last in the query list */ query->list.last = node; } } } /* 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_node_t *node) { ecs_query_table_match_t *match = node->match; if (query->group_by) { return flecs_query_get_group(query, match->node.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_node_t *node) { ecs_query_table_match_t *match = node->match; if (query->group_by) { return flecs_query_ensure_group(query, match->node.group_id); } else { return &query->list; } } /* Remove node from list */ static void flecs_query_remove_table_node( ecs_query_t *query, ecs_query_table_node_t *node) { ecs_query_table_node_t *prev = node->prev; ecs_query_table_node_t *next = node->next; ecs_assert(prev != node, ECS_INTERNAL_ERROR, NULL); ecs_assert(next != node, 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, node); if (!list || !list->first) { /* If list contains no nodes, the node 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 == node, ECS_INTERNAL_ERROR, NULL); ecs_assert(next != NULL || query->list.last == node, 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) { ecs_query_table_match_t *match = node->match; uint64_t group_id = match->node.group_id; /* Make sure query.list is updated if this is the first or last group */ if (query->list.first == node) { ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); query->list.first = next; prev = next; } if (query->list.last == node) { 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->match->node.group_id != group_id) { /* The previous node belonged to another group */ prev = next; } if (next && next->match->node.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->match->node.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 == node) { list->first = next; } if (list->last == node) { list->last = prev; } } node->prev = NULL; node->next = NULL; query->match_count ++; } /* Add node to list */ static void flecs_query_insert_table_node( ecs_query_t *query, ecs_query_table_node_t *node) { /* Node should not be part of an existing list */ ecs_assert(node->prev == NULL && node->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, node->match); ecs_query_table_list_t *list = flecs_query_ensure_node_list(query, node); 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_node_t *last = list->last; ecs_query_table_node_t *last_next = last->next; node->prev = last; node->next = last_next; last->next = node; if (last_next) { last_next->prev = node; } list->last = node; if (query->group_by) { /* Make sure to update query list if this is the last group */ if (query->list.last == last) { query->list.last = node; } } } else { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); list->first = node; list->last = node; if (query->group_by) { /* Initialize group with its first node */ flecs_query_create_group(query, node); } } if (query->group_by) { list->info.table_count ++; list->info.match_count ++; } query->list.info.table_count ++; query->match_count ++; ecs_assert(node->prev != node, ECS_INTERNAL_ERROR, NULL); ecs_assert(node->next != node, 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 == node, 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); ecs_query_table_node_t *node = &result->node; node->match = result; 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 *dirty_state; int32_t column; } table_dirty_state_t; static void flecs_query_get_dirty_state( ecs_query_t *query, ecs_query_table_match_t *match, int32_t term, table_dirty_state_t *out) { ecs_world_t *world = query->filter.world; ecs_entity_t subject = match->sources[term]; ecs_table_t *table; int32_t column = -1; if (!subject) { table = match->node.table; column = match->storage_columns[term]; } else { table = ecs_get_table(world, subject); int32_t ref_index = -match->columns[term] - 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; column = ecs_table_type_to_storage_index(table, column); } } out->table = table; out->column = column; out->dirty_state = flecs_table_get_dirty_state(world, table); } /* 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, t = -1, term_count = f->term_count; table_dirty_state_t cur_dirty_state; for (i = 0; i < term_count; i ++) { if (t == f->terms[i].field_index) { if (monitor[t + 1] != -1) { continue; } } t = f->terms[i].field_index; monitor[t + 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 */ } /* If term is not matched on this, don't track */ if (!ecs_term_match_this(&f->terms[i])) { continue; } int32_t column = match->columns[t]; if (column == 0) { continue; /* Don't track terms that aren't matched */ } flecs_query_get_dirty_state(query, match, t, &cur_dirty_state); if (cur_dirty_state.column == -1) { continue; /* Don't track terms that aren't stored */ } monitor[t + 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->node.table; int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); table_dirty_state_t cur; monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ int32_t i, term_count = query->filter.term_count; for (i = 0; i < term_count; i ++) { int32_t t = query->filter.terms[i].field_index; if (monitor[t + 1] == -1) { continue; } flecs_query_get_dirty_state(query, match, t, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); monitor[t + 1] = cur.dirty_state[cur.column + 1]; } } /* 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; ecs_table_t *table = match->node.table; int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); table_dirty_state_t cur; int32_t state = monitor[term]; if (state == -1) { return false; } if (!term) { return monitor[0] != dirty_state[0]; } flecs_query_get_dirty_state(query, match, term - 1, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); return monitor[term] != cur.dirty_state[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) { 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->node.table; int32_t *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; } ecs_world_t *world = query->filter.world; int32_t i, field_count = query->filter.field_count; int32_t *storage_columns = match->storage_columns; int32_t *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 (column >= 0) { /* owned component */ 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); 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->column; column = ecs_table_type_to_storage_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; } } } 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_node_t *cur, *end = table->last->node.next; for (cur = &table->first->node; 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)) { 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_node_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->node.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); qm->sizes = flecs_balloc(&query->allocators.sizes); /* Insert match to iteration list if table is not empty */ if (!table || ecs_table_count(table) != 0) { flecs_query_insert_table_node(query, &qm->node); } 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); ecs_os_memcpy_n(qm->sizes, it->sizes, ecs_size_t, field_count); if (table) { /* Initialize storage columns for faster access to component storage */ for (i = 0; i < field_count; i ++) { int32_t column = qm->columns[i]; if (column > 0) { qm->storage_columns[i] = ecs_table_type_to_storage_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); } /* Add references for substituted terms */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (!ecs_term_match_this(term)) { /* non-This terms are set during iteration */ continue; } int32_t field = terms[i].field_index; ecs_entity_t src = it->sources[field]; ecs_size_t size = 0; if (it->sizes) { size = it->sizes[field]; } if (src) { ecs_id_t id = it->ids[field]; ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); if (id) { flecs_query_add_ref(world, query, qm, id, src, size); /* Use column index to bind term and ref */ if (qm->columns[field] != 0) { qm->columns[field] = -ecs_vec_count(&qm->refs); } } } } } static ecs_query_table_t* flecs_query_table_insert( ecs_world_t *world, ecs_query_t *query, ecs_table_t *table) { ecs_query_table_t *qt = flecs_bcalloc(&world->allocators.query_table); if (table) { qt->table_id = table->id; } else { qt->table_id = 0; } ecs_table_cache_insert(&query->cache, table, &qt->hdr); return qt; } /** Populate query cache with tables */ static void flecs_query_match_tables( ecs_world_t *world, ecs_query_t *query) { ecs_table_t *table = NULL; ecs_query_table_t *qt = NULL; ecs_iter_t it = ecs_filter_iter(world, &query->filter); ECS_BIT_SET(it.flags, EcsIterIsInstanced); ECS_BIT_SET(it.flags, EcsIterIsFilter); 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|EcsIterIsFilter|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_type_info_t *ti = table->type_info[column_index]; ecs_vec_t *column = &data->columns[column_index]; size = ti->size; ptr = ecs_vec_first(column); } 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_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; } int to_sort = 0; sort_helper_t *helper = ecs_os_malloc_n(sort_helper_t, table_count); ecs_query_table_node_t *cur, *end = list->last->next; for (cur = list->first; cur != end; cur = cur->next) { ecs_query_table_match_t *match = cur->match; ecs_table_t *table = match->node.table; ecs_data_t *data = &table->data; ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); int32_t index = -1; if (id) { index = ecs_search(world, table->storage_table, id, 0); } if (index != -1) { ecs_type_info_t *ti = table->type_info[index]; ecs_vec_t *column = &data->columns[index]; int32_t size = ti->size; helper[to_sort].ptr = ecs_vec_first(column); helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else if (id) { /* Find component in prefab */ ecs_entity_t base = 0; ecs_search_relation(world, table, 0, id, EcsIsA, EcsUp, &base, 0, 0); /* If a base was not found, the query should not have allowed using * the component for sorting */ ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); const EcsComponent *cptr = ecs_get(world, id, EcsComponent); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); helper[to_sort].ptr = ecs_get_id(world, base, id); helper[to_sort].elem_size = cptr->size; helper[to_sort].shared = true; } else { helper[to_sort].ptr = NULL; helper[to_sort].elem_size = 0; helper[to_sort].shared = false; } helper[to_sort].match = match; 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->match != cur_helper->match) { cur = ecs_vector_add(&query->table_slices, ecs_query_table_node_t); ecs_assert(cur != NULL, ECS_INTERNAL_ERROR, NULL); cur->match = 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_vector_count(query->table_slices); ecs_query_table_node_t *nodes = ecs_vector_first( query->table_slices, ecs_query_table_node_t); 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; ecs_os_free(helper); } static void flecs_query_build_sorted_tables( ecs_query_t *query) { ecs_vector_clear(query->table_slices); if (query->group_by) { /* Populate sorted node list in grouping order */ ecs_query_table_node_t *cur = query->list.first; if (cur) { do { /* Find list for current group */ ecs_query_table_match_t *match = cur->match; ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); uint64_t group_id = match->node.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 i, order_by_term = -1; /* Find term that iterates over component (must be at least one) */ if (order_by_component) { const ecs_filter_t *f = &query->filter; int32_t term_count = f->term_count; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &f->terms[i]; if (!ecs_term_match_this(term)) { continue; } if (term->id == order_by_component) { order_by_term = i; break; } } ecs_assert(order_by_term != -1, ECS_INTERNAL_ERROR, NULL); } /* Iterate over non-empty tables. Don't bother with empty tables as they * have nothing to sort */ bool tables_sorted = false; ecs_table_cache_iter_t it; ecs_query_table_t *qt; flecs_table_cache_iter(&query->cache, &it); while ((qt = flecs_table_cache_next(&it, ecs_query_table_t))) { ecs_table_t *table = qt->hdr.table; bool dirty = false; if (flecs_query_check_table_monitor(query, qt, 0)) { dirty = true; } int32_t column = -1; if (order_by_component) { if (flecs_query_check_table_monitor(query, qt, order_by_term + 1)) { dirty = true; } if (dirty) { column = -1; ecs_table_t *storage_table = table->storage_table; if (storage_table) { column = ecs_search(world, storage_table, order_by_component, 0); } 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) { query->flags |= EcsQueryHasOutColumns; } 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; } if ((src->flags & EcsTraverseFlags) == EcsSelf) { if (src->flags & EcsIsEntity) { flecs_add_flag(world, term->src.id, EcsEntityObserved); } } } 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->node); } else { ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); flecs_query_insert_table_node(query, &cur->node); } } } 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_query_t **elem = ecs_vector_add(&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) { ecs_query_t **queries = ecs_vector_first(query->subqueries, ecs_query_t*); int32_t i, count = ecs_vector_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); flecs_bfree(&query->allocators.sizes, cur->sizes); if (cur->monitor) { flecs_bfree(&query->allocators.monitors, cur->monitor); } if (!elem->hdr.empty) { flecs_query_remove_table_node(query, &cur->node); } ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); flecs_entity_filter_fini(world, &cur->entity_filter); next = cur->next_match; flecs_bfree(&world->allocators.query_table_match, cur); } } static void flecs_query_table_free( ecs_query_t *query, ecs_query_table_t *elem) { flecs_query_table_match_free(query, elem, elem->first); flecs_bfree(&query->filter.world->allocators.query_table, elem); } static void flecs_query_unmatch_table( ecs_query_t *query, ecs_table_t *table, ecs_query_table_t *elem) { if (!elem) { elem = ecs_table_cache_get(&query->cache, table); } if (elem) { ecs_table_cache_remove(&query->cache, elem->table_id, &elem->hdr); flecs_query_table_free(query, elem); } } /* Rematch system with tables after a change happened to a watched entity */ static void flecs_query_rematch_tables( ecs_world_t *world, ecs_query_t *query, ecs_query_t *parent_query) { ecs_iter_t it, parent_it; ecs_table_t *table = NULL; ecs_query_table_t *qt = NULL; ecs_query_table_match_t *qm = NULL; if (query->monitor_generation == world->monitor_generation) { return; } query->monitor_generation = world->monitor_generation; if (parent_query) { parent_it = ecs_query_iter(world, parent_query); it = ecs_filter_chain_iter(&parent_it, &query->filter); } else { it = ecs_filter_iter(world, &query->filter); } ECS_BIT_SET(it.flags, EcsIterIsInstanced); ECS_BIT_SET(it.flags, EcsIterIsFilter); 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->node.group_id) { /* Update table group */ flecs_query_remove_table_node(query, &qm->node); flecs_query_insert_table_node(query, &qm->node); } } } 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 != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vector_count(parent->subqueries); ecs_query_t **sq = ecs_vector_first(parent->subqueries, ecs_query_t*); for (i = 0; i < count; i ++) { if (sq[i] == sub) { break; } } ecs_vector_remove(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) { 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); query->order_by_component = order_by_component; query->order_by = order_by; query->sort_table = action; ecs_vector_free(query->table_slices); query->table_slices = NULL; flecs_query_sort_tables(world, query); if (!query->table_slices) { flecs_query_build_sorted_tables(query); } 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_query_t*)poly); iter[0] = ecs_term_chain_iter(&iter[1], filter); } else { iter[0] = ecs_query_iter(world, (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) { 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) { 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.sizes, field_count * ECS_SIZEOF(ecs_size_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.sizes); 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_vector_free(query->subqueries); ecs_vector_free(query->table_slices); ecs_filter_fini(&query->filter); flecs_query_allocators_fini(query); 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; } 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; 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); 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( ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); return &query->filter; } ecs_iter_t ecs_query_iter( const ecs_world_t *stage, ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); ecs_world_t *world = query->filter.world; ecs_poly_assert(world, ecs_world_t); /* Process table events to ensure that the list of iterated tables doesn't * contain empty tables. */ flecs_process_pending_tables(world); /* If query has order_by, apply sort */ flecs_query_sort_tables(world, query); /* If monitors changed, do query rematching */ if (!(world->flags & EcsWorldReadonly) && query->flags & EcsQueryHasRefs) { flecs_eval_component_monitors(world); } query->prev_match_count = query->match_count; /* Prepare iterator */ int32_t table_count; if (query->table_slices) { table_count = ecs_vector_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_vector_first(query->table_slices, ecs_query_table_node_t); } ecs_flags32_t flags = 0; ECS_BIT_COND(flags, EcsIterIsFilter, ECS_BIT_IS_SET(query->filter.flags, EcsFilterNoData)); ECS_BIT_COND(flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->filter.flags, EcsFilterIsInstanced)); ecs_iter_t result = { .real_world = world, .world = (ecs_world_t*)stage, .terms = query->filter.terms, .field_count = query->filter.field_count, .table_count = table_count, .flags = flags, .priv.iter.query = it, .next = ecs_query_next, }; ecs_filter_t *filter = &query->filter; if (filter->flags & EcsFilterMatchOnlyThis) { /* When the query only matches This terms, we can reuse the storage from * the cache to populate the iterator */ flecs_iter_init(stage, &result, flecs_iter_cache_ptrs); } else { /* Check if non-This terms (like singleton terms) still match */ ecs_iter_t fit = flecs_filter_iter_w_flags( (ecs_world_t*)stage, &query->filter, EcsIterIgnoreThis); if (!ecs_filter_next(&fit)) { /* No match, so return nothing */ ecs_iter_fini(&fit); goto noresults; } /* Initialize iterator with private storage for ids, ptrs, sizes and * columns so we have a place to store the non-This data */ flecs_iter_init(stage, &result, flecs_iter_cache_ptrs | flecs_iter_cache_ids | flecs_iter_cache_columns | flecs_iter_cache_sizes); /* Copy the data */ int32_t term_count = filter->field_count; if (term_count) { if (result.ptrs) { ecs_os_memcpy_n(result.ptrs, fit.ptrs, void*, term_count); } ecs_os_memcpy_n(result.ids, fit.ids, ecs_id_t, term_count); ecs_os_memcpy_n(result.sizes, fit.sizes, ecs_size_t, term_count); ecs_os_memcpy_n(result.columns, fit.columns, int32_t, term_count); } ecs_iter_fini(&fit); } 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_node_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( 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( 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->node.table; if (!table) { return; } int32_t *dirty_state = table->dirty_state; if (dirty_state) { int32_t *storage_columns = qm->storage_columns; ecs_filter_t *filter = &query->filter; 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->inout == EcsIn || term->inout == EcsInOutNone) { /* Don't mark readonly terms dirty */ continue; } int32_t field = term->field_index; int32_t column = storage_columns[field]; if (column < 0) { continue; } dirty_state[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_node_t *node = iter->node; ecs_query_t *query = iter->query; if ((query->flags & EcsQueryHasOutColumns)) { ecs_query_table_node_t *prev = iter->prev; if (prev && it->count) { flecs_query_mark_columns_dirty(query, prev->match); } } 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: ecs_iter_fini(it); return false; } void ecs_query_populate( ecs_iter_t *it) { 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_table_node_t *node = iter->prev; ecs_assert(node != NULL, ECS_INVALID_OPERATION, NULL); ecs_table_t *table = node->table; ecs_query_table_match_t *match = node->match; ecs_query_t *query = iter->query; ecs_world_t *world = query->filter.world; const ecs_filter_t *filter = &query->filter; bool only_this = filter->flags & EcsFilterMatchOnlyThis; /* Match has been iterated, update monitor for change tracking */ if (query->flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, match); } if (only_this) { /* If query has only This terms, reuse cache storage */ it->ids = match->ids; it->columns = match->columns; it->sizes = match->sizes; } else { /* If query has non-This terms make sure not to overwrite them */ int32_t t, term_count = filter->term_count; for (t = 0; t < term_count; t ++) { ecs_term_t *term = &filter->terms[t]; if (!ecs_term_match_this(term)) { continue; } int32_t field = term->field_index; it->ids[field] = match->ids[field]; it->columns[field] = match->columns[field]; it->sizes[field] = match->sizes[field]; } } it->sources = match->sources; it->references = ecs_vec_first(&match->refs); it->instance_count = 0; flecs_iter_populate_data(world, it, table, 0, ecs_table_count(table), it->ptrs, NULL); error: return; } 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_world_t *world = query->filter.world; ecs_flags32_t flags = query->flags; const ecs_filter_t *filter = &query->filter; bool only_this = filter->flags & EcsFilterMatchOnlyThis; ecs_poly_assert(world, ecs_world_t); ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; ecs_query_table_node_t *node, *next, *prev, *last; if ((prev = iter->prev)) { /* Match has been iterated, update monitor for change tracking */ if (flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor(query, prev->match); } if (flags & EcsQueryHasOutColumns) { flecs_query_mark_columns_dirty(query, prev->match); } } flecs_iter_validate(it); iter->skip_count = 0; last = iter->last; for (node = iter->node; node != last; node = next) { ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_range_t *range = &ent_it->range; ecs_query_table_match_t *match = node->match; ecs_table_t *table = match->node.table; next = node->next; if (table) { range->offset = node->offset; range->count = node->count; if (!range->count) { range->count = ecs_table_count(table); ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL); } if (match->entity_filter.has_filter) { ent_it->entity_filter = &match->entity_filter; ent_it->columns = match->columns; ent_it->range.table = table; switch(flecs_entity_filter_next(ent_it)) { case 1: continue; case -1: next = node; default: break; } } it->group_id = match->node.group_id; } else { range->offset = 0; range->count = 0; } if (only_this) { /* If query has only This terms, reuse cache storage */ it->ids = match->ids; it->columns = match->columns; it->sizes = match->sizes; } else { /* If query has non-This terms make sure not to overwrite them */ int32_t t, term_count = filter->term_count; for (t = 0; t < term_count; t ++) { ecs_term_t *term = &filter->terms[t]; if (!ecs_term_match_this(term)) { continue; } int32_t field = term->field_index; it->ids[field] = match->ids[field]; it->columns[field] = match->columns[field]; it->sizes[field] = match->sizes[field]; } } it->sources = match->sources; it->references = ecs_vec_first(&match->refs); it->instance_count = 0; flecs_iter_populate_data(world, it, table, range->offset, range->count, it->ptrs, NULL); iter->node = next; iter->prev = node; goto yield; } error: ecs_iter_fini(it); return false; yield: 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); } 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( 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; } /** * @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) { ecs_map_init_w_params_if(&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 < ECS_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 < ECS_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 < ECS_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(ecs_map_is_init(&edges->hi), 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; ecs_os_zeromem(&edges->hi); } 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->type_info = NULL; table->flags = 0; table->dirty_state = NULL; table->lock = 0; table->refcount = 1; 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->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; flecs_init_table(world, result, prev); flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &result->type, .event = EcsOnTableCreate, .table = result, .flags = EcsEventTableOnly, .observable = world }); /* Update counters */ world->info.table_count ++; world->info.table_record_count += result->record_count; world->info.table_storage_count += result->storage_count; world->info.empty_table_count ++; world->info.table_create_total ++; if (!result->storage_count) { world->info.tag_table_count ++; } else { world->info.trivial_table_count += !(result->flags & EcsTableIsComplex); } 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]; if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { flecs_type_add(world, dst_type, id & ~ECS_OVERRIDE); } } } if (flags & EcsTableHasIsA) { const 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->column, 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; } const ecs_table_record_t *tr = flecs_id_record_get_table( idr_with_wildcard, table); if (tr) { int32_t i = tr->column, 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_ensure_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 */ const 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->column] = 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); } 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}; 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); table_node->add.lo = NULL; table_node->remove.lo = 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); } /** * @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_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_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, sizes, ecs_size_t, it->field_count); if (!ECS_BIT_IS_SET(it->flags, EcsIterIsFilter)) { INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count); } else { it->ptrs = NULL; } } void flecs_iter_validate( ecs_iter_t *it) { ECS_BIT_SET(it->flags, EcsIterIsValid); } 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, sizes, ecs_size_t, it->field_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 ecs_size_t flecs_iter_get_size_for_id( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return 0; } if (idr->flags & EcsIdUnion) { return ECS_SIZEOF(ecs_entity_t); } if (idr->type_info) { return idr->type_info->size; } return 0; } static bool flecs_iter_populate_term_data( ecs_world_t *world, ecs_iter_t *it, int32_t t, int32_t column, void **ptr_out, ecs_size_t *size_out) { bool is_shared = false; ecs_table_t *table; void *data; ecs_size_t size = 0; 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) { if (size_out) { size = flecs_iter_get_size_for_id(world, it->ids[t]); } goto no_data; } if (column < 0) { is_shared = true; /* Data is not from This */ if (it->references) { /* 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; } /* If cached references were provided, the code that populated * the iterator also had a chance to cache sizes, so size array * should already have been assigned. This saves us from having * to do additional lookups to find the component size. */ ecs_assert(size_out == NULL, ECS_INTERNAL_ERROR, NULL); 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_t *s_table = table->storage_table; ecs_table_record_t *tr; if (!s_table || !(tr = flecs_table_record_get(world, s_table, id))){ 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_type_info_t *ti = table->type_info[column]; ecs_vec_t *s = &table->data.columns[column]; size = ti->size; 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_storage_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; } ecs_type_info_t *ti = table->type_info[storage_column]; size = ti->size; if (!it->count) { goto no_data; } ecs_vec_t *s = &table->data.columns[storage_column]; data = ecs_vec_first(s); /* Fallthrough to has_data */ } has_data: if (ptr_out) ptr_out[0] = ECS_ELEM(data, size, row); if (size_out) size_out[0] = size; 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_switch_t *sw = &table->data.sw_columns[u_index]; data = ecs_vec_first(flecs_switch_values(sw)); size = ECS_SIZEOF(ecs_entity_t); goto has_data; } no_data: if (ptr_out) ptr_out[0] = NULL; if (size_out) size_out[0] = size; 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_size_t *sizes) { 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), 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, EcsIterIsFilter)) { ECS_BIT_CLEAR(it->flags, EcsIterHasShared); if (!sizes) { return; } /* Fetch sizes, skip fetching data */ for (t = 0; t < field_count; t ++) { sizes[t] = flecs_iter_get_size_for_id(world, it->ids[t]); } return; } bool has_shared = false; if (ptrs && sizes) { 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], &sizes[t]); } } else if (ptrs || sizes) { for (t = 0; t < field_count; t ++) { ecs_assert(it->columns != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = it->columns[t]; void **ptr = NULL; if (ptrs) { ptr = &ptrs[t]; } ecs_size_t *size = NULL; if (sizes) { size = &sizes[t]; } has_shared |= flecs_iter_populate_term_data(world, it, t, column, ptr, size); } } ECS_BIT_COND(it->flags, EcsIterHasShared, has_shared); } bool flecs_iter_next_row( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); if (!is_instanced) { int32_t instance_count = it->instance_count; int32_t count = it->count; int32_t offset = it->offset; if (instance_count > count && offset < (instance_count - 1)) { ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); int t, field_count = it->field_count; for (t = 0; t < field_count; t ++) { int32_t column = it->columns[t]; if (column >= 0) { void *ptr = it->ptrs[t]; if (ptr) { it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); } } } if (it->entities) { it->entities ++; } it->offset ++; return true; } } return false; } bool flecs_iter_next_instanced( ecs_iter_t *it, bool result) { it->instance_count = it->count; bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); bool has_shared = ECS_BIT_IS_SET(it->flags, EcsIterHasShared); if (result && !is_instanced && it->count && has_shared) { it->count = 1; } return result; } /* --- Public API --- */ void* ecs_field_w_size( const ecs_iter_t *it, size_t size, int32_t term) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_check(!size || ecs_field_size(it, term) == size || (!ecs_field_size(it, term) && (!it->ptrs || !it->ptrs[term - 1])), ECS_INVALID_PARAMETER, NULL); (void)size; if (!term) { return it->entities; } if (!it->ptrs) { return NULL; } return it->ptrs[term - 1]; error: return NULL; } bool ecs_field_is_readonly( const ecs_iter_t *it, int32_t term_index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); ecs_term_t *term = &it->terms[term_index - 1]; ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); if (term->inout == EcsIn) { return true; } else { ecs_term_id_t *src = &term->src; if (term->inout == EcsInOutDefault) { if (!(ecs_term_match_this(term))) { return true; } if (!(src->flags & EcsSelf)) { return true; } } } error: return false; } bool ecs_field_is_writeonly( const ecs_iter_t *it, int32_t term_index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); ecs_check(term_index > 0, ECS_INVALID_PARAMETER, NULL); ecs_term_t *term = &it->terms[term_index - 1]; ecs_check(term != NULL, ECS_INVALID_PARAMETER, NULL); if (term->inout == EcsOut) { return true; } 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); 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(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, 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(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); return it->ids[index - 1]; } ecs_entity_t ecs_field_src( const ecs_iter_t *it, int32_t index) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, 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(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); if (index == 0) { return sizeof(ecs_entity_t); } else { return (size_t)it->sizes[index - 1]; } } char* ecs_iter_str( const ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_strbuf_t buf = ECS_STRBUF_INIT; int i; if (it->field_count) { ecs_strbuf_list_push(&buf, "term: ", ","); 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, "subj: ", ","); 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"); } if (it->variable_count) { int32_t actual_count = 0; for (i = 0; i < it->variable_count; i ++) { const char *var_name = it->variable_names[i]; if (!var_name || var_name[0] == '_' || var_name[0] == '.') { /* 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, "vars: ", ","); } 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, EcsIterIsFilter); 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, EcsIterIsFilter); 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, EcsIterIsFilter); ECS_BIT_SET(it->flags, EcsIterIsInstanced); bool result = ecs_iter_next(it); if (result) { ecs_iter_fini(it); } return result; error: return false; } ecs_entity_t ecs_iter_get_var( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &it->variables[var_id]; ecs_entity_t e = var->entity; if (!e) { ecs_table_t *table = var->range.table; if (table) { if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { ecs_assert(ecs_table_count(table) > var->range.offset, ECS_INTERNAL_ERROR, NULL); e = ecs_vec_get_t(&table->data.entities, ecs_entity_t, var->range.offset)[0]; } } } else { ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); } return e; error: return 0; } ecs_table_t* ecs_iter_get_var_as_table( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &it->variables[var_id]; ecs_table_t *table = var->range.table; if (!table) { /* If table is not set, try to get table from entity */ ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { table = r->table; if (ecs_table_count(table) != 1) { /* If table contains more than the entity, make sure not to * return a partial table. */ return NULL; } } } } if (table) { if (var->range.offset) { /* Don't return whole table if only partial table is matched */ return NULL; } if (!var->range.count || ecs_table_count(table) == var->range.count) { /* Return table if count matches */ return table; } } error: return NULL; } ecs_table_range_t ecs_iter_get_var_as_range( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, NULL); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_range_t result = { 0 }; ecs_var_t *var = &it->variables[var_id]; ecs_table_t *table = var->range.table; if (!table) { ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { result.table = r->table; result.offset = ECS_RECORD_TO_ROW(r->row); result.count = 1; } } } else { result.table = table; result.offset = var->range.offset; result.count = var->range.count; if (!result.count) { result.count = ecs_table_count(table); } } return result; error: return (ecs_table_range_t){0}; } void ecs_iter_set_var( ecs_iter_t *it, int32_t var_id, ecs_entity_t entity) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < ECS_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); 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_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 < ECS_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; } 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.iter.page = (ecs_page_iter_t){ .offset = offset, .limit = limit, .remaining = limit }; result.next = ecs_page_next; result.chain_it = (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.iter.worker = (ecs_worker_iter_t){ .index = index, .count = count }; result.next = ecs_worker_next; result.chain_it = (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 { return false; } } } while (!per_worker); it->instance_count = instances_per_worker; it->frame_offset += first; flecs_offset_iter(it, it->offset + first); it->count = per_worker; if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { it->offset += first; } else { it->offset = 0; } return true; error: return false; } bool ecs_worker_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); error: return false; } /** * @file misc.c * @brief Miscallaneous functions. */ #include #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_entity_compare_qsort( const void *e1, const void *e2) { ecs_entity_t v1 = *(ecs_entity_t*)e1; ecs_entity_t v2 = *(ecs_entity_t*)e2; return flecs_entity_compare(v1, NULL, v2, NULL); } 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; } /* This code was taken from sokol_time.h zlib/libpng license Copyright (c) 2018 Andre Weissflog This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ /** * @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( 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"); 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; } 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 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. */ /* -- 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 & EcsWorldFini) { return; } ecs_vector_t *marked_ids = world->store.marked_ids; int32_t i, count = ecs_vector_count(marked_ids); for (i = 0; i < count; i ++) { ecs_marked_id_t *mid = ecs_vector_get(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; } } if (ecs_id_in_use(world, ecs_pair(rel, EcsWildcard))) { char *r_str = ecs_get_fullpath(world, rel); char *p_str = ecs_get_fullpath(world, property); ecs_throw(ECS_ID_IN_USE, "cannot change property '%s' for relationship '%s': already in use", p_str, r_str); ecs_os_free(r_str); ecs_os_free(p_str); } error: return; } static bool flecs_set_id_flag( ecs_id_record_t *idr, ecs_flags32_t flag) { if (!(idr->flags & flag)) { idr->flags |= flag; return true; } return false; } static bool flecs_unset_id_flag( ecs_id_record_t *idr, ecs_flags32_t flag) { if ((idr->flags & flag)) { idr->flags &= ~flag; return true; } return false; } static void flecs_register_id_flag_for_relation( ecs_iter_t *it, ecs_entity_t prop, ecs_flags32_t flag, ecs_flags32_t not_flag, ecs_flags32_t entity_flag) { ecs_world_t *world = it->world; ecs_entity_t event = it->event; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; bool changed = false; if (event == EcsOnAdd) { ecs_id_record_t *idr = flecs_id_record_ensure(world, e); changed |= flecs_set_id_flag(idr, flag); idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); do { changed |= flecs_set_id_flag(idr, flag); } while ((idr = idr->first.next)); if (entity_flag) flecs_add_flag(world, e, entity_flag); } else if (event == EcsOnRemove) { ecs_id_record_t *idr = flecs_id_record_get(world, e); if (idr) changed |= flecs_unset_id_flag(idr, not_flag); idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)); if (idr) { do { changed |= flecs_unset_id_flag(idr, not_flag); } while ((idr = idr->first.next)); } } if (changed) { flecs_assert_relation_unused(world, e, prop); } } } static void flecs_register_final(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (flecs_id_record_get(world, ecs_pair(EcsIsA, e)) != NULL) { char *e_str = ecs_get_fullpath(world, e); ecs_throw(ECS_ID_IN_USE, "cannot change property 'Final' for '%s': already inherited from", e_str); ecs_os_free(e_str); error: continue; } } } static void flecs_register_on_delete(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 1); flecs_register_id_flag_for_relation(it, EcsOnDelete, ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteMask, EcsEntityObservedId); } 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_OBJECT_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteObjectMask, EcsEntityObservedId); } static void flecs_register_acyclic(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsAcyclic, EcsIdAcyclic, EcsIdAcyclic, 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_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_entity_t rel = ECS_PAIR_FIRST(pair); ecs_entity_t obj = ECS_PAIR_SECOND(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_init(world, &(ecs_observer_desc_t){ .entity = ecs_entity(world, {.add = {ecs_childof(r)}}), .filter.terms[0] = { .id = ecs_pair(r, EcsWildcard) }, .callback = flecs_on_symmetric_add_remove, .events = {EcsOnAdd, EcsOnRemove} }); } } static void flecs_on_component(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsComponent *c = ecs_field(it, EcsComponent, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; uint32_t component_id = (uint32_t)e; /* Strip generation */ ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, "component id must be smaller than %u", ECS_MAX_COMPONENT_ID); (void)component_id; if (it->event == EcsOnSet) { if (flecs_type_info_init_id( world, e, c[i].size, c[i].alignment, NULL)) { flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); } } else if (it->event == EcsOnRemove) { flecs_type_info_free(world, e); } } } static void flecs_ensure_module_tag(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } } /* -- Triggers for keeping hashed ids in sync -- */ static void flecs_on_parent_change(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_table_t *other_table = it->other_table, *table = it->table; EcsIdentifier *names = ecs_table_get_pair(it->real_world, table, EcsIdentifier, EcsName, it->offset); bool has_name = names != NULL; if (!has_name) { /* If tables don't have names, index does not need to be updated */ return; } ecs_id_t to_pair = it->event_id; ecs_id_t from_pair = ecs_childof(0); /* Find the other ChildOf relationship */ ecs_search(it->real_world, other_table, ecs_pair(EcsChildOf, EcsWildcard), &from_pair); bool other_has_name = ecs_search(it->real_world, other_table, ecs_pair(ecs_id(EcsIdentifier), EcsName), 0) != -1; bool to_has_name = has_name, from_has_name = other_has_name; if (it->event == EcsOnRemove) { if (from_pair != ecs_childof(0)) { /* Because ChildOf is an exclusive relationship, events always come * in OnAdd/OnRemove pairs (add for the new, remove for the old * parent). We only need one of those events, so filter out the * OnRemove events except for the case where a parent is removed and * not replaced with another parent. */ return; } ecs_id_t temp = from_pair; from_pair = to_pair; to_pair = temp; to_has_name = other_has_name; from_has_name = has_name; } ecs_hashmap_t *from_index = 0; if (from_has_name) { from_index = flecs_id_name_index_get(world, from_pair); } ecs_hashmap_t *to_index = NULL; if (to_has_name) { to_index = flecs_id_name_index_ensure(world, to_pair); } int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; EcsIdentifier *name = &names[i]; uint64_t index_hash = name->index_hash; if (from_index && index_hash) { flecs_name_index_remove(from_index, e, index_hash); } const char *name_str = name->value; if (to_index && name_str) { ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); flecs_name_index_ensure( to_index, e, name_str, name->length, name->hash); name->index = to_index; } } } /* -- Iterable mixins -- */ static void flecs_on_event_iterable_init( const ecs_world_t *world, const ecs_poly_t *poly, /* Observable */ ecs_iter_t *it, ecs_term_t *filter) { ecs_iter_poly(world, poly, it, filter); it->event_id = filter->id; } /* -- Bootstrapping -- */ #define flecs_bootstrap_builtin_t(world, table, name)\ flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ ECS_ALIGNOF(name)) static void flecs_bootstrap_builtin( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, const char *symbol, ecs_size_t size, ecs_size_t alignment) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *columns = table->data.columns; ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_ensure(world, entity); record->table = table; int32_t index = flecs_table_append(world, table, entity, record, false, false); record->row = ECS_ROW_TO_RECORD(index, 0); EcsComponent *component = ecs_vec_first(&columns[0]); 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]); name_col[index].value = ecs_os_strdup(name); name_col[index].length = name_length; name_col[index].hash = flecs_hash(name, name_length); name_col[index].index_hash = 0; name_col[index].index = NULL; EcsIdentifier *symbol_col = ecs_vec_first(&columns[2]); 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 | EcsIdAcyclic | EcsIdTag; idr = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdDontInherit | EcsIdAcyclic | EcsIdTag | EcsIdExclusive; idr = flecs_id_record_ensure( world, ecs_pair(ecs_id(EcsIdentifier), EcsWildcard)); idr->flags |= EcsIdDontInherit; world->idr_childof_0 = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, 0)); ecs_id_t ids[] = { ecs_id(EcsComponent), EcsFinal, ecs_pair(ecs_id(EcsIdentifier), EcsName), ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), ecs_pair(EcsChildOf, EcsFlecsCore), ecs_pair(EcsOnDelete, EcsPanic) }; ecs_type_t array = { .array = ids, .count = 6 }; ecs_table_t *result = flecs_table_find_or_create(world, &array); ecs_data_t *data = &result->data; /* Preallocate enough memory for initial components */ ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->records, ecs_record_t*, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[0], EcsComponent, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[1], EcsIdentifier, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[2], EcsIdentifier, EcsFirstUserComponentId); return result; } static void flecs_bootstrap_entity( ecs_world_t *world, ecs_entity_t id, const char *name, ecs_entity_t parent) { char symbol[256]; ecs_os_strcpy(symbol, "flecs.core."); ecs_os_strcat(symbol, name); ecs_ensure(world, id); ecs_add_pair(world, id, EcsChildOf, parent); ecs_set_name(world, id, name); ecs_set_symbol(world, id, symbol); ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); if (!parent || parent == EcsFlecsCore) { ecs_assert(ecs_lookup_fullpath(world, name) == id, ECS_INTERNAL_ERROR, NULL); } } void flecs_bootstrap( ecs_world_t *world) { ecs_log_push(); ecs_set_name_prefix(world, "Ecs"); /* Ensure builtin ids are alive */ ecs_ensure(world, ecs_id(EcsComponent)); ecs_ensure(world, EcsFinal); ecs_ensure(world, ecs_id(EcsIdentifier)); ecs_ensure(world, EcsName); ecs_ensure(world, EcsSymbol); ecs_ensure(world, EcsAlias); ecs_ensure(world, EcsChildOf); ecs_ensure(world, EcsFlecs); ecs_ensure(world, EcsFlecsCore); ecs_ensure(world, EcsOnAdd); ecs_ensure(world, EcsOnRemove); ecs_ensure(world, EcsOnSet); ecs_ensure(world, EcsUnSet); ecs_ensure(world, EcsOnDelete); ecs_ensure(world, EcsPanic); ecs_ensure(world, EcsFlag); ecs_ensure(world, EcsIsA); ecs_ensure(world, EcsWildcard); ecs_ensure(world, EcsAny); ecs_ensure(world, EcsTag); /* Bootstrap 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 }); /* Cache often used id records */ flecs_init_id_records(world); /* Create table for initial components */ ecs_table_t *table = flecs_bootstrap_component_table(world); assert(table != NULL); 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); world->info.last_component_id = EcsFirstUserComponentId; world->info.last_id = 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 }); ecs_observer_init(world, &(ecs_observer_desc_t){ .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 & EcsEntityObservedAcyclic, ECS_INTERNAL_ERROR, NULL); (void)r; /* Initialize builtin entities */ flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); flecs_bootstrap_entity(world, EcsThis, "This", EcsFlecsCore); flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); /* Component/relationship properties */ flecs_bootstrap_tag(world, EcsTransitive); flecs_bootstrap_tag(world, EcsReflexive); flecs_bootstrap_tag(world, EcsSymmetric); flecs_bootstrap_tag(world, EcsFinal); flecs_bootstrap_tag(world, EcsDontInherit); flecs_bootstrap_tag(world, EcsTag); flecs_bootstrap_tag(world, EcsUnion); flecs_bootstrap_tag(world, EcsExclusive); flecs_bootstrap_tag(world, EcsAcyclic); flecs_bootstrap_tag(world, EcsWith); flecs_bootstrap_tag(world, EcsOneOf); flecs_bootstrap_tag(world, EcsOnDelete); flecs_bootstrap_tag(world, EcsOnDeleteTarget); flecs_bootstrap_tag(world, EcsRemove); flecs_bootstrap_tag(world, EcsDelete); flecs_bootstrap_tag(world, EcsPanic); flecs_bootstrap_tag(world, EcsDefaultChildComponent); /* 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, EcsDefaultChildComponent, EcsTag); ecs_add_id(world, EcsUnion, EcsTag); ecs_add_id(world, EcsFlag, EcsTag); /* Exclusive properties */ ecs_add_id(world, EcsChildOf, EcsExclusive); ecs_add_id(world, EcsOnDelete, EcsExclusive); ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); ecs_add_id(world, EcsDefaultChildComponent, EcsExclusive); /* Sync properties of ChildOf and Identifier with bootstrapped flags */ ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); ecs_add_id(world, EcsChildOf, EcsAcyclic); ecs_add_id(world, EcsChildOf, 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 }; ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_pair(EcsChildOf, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = { EcsOnAdd, EcsOnRemove }, .yield_existing = true, .callback = flecs_on_parent_change }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = EcsFinal, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_final }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete_object }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = EcsAcyclic, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_acyclic }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = EcsExclusive, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_exclusive }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = EcsSymmetric, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_symmetric }); ecs_observer_init(world, &(ecs_observer_desc_t){ .entity = ecs_entity(world, { .name = "RegisterDontInherit" }), .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_dont_inherit }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_pair(EcsWith, EcsWildcard), .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_with }); ecs_observer_init(world, &(ecs_observer_desc_t){ .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_init(world, &(ecs_observer_desc_t){ .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_init(world, &(ecs_observer_desc_t){ .filter.terms = {{ .id = EcsModule, .src.flags = EcsSelf }, match_prefab}, .events = {EcsOnAdd}, .callback = flecs_ensure_module_tag }); /* Set scope back to flecs core */ ecs_set_scope(world, EcsFlecsCore); /* Acyclic components */ ecs_add_id(world, EcsIsA, EcsAcyclic); ecs_add_id(world, EcsDependsOn, EcsAcyclic); ecs_add_id(world, EcsWith, EcsAcyclic); /* DontInherit components */ ecs_add_id(world, EcsPrefab, EcsDontInherit); /* Transitive relationships are always Acyclic */ ecs_add_pair(world, EcsTransitive, 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); /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_hierarchy(world); ecs_set_scope(world, 0); ecs_set_name_prefix(world, NULL); ecs_log_pop(); } /** * @file hierarchy.c * @brief API for entity paths and name lookups. */ #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; } static bool flecs_is_string_number( 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; } static 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; } 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_init(world, &(ecs_observer_desc_t){ .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_is_string_number(name)) { ecs_entity_t result = flecs_name_to_id(world, name); if (result && ecs_is_alive(world, result)) { if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { return 0; } return result; } } ecs_id_t pair = ecs_childof(parent); ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); if (index) { return flecs_name_index_find(index, name, 0, 0); } else { return 0; } error: return 0; } ecs_entity_t ecs_lookup( const ecs_world_t *world, const char *name) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = flecs_get_builtin(name); if (e) { return e; } if (flecs_is_string_number(name)) { return flecs_name_to_id(world, name); } e = flecs_name_index_find(&world->aliases, name, 0, 0); if (e) { return e; } return ecs_lookup_child(world, 0, name); error: return 0; } ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, const char *name, bool lookup_as_path) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = flecs_name_index_find(&world->symbols, name, 0, 0); if (e) { return e; } if (lookup_as_path) { return ecs_lookup_fullpath(world, name); } 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; ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); 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); ecs_entity_t *cur = stage->lookup_path; stage->lookup_path = (ecs_entity_t*)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); return 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; } 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; 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) { ecs_add_pair(world, e, EcsChildOf, cur); } else if (last_elem && root_path) { ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); } ecs_set_name(world, 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 { if (parent) { ecs_add_pair(world, entity, EcsChildOf, parent); } ecs_set_name(world, 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) { if (!sep) { sep = "."; } return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } /** * @file id_record.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 & EcsIdAcyclic) { flecs_id_record_elem_insert(widr, idr, &idr->acyclic); } } } 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 & EcsIdAcyclic) { flecs_id_record_elem_remove(idr, &idr->acyclic); } } } 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 >= ECS_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); ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; if (ECS_HAS_ID_FLAG(id, PAIR)) { rel = ecs_pair_first(world, id); 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); if (tgt) { tgt = ecs_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); } /* Check constraints */ if (tgt && !ecs_id_is_wildcard(tgt)) { /* Check if target of relationship satisfies OneOf property */ ecs_entity_t oneof = flecs_get_oneof(world, rel); ecs_check( !oneof || ecs_has_pair(world, tgt, EcsChildOf, oneof), ECS_CONSTRAINT_VIOLATED, NULL); (void)oneof; /* Check if we're not trying to inherit from a final target */ if (rel == EcsIsA) { bool is_final = ecs_has_id(world, tgt, EcsFinal); ecs_check(!is_final, ECS_CONSTRAINT_VIOLATED, "cannot inherit from final entity"); (void)is_final; } } if (!is_wildcard && ECS_IS_PAIR(id) && (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 objecs 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; rel = ecs_get_alive(world, rel); ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); } /* Initialize type info if id is not a tag */ if (!is_wildcard && (!role || ECS_IS_PAIR(id))) { 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, EcsEntityObservedId); if (tgt) { /* Flag for OnDeleteTarget policies */ flecs_add_flag(world, tgt, EcsEntityObservedTarget); if (idr->flags & EcsIdAcyclic) { /* Flag used to determine if object should be traversed when * propagating events or with super/subset queries */ ecs_record_t *r = flecs_add_flag( world, tgt, EcsEntityObservedAcyclic); /* Add reference to (*, tgt) id record to entity record */ r->idr = idr_t; } } /* Flags for events */ if (flecs_check_observers_for_event(world, id, EcsOnAdd)) { idr->flags |= EcsIdHasOnAdd; } if (flecs_check_observers_for_event(world, id, EcsOnRemove)) { idr->flags |= EcsIdHasOnRemove; } if (flecs_check_observers_for_event(world, id, EcsOnSet)) { idr->flags |= EcsIdHasOnSet; } if (flecs_check_observers_for_event(world, id, EcsUnSet)) { idr->flags |= EcsIdHasUnSet; } 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 ++; if (!is_wildcard) { world->info.id_count ++; if (idr->type_info) { world->info.component_id_count ++; } else { world->info.tag_id_count ++; } if (ECS_IS_PAIR(id)) { world->info.pair_id_count ++; } } else { world->info.wildcard_id_count ++; } return idr; error: return NULL; } 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); if (!(world->flags & EcsWorldQuit)) { /* Id is still in use by a filter, query, rule or observer */ ecs_assert((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(world, id); ecs_entity_t obj = 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, obj)); } } 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 ++; if (!ecs_id_is_wildcard(id)) { world->info.id_count --; if (ECS_IS_PAIR(id)) { world->info.pair_id_count --; } if (idr->type_info) { world->info.component_id_count --; } else { world->info.tag_id_count --; } } else { world->info.wildcard_id_count --; } /* 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 >= ECS_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; } ecs_id_t hash = flecs_id_record_hash(id); ecs_id_record_t *idr = NULL; if (hash >= ECS_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))) { /* Tables can hold claims on each other, so releasing a table can * cause the next element to get invalidated. Claim the next table * so that we can safely iterate. */ ecs_table_t *next = NULL; if (it.next) { next = it.next->table; flecs_table_claim(world, next); } /* Release current table */ flecs_table_release(world, tr->hdr.table); /* Check if the only thing keeping the next table alive is our * claim. If so, move to the next record before releasing */ if (next) { if (next->refcount == 1) { it.next = it.next->next; } flecs_table_release(world, next); } } } } bool flecs_id_record_set_type_info( ecs_world_t *world, ecs_id_record_t *idr, const ecs_type_info_t *ti) { if (!ecs_id_is_wildcard(idr->id)) { 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_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); 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_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); } const 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) { ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); while (ecs_map_next(&it)) { flecs_id_record_release(world, ecs_map_ptr(&it)); } int32_t i; for (i = 0; i < ECS_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); }