/** * @file bootstrap.c * @brief Bootstrap entities in the flecs.core namespace. * * Before the ECS storage can be used, core entities such first need to be * initialized. For example, components in Flecs are stored as entities in the * ECS storage itself with an EcsComponent component, but before this component * can be stored, the component itself needs to be initialized. * * The bootstrap code uses lower-level APIs to initialize the data structures. * After bootstrap is completed, regular ECS operations can be used to create * entities and components. * * The bootstrap file also includes several lifecycle hooks and observers for * builtin features, such as relationship properties and hooks for keeping the * entity name administration in sync with the (Identifier, Name) component. */ #include "flecs.h" /** * @file private_api.h * @brief Private functions. */ #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H /** * @file private_types.h * @brief Private types. */ #ifndef FLECS_PRIVATE_TYPES_H #define FLECS_PRIVATE_TYPES_H #ifndef __MACH__ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif #include #include #include /** * @file datastructures/entity_index.h * @brief Entity index data structure. * * The entity index stores the table, row for an entity id. */ #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_PAGE_SIZE (1 << FLECS_ENTITY_PAGE_BITS) #define FLECS_ENTITY_PAGE_MASK (FLECS_ENTITY_PAGE_SIZE - 1) typedef struct ecs_entity_index_page_t { ecs_record_t records[FLECS_ENTITY_PAGE_SIZE]; } ecs_entity_index_page_t; typedef struct ecs_entity_index_t { ecs_vec_t dense; ecs_vec_t pages; int32_t alive_count; uint64_t max_id; ecs_block_allocator_t page_allocator; ecs_allocator_t *allocator; } ecs_entity_index_t; /** Initialize entity index. */ void flecs_entity_index_init( ecs_allocator_t *allocator, ecs_entity_index_t *index); /** Deinitialize entity index. */ void flecs_entity_index_fini( ecs_entity_index_t *index); /* Get entity (must exist/must be alive) */ ecs_record_t* flecs_entity_index_get( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (must exist/may not be alive) */ ecs_record_t* flecs_entity_index_get_any( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (may not exist/must be alive) */ ecs_record_t* flecs_entity_index_try_get( const ecs_entity_index_t *index, uint64_t entity); /* Get entity (may not exist/may not be alive) */ ecs_record_t* flecs_entity_index_try_get_any( const ecs_entity_index_t *index, uint64_t entity); /** Ensure entity exists. */ ecs_record_t* flecs_entity_index_ensure( ecs_entity_index_t *index, uint64_t entity); /* Remove entity */ void flecs_entity_index_remove( ecs_entity_index_t *index, uint64_t entity); /* Set generation of entity */ void flecs_entity_index_make_alive( ecs_entity_index_t *index, uint64_t entity); /* Get current generation of entity */ uint64_t flecs_entity_index_get_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is alive */ bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity is valid */ bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity); /* Return whether entity exists */ bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity); /* Create or recycle entity id */ uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index); /* Bulk create or recycle new entity ids */ uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count); /* Set size of index */ void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size); /* Return number of entities in index */ int32_t flecs_entity_index_count( const ecs_entity_index_t *index); /* Return number of allocated entities in index */ int32_t flecs_entity_index_size( const ecs_entity_index_t *index); /* Return number of not alive entities in index */ int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index); /* Clear entity index */ void flecs_entity_index_clear( ecs_entity_index_t *index); /* Return number of alive entities in index */ const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index); #define ecs_eis(world) (&((world)->store.entity_index)) #define flecs_entities_init(world) flecs_entity_index_init(&world->allocator, ecs_eis(world)) #define flecs_entities_fini(world) flecs_entity_index_fini(ecs_eis(world)) #define flecs_entities_get(world, entity) flecs_entity_index_get(ecs_eis(world), entity) #define flecs_entities_try(world, entity) flecs_entity_index_try_get(ecs_eis(world), entity) #define flecs_entities_get_any(world, entity) flecs_entity_index_get_any(ecs_eis(world), entity) #define flecs_entities_ensure(world, entity) flecs_entity_index_ensure(ecs_eis(world), entity) #define flecs_entities_remove(world, entity) flecs_entity_index_remove(ecs_eis(world), entity) #define flecs_entities_make_alive(world, entity) flecs_entity_index_make_alive(ecs_eis(world), entity) #define flecs_entities_get_alive(world, entity) flecs_entity_index_get_alive(ecs_eis(world), entity) #define flecs_entities_is_alive(world, entity) flecs_entity_index_is_alive(ecs_eis(world), entity) #define flecs_entities_is_valid(world, entity) flecs_entity_index_is_valid(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_entity_index_exists(ecs_eis(world), entity) #define flecs_entities_new_id(world) flecs_entity_index_new_id(ecs_eis(world)) #define flecs_entities_new_ids(world, count) flecs_entity_index_new_ids(ecs_eis(world), count) #define flecs_entities_max_id(world) (ecs_eis(world)->max_id) #define flecs_entities_set_size(world, size) flecs_entity_index_set_size(ecs_eis(world), size) #define flecs_entities_count(world) flecs_entity_index_count(ecs_eis(world)) #define flecs_entities_size(world) flecs_entity_index_size(ecs_eis(world)) #define flecs_entities_not_alive_count(world) flecs_entity_index_not_alive_count(ecs_eis(world)) #define flecs_entities_clear(world) flecs_entity_index_clear(ecs_eis(world)) #define flecs_entities_ids(world) flecs_entity_index_ids(ecs_eis(world)) #endif /** * @file bitset.h * @brief Bitset data structure. */ #ifndef FLECS_BITSET_H #define FLECS_BITSET_H #ifdef __cplusplus extern "C" { #endif typedef struct ecs_bitset_t { uint64_t *data; int32_t count; ecs_size_t size; } ecs_bitset_t; /** Initialize bitset. */ FLECS_DBG_API void flecs_bitset_init( ecs_bitset_t *bs); /** Deinitialize bitset. */ FLECS_DBG_API void flecs_bitset_fini( ecs_bitset_t *bs); /** Add n elements to bitset. */ FLECS_DBG_API void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count); /** Ensure element exists. */ FLECS_DBG_API void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count); /** Set element. */ FLECS_DBG_API void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value); /** Get element. */ FLECS_DBG_API bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem); /** Return number of elements. */ FLECS_DBG_API int32_t flecs_bitset_count( const ecs_bitset_t *bs); /** Remove from bitset. */ FLECS_DBG_API void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem); /** Swap values in bitset. */ FLECS_DBG_API void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b); #ifdef __cplusplus } #endif #endif /** * @file storage/table.h * @brief Table storage implementation. */ #ifndef FLECS_TABLE_H #define FLECS_TABLE_H /** * @file storage/table_graph.h * @brief Table graph types and functions. */ #ifndef FLECS_TABLE_GRAPH_H #define FLECS_TABLE_GRAPH_H /** Cache of added/removed components for non-trivial edges between tables */ #define ECS_TABLE_DIFF_INIT { .added = {0}} /** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to * conserve memory on table edges (a type doesn't have the size field), whereas * a vec for the builder is more convenient to use & has allocator support. */ typedef struct ecs_table_diff_builder_t { ecs_vec_t added; ecs_vec_t removed; } ecs_table_diff_builder_t; typedef struct ecs_table_diff_t { ecs_type_t added; /* Components added between tables */ ecs_type_t removed; /* Components removed between tables */ } ecs_table_diff_t; /** Edge linked list (used to keep track of incoming edges) */ typedef struct ecs_graph_edge_hdr_t { struct ecs_graph_edge_hdr_t *prev; struct ecs_graph_edge_hdr_t *next; } ecs_graph_edge_hdr_t; /** Single edge. */ typedef struct ecs_graph_edge_t { ecs_graph_edge_hdr_t hdr; ecs_table_t *from; /* Edge source table */ ecs_table_t *to; /* Edge destination table */ ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ ecs_id_t id; /* Id associated with edge */ } ecs_graph_edge_t; /* Edges to other tables. */ typedef struct ecs_graph_edges_t { ecs_graph_edge_t *lo; /* Small array optimized for low edges */ ecs_map_t *hi; /* Map for hi edges (map) */ } ecs_graph_edges_t; /* Table graph node */ typedef struct ecs_graph_node_t { /* Outgoing edges */ ecs_graph_edges_t add; ecs_graph_edges_t remove; /* Incoming edges (next = add edges, prev = remove edges) */ ecs_graph_edge_hdr_t refs; } ecs_graph_node_t; /* Find table by adding id to current table */ ecs_table_t *flecs_table_traverse_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); /* Find table by removing id from current table */ ecs_table_t *flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); /* Cleanup incoming and outgoing edges for table */ void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table); /* Table diff builder, used to build id lists that indicate the difference in * ids between two tables. */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder); void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src); void flecs_table_diff_build( ecs_world_t *world, ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, int32_t removed_offset); void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff); #endif /* Table event type for notifying tables of world events */ typedef enum ecs_table_eventkind_t { EcsTableTriggersForId, EcsTableNoTriggersForId, } ecs_table_eventkind_t; typedef struct ecs_table_event_t { ecs_table_eventkind_t kind; /* Component info event */ ecs_entity_t component; /* Event match */ ecs_entity_t event; /* If the number of fields gets out of hand, this can be turned into a union * but since events are very temporary objects, this works for now and makes * initializing an event a bit simpler. */ } ecs_table_event_t; /** Infrequently accessed data not stored inline in ecs_table_t */ typedef struct ecs_table__t { uint64_t hash; /* Type hash */ int32_t lock; /* Prevents modifications */ int32_t traversable_count; /* Traversable relationship targets in table */ uint16_t generation; /* Used for table cleanup */ int16_t record_count; /* Table record count including wildcards */ struct ecs_table_record_t *records; /* Array with table records */ ecs_hashmap_t *name_index; /* Cached pointer to name index */ ecs_bitset_t *bs_columns; /* Bitset columns */ int16_t bs_count; int16_t bs_offset; int16_t ft_offset; } ecs_table__t; /** Table column */ typedef struct ecs_column_t { ecs_vec_t data; /* Vector with component data */ ecs_id_t id; /* Component id */ ecs_type_info_t *ti; /* Component type info */ ecs_size_t size; /* Component size */ } ecs_column_t; /** Table data */ struct ecs_data_t { ecs_vec_t entities; /* Entity ids */ ecs_column_t *columns; /* Component data */ }; /** A table is the Flecs equivalent of an archetype. Tables store all entities * with a specific set of components. Tables are automatically created when an * entity has a set of components not previously observed before. When a new * table is created, it is automatically matched with existing queries */ struct ecs_table_t { uint64_t id; /* Table id in sparse set */ ecs_flags32_t flags; /* Flags for testing table properties */ int16_t column_count; /* Number of components (excluding tags) */ ecs_type_t type; /* Vector with component ids */ ecs_data_t data; /* Component storage */ ecs_graph_node_t node; /* Graph node */ int32_t *dirty_state; /* Keep track of changes in columns */ int32_t *column_map; /* Map type index <-> column * - 0..count(T): type index -> column * - count(T)..count(C): column -> type index */ ecs_table__t *_; /* Infrequently accessed table metadata */ }; /* Init table */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from); /** Copy type. */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src); /** Free type. */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type); /** Find or create table for a set of components */ ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type); /* Initialize columns for data */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from a table. */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table); /* Reset a table to its initial state */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from the table. Do not invoke OnRemove systems */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table); /* Return number of entities in data */ int32_t flecs_table_data_count( const ecs_data_t *data); /* Add a new entry to the table for the specified entity */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, bool construct, bool on_add); /* Delete an entity from the table. */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct); /* Make sure table records are in correct table cache list */ bool flecs_table_records_update_empty( ecs_table_t *table); /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, int32_t new_index, ecs_table_t *old_table, int32_t old_index, bool construct); /* Grow table with specified number of records. Populate table with entities, * starting from specified entity id. */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count, const ecs_entity_t *ids); /* Set table to a fixed size. Useful for preallocating memory in advance. */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count); /* Shrink table to contents */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table); /* Get dirty state for table columns */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table); /* Initialize root table */ void flecs_init_root_table( ecs_world_t *world); /* Unset components in table */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table); /* Merge data of one table into another table */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *new_table, ecs_table_t *old_table); void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2); void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component); void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_table_event_t *event); void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table); /* Increase observer count of table */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value); const ecs_vec_t* flecs_table_entities( const ecs_table_t *table); ecs_entity_t* flecs_table_entities_array( const ecs_table_t *table); void flecs_table_emit( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event); int32_t flecs_table_get_toggle_column( ecs_table_t *table, ecs_id_t id); ecs_bitset_t* flecs_table_get_toggle( ecs_table_t *table, ecs_id_t id); #endif /* Used in id records to keep track of entities used with id flags */ extern const ecs_entity_t EcsFlag; #define ECS_MAX_JOBS_PER_WORKER (16) #define ECS_MAX_DEFER_STACK (8) /* Magic number for a flecs object */ #define ECS_OBJECT_MAGIC (0x6563736f) /* Tags associated with poly for (Poly, tag) components */ #define ecs_world_t_tag invalid #define ecs_stage_t_tag invalid #define ecs_query_t_tag EcsQuery #define ecs_observer_t_tag EcsObserver /* Mixin kinds */ typedef enum ecs_mixin_kind_t { EcsMixinWorld, EcsMixinEntity, EcsMixinObservable, EcsMixinDtor, EcsMixinMax } ecs_mixin_kind_t; /* The mixin array contains 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_query_t_mixins; extern ecs_mixins_t ecs_observer_t_mixins; /* Types that have no mixins */ #define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) /* Scope for flecs internals, like observers used for builtin features */ extern const ecs_entity_t EcsFlecsInternals; /** Type used for internal string hashmap */ typedef struct ecs_hashed_string_t { char *value; ecs_size_t length; uint64_t hash; } ecs_hashed_string_t; /** Must appear as first member in payload of table cache */ typedef struct ecs_table_cache_hdr_t { struct ecs_table_cache_t *cache; ecs_table_t *table; struct ecs_table_cache_hdr_t *prev, *next; bool empty; } ecs_table_cache_hdr_t; /** Linked list of tables in table cache */ typedef struct ecs_table_cache_list_t { ecs_table_cache_hdr_t *first; ecs_table_cache_hdr_t *last; int32_t count; } ecs_table_cache_list_t; /** Table cache */ typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_table_cache_list_t tables; ecs_table_cache_list_t empty_tables; } ecs_table_cache_t; /* 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_block_allocator_t query_impl; ecs_block_allocator_t query_cache; } ecs_stage_allocators_t; /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { EcsCmdClone, EcsCmdBulkNew, EcsCmdAdd, EcsCmdRemove, EcsCmdSet, EcsCmdEmplace, EcsCmdEnsure, EcsCmdModified, EcsCmdModifiedNoHook, EcsCmdAddModified, EcsCmdPath, EcsCmdDelete, EcsCmdClear, EcsCmdOnDeleteAction, EcsCmdEnable, EcsCmdDisable, EcsCmdEvent, EcsCmdSkip } ecs_cmd_kind_t; /* Entity specific metadata for command in queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / ensure) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ ecs_cmd_entry_t *entry; ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; ecs_entity_t system; /* System that enqueued the command */ } ecs_cmd_t; /* Data structures that store the command queue */ typedef struct ecs_commands_t { ecs_vec_t queue; ecs_stack_t stack; /* Temp memory used by deferred commands */ ecs_sparse_t entries; /* - command batching */ } ecs_commands_t; /** Callback used to capture commands of a frame */ typedef void (*ecs_on_commands_action_t)( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx); /** A stage is a context that allows for safely using the API from multiple * threads. Stage pointers can be passed to the world argument of API * operations, which causes the operation to be ran on the stage instead of the * world. The features provided by a stage are: * * - A command queue for deferred ECS operations and events * - Thread specific allocators * - Thread specific world state (like current scope, with, current system) * - Thread specific buffers for preventing allocations */ struct ecs_stage_t { ecs_header_t hdr; /* Unique id that identifies the stage */ int32_t id; /* Zero if not deferred, positive if deferred, negative if suspended */ int32_t defer; /* Command queue stack, for nested execution */ ecs_commands_t *cmd; ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; int32_t cmd_sp; /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ ecs_world_t *world; /* Reference to world */ ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_vec_t post_frame_actions; /* Namespacing */ ecs_entity_t scope; /* Entity of current scope */ ecs_entity_t with; /* Id to add by default to new entities */ ecs_entity_t base; /* Currently instantiated top-level base */ const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Running system */ ecs_entity_t system; /* Thread specific allocators */ ecs_stage_allocators_t allocators; ecs_allocator_t allocator; /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; /* Temporary token storage for DSL parser. This allows for parsing and * interpreting a term without having to do allocations. */ char parser_tokens[1024]; char *parser_token; /* Pointer to next token */ }; /* Component monitor */ typedef struct ecs_monitor_t { ecs_vec_t queries; /* vector */ bool is_dirty; /* Should queries be rematched? */ } ecs_monitor_t; /* Component monitors */ typedef struct ecs_monitor_set_t { ecs_map_t monitors; /* map */ bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { ecs_id_record_t *idr; ecs_id_t id; ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; } ecs_marked_id_t; typedef struct ecs_store_t { /* Entity lookup */ ecs_entity_index_t entity_index; /* Tables */ ecs_sparse_t tables; /* sparse */ /* Table lookup by hash */ ecs_hashmap_t table_map; /* hashmap */ /* Root table */ ecs_table_t root; /* Observers */ ecs_sparse_t observers; /* sparse */ /* Records cache */ ecs_vec_t records; /* Stack of ids being deleted. */ ecs_vec_t marked_ids; /* vector */ /* Entity ids associated with depth (for flat hierarchies) */ ecs_vec_t depth_ids; ecs_map_t entity_to_depth; /* What it says */ } ecs_store_t; /* fini actions */ typedef struct ecs_action_elem_t { ecs_fini_action_t action; void *ctx; } ecs_action_elem_t; typedef struct ecs_pipeline_state_t ecs_pipeline_state_t; /** The world stores and manages all ECS data. An application can have more than * one world, but data is not shared between worlds. */ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ ecs_id_record_t *id_index_lo; ecs_map_t id_index_hi; /* map */ ecs_sparse_t type_info; /* sparse */ /* -- Cached handle to id records -- */ ecs_id_record_t *idr_wildcard; ecs_id_record_t *idr_wildcard_wildcard; ecs_id_record_t *idr_any; ecs_id_record_t *idr_isa_wildcard; ecs_id_record_t *idr_childof_0; ecs_id_record_t *idr_childof_wildcard; ecs_id_record_t *idr_identifier_name; /* -- Mixins -- */ ecs_world_t *self; ecs_observable_t observable; /* Unique id per generated event used to prevent duplicate notifications */ int32_t event_id; /* Is entity range checking enabled? */ bool range_check_enabled; /* -- Data storage -- */ ecs_store_t store; /* -- Pending table event buffers -- */ ecs_sparse_t *pending_buffer; /* sparse */ ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ ecs_stage_t **stages; /* Stages */ int32_t stage_count; /* Number of stages */ /* Internal callback for command inspection. Only one callback can be set at * a time. After assignment the action will become active at the start of * the next frame, set by ecs_frame_begin, and will be reset by * ecs_frame_end. */ ecs_on_commands_action_t on_commands; ecs_on_commands_action_t on_commands_active; void *on_commands_ctx; void *on_commands_ctx_active; /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ /* -- Time management -- */ ecs_time_t world_start_time; /* Timestamp of simulation start */ ecs_time_t frame_start_time; /* Timestamp of frame start */ ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ /* -- Metrics -- */ ecs_world_info_t info; /* -- World flags -- */ ecs_flags32_t flags; /* -- Default query flags -- */ ecs_flags32_t default_query_flags; /* Count that increases when component monitors change */ int32_t monitor_generation; /* -- Allocators -- */ ecs_world_allocators_t allocators; /* Static allocation sizes */ ecs_allocator_t allocator; /* Dynamic allocation sizes */ void *ctx; /* Application context */ void *binding_ctx; /* Binding-specific context */ ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ }; #endif /** * @file storage/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_insert_w_empty( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result, bool is_empty); 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 #define flecs_table_cache_all_count(cache) ((cache)->tables.count + (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 storage/id_index.h * @brief Index for looking up tables by (component) id. */ #ifndef FLECS_ID_INDEX_H #define FLECS_ID_INDEX_H /* Payload for id cache */ struct ecs_table_record_t { ecs_table_cache_hdr_t hdr; /* Table cache header */ int16_t index; /* First type index where id occurs in table */ int16_t count; /* Number of times id occurs in table */ int16_t column; /* First column index where id occurs */ }; /* Linked list of id records */ typedef struct ecs_id_record_elem_t { struct ecs_id_record_t *prev, *next; } ecs_id_record_elem_t; typedef struct ecs_reachable_elem_t { const ecs_table_record_t *tr; ecs_record_t *record; ecs_entity_t src; ecs_id_t id; #ifndef NDEBUG ecs_table_t *table; #endif } ecs_reachable_elem_t; typedef struct ecs_reachable_cache_t { int32_t generation; int32_t current; ecs_vec_t ids; /* vec */ } ecs_reachable_cache_t; /* Payload for id index which contains all data structures for an id. */ struct ecs_id_record_t { /* Cache with all tables that contain the id. Must be first member. */ ecs_table_cache_t cache; /* table_cache */ /* Id of record */ ecs_id_t id; /* Flags for id */ ecs_flags32_t flags; /* Cached pointer to type info for id, if id contains data. */ const ecs_type_info_t *type_info; /* Name lookup index (currently only used for ChildOf pairs) */ ecs_hashmap_t *name_index; /* Storage for sparse components or union relationships */ void *sparse; /* Lists for all id records that match a pair wildcard. The wildcard id * record is at the head of the list. */ ecs_id_record_elem_t first; /* (R, *) */ ecs_id_record_elem_t second; /* (*, O) */ ecs_id_record_elem_t trav; /* (*, O) with only traversable relationships */ /* Parent id record. For pair records the parent is the (R, *) record. */ ecs_id_record_t *parent; /* Refcount */ int32_t refcount; /* Keep alive count. This count must be 0 when the id record is deleted. If * it is not 0, an application attempted to delete an id that was still * queried for. */ int32_t keep_alive; /* Cache for finding components that are reachable through a relationship */ ecs_reachable_cache_t reachable; }; /* Get id record for id */ ecs_id_record_t* flecs_id_record_get( const ecs_world_t *world, ecs_id_t id); /* Ensure id record for id */ ecs_id_record_t* flecs_id_record_ensure( ecs_world_t *world, ecs_id_t id); /* Increase refcount of id record */ void flecs_id_record_claim( ecs_world_t *world, ecs_id_record_t *idr); /* Decrease refcount of id record, delete if 0 */ int32_t flecs_id_record_release( ecs_world_t *world, ecs_id_record_t *idr); /* Release all empty tables in id record */ void flecs_id_record_release_tables( ecs_world_t *world, ecs_id_record_t *idr); /* Set (component) type info for id record */ bool flecs_id_record_set_type_info( ecs_world_t *world, ecs_id_record_t *idr, const ecs_type_info_t *ti); /* Ensure id record has name index */ ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id); ecs_hashmap_t* flecs_id_record_name_index_ensure( ecs_world_t *world, ecs_id_record_t *idr); /* Get name index for id record */ ecs_hashmap_t* flecs_id_name_index_get( const ecs_world_t *world, ecs_id_t id); /* Find table record for id */ ecs_table_record_t* flecs_table_record_get( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id); /* Find table record for id record */ ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table); /* Init sparse storage */ void flecs_id_record_init_sparse( ecs_world_t *world, ecs_id_record_t *idr); /* 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 query/query.h * @brief Query implementation. */ /** * @file query/compiler/compiler.h * @brief Query compiler functions. */ /** * @file query/types.h * @brief Internal types and functions for queries. */ #ifndef FLECS_QUERY_TYPES #define FLECS_QUERY_TYPES typedef struct ecs_query_impl_t ecs_query_impl_t; typedef uint8_t ecs_var_id_t; typedef int16_t ecs_query_lbl_t; typedef ecs_flags64_t ecs_write_flags_t; #define flecs_query_impl(query) (ECS_CONST_CAST(ecs_query_impl_t*, query)) #define EcsQueryMaxVarCount (64) #define EcsVarNone ((ecs_var_id_t)-1) #define EcsThisName "this" /* -- Variable types -- */ typedef enum { EcsVarEntity, /* Variable that stores an entity id */ EcsVarTable, /* Variable that stores a table */ EcsVarAny /* Used when requesting either entity or table var */ } ecs_var_kind_t; typedef struct ecs_query_var_t { int8_t kind; /* variable kind (EcsVarEntity or EcsVarTable) */ bool anonymous; /* variable is anonymous */ ecs_var_id_t id; /* variable id */ ecs_var_id_t table_id; /* id to table variable, if any */ ecs_var_id_t base_id; /* id to base entity variable, for lookups */ const char *name; /* variable name */ const char *lookup; /* Lookup string for variable */ #ifdef FLECS_DEBUG const char *label; /* for debugging */ #endif } ecs_query_var_t; /* -- Instruction kinds -- */ typedef enum { EcsQueryAnd, /* And operator: find or match id against variable source */ EcsQueryAndId, /* And operator for fixed id (no wildcards/variables) */ EcsQueryAndAny, /* And operator with support for matching Any src/id */ EcsQueryOnlyAny, /* Dedicated instruction for _ queries where the src is unknown */ EcsQueryTriv, /* Trivial search (batches multiple terms) */ EcsQueryTrivData, /* Trivial search with setting data fields */ EcsQueryTrivWildcard, /* Trivial search with (exclusive) wildcard ids */ EcsQueryCache, /* Cached search */ EcsQueryCacheData, /* Cached search with setting data fields */ EcsQueryIsCache, /* Cached search for queries that are entirely cached */ EcsQueryIsCacheData, /* Same as EcsQueryIsCache with data fields */ EcsQueryUp, /* Up traversal */ EcsQueryUpId, /* Up traversal for fixed id (like AndId) */ EcsQuerySelfUp, /* Self|up traversal */ EcsQuerySelfUpId, /* Self|up traversal for fixed id (like AndId) */ EcsQueryWith, /* Match id against fixed or variable source */ EcsQueryTrav, /* Support for transitive/reflexive queries */ EcsQueryAndFrom, /* AndFrom operator */ EcsQueryOrFrom, /* OrFrom operator */ EcsQueryNotFrom, /* NotFrom operator */ EcsQueryIds, /* Test for existence of ids matching wildcard */ EcsQueryIdsRight, /* Find ids in use that match (R, *) wildcard */ EcsQueryIdsLeft, /* Find ids in use that match (*, T) wildcard */ EcsQueryEach, /* Iterate entities in table, populate entity variable */ EcsQueryStore, /* Store table or entity in variable */ EcsQueryReset, /* Reset value of variable to wildcard (*) */ EcsQueryOr, /* Or operator */ EcsQueryOptional, /* Optional operator */ EcsQueryIfVar, /* Conditional execution on whether variable is set */ EcsQueryIfSet, /* Conditional execution on whether term is set */ EcsQueryNot, /* Sets iterator state after term was not matched */ EcsQueryEnd, /* End of control flow block */ EcsQueryPredEq, /* Test if variable is equal to, or assign to if not set */ EcsQueryPredNeq, /* Test if variable is not equal to */ EcsQueryPredEqName, /* Same as EcsQueryPredEq but with matching by name */ EcsQueryPredNeqName, /* Same as EcsQueryPredNeq but with matching by name */ EcsQueryPredEqMatch, /* Same as EcsQueryPredEq but with fuzzy matching by name */ EcsQueryPredNeqMatch, /* Same as EcsQueryPredNeq but with fuzzy matching by name */ EcsQueryMemberEq, /* Compare member value */ EcsQueryMemberNeq, /* Compare member value */ EcsQueryToggle, /* Evaluate toggle bitset, if present */ EcsQueryToggleOption, /* Toggle for optional terms */ EcsQueryUnionEq, /* Evaluate union relationship */ EcsQueryUnionEqWith, /* Evaluate union relationship against fixed or variable source */ EcsQueryUnionNeq, /* Evaluate union relationship */ EcsQueryUnionEqUp, /* Evaluate union relationship w/up traversal */ EcsQueryUnionEqSelfUp, /* Evaluate union relationship w/self|up traversal */ EcsQueryLookup, /* Lookup relative to variable */ EcsQuerySetVars, /* Populate it.sources from variables */ EcsQuerySetThis, /* Populate This entity variable */ EcsQuerySetFixed, /* Set fixed source entity ids */ EcsQuerySetIds, /* Set fixed (component) ids */ EcsQuerySetId, /* Set id if not set */ EcsQueryContain, /* Test if table contains entity */ EcsQueryPairEq, /* Test if both elements of pair are the same */ EcsQueryPopulate, /* Populate any data fields */ EcsQueryPopulateSelf, /* Populate only self (owned) data fields */ EcsQueryPopulateSparse, /* Populate sparse fields */ EcsQueryYield, /* Yield result back to application */ EcsQueryNothing /* Must be last */ } ecs_query_op_kind_t; /* Op flags to indicate if ecs_query_ref_t is entity or variable */ #define EcsQueryIsEntity (1 << 0) #define EcsQueryIsVar (1 << 1) #define EcsQueryIsSelf (1 << 6) /* Op flags used to shift EcsQueryIsEntity and EcsQueryIsVar */ #define EcsQuerySrc 0 #define EcsQueryFirst 2 #define EcsQuerySecond 4 /* References to variable or entity */ typedef union { ecs_var_id_t var; ecs_entity_t entity; } ecs_query_ref_t; /* Query instruction */ typedef struct ecs_query_op_t { uint8_t kind; /* Instruction kind */ ecs_flags8_t flags; /* Flags storing whether 1st/2nd are variables */ int8_t field_index; /* Query field corresponding with operation */ int8_t term_index; /* Query term corresponding with operation */ ecs_query_lbl_t prev; /* Backtracking label (no data) */ ecs_query_lbl_t next; /* Forwarding label. Must come after prev */ ecs_query_lbl_t other; /* Misc register used for control flow */ ecs_flags16_t match_flags; /* Flags that modify matching behavior */ ecs_query_ref_t src; ecs_query_ref_t first; ecs_query_ref_t second; ecs_flags64_t written; /* Bitset with variables written by op */ } ecs_query_op_t; /* And context */ typedef struct { ecs_id_record_t *idr; ecs_table_cache_iter_t it; int16_t column; int16_t remaining; } ecs_query_and_ctx_t; /* Union context */ typedef struct { ecs_id_record_t *idr; ecs_table_range_t range; ecs_map_iter_t tgt_iter; ecs_entity_t cur; ecs_entity_t tgt; int32_t row; } ecs_query_union_ctx_t; /* Down traversal cache (for resolving up queries w/unknown source) */ typedef struct { ecs_table_t *table; bool leaf; /* Table owns and inherits id (for Up queries without Self) */ } ecs_trav_down_elem_t; typedef struct { ecs_vec_t elems; /* vector */ bool ready; } ecs_trav_down_t; typedef struct { ecs_entity_t src; ecs_id_t id; int32_t column; bool ready; } ecs_trav_up_t; typedef enum { EcsTravUp = 1, EcsTravDown = 2 } ecs_trav_direction_t; typedef struct { ecs_map_t src; /* map or map */ ecs_id_t with; ecs_trav_direction_t dir; } ecs_trav_up_cache_t; /* And up context */ typedef struct { union { ecs_query_and_ctx_t and; ecs_query_union_ctx_t union_; } is; ecs_table_t *table; int32_t row; int32_t end; ecs_entity_t trav; ecs_id_t with; ecs_id_t matched; ecs_id_record_t *idr_with; ecs_id_record_t *idr_trav; ecs_trav_down_t *down; int32_t cache_elem; ecs_trav_up_cache_t cache; } ecs_query_up_ctx_t; /* Cache for storing results of upward/downward "all" traversal. This type of * traversal iterates and caches the entire tree. */ typedef struct { ecs_entity_t entity; ecs_id_record_t *idr; int32_t column; } ecs_trav_elem_t; typedef struct { ecs_id_t id; ecs_id_record_t *idr; ecs_vec_t entities; bool up; } ecs_trav_cache_t; /* Trav context */ typedef struct { ecs_query_and_ctx_t and; int32_t index; int32_t offset; int32_t count; ecs_trav_cache_t cache; bool yield_reflexive; } ecs_query_trav_ctx_t; /* Eq context */ typedef struct { ecs_table_range_t range; int32_t index; int16_t name_col; bool redo; } ecs_query_eq_ctx_t; /* Each context */ typedef struct { int32_t row; } ecs_query_each_ctx_t; /* Setthis context */ typedef struct { ecs_table_range_t range; } ecs_query_setthis_ctx_t; /* Ids context */ typedef struct { ecs_id_record_t *cur; } ecs_query_ids_ctx_t; /* Control flow context */ typedef struct { ecs_query_lbl_t op_index; ecs_id_t field_id; bool is_set; } ecs_query_ctrl_ctx_t; /* Trivial iterator context */ typedef struct { ecs_table_cache_iter_t it; const ecs_table_record_t *tr; int32_t first_to_eval; } ecs_query_trivial_ctx_t; /* *From operator iterator context */ typedef struct { ecs_query_and_ctx_t and; ecs_type_t *type; int32_t first_id_index; int32_t cur_id_index; } ecs_query_xfrom_ctx_t; /* Member equality context */ typedef struct { ecs_query_each_ctx_t each; void *data; } ecs_query_membereq_ctx_t; /* Toggle context */ typedef struct { ecs_table_range_t range; int32_t cur; int32_t block_index; ecs_flags64_t block; ecs_termset_t prev_set_fields; bool optional_not; bool has_bitset; } ecs_query_toggle_ctx_t; typedef struct ecs_query_op_ctx_t { union { ecs_query_and_ctx_t and; ecs_query_xfrom_ctx_t xfrom; ecs_query_up_ctx_t up; ecs_query_trav_ctx_t trav; ecs_query_ids_ctx_t ids; ecs_query_eq_ctx_t eq; ecs_query_each_ctx_t each; ecs_query_setthis_ctx_t setthis; ecs_query_ctrl_ctx_t ctrl; ecs_query_trivial_ctx_t trivial; ecs_query_membereq_ctx_t membereq; ecs_query_toggle_ctx_t toggle; ecs_query_union_ctx_t union_; } is; } ecs_query_op_ctx_t; typedef struct { /* Labels used for control flow */ ecs_query_lbl_t lbl_query; /* Used to find the op that does the actual searching */ ecs_query_lbl_t lbl_begin; ecs_query_lbl_t lbl_cond_eval; ecs_write_flags_t written_or; /* Cond written flags at start of or chain */ ecs_write_flags_t cond_written_or; /* Cond written flags at start of or chain */ ecs_query_ref_t src_or; /* Source for terms in current or chain */ bool src_written_or; /* Was src populated before OR chain */ bool in_or; /* Whether we're in an or chain */ } ecs_query_compile_ctrlflow_t; /* Query compiler state */ typedef struct { ecs_vec_t *ops; ecs_write_flags_t written; /* Bitmask to check which variables have been written */ ecs_write_flags_t cond_written; /* Track conditional writes (optional operators) */ /* Maintain control flow per scope */ ecs_query_compile_ctrlflow_t ctrlflow[FLECS_QUERY_SCOPE_NESTING_MAX]; ecs_query_compile_ctrlflow_t *cur; /* Current scope */ int32_t scope; /* Nesting level of query scopes */ ecs_flags32_t scope_is_not; /* Whether scope is prefixed with not */ ecs_oper_kind_t oper; /* Temp storage to track current operator for term */ int32_t skipped; /* Term skipped during compilation */ } ecs_query_compile_ctx_t; /* Query run state */ typedef struct { uint64_t *written; /* Bitset to check which variables have been written */ ecs_query_lbl_t op_index; /* Currently evaluated operation */ ecs_var_t *vars; /* Variable storage */ ecs_iter_t *it; /* Iterator */ ecs_query_op_ctx_t *op_ctx; /* Operation context (stack) */ ecs_world_t *world; /* Reference to world */ const ecs_query_impl_t *query; /* Reference to query */ const ecs_query_var_t *query_vars; /* Reference to query variable array */ ecs_query_iter_t *qit; } ecs_query_run_ctx_t; typedef struct { ecs_query_var_t var; const char *name; } ecs_query_var_cache_t; struct ecs_query_impl_t { ecs_query_t pub; /* Public query data */ ecs_stage_t *stage; /* Stage used for allocations */ /* Variables */ ecs_query_var_t *vars; /* Variables */ int32_t var_count; /* Number of variables */ int32_t var_size; /* Size of variable array */ ecs_hashmap_t tvar_index; /* Name index for table variables */ ecs_hashmap_t evar_index; /* Name index for entity variables */ ecs_query_var_cache_t vars_cache; /* For trivial queries with only This variables */ ecs_var_id_t *src_vars; /* Array with ids to source variables for fields */ /* Query plan */ ecs_query_op_t *ops; /* Operations */ int32_t op_count; /* Number of operations */ /* Query cache */ struct ecs_query_cache_t *cache; /* Cache, if query contains cached terms */ int8_t *field_map; /* Map field indices from cache to query */ /* Change detection */ int32_t *monitor; /* Change monitor for fields with fixed src */ /* Misc */ int16_t tokens_len; /* Length of tokens buffer */ char *tokens; /* Buffer with string tokens used by terms */ /* User context */ ecs_ctx_free_t ctx_free; /* Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ /* Mixins */ flecs_poly_dtor_t dtor; }; /* Query cache types */ /** Table match data. * Each table matched by the query is represented by an ecs_query_cache_table_match_t * instance, which are linked together in a list. A table may match a query * multiple times (due to wildcard queries) with different columns being matched * by the query. */ struct ecs_query_cache_table_match_t { ecs_query_cache_table_match_t *next, *prev; ecs_table_t *table; /* The current table. */ int32_t offset; /* Starting point in table */ int32_t count; /* Number of entities to iterate in table */ int32_t *columns; /* Mapping from query fields to table columns */ int32_t *storage_columns; /* Mapping from query fields to storage columns */ ecs_id_t *ids; /* Resolved (component) ids for current table */ ecs_entity_t *sources; /* Subjects (sources) of ids */ ecs_vec_t refs; /* Cached components for non-this terms */ ecs_termset_t set_fields; /* Fields that are set */ ecs_termset_t up_fields; /* Fields that are matched through traversal */ uint64_t group_id; /* Value used to organize tables in groups */ int32_t *monitor; /* Used to monitor table for changes */ /* Next match in cache for same table (includes empty tables) */ ecs_query_cache_table_match_t *next_match; }; /** Table record type for query table cache. A query only has one per table. */ typedef struct ecs_query_cache_table_t { ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ ecs_query_cache_table_match_t *first; /* List with matches for table */ ecs_query_cache_table_match_t *last; /* Last discovered match for table */ uint64_t table_id; int32_t rematch_count; /* Track whether table was rematched */ } ecs_query_cache_table_t; /** Points to the beginning & ending of a query group */ typedef struct ecs_query_cache_table_list_t { ecs_query_cache_table_match_t *first; ecs_query_cache_table_match_t *last; ecs_query_group_info_t info; } ecs_query_cache_table_list_t; /* Query event type for notifying queries of world events */ typedef enum ecs_query_cache_eventkind_t { EcsQueryTableMatch, EcsQueryTableRematch, EcsQueryTableUnmatch, } ecs_query_cache_eventkind_t; typedef struct ecs_query_cache_event_t { ecs_query_cache_eventkind_t kind; ecs_table_t *table; } ecs_query_cache_event_t; /* Query level block allocators have sizes that depend on query field count */ typedef struct ecs_query_cache_allocators_t { ecs_block_allocator_t columns; ecs_block_allocator_t ids; ecs_block_allocator_t sources; ecs_block_allocator_t monitors; } ecs_query_cache_allocators_t; /** Query that is automatically matched against tables */ typedef struct ecs_query_cache_t { /* Uncached query used to populate the cache */ ecs_query_t *query; /* Observer to keep the cache in sync */ ecs_observer_t *observer; /* Tables matched with query */ ecs_table_cache_t cache; /* Linked list with all matched non-empty tables, in iteration order */ ecs_query_cache_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; ecs_order_by_action_t order_by_callback; ecs_sort_table_action_t order_by_table_callback; ecs_vec_t table_slices; int32_t order_by_term; /* Table grouping */ ecs_entity_t group_by; ecs_group_by_action_t group_by_callback; ecs_group_create_action_t on_group_create; ecs_group_delete_action_t on_group_delete; void *group_by_ctx; ecs_ctx_free_t group_by_ctx_free; /* Monitor generation */ int32_t monitor_generation; int32_t cascade_by; /* Identify cascade term */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ ecs_entity_t entity; /* Query-level allocators */ ecs_query_cache_allocators_t allocators; } ecs_query_cache_t; #endif /* Compile query to list of operations */ int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query); /* Compile single term */ int flecs_query_compile_term( ecs_world_t *world, ecs_query_impl_t *query, ecs_term_t *term, ecs_flags64_t *populated, ecs_query_compile_ctx_t *ctx); /* Compile term ref (first, second or src) */ void flecs_query_compile_term_ref( ecs_world_t *world, ecs_query_impl_t *query, ecs_query_op_t *op, ecs_term_ref_t *term_ref, ecs_query_ref_t *ref, ecs_flags8_t ref_kind, ecs_var_kind_t kind, ecs_query_compile_ctx_t *ctx, bool create_wildcard_vars); /* Mark variable as written */ void flecs_query_write( ecs_var_id_t var_id, uint64_t *written); /* Mark variable as written in compiler context */ void flecs_query_write_ctx( ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx, bool cond_write); /* Add operation to query plan */ ecs_query_lbl_t flecs_query_op_insert( ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx); /* Insert each instruction */ void flecs_query_insert_each( ecs_var_id_t tvar, ecs_var_id_t evar, ecs_query_compile_ctx_t *ctx, bool cond_write); /* Insert instruction that populates field */ void flecs_query_insert_populate( ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, ecs_flags64_t populated); /* Add discovered variable */ ecs_var_id_t flecs_query_add_var( ecs_query_impl_t *query, const char *name, ecs_vec_t *vars, ecs_var_kind_t kind); /* Find variable by name/kind */ ecs_var_id_t flecs_query_find_var_id( const ecs_query_impl_t *query, const char *name, ecs_var_kind_t kind); ecs_query_op_t* flecs_query_begin_block( ecs_query_op_kind_t kind, ecs_query_compile_ctx_t *ctx); void flecs_query_end_block( ecs_query_compile_ctx_t *ctx, bool reset); /** * @file query/engine/engine.h * @brief Query engine functions. */ /** * @file query/engine/cache.h * @brief Query cache functions. */ /* Create query cache */ ecs_query_cache_t* flecs_query_cache_init( ecs_query_impl_t *impl, const ecs_query_desc_t *desc); /* Destroy query cache */ void flecs_query_cache_fini( ecs_query_impl_t *impl); /* Notify query cache of event (separate from query observer) */ void flecs_query_cache_notify( ecs_world_t *world, ecs_query_t *q, ecs_query_cache_event_t *event); /* Get cache entry for table */ ecs_query_cache_table_t* flecs_query_cache_get_table( ecs_query_cache_t *query, ecs_table_t *table); /* Sort tables (order_by implementation) */ void flecs_query_cache_sort_tables( ecs_world_t *world, ecs_query_impl_t *impl); void flecs_query_cache_build_sorted_tables( ecs_query_cache_t *cache); /* Return number of tables in cache */ int32_t flecs_query_cache_table_count( ecs_query_cache_t *cache); /* Return number of empty tables in cache */ int32_t flecs_query_cache_empty_table_count( ecs_query_cache_t *cache); /* Return number of entities in cache (requires iterating tables) */ int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache); /** * @file query/engine/cache_iter.h * @brief Cache iterator functions. */ /* Cache search without data */ bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx); /* Cache search with data */ bool flecs_query_cache_data_search( const ecs_query_run_ctx_t *ctx); /* Cache search without data where entire query is cached */ bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx); /* Cache search with data where entire query is cached */ bool flecs_query_is_cache_data_search( const ecs_query_run_ctx_t *ctx); /* Cache test without data */ bool flecs_query_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test without data where entire query is cached */ bool flecs_query_is_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test with data */ bool flecs_query_cache_data_test( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test with data where entire query is cached */ bool flecs_query_is_cache_data_test( const ecs_query_run_ctx_t *ctx, bool redo); /** * @file query/engine/change_detection.h * @brief Query change detection functions. */ /* Synchronize cache monitor with table dirty state */ void flecs_query_sync_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_table_match_t *match); /* Mark iterated out fields dirty */ void flecs_query_mark_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it); /* Compare cache monitor with table dirty state to detect changes */ bool flecs_query_check_table_monitor( ecs_query_impl_t *impl, ecs_query_cache_table_t *table, int32_t term); /* Mark out fields with fixed source dirty */ void flecs_query_mark_fixed_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it); /* Synchronize fixed source monitor */ bool flecs_query_update_fixed_monitor( ecs_query_impl_t *impl); /* Compare fixed source monitor */ bool flecs_query_check_fixed_monitor( ecs_query_impl_t *impl); /** * @file query/engine/trav_cache.h * @brief Traversal cache functions */ /* Traversal cache for transitive queries. Finds all reachable entities by * following a relationship */ /* Find all entities when traversing downwards */ void flecs_query_get_trav_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity); /* Find all entities when traversing upwards */ void flecs_query_get_trav_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table); /* Free traversal cache */ void flecs_query_trav_cache_fini( ecs_allocator_t *a, ecs_trav_cache_t *cache); /* Traversal caches for up traversal. Enables searching upwards until an entity * with the queried for id has been found. */ /* Traverse downwards from starting entity to find all tables for which the * specified entity is the source of the queried for id ('with'). */ ecs_trav_down_t* flecs_query_get_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_with, bool self, bool empty); /* Free down traversal cache */ void flecs_query_down_cache_fini( ecs_allocator_t *a, ecs_trav_up_cache_t *cache); ecs_trav_up_t* flecs_query_get_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_table_t *table, ecs_id_t with, ecs_entity_t trav, ecs_id_record_t *idr_with, ecs_id_record_t *idr_trav); /* Free up traversal cache */ void flecs_query_up_cache_fini( ecs_trav_up_cache_t *cache); /** * @file query/engine/trivial_iter.h * @brief Trivial iterator functions. */ /* Iterator for trivial queries. */ bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t field_set); /* Iterator for trivial queries. */ bool flecs_query_trivial_search_nodata( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t field_set); /* Iterator for trivial queries with wildcard matching. */ bool flecs_query_trivial_search_w_wildcards( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t field_set); /* Trivial test for constrained $this. */ bool flecs_query_trivial_test( const ecs_query_run_ctx_t *ctx, bool first, ecs_flags64_t field_set); /* Trivial test for constrained $this with wildcard matching. */ bool flecs_query_trivial_test_w_wildcards( const ecs_query_run_ctx_t *ctx, bool redo, ecs_flags64_t field_set); /* Query evaluation utilities */ void flecs_query_set_iter_this( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx); ecs_query_op_ctx_t* flecs_op_ctx_( const ecs_query_run_ctx_t *ctx); #define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) void flecs_reset_source_set_flag( ecs_iter_t *it, int32_t field_index); void flecs_set_source_set_flag( ecs_iter_t *it, int32_t field_index); ecs_table_range_t flecs_range_from_entity( ecs_entity_t e, const ecs_query_run_ctx_t *ctx); ecs_table_range_t flecs_query_var_get_range( int32_t var_id, const ecs_query_run_ctx_t *ctx); ecs_table_t* flecs_query_var_get_table( int32_t var_id, const ecs_query_run_ctx_t *ctx); ecs_table_t* flecs_query_get_table( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx); ecs_table_range_t flecs_query_get_range( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx); ecs_entity_t flecs_query_var_get_entity( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx); void flecs_query_var_reset( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx); void flecs_query_var_set_range( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx); void flecs_query_var_narrow_range( ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx); void flecs_query_var_set_entity( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_entity_t entity, const ecs_query_run_ctx_t *ctx); void flecs_query_set_vars( const ecs_query_op_t *op, ecs_id_t id, const ecs_query_run_ctx_t *ctx); ecs_table_range_t flecs_get_ref_range( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx); ecs_entity_t flecs_get_ref_entity( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx); ecs_id_t flecs_query_op_get_id_w_written( const ecs_query_op_t *op, uint64_t written, const ecs_query_run_ctx_t *ctx); ecs_id_t flecs_query_op_get_id( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx); int16_t flecs_query_next_column( ecs_table_t *table, ecs_id_t id, int32_t column); void flecs_query_it_set_column( ecs_iter_t *it, int32_t field_index, int32_t column); ecs_id_t flecs_query_it_set_id( ecs_iter_t *it, ecs_table_t *table, int32_t field_index, int32_t column); void flecs_query_set_match( const ecs_query_op_t *op, ecs_table_t *table, int32_t column, const ecs_query_run_ctx_t *ctx); void flecs_query_set_trav_match( const ecs_query_op_t *op, int32_t column, ecs_entity_t trav, ecs_entity_t second, const ecs_query_run_ctx_t *ctx); bool flecs_query_table_filter( ecs_table_t *table, ecs_query_lbl_t other, ecs_flags32_t filter_mask); void flecs_query_populate_field_from_range( ecs_iter_t *it, ecs_table_range_t *range, int8_t field_index, int32_t index); bool flecs_query_setids( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_run_until( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last); /* Select evaluation */ bool flecs_query_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_select_w_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_id_t id, ecs_flags32_t filter_mask); typedef enum ecs_query_up_select_trav_kind_t { FlecsQueryUpSelectUp, FlecsQueryUpSelectSelfUp } ecs_query_up_select_trav_kind_t; typedef enum ecs_query_up_select_kind_t { FlecsQueryUpSelectDefault, FlecsQueryUpSelectId, FlecsQueryUpSelectUnion } ecs_query_up_select_kind_t; bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind); bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_self_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool id_only); /* Populate data fields */ bool flecs_query_populate( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_populate_self( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_populate_sparse( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Union evaluation */ bool flecs_query_union_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_union( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_union_neq( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_union_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool neq); bool flecs_query_union_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_union_self_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); /* Toggle evaluation*/ bool flecs_query_toggle( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_toggle_option( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Equality predicate evaluation */ bool flecs_query_pred_eq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_neq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_eq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_neq_w_range( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_table_range_t r); bool flecs_query_pred_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_pred_neq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Component member evaluation */ bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); /* Transitive relationship traversal */ bool flecs_query_trav( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); /** * @file query/util.h * @brief Utility functions */ /* Helper type for passing around context required for error messages */ typedef struct { const ecs_world_t *world; const ecs_query_desc_t *desc; ecs_query_t *query; ecs_term_t *term; int32_t term_index; } ecs_query_validator_ctx_t; /* Convert integer to label */ ecs_query_lbl_t flecs_itolbl( int64_t val); /* Convert integer to variable id */ ecs_var_id_t flecs_itovar( int64_t val); /* Convert unsigned integer to variable id */ ecs_var_id_t flecs_utovar( uint64_t val); /* Get name for term ref */ const char* flecs_term_ref_var_name( ecs_term_ref_t *ref); /* Is term ref wildcard */ bool flecs_term_ref_is_wildcard( ecs_term_ref_t *ref); /* Does term use builtin predicates (eq, neq, ...)*/ bool flecs_term_is_builtin_pred( ecs_term_t *term); /* Does term have fixed id */ bool flecs_term_is_fixed_id( ecs_query_t *q, ecs_term_t *term); /* Is term part of OR chain */ bool flecs_term_is_or( const ecs_query_t *q, const ecs_term_t *term); /* Get ref flags (IsEntity) or IsVar) for ref (Src, First, Second) */ ecs_flags16_t flecs_query_ref_flags( ecs_flags16_t flags, ecs_flags16_t kind); /* Check if variable is written */ bool flecs_query_is_written( ecs_var_id_t var_id, uint64_t written); /* Check if ref is written (calls flecs_query_is_written)*/ bool flecs_ref_is_written( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t kind, uint64_t written); /* Get allocator from iterator */ ecs_allocator_t* flecs_query_get_allocator( const ecs_iter_t *it); /* Convert instruction kind to string */ const char* flecs_query_op_str( uint16_t kind); /* Convert term to string */ void flecs_term_to_buf( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf, int32_t t); #ifdef FLECS_DEBUG #define flecs_set_var_label(var, lbl) (var)->label = lbl #else #define flecs_set_var_label(var, lbl) #endif /* Finalize query data & validate */ int flecs_query_finalize_query( ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc); /* Internal function for creating iterator, doesn't run aperiodic tasks */ ecs_iter_t flecs_query_iter( const ecs_world_t *world, const ecs_query_t *q); /* Internal function for initializing an iterator after vars are constrained */ void flecs_query_iter_constrain( ecs_iter_t *it); /** * @file observable.h * @brief Functions for sending events. */ #ifndef FLECS_OBSERVABLE_H #define FLECS_OBSERVABLE_H /** 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; typedef struct ecs_observer_impl_t { ecs_observer_t pub; int32_t *last_event_id; /**< Last handled event id */ int32_t last_event_id_storage; ecs_id_t register_id; /**< Id observer is registered with (single term observers only) */ int32_t term_index; /**< Index of the term in parent observer (single term observers only) */ ecs_flags32_t flags; /**< Observer flags */ uint64_t id; /**< Internal id (not entity id) */ ecs_vec_t children; /**< If multi observer, vector stores child observers */ ecs_query_t *not_query; /**< Query used to populate observer data when a term with a not operator triggers. */ /* Mixins */ flecs_poly_dtor_t dtor; } ecs_observer_impl_t; #define flecs_observer_impl(observer) (ECS_CONST_CAST(ecs_observer_impl_t*, observer)) ecs_event_record_t* flecs_event_record_get( const ecs_observable_t *o, ecs_entity_t event); ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event); ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id); ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id); void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id); void flecs_observable_init( ecs_observable_t *observable); void flecs_observable_fini( ecs_observable_t *observable); bool flecs_observers_exist( ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event); ecs_observer_t* flecs_observer_init( ecs_world_t *world, ecs_entity_t entity, const ecs_observer_desc_t *desc); void flecs_observer_fini( ecs_observer_t *observer); void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_flags64_t set_mask, ecs_event_desc_t *desc); bool flecs_default_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); void flecs_observer_set_disable_bit( ecs_world_t *world, ecs_entity_t e, ecs_flags32_t bit, bool cond); #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); bool flecs_iter_next_row( ecs_iter_t *it); bool flecs_iter_next_instanced( ecs_iter_t *it, bool result); void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align); #define flecs_iter_calloc_t(it, T)\ flecs_iter_calloc(it, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_iter_calloc_n(it, T, count)\ flecs_iter_calloc(it, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) void flecs_iter_free( void *ptr, ecs_size_t size); #define flecs_iter_free_t(ptr, T)\ flecs_iter_free(ptr, ECS_SIZEOF(T)) #define flecs_iter_free_n(ptr, T, count)\ flecs_iter_free(ptr, ECS_SIZEOF(T) * count) #endif /** * @file poly.h * @brief Functions for managing poly objects. */ #ifndef FLECS_POLY_H #define FLECS_POLY_H #include /* Initialize poly */ void* flecs_poly_init_( ecs_poly_t *object, int32_t kind, ecs_size_t size, ecs_mixins_t *mixins); #define flecs_poly_init(object, type)\ flecs_poly_init_(object, type##_magic, sizeof(type), &type##_mixins) /* Deinitialize object for specified type */ void flecs_poly_fini_( ecs_poly_t *object, int32_t kind); #define flecs_poly_fini(object, type)\ flecs_poly_fini_(object, type##_magic) /* Utility functions for creating an object on the heap */ #define flecs_poly_new(type)\ (type*)flecs_poly_init(ecs_os_calloc_t(type), type) #define flecs_poly_free(obj, type)\ flecs_poly_fini(obj, type);\ ecs_os_free(obj) /* Get or create poly component for an entity */ EcsPoly* flecs_poly_bind_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_bind(world, entity, T) \ flecs_poly_bind_(world, entity, T##_tag) void flecs_poly_modified_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_modified(world, entity, T) \ flecs_poly_modified_(world, entity, T##_tag) /* Get poly component for an entity */ const EcsPoly* flecs_poly_bind_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_bind_get(world, entity, T) \ flecs_poly_bind_get_(world, entity, T##_tag) ecs_poly_t* flecs_poly_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define flecs_poly_get(world, entity, T) \ ((T*)flecs_poly_get_(world, entity, T##_tag)) /* Utilities for testing/asserting an object type */ #ifndef FLECS_NDEBUG #define flecs_poly_assert(object, ty)\ do {\ ecs_assert(object != NULL, ECS_INVALID_PARAMETER, NULL);\ const ecs_header_t *hdr = (const ecs_header_t *)object;\ const char *type_name = hdr->mixins->type_name;\ ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, type_name);\ ecs_assert(hdr->type == ty##_magic, ECS_INVALID_PARAMETER, type_name);\ } while (0) #else #define flecs_poly_assert(object, ty) #endif ecs_observable_t* ecs_get_observable( const ecs_poly_t *object); flecs_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 /* Post-frame merge actions */ void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_cmd( ecs_stage_t *stage); bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component); bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value); bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out); bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name); bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity); bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity); bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action); bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component, bool enable); bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id); void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t op_kind, ecs_entity_t entity, ecs_entity_t component, ecs_size_t size, void *value, bool *is_new); bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage); void flecs_enqueue( ecs_world_t *world, ecs_stage_t *stage, ecs_event_desc_t *desc); void flecs_commands_push( ecs_stage_t *stage); void flecs_commands_pop( ecs_stage_t *stage); ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system); ecs_allocator_t* flecs_stage_get_allocator( ecs_world_t *world); ecs_stack_t* flecs_stage_get_stack_allocator( ecs_world_t *world); #endif /** * @file world.h * @brief World-level API. */ #ifndef FLECS_WORLD_H #define FLECS_WORLD_H /* Get current stage */ ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr); /* Get current thread-specific stage from readonly world */ 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); /* Convenience macro's for world allocator */ #define flecs_walloc(world, size)\ flecs_alloc(&world->allocator, size) #define flecs_walloc_t(world, T)\ flecs_alloc_t(&world->allocator, T) #define flecs_walloc_n(world, T, count)\ flecs_alloc_n(&world->allocator, T, count) #define flecs_wcalloc(world, size)\ flecs_calloc(&world->allocator, size) #define flecs_wfree_t(world, T, ptr)\ flecs_free_t(&world->allocator, T, ptr) #define flecs_wcalloc_n(world, T, count)\ flecs_calloc_n(&world->allocator, T, count) #define flecs_wfree(world, size, ptr)\ flecs_free(&world->allocator, size, ptr) #define flecs_wfree_n(world, T, count, ptr)\ flecs_free_n(&world->allocator, T, count, ptr) #define flecs_wrealloc(world, size_dst, size_src, ptr)\ flecs_realloc(&world->allocator, size_dst, size_src, ptr) #define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) #define flecs_wdup(world, size, ptr)\ flecs_dup(&world->allocator, size, ptr) #define flecs_wdup_n(world, T, count, ptr)\ flecs_dup_n(&world->allocator, T, count, ptr) #endif /** * @file datastructures/name_index.h * @brief Data structure for resolving 64bit keys by string (name). */ #ifndef FLECS_NAME_INDEX_H #define FLECS_NAME_INDEX_H void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator); void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator); bool flecs_name_index_is_init( const ecs_hashmap_t *hm); ecs_hashmap_t* flecs_name_index_new( ecs_world_t *world, ecs_allocator_t *allocator); void flecs_name_index_fini( ecs_hashmap_t *map); void flecs_name_index_free( ecs_hashmap_t *map); ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *dst); ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash); const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash); void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t id, uint64_t hash); void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name); #endif //////////////////////////////////////////////////////////////////////////////// //// Bootstrap API //////////////////////////////////////////////////////////////////////////////// /* Bootstrap world */ void flecs_bootstrap( ecs_world_t *world); #define flecs_bootstrap_component(world, id_)\ ecs_component_init(world, &(ecs_component_desc_t){\ .entity = ecs_entity(world, { .id = ecs_id(id_), .name = #id_, .symbol = #id_ }),\ .type.size = sizeof(id_),\ .type.alignment = ECS_ALIGNOF(id_)\ }); #define flecs_bootstrap_tag(world, name)\ ecs_make_alive(world, name);\ ecs_add_id(world, name, EcsFinal);\ ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ ecs_set_name(world, name, (const char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ ecs_set_symbol(world, name, #name); #define flecs_bootstrap_trait(world, name)\ flecs_bootstrap_tag(world, name)\ ecs_add_id(world, name, EcsTrait) /* Bootstrap functions for other parts in the code */ void flecs_bootstrap_hierarchy(ecs_world_t *world); //////////////////////////////////////////////////////////////////////////////// //// Entity API //////////////////////////////////////////////////////////////////////////////// /* Mark an entity as being watched. This is used to trigger automatic rematching * when entities used in system expressions change their components. */ void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag); void flecs_record_add_flag( ecs_record_t *record, uint32_t flag); ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e); void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *diff); void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *type, bool owned); int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table); void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count); void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_id_record_t *table_index, int32_t recur_depth); void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook); //////////////////////////////////////////////////////////////////////////////// //// Query API //////////////////////////////////////////////////////////////////////////////// void flecs_query_apply_iter_flags( ecs_iter_t *it, const ecs_query_t *query); //////////////////////////////////////////////////////////////////////////////// //// Safe(r) integer casting //////////////////////////////////////////////////////////////////////////////// #define FLECS_CONVERSION_ERR(T, value)\ "illegal conversion from value " #value " to type " #T #define flecs_signed_char__ (CHAR_MIN < 0) #define flecs_signed_short__ true #define flecs_signed_int__ true #define flecs_signed_long__ true #define flecs_signed_size_t__ false #define flecs_signed_int8_t__ true #define flecs_signed_int16_t__ true #define flecs_signed_int32_t__ true #define flecs_signed_int64_t__ true #define flecs_signed_intptr_t__ true #define flecs_signed_uint8_t__ false #define flecs_signed_uint16_t__ false #define flecs_signed_uint32_t__ false #define flecs_signed_uint64_t__ false #define flecs_signed_uintptr_t__ false #define flecs_signed_ecs_size_t__ true #define flecs_signed_ecs_entity_t__ false uint64_t flecs_ito_( size_t dst_size, bool dst_signed, bool lt_zero, uint64_t value, const char *err); #ifndef FLECS_NDEBUG #define flecs_ito(T, value)\ (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ (value) < 0,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #define flecs_uto(T, value)\ (T)flecs_ito_(\ sizeof(T),\ flecs_signed_##T##__,\ false,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #else #define flecs_ito(T, value) (T)(value) #define flecs_uto(T, value) (T)(value) #endif #define flecs_itosize(value) flecs_ito(size_t, (value)) #define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) #define flecs_itoi16(value) flecs_ito(int16_t, (value)) #define flecs_itoi32(value) flecs_ito(int32_t, (value)) //////////////////////////////////////////////////////////////////////////////// //// Utilities //////////////////////////////////////////////////////////////////////////////// uint64_t flecs_hash( const void *data, ecs_size_t length); uint64_t flecs_wyhash( const void *data, ecs_size_t length); /* Get next power of 2 */ int32_t flecs_next_pow_of_2( int32_t n); /* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the * entity index */ ecs_record_t flecs_to_row( uint64_t value); /* Get 64bit integer from ecs_record_t */ uint64_t flecs_from_row( ecs_record_t record); /* Convert a symbol name to an entity name by removing the prefix */ const char* flecs_name_from_symbol( ecs_world_t *world, const char *type_name); /* Compare function for entity ids used for order_by */ int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); /* Compare function for component ids used for qsort */ int flecs_id_qsort_cmp( const void *a, const void *b); /* Load file contents into string */ char* flecs_load_from_file( const char *filename); bool flecs_name_is_id( const char *name); ecs_entity_t flecs_name_to_id( const char *name); /* Convert floating point to string */ char * ecs_ftoa( double f, char * buf, int precision); uint64_t flecs_string_hash( const void *ptr); void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm); void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf); int32_t flecs_search_w_idr( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t *id_out, ecs_id_record_t *idr); int32_t flecs_search_relation_w_idr( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags64_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, ecs_id_record_t *idr); bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_id_record_t *idr, ecs_id_t id); int ecs_term_finalize( const ecs_world_t *world, ecs_term_t *term); int32_t flecs_query_pivot_term( const ecs_world_t *world, const ecs_query_t *query); #endif /* -- Identifier Component -- */ static ECS_DTOR(EcsIdentifier, ptr, { ecs_os_strset(&ptr->value, NULL); }) static ECS_COPY(EcsIdentifier, dst, src, { ecs_os_strset(&dst->value, src->value); dst->hash = src->hash; dst->length = src->length; dst->index_hash = src->index_hash; dst->index = src->index; }) static ECS_MOVE(EcsIdentifier, dst, src, { ecs_os_strset(&dst->value, NULL); dst->value = src->value; dst->hash = src->hash; dst->length = src->length; dst->index_hash = src->index_hash; dst->index = src->index; src->value = NULL; src->hash = 0; src->index_hash = 0; src->index = 0; src->length = 0; }) static void ecs_on_set(EcsIdentifier)(ecs_iter_t *it) { EcsIdentifier *ptr = ecs_field(it, EcsIdentifier, 0); 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)) { flecs_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) { flecs_poly_dtor_t *dtor = ecs_get_dtor(ptr->poly); ecs_assert(dtor != NULL, ECS_INTERNAL_ERROR, NULL); dtor[0](ptr->poly); } }) /* -- Builtin triggers -- */ static void flecs_assert_relation_unused( ecs_world_t *world, ecs_entity_t rel, ecs_entity_t property) { if (world->flags & (EcsWorldInit|EcsWorldFini)) { return; } ecs_vec_t *marked_ids = &world->store.marked_ids; int32_t i, count = ecs_vec_count(marked_ids); for (i = 0; i < count; i ++) { ecs_marked_id_t *mid = ecs_vec_get_t(marked_ids, ecs_marked_id_t, i); if (mid->id == ecs_pair(rel, EcsWildcard)) { /* If id is being cleaned up, no need to throw error as tables will * be cleaned up */ return; } } bool in_use = ecs_id_in_use(world, ecs_pair(rel, EcsWildcard)); /* Hack to make enum unions work. C++ enum reflection registers enum * constants right after creating the enum entity. The enum constant * entities have a component of the enum type with the constant value, which * is why it shows up as in use. */ if (property != EcsUnion) { in_use |= ecs_id_in_use(world, rel); } if (in_use) { char *r_str = ecs_get_path(world, rel); char *p_str = ecs_get_path(world, 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_world_t *world, ecs_id_record_t *idr, ecs_flags32_t flag) { if (!(idr->flags & flag)) { idr->flags |= flag; if (flag == EcsIdIsSparse) { flecs_id_record_init_sparse(world, idr); } return true; } return false; } static bool flecs_unset_id_flag( ecs_id_record_t *idr, ecs_flags32_t flag) { if ((idr->flags & flag)) { idr->flags &= ~flag; return true; } return false; } static void flecs_register_id_flag_for_relation( ecs_iter_t *it, ecs_entity_t prop, ecs_flags32_t flag, ecs_flags32_t not_flag, ecs_flags32_t entity_flag) { ecs_world_t *world = it->world; ecs_entity_t event = it->event; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; bool changed = false; if (event == EcsOnAdd) { ecs_id_record_t *idr; if (!ecs_has_id(world, e, EcsRelationship) && !ecs_has_id(world, e, EcsTarget)) { idr = flecs_id_record_ensure(world, e); changed |= flecs_set_id_flag(world, idr, flag); } idr = flecs_id_record_ensure(world, ecs_pair(e, EcsWildcard)); do { changed |= flecs_set_id_flag(world, 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_path(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_tag(ecs_iter_t *it) { flecs_register_id_flag_for_relation(it, EcsPairIsTag, 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, EcsPairIsTag); } idr->type_info = NULL; } while ((idr = idr->first.next)); } } } static void flecs_register_on_delete(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 0); flecs_register_id_flag_for_relation(it, EcsOnDelete, ECS_ID_ON_DELETE_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteMask, EcsEntityIsId); } static void flecs_register_on_delete_object(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 0); flecs_register_id_flag_for_relation(it, EcsOnDeleteTarget, ECS_ID_ON_DELETE_TARGET_FLAG(ECS_PAIR_SECOND(id)), EcsIdOnDeleteObjectMask, EcsEntityIsId); } static void flecs_register_on_instantiate(ecs_iter_t *it) { ecs_id_t id = ecs_field_id(it, 0); flecs_register_id_flag_for_relation(it, EcsOnInstantiate, ECS_ID_ON_INSTANTIATE_FLAG(ECS_PAIR_SECOND(id)), 0, 0); } typedef struct ecs_on_trait_ctx_t { ecs_flags32_t flag, not_flag; } ecs_on_trait_ctx_t; static void flecs_register_trait(ecs_iter_t *it) { ecs_on_trait_ctx_t *ctx = it->ctx; flecs_register_id_flag_for_relation( it, it->ids[0], ctx->flag, ctx->not_flag, 0); } static void flecs_register_trait_pair(ecs_iter_t *it) { ecs_on_trait_ctx_t *ctx = it->ctx; flecs_register_id_flag_for_relation( it, ecs_pair_first(it->world, it->ids[0]), ctx->flag, ctx->not_flag, 0); } static void flecs_register_slot_of(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_add_id(it->world, it->entities[i], EcsUnion); } } static void flecs_on_symmetric_add_remove(ecs_iter_t *it) { ecs_entity_t pair = ecs_field_id(it, 0); if (!ECS_HAS_ID_FLAG(pair, PAIR)) { /* If relationship was not added as a pair, there's nothing to do */ return; } ecs_world_t *world = it->world; ecs_entity_t rel = ECS_PAIR_FIRST(pair); ecs_entity_t obj = ecs_pair_second(world, pair); ecs_entity_t event = it->event; if (obj) { int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t subj = it->entities[i]; if (event == EcsOnAdd) { if (!ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { ecs_add_pair(it->world, obj, rel, subj); } } else { if (ecs_has_id(it->real_world, obj, ecs_pair(rel, subj))) { ecs_remove_pair(it->world, obj, rel, subj); } } } } } static void flecs_register_symmetric(ecs_iter_t *it) { ecs_world_t *world = it->real_world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t r = it->entities[i]; flecs_assert_relation_unused(world, r, EcsSymmetric); /* Create observer that adds the reverse relationship when R(X, Y) is * added, or remove the reverse relationship when R(X, Y) is removed. */ ecs_observer(world, { .entity = ecs_entity(world, { .parent = r }), .query.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, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; uint32_t component_id = (uint32_t)e; /* Strip generation */ ecs_assert(component_id < ECS_MAX_COMPONENT_ID, ECS_OUT_OF_RANGE, "component id must be smaller than %u", ECS_MAX_COMPONENT_ID); (void)component_id; if (it->event != EcsOnRemove) { ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } if (it->event == EcsOnSet) { if (flecs_type_info_init_id( world, e, c[i].size, c[i].alignment, NULL)) { flecs_assert_relation_unused(world, e, ecs_id(EcsComponent)); } } else if (it->event == EcsOnRemove) { flecs_type_info_free(world, e); } } } static void flecs_ensure_module_tag(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } } static void flecs_disable_observer( ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_entity_t evt = it->event; int32_t i, count = it->count; for (i = 0; i < count; i ++) { flecs_observer_set_disable_bit(world, it->entities[i], EcsObserverIsDisabled, evt == EcsOnAdd); } } static void flecs_disable_module_observers( ecs_world_t *world, ecs_entity_t module, bool should_disable) { ecs_iter_t child_it = ecs_children(world, module); while (ecs_children_next(&child_it)) { ecs_table_t *table = child_it.table; bool table_disabled = table->flags & EcsTableIsDisabled; int32_t i; /* Recursively walk modules, don't propagate to disabled modules */ if (ecs_table_has_id(world, table, EcsModule) && !table_disabled) { for (i = 0; i < child_it.count; i ++) { flecs_disable_module_observers( world, child_it.entities[i], should_disable); } continue; } /* Only disable observers */ if (!ecs_table_has_id(world, table, EcsObserver)) { continue; } for (i = 0; i < child_it.count; i ++) { flecs_observer_set_disable_bit(world, child_it.entities[i], EcsObserverIsParentDisabled, should_disable); } } } static void flecs_disable_module(ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { flecs_disable_module_observers( it->real_world, it->entities[i], it->event == EcsOnAdd); } } /* -- Bootstrapping -- */ #define flecs_bootstrap_builtin_t(world, table, name)\ flecs_bootstrap_builtin(world, table, ecs_id(name), #name, sizeof(name),\ ECS_ALIGNOF(name)) static void flecs_bootstrap_builtin( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, const char *symbol, ecs_size_t size, ecs_size_t alignment) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *columns = table->data.columns; ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_ensure(world, entity); record->table = table; int32_t index = flecs_table_append(world, table, entity, false, false); record->row = ECS_ROW_TO_RECORD(index, 0); EcsComponent *component = ecs_vec_first(&columns[0].data); component[index].size = size; component[index].alignment = alignment; const char *name = &symbol[3]; /* Strip 'Ecs' */ ecs_size_t symbol_length = ecs_os_strlen(symbol); ecs_size_t name_length = symbol_length - 3; EcsIdentifier *name_col = ecs_vec_first(&columns[1].data); uint64_t name_hash = flecs_hash(name, name_length); name_col[index].value = ecs_os_strdup(name); name_col[index].length = name_length; name_col[index].hash = name_hash; name_col[index].index_hash = 0; name_col[index].index = table->_->name_index; flecs_name_index_ensure( table->_->name_index, entity, name, name_length, name_hash); EcsIdentifier *symbol_col = ecs_vec_first(&columns[2].data); symbol_col[index].value = ecs_os_strdup(symbol); symbol_col[index].length = symbol_length; symbol_col[index].hash = flecs_hash(symbol, symbol_length); symbol_col[index].index_hash = 0; symbol_col[index].index = NULL; } /** Initialize component table. This table is manually constructed to bootstrap * flecs. After this function has been called, the builtin components can be * created. * The reason this table is constructed manually is because it requires the size * and alignment of the EcsComponent and EcsIdentifier components, which haven't * been created yet */ static ecs_table_t* flecs_bootstrap_component_table( ecs_world_t *world) { /* Before creating table, manually set flags for ChildOf/Identifier, as this * can no longer be done after they are in use. */ ecs_id_record_t *idr = flecs_id_record_ensure(world, EcsChildOf); idr->flags |= EcsIdOnDeleteObjectDelete | EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdTag; /* Initialize id records cached on world */ world->idr_childof_wildcard = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, EcsWildcard)); world->idr_childof_wildcard->flags |= EcsIdOnDeleteObjectDelete | EcsIdOnInstantiateDontInherit | EcsIdTraversable | EcsIdTag | EcsIdExclusive; idr = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsWildcard)); idr->flags |= EcsIdOnInstantiateDontInherit; world->idr_identifier_name = flecs_id_record_ensure(world, ecs_pair_t(EcsIdentifier, EcsName)); world->idr_childof_0 = flecs_id_record_ensure(world, ecs_pair(EcsChildOf, 0)); ecs_id_t ids[] = { ecs_id(EcsComponent), EcsFinal, ecs_pair_t(EcsIdentifier, EcsName), ecs_pair_t(EcsIdentifier, EcsSymbol), ecs_pair(EcsChildOf, EcsFlecsCore), ecs_pair(EcsOnDelete, EcsPanic) }; ecs_type_t array = { .array = ids, .count = 6 }; ecs_table_t *result = flecs_table_find_or_create(world, &array); ecs_data_t *data = &result->data; /* Preallocate enough memory for initial components */ ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &data->entities, ecs_entity_t, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[0].data, EcsComponent, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[1].data, EcsIdentifier, EcsFirstUserComponentId); ecs_vec_init_t(a, &data->columns[2].data, EcsIdentifier, EcsFirstUserComponentId); return result; } static void flecs_bootstrap_entity( ecs_world_t *world, ecs_entity_t id, const char *name, ecs_entity_t parent) { char symbol[256]; ecs_os_strcpy(symbol, "flecs.core."); ecs_os_strcat(symbol, name); ecs_make_alive(world, id); ecs_add_pair(world, id, EcsChildOf, parent); ecs_set_name(world, id, name); ecs_set_symbol(world, id, symbol); ecs_assert(ecs_get_name(world, id) != NULL, ECS_INTERNAL_ERROR, NULL); if (!parent || parent == EcsFlecsCore) { ecs_assert(ecs_lookup(world, name) == id, ECS_INTERNAL_ERROR, NULL); } } void flecs_bootstrap( ecs_world_t *world) { ecs_log_push(); ecs_set_name_prefix(world, "Ecs"); /* Ensure builtin ids are alive */ ecs_make_alive(world, ecs_id(EcsComponent)); ecs_make_alive(world, EcsFinal); ecs_make_alive(world, ecs_id(EcsIdentifier)); ecs_make_alive(world, EcsName); ecs_make_alive(world, EcsSymbol); ecs_make_alive(world, EcsAlias); ecs_make_alive(world, EcsChildOf); ecs_make_alive(world, EcsFlecs); ecs_make_alive(world, EcsFlecsCore); ecs_make_alive(world, EcsOnAdd); ecs_make_alive(world, EcsOnRemove); ecs_make_alive(world, EcsOnSet); ecs_make_alive(world, EcsOnDelete); ecs_make_alive(world, EcsPanic); ecs_make_alive(world, EcsFlag); ecs_make_alive(world, EcsIsA); ecs_make_alive(world, EcsWildcard); ecs_make_alive(world, EcsAny); ecs_make_alive(world, EcsPairIsTag); ecs_make_alive(world, EcsCanToggle); ecs_make_alive(world, EcsTrait); ecs_make_alive(world, EcsRelationship); ecs_make_alive(world, EcsTarget); ecs_make_alive(world, EcsSparse); ecs_make_alive(world, EcsUnion); /* Register type information for builtin components */ flecs_type_info_init(world, EcsComponent, { .ctor = flecs_default_ctor, .on_set = flecs_on_component, .on_remove = flecs_on_component }); flecs_type_info_init(world, EcsIdentifier, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsIdentifier), .copy = ecs_copy(EcsIdentifier), .move = ecs_move(EcsIdentifier), .on_set = ecs_on_set(EcsIdentifier), .on_remove = ecs_on_set(EcsIdentifier) }); flecs_type_info_init(world, EcsPoly, { .ctor = flecs_default_ctor, .copy = ecs_copy(EcsPoly), .move = ecs_move(EcsPoly), .dtor = ecs_dtor(EcsPoly) }); flecs_type_info_init(world, EcsDefaultChildComponent, { .ctor = flecs_default_ctor, }); /* Create and cache often used id records on world */ flecs_init_id_records(world); /* Create table for builtin components. This table temporarily stores the * entities associated with builtin components, until they get moved to * other tables once properties are added (see below) */ ecs_table_t *table = flecs_bootstrap_component_table(world); assert(table != NULL); /* Bootstrap builtin components */ flecs_bootstrap_builtin_t(world, table, EcsIdentifier); flecs_bootstrap_builtin_t(world, table, EcsComponent); flecs_bootstrap_builtin_t(world, table, EcsPoly); flecs_bootstrap_builtin_t(world, table, EcsDefaultChildComponent); /* Initialize default entity id range */ world->info.last_component_id = EcsFirstUserComponentId; flecs_entities_max_id(world) = EcsFirstUserEntityId; world->info.min_id = 0; world->info.max_id = 0; /* Register observer for tag property before adding EcsPairIsTag */ ecs_observer(world, { .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), .query.terms[0] = { .id = EcsPairIsTag, .src.id = 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, EcsNotQueryable); flecs_bootstrap_tag(world, EcsEmpty); /* Initialize builtin modules */ ecs_set_name(world, EcsFlecs, "flecs"); ecs_add_id(world, EcsFlecs, EcsModule); ecs_add_pair(world, EcsFlecs, EcsOnDelete, EcsPanic); ecs_add_pair(world, EcsFlecsCore, EcsChildOf, EcsFlecs); ecs_set_name(world, EcsFlecsCore, "core"); ecs_add_id(world, EcsFlecsCore, EcsModule); ecs_add_pair(world, EcsFlecsInternals, EcsChildOf, EcsFlecsCore); ecs_set_name(world, EcsFlecsInternals, "internals"); ecs_add_id(world, EcsFlecsInternals, EcsModule); /* Self check */ ecs_record_t *r = flecs_entities_get(world, EcsFlecs); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->row & EcsEntityIsTraversable, ECS_INTERNAL_ERROR, NULL); (void)r; /* Initialize builtin entities */ flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); flecs_bootstrap_entity(world, EcsAny, "_", EcsFlecsCore); flecs_bootstrap_entity(world, EcsThis, "this", EcsFlecsCore); flecs_bootstrap_entity(world, EcsVariable, "$", EcsFlecsCore); flecs_bootstrap_entity(world, EcsFlag, "Flag", EcsFlecsCore); /* Component/relationship properties */ flecs_bootstrap_trait(world, EcsTransitive); flecs_bootstrap_trait(world, EcsReflexive); flecs_bootstrap_trait(world, EcsSymmetric); flecs_bootstrap_trait(world, EcsFinal); flecs_bootstrap_trait(world, EcsPairIsTag); flecs_bootstrap_trait(world, EcsExclusive); flecs_bootstrap_trait(world, EcsAcyclic); flecs_bootstrap_trait(world, EcsTraversable); flecs_bootstrap_trait(world, EcsWith); flecs_bootstrap_trait(world, EcsOneOf); flecs_bootstrap_trait(world, EcsCanToggle); flecs_bootstrap_trait(world, EcsTrait); flecs_bootstrap_trait(world, EcsRelationship); flecs_bootstrap_trait(world, EcsTarget); flecs_bootstrap_trait(world, EcsOnDelete); flecs_bootstrap_trait(world, EcsOnDeleteTarget); flecs_bootstrap_trait(world, EcsOnInstantiate); flecs_bootstrap_trait(world, EcsSparse); flecs_bootstrap_trait(world, EcsUnion); flecs_bootstrap_tag(world, EcsRemove); flecs_bootstrap_tag(world, EcsDelete); flecs_bootstrap_tag(world, EcsPanic); flecs_bootstrap_tag(world, EcsOverride); flecs_bootstrap_tag(world, EcsInherit); flecs_bootstrap_tag(world, EcsDontInherit); /* Builtin predicates */ flecs_bootstrap_tag(world, EcsPredEq); flecs_bootstrap_tag(world, EcsPredMatch); flecs_bootstrap_tag(world, EcsPredLookup); flecs_bootstrap_tag(world, EcsScopeOpen); flecs_bootstrap_tag(world, EcsScopeClose); /* Builtin relationships */ flecs_bootstrap_tag(world, EcsIsA); flecs_bootstrap_tag(world, EcsChildOf); flecs_bootstrap_tag(world, EcsDependsOn); /* Builtin events */ flecs_bootstrap_entity(world, EcsOnAdd, "OnAdd", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnRemove, "OnRemove", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnSet, "OnSet", EcsFlecsCore); flecs_bootstrap_entity(world, EcsMonitor, "EcsMonitor", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableCreate, "OnTableCreate", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableDelete, "OnTableDelete", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", EcsFlecsCore); /* Unqueryable entities */ ecs_add_id(world, EcsThis, EcsNotQueryable); ecs_add_id(world, EcsWildcard, EcsNotQueryable); ecs_add_id(world, EcsAny, EcsNotQueryable); /* Tag relationships (relationships that should never have data) */ ecs_add_id(world, EcsIsA, EcsPairIsTag); ecs_add_id(world, EcsChildOf, EcsPairIsTag); ecs_add_id(world, EcsSlotOf, EcsPairIsTag); ecs_add_id(world, EcsDependsOn, EcsPairIsTag); ecs_add_id(world, EcsFlag, EcsPairIsTag); ecs_add_id(world, EcsWith, EcsPairIsTag); /* Exclusive properties */ ecs_add_id(world, EcsChildOf, EcsExclusive); ecs_add_id(world, EcsOnDelete, EcsExclusive); ecs_add_id(world, EcsOnDeleteTarget, EcsExclusive); ecs_add_id(world, EcsOnInstantiate, EcsExclusive); /* Relationships */ ecs_add_id(world, EcsChildOf, EcsRelationship); ecs_add_id(world, EcsIsA, EcsRelationship); ecs_add_id(world, EcsSlotOf, EcsRelationship); ecs_add_id(world, EcsDependsOn, EcsRelationship); ecs_add_id(world, EcsWith, EcsRelationship); ecs_add_id(world, EcsOnDelete, EcsRelationship); ecs_add_id(world, EcsOnDeleteTarget, EcsRelationship); ecs_add_id(world, EcsOnInstantiate, EcsRelationship); ecs_add_id(world, ecs_id(EcsIdentifier), EcsRelationship); /* Targets */ ecs_add_id(world, EcsOverride, EcsTarget); ecs_add_id(world, EcsInherit, EcsTarget); ecs_add_id(world, EcsDontInherit, EcsTarget); /* Sync properties of ChildOf and Identifier with bootstrapped flags */ ecs_add_pair(world, EcsChildOf, EcsOnDeleteTarget, EcsDelete); ecs_add_id(world, EcsChildOf, EcsTrait); ecs_add_id(world, EcsChildOf, EcsAcyclic); ecs_add_id(world, EcsChildOf, EcsTraversable); ecs_add_pair(world, EcsChildOf, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, ecs_id(EcsIdentifier), EcsOnInstantiate, EcsDontInherit); /* Create triggers in internals scope */ ecs_set_scope(world, EcsFlecsInternals); /* Register observers for components/relationship properties. Most observers * set flags on an id record when a property is added to a component, which * allows for quick property testing in various operations. */ ecs_observer(world, { .query.terms = {{ .id = EcsFinal, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_final }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard), .src.id = EcsSelf } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard), .src.id = EcsSelf } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete_object }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnInstantiate, EcsWildcard), .src.id = EcsSelf } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_on_instantiate }); ecs_observer(world, { .query.terms = {{ .id = EcsSymmetric, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_symmetric }); static ecs_on_trait_ctx_t traversable_trait = { EcsIdTraversable, EcsIdTraversable }; ecs_observer(world, { .query.terms = {{ .id = EcsTraversable, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_trait, .ctx = &traversable_trait }); static ecs_on_trait_ctx_t exclusive_trait = { EcsIdExclusive, EcsIdExclusive }; ecs_observer(world, { .query.terms = {{ .id = EcsExclusive, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_trait, .ctx = &exclusive_trait }); static ecs_on_trait_ctx_t toggle_trait = { EcsIdCanToggle, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsCanToggle, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &toggle_trait }); static ecs_on_trait_ctx_t with_trait = { EcsIdWith, 0 }; ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsWith, EcsWildcard), .src.id = EcsSelf }, }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait_pair, .ctx = &with_trait }); static ecs_on_trait_ctx_t sparse_trait = { EcsIdIsSparse, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsSparse, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &sparse_trait }); static ecs_on_trait_ctx_t union_trait = { EcsIdIsUnion, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsUnion, .src.id = EcsSelf }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_trait, .ctx = &union_trait }); /* Entities used as slot are marked as exclusive to ensure a slot can always * only point to a single entity. */ ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsSlotOf, EcsWildcard), .src.id = EcsSelf } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_slot_of }); /* Define observer to make sure that adding a module to a child entity also * adds it to the parent. */ ecs_observer(world, { .query.terms = {{ .id = EcsModule, .src.id = EcsSelf } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_ensure_module_tag }); /* Observer that tracks whether observers are disabled */ ecs_observer(world, { .query.terms = { { .id = EcsObserver, .src.id = EcsSelf, .inout = EcsInOutFilter }, { .id = EcsDisabled, .src.id = EcsSelf }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_observer }); /* Observer that tracks whether modules are disabled */ ecs_observer(world, { .query.terms = { { .id = EcsModule, .src.id = EcsSelf, .inout = EcsInOutFilter }, { .id = EcsDisabled, .src.id = EcsSelf }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_module }); /* Set scope back to flecs core */ ecs_set_scope(world, EcsFlecsCore); /* Traversable relationships are always acyclic */ ecs_add_pair(world, EcsTraversable, EcsWith, EcsAcyclic); /* Transitive relationships are always Traversable */ ecs_add_pair(world, EcsTransitive, EcsWith, EcsTraversable); /* DontInherit components */ ecs_add_pair(world, EcsPrefab, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, ecs_id(EcsComponent), EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, EcsOnDelete, EcsOnInstantiate, EcsDontInherit); ecs_add_pair(world, EcsUnion, EcsOnInstantiate, EcsDontInherit); /* Acyclic/Traversable components */ ecs_add_id(world, EcsIsA, EcsTraversable); ecs_add_id(world, EcsDependsOn, EcsTraversable); ecs_add_id(world, EcsWith, EcsAcyclic); /* Transitive relationships */ ecs_add_id(world, EcsIsA, EcsTransitive); ecs_add_id(world, EcsIsA, EcsReflexive); /* Exclusive properties */ ecs_add_id(world, EcsSlotOf, EcsExclusive); ecs_add_id(world, EcsOneOf, EcsExclusive); /* Private properties */ ecs_add_id(world, ecs_id(EcsPoly), EcsPrivate); ecs_add_id(world, ecs_id(EcsIdentifier), EcsPrivate); /* Inherited components */ ecs_add_pair(world, EcsIsA, EcsOnInstantiate, EcsInherit); ecs_add_pair(world, EcsDependsOn, EcsOnInstantiate, EcsInherit); ecs_add_pair(world, EcsDisabled, EcsOnInstantiate, EcsInherit); /* Run bootstrap functions for other parts of the code */ flecs_bootstrap_hierarchy(world); ecs_set_scope(world, 0); ecs_set_name_prefix(world, NULL); ecs_assert(world->idr_childof_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(world->idr_isa_wildcard != NULL, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } /** * @file query/each.c * @brief Simple iterator for a single component id. */ ecs_iter_t ecs_each_id( const ecs_world_t *stage, ecs_id_t id) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); flecs_process_pending_tables(world); ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), .field_count = 1, .next = ecs_each_next }; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return it; } ecs_each_iter_t *each_iter = &it.priv_.iter.each; each_iter->ids = id; each_iter->sizes = 0; if (idr->type_info) { each_iter->sizes = idr->type_info->size; } each_iter->sources = 0; flecs_table_cache_iter((ecs_table_cache_t*)idr, &each_iter->it); return it; error: return (ecs_iter_t){0}; } bool ecs_each_next( ecs_iter_t *it) { ecs_each_iter_t *each_iter = &it->priv_.iter.each; ecs_table_record_t *next = flecs_table_cache_next( &each_iter->it, ecs_table_record_t); it->flags |= EcsIterIsValid; if (next) { ecs_table_t *table = next->hdr.table; it->table = table; it->count = ecs_table_count(table); it->entities = flecs_table_entities_array(table); it->ids = &table->type.array[next->index]; it->columns = &each_iter->columns; it->sources = &each_iter->sources; it->sizes = &each_iter->sizes; it->ptrs = &each_iter->ptrs; it->set_fields = 1; each_iter->columns = next->index; if (next->column != -1) { each_iter->ptrs = ecs_vec_first( &it->table->data.columns[next->column].data); } return true; } else { return false; } } ecs_iter_t ecs_children( const ecs_world_t *stage, ecs_entity_t parent) { return ecs_each_id(stage, ecs_childof(parent)); } bool ecs_children_next( ecs_iter_t *it) { return ecs_each_next(it); } /** * @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 #ifdef FLECS_SCRIPT /** * @file addons/script/script.h * @brief Flecs script implementation. */ #ifndef FLECS_SCRIPT_PRIVATE_H #define FLECS_SCRIPT_PRIVATE_H #ifdef FLECS_SCRIPT #include typedef struct ecs_script_scope_t ecs_script_scope_t; typedef struct ecs_script_entity_t ecs_script_entity_t; typedef struct ecs_script_impl_t { ecs_script_t pub; ecs_allocator_t allocator; ecs_script_scope_t *root; char *token_buffer; int32_t token_buffer_size; int32_t refcount; } ecs_script_impl_t; typedef struct ecs_script_parser_t ecs_script_parser_t; #define flecs_script_impl(script) ((ecs_script_impl_t*)script) /** * @file addons/script/tokenizer.h * @brief Script tokenizer. */ #ifndef FLECS_SCRIPT_TOKENIZER_H #define FLECS_SCRIPT_TOKENIZER_H /* Tokenizer */ typedef enum ecs_script_token_kind_t { EcsTokEnd = '\0', EcsTokUnknown, EcsTokScopeOpen = '{', EcsTokScopeClose = '}', EcsTokParenOpen = '(', EcsTokParenClose = ')', EcsTokBracketOpen = '[', EcsTokBracketClose = ']', EcsTokMul = '*', EcsTokComma = ',', EcsTokSemiColon = ';', EcsTokColon = ':', EcsTokAssign = '=', EcsTokBitwiseOr = '|', EcsTokNot = '!', EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', EcsTokEq, EcsTokNeq, EcsTokMatch, EcsTokOr, EcsTokIdentifier, EcsTokString, EcsTokNumber, EcsTokKeywordModule, EcsTokKeywordUsing, EcsTokKeywordWith, EcsTokKeywordIf, EcsTokKeywordElse, EcsTokKeywordTemplate, EcsTokKeywordProp, EcsTokKeywordConst, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { const char *value; ecs_script_token_kind_t kind; } ecs_script_token_t; typedef struct ecs_script_tokens_t { int32_t count; ecs_script_token_t tokens[256]; } ecs_script_tokens_t; const char* flecs_script_expr( ecs_script_parser_t *parser, const char *ptr, ecs_script_token_t *out, char until); const char* flecs_script_until( ecs_script_parser_t *parser, const char *ptr, ecs_script_token_t *out, char until); const char* flecs_script_token_kind_str( ecs_script_token_kind_t kind); const char* flecs_script_token( ecs_script_parser_t *parser, const char *ptr, ecs_script_token_t *out, bool is_lookahead); const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos); #endif struct ecs_script_parser_t { ecs_script_impl_t *script; ecs_script_scope_t *scope; const char *pos; char *token_cur; char *token_keep; bool significant_newline; /* For term parser */ ecs_term_t *term; ecs_oper_kind_t extra_oper; ecs_term_ref_t *extra_args; }; /** * @file addons/script/ast.h * @brief Script AST. */ #ifndef FLECS_SCRIPT_AST_H #define FLECS_SCRIPT_AST_H typedef enum ecs_script_node_kind_t { EcsAstScope, EcsAstTag, EcsAstComponent, EcsAstDefaultComponent, EcsAstVarComponent, EcsAstWithVar, EcsAstWithTag, EcsAstWithComponent, EcsAstWith, EcsAstUsing, EcsAstModule, EcsAstAnnotation, EcsAstTemplate, EcsAstProp, EcsAstConst, EcsAstEntity, EcsAstPairScope, EcsAstIf, } ecs_script_node_kind_t; typedef struct ecs_script_node_t { ecs_script_node_kind_t kind; const char *pos; } ecs_script_node_t; struct ecs_script_scope_t { ecs_script_node_t node; ecs_vec_t stmts; ecs_script_scope_t *parent; ecs_id_t default_component_eval; }; typedef struct ecs_script_id_t { const char *first; const char *second; ecs_id_t flag; ecs_id_t eval; } ecs_script_id_t; typedef struct ecs_script_tag_t { ecs_script_node_t node; ecs_script_id_t id; } ecs_script_tag_t; typedef struct ecs_script_component_t { ecs_script_node_t node; ecs_script_id_t id; const char *expr; ecs_value_t eval; bool is_collection; } ecs_script_component_t; typedef struct ecs_script_default_component_t { ecs_script_node_t node; const char *expr; ecs_value_t eval; } ecs_script_default_component_t; typedef struct ecs_script_var_component_t { ecs_script_node_t node; const char *name; } ecs_script_var_component_t; struct ecs_script_entity_t { ecs_script_node_t node; const char *kind; const char *name; bool name_is_var; bool kind_w_expr; ecs_script_scope_t *scope; // Populated during eval ecs_script_entity_t *parent; ecs_entity_t eval; ecs_entity_t eval_kind; }; typedef struct ecs_script_with_t { ecs_script_node_t node; ecs_script_scope_t *expressions; ecs_script_scope_t *scope; } ecs_script_with_t; typedef struct ecs_script_inherit_t { ecs_script_node_t node; ecs_script_scope_t *base_list; } ecs_script_inherit_t; typedef struct ecs_script_pair_scope_t { ecs_script_node_t node; ecs_script_id_t id; ecs_script_scope_t *scope; } ecs_script_pair_scope_t; typedef struct ecs_script_using_t { ecs_script_node_t node; const char *name; } ecs_script_using_t; typedef struct ecs_script_module_t { ecs_script_node_t node; const char *name; } ecs_script_module_t; typedef struct ecs_script_annot_t { ecs_script_node_t node; const char *name; const char *expr; } ecs_script_annot_t; typedef struct ecs_script_template_node_t { ecs_script_node_t node; const char *name; ecs_script_scope_t* scope; } ecs_script_template_node_t; typedef struct ecs_script_var_node_t { ecs_script_node_t node; const char *name; const char *type; const char *expr; } ecs_script_var_node_t; typedef struct ecs_script_if_t { ecs_script_node_t node; ecs_script_scope_t *if_true; ecs_script_scope_t *if_false; const char *expr; } ecs_script_if_t; #define ecs_script_node(kind, node)\ ((ecs_script_##kind##_t*)node) bool flecs_scope_is_empty( ecs_script_scope_t *scope); ecs_script_scope_t* flecs_script_insert_scope( ecs_script_parser_t *parser); ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, const char *name); ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_script_parser_t *parser, const char *first, const char *second); ecs_script_with_t* flecs_script_insert_with( ecs_script_parser_t *parser); ecs_script_using_t* flecs_script_insert_using( ecs_script_parser_t *parser, const char *name); ecs_script_module_t* flecs_script_insert_module( ecs_script_parser_t *parser, const char *name); ecs_script_template_node_t* flecs_script_insert_template( ecs_script_parser_t *parser, const char *name); ecs_script_annot_t* flecs_script_insert_annot( ecs_script_parser_t *parser, const char *name, const char *expr); ecs_script_var_node_t* flecs_script_insert_var( ecs_script_parser_t *parser, const char *name); ecs_script_tag_t* flecs_script_insert_tag( ecs_script_parser_t *parser, const char *name); ecs_script_tag_t* flecs_script_insert_pair_tag( ecs_script_parser_t *parser, const char *first, const char *second); ecs_script_component_t* flecs_script_insert_component( ecs_script_parser_t *parser, const char *name); ecs_script_component_t* flecs_script_insert_pair_component( ecs_script_parser_t *parser, const char *first, const char *second); ecs_script_default_component_t* flecs_script_insert_default_component( ecs_script_parser_t *parser); ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_parser_t *parser, const char *name); ecs_script_if_t* flecs_script_insert_if( ecs_script_parser_t *parser); #endif /** * @file addons/script/visit.h * @brief Script AST visitor utilities. */ #ifndef FLECS_SCRIPT_VISIT_H #define FLECS_SCRIPT_VISIT_H typedef struct ecs_script_visit_t ecs_script_visit_t; typedef int (*ecs_visit_action_t)( ecs_script_visit_t *visitor, ecs_script_node_t *node); struct ecs_script_visit_t { ecs_script_impl_t *script; ecs_visit_action_t visit; ecs_script_node_t* nodes[256]; ecs_script_node_t *prev, *next; int32_t depth; }; int ecs_script_visit_( ecs_script_visit_t *visitor, ecs_visit_action_t visit, ecs_script_impl_t *script); #define ecs_script_visit(script, visitor, visit) \ ecs_script_visit_((ecs_script_visit_t*)visitor,\ (ecs_visit_action_t)visit,\ script) int ecs_script_visit_node_( ecs_script_visit_t *v, ecs_script_node_t *node); #define ecs_script_visit_node(visitor, node) \ ecs_script_visit_node_((ecs_script_visit_t*)visitor, \ (ecs_script_node_t*)node) int ecs_script_visit_scope_( ecs_script_visit_t *v, ecs_script_scope_t *node); #define ecs_script_visit_scope(visitor, node) \ ecs_script_visit_scope_((ecs_script_visit_t*)visitor, node) ecs_script_node_t* ecs_script_parent_node_( ecs_script_visit_t *v); #define ecs_script_parent_node(visitor) \ ecs_script_parent_node_((ecs_script_visit_t*)visitor) ecs_script_scope_t* ecs_script_current_scope_( ecs_script_visit_t *v); #define ecs_script_current_scope(visitor) \ ecs_script_current_scope_((ecs_script_visit_t*)visitor) ecs_script_node_t* ecs_script_parent_( ecs_script_visit_t *v, ecs_script_node_t *node); #define ecs_script_parent(visitor, node) \ ecs_script_parent_((ecs_script_visit_t*)visitor, (ecs_script_node_t*)node) ecs_script_node_t* ecs_script_next_node_( ecs_script_visit_t *v); #define ecs_script_next_node(visitor) \ ecs_script_next_node_((ecs_script_visit_t*)visitor) int32_t ecs_script_node_line_number_( ecs_script_impl_t *script, ecs_script_node_t *node); #define ecs_script_node_line_number(script, node) \ ecs_script_node_line_number_(script, (ecs_script_node_t*)node) #endif /** * @file addons/script/visit_eval.h * @brief Script evaluation visitor. */ #ifndef FLECS_SCRIPT_VISIT_EVAL_H #define FLECS_SCRIPT_VISIT_EVAL_H typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; ecs_allocator_t *allocator; ecs_script_template_t *template; ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; ecs_vec_t using; ecs_vec_t with; ecs_vec_t annot; ecs_entity_t with_relationship; int32_t with_relationship_sp; ecs_script_vars_t *vars; ecs_stack_t stack; } ecs_script_eval_visitor_t; void flecs_script_eval_error_( ecs_script_eval_visitor_t *v, ecs_script_node_t *node, const char *fmt, ...); #define flecs_script_eval_error(v, node, ...)\ flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) ecs_entity_t flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, const char *path); ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, const char *name); const ecs_type_info_t* flecs_script_get_type_info( ecs_script_eval_visitor_t *v, void *node, ecs_id_t id); int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, const char *expr, ecs_value_t *value); int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *template); ecs_script_template_t* flecs_script_template_init( ecs_script_impl_t *script); void flecs_script_template_fini( ecs_script_impl_t *script, ecs_script_template_t *template); void flecs_script_eval_visit_init( ecs_script_impl_t *script, ecs_script_eval_visitor_t *v); void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v); int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); #endif struct ecs_script_template_t { /* Template handle */ ecs_entity_t entity; /* Template AST node */ ecs_script_template_node_t *node; /* Hoisted using statements */ ecs_vec_t using_; /* Hoisted variables */ ecs_script_vars_t *vars; /* Default values for props */ ecs_vec_t prop_defaults; /* Type info for template component */ const ecs_type_info_t *type_info; }; ecs_script_t* flecs_script_new( ecs_world_t *world); ecs_script_scope_t* flecs_script_scope_new( ecs_script_parser_t *parser); int flecs_script_visit_free( ecs_script_t *script); ecs_script_vars_t* flecs_script_vars_push( ecs_script_vars_t *parent, ecs_stack_t *stack, ecs_allocator_t *allocator); int flecs_terms_parse( ecs_script_t *script, ecs_term_t *terms, int32_t *term_count_out); const char* flecs_id_parse( const ecs_world_t *world, const char *name, const char *expr, ecs_id_t *id); const char* flecs_term_parse( ecs_world_t *world, const char *name, const char *expr, ecs_term_t *term, char *token_buffer); #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H #endif static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **c_info, bool move, int32_t *row_out, ecs_table_diff_t *diff); typedef struct { const ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; static flecs_component_ptr_t flecs_table_get_component( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); ecs_column_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = column->ti, .ptr = ecs_vec_get(&column->data, column->size, row) }; error: return (flecs_component_ptr_t){0}; } static flecs_component_ptr_t flecs_get_component_ptr( ecs_table_t *table, int32_t row, ecs_id_record_t *idr) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!idr) { return (flecs_component_ptr_t){0}; } if (idr->flags & EcsIdIsSparse) { ecs_entity_t entity = flecs_table_entities_array(table)[row]; return (flecs_component_ptr_t){ .ti = idr->type_info, .ptr = flecs_sparse_get_any(idr->sparse, 0, entity) }; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr || (tr->column == -1)) { return (flecs_component_ptr_t){0}; } return flecs_table_get_component(table, tr->column, row); } static void* flecs_get_component( ecs_table_t *table, int32_t row, ecs_id_record_t *idr) { return flecs_get_component_ptr(table, row, idr).ptr; } void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_id_record_t *idr, int32_t recur_depth) { ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, "cycle detected in IsA relationship"); /* Table (and thus entity) does not have component, look for base */ if (!(table->flags & EcsTableHasIsA)) { return NULL; } if (!(idr->flags & EcsIdOnInstantiateInherit)) { return NULL; } /* Exclude Name */ if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { return NULL; } /* Table should always be in the table index for (IsA, *), otherwise the * HasBase flag should not have been set */ ecs_table_record_t *tr_isa = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t i = tr_isa->index, end = tr_isa->count + tr_isa->index; void *ptr = NULL; do { ecs_id_t pair = ids[i ++]; ecs_entity_t base = ecs_pair_second(world, pair); ecs_record_t *r = flecs_entities_get(world, base); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); table = r->table; if (!table) { continue; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { ptr = flecs_get_base_component(world, table, id, idr, recur_depth + 1); } else { if (idr->flags & EcsIdIsSparse) { return flecs_sparse_get_any(idr->sparse, 0, base); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_table_get_component(table, tr->column, row).ptr; } } } while (!ptr && (i < end)); return ptr; error: return NULL; } 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_path(world, slot_of); ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " "slot (slots must be named)", slot_of_str); ecs_os_free(slot_of_str); return; } /* The 'slot' variable is currently pointing to a child (or * grandchild) of the current base. Find the original slot by * looking it up under the prefab it was registered. */ if (depth == 0) { /* If the current instance is an instance of slot_of, just * lookup the slot by name, which is faster than having to * create a relative path. */ slot = ecs_lookup_child(world, slot_of, name); } else { /* If the slot is more than one level away from the slot_of * parent, use a relative path to find the slot */ char *path = ecs_get_path_w_sep(world, parent, child, ".", NULL); slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", NULL, false); ecs_os_free(path); } if (slot == 0) { char *slot_of_str = ecs_get_path(world, slot_of); char *slot_str = ecs_get_path(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } ecs_add_pair(world, parent, slot, child); break; } depth ++; } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); if (parent == 0) { char *slot_of_str = ecs_get_path(world, slot_of); char *slot_str = ecs_get_path(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } } error: return; } static ecs_table_t* flecs_find_table_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_add(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_remove(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static int32_t flecs_child_type_insert( ecs_type_t *type, void **component_data, ecs_id_t id) { int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t cur = type->array[i]; if (cur == id) { /* Id is already part of type */ return -1; } if (cur > id) { /* A larger id was found so id can't be part of the type. */ break; } } /* Assumes that the array has enough memory to store the new element. */ int32_t to_move = type->count - i; if (to_move) { ecs_os_memmove(&type->array[i + 1], &type->array[i], to_move * ECS_SIZEOF(ecs_id_t)); ecs_os_memmove(&component_data[i + 1], &component_data[i], to_move * ECS_SIZEOF(void*)); } component_data[i] = NULL; type->array[i] = id; type->count ++; return i; } static void flecs_instantiate_children( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count, ecs_table_t *child_table) { if (!ecs_table_count(child_table)) { return; } ecs_type_t type = child_table->type; ecs_data_t *child_data = &child_table->data; ecs_entity_t slot_of = 0; ecs_entity_t *ids = type.array; int32_t type_count = type.count; /* Instantiate child table for each instance */ /* Create component array for creating the table */ ecs_table_diff_t diff = { .added = {0}}; diff.added.array = ecs_os_alloca_n(ecs_entity_t, type_count + 1); void **component_data = ecs_os_alloca_n(void*, type_count + 1); /* Copy in component identifiers. Find the base index in the component * array, since we'll need this to replace the base with the instance id */ int j, i, childof_base_index = -1; for (i = 0; i < type_count; i ++) { ecs_id_t id = ids[i]; /* If id has DontInherit flag don't inherit it, except for the name * and ChildOf pairs. The name is preserved so applications can lookup * the instantiated children by name. The ChildOf pair is replaced later * with the instance parent. */ if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && ECS_PAIR_FIRST(id) != EcsChildOf) { ecs_table_record_t *tr = &child_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdOnInstantiateDontInherit) { continue; } } /* If child is a slot, keep track of which parent to add it to, but * don't add slot relationship to child of instance. If this is a child * of a prefab, keep the SlotOf relationship intact. */ if (!(table->flags & EcsTableIsPrefab)) { if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); slot_of = ecs_pair_second(world, id); continue; } } /* Keep track of the element that creates the ChildOf relationship with * the prefab parent. We need to replace this element to make sure the * created children point to the instance and not the prefab */ if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == (uint32_t)base)) { childof_base_index = diff.added.count; } int32_t storage_index = ecs_table_type_to_column_index(child_table, i); if (storage_index != -1) { ecs_vec_t *column = &child_data->columns[storage_index].data; component_data[diff.added.count] = ecs_vec_first(column); } else { component_data[diff.added.count] = NULL; } diff.added.array[diff.added.count] = id; diff.added.count ++; } /* Table must contain children of base */ ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); /* If children are added to a prefab, make sure they are prefabs too */ if (table->flags & EcsTableIsPrefab) { if (flecs_child_type_insert( &diff.added, component_data, EcsPrefab) != -1) { childof_base_index ++; } } /* Instantiate the prefab child table for each new instance */ ecs_entity_t *instances = ecs_vec_first(&table->data.entities); int32_t child_count = ecs_vec_count(&child_data->entities); for (i = row; i < count + row; i ++) { ecs_entity_t instance = instances[i]; ecs_table_t *i_table = NULL; /* Replace ChildOf element in the component array with instance id */ diff.added.array[childof_base_index] = ecs_pair(EcsChildOf, instance); /* Find or create table */ i_table = flecs_table_find_or_create(world, &diff.added); ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == diff.added.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself * which would cause infinite recursion. */ ecs_entity_t *children = ecs_vec_first(&child_data->entities); #ifdef FLECS_DEBUG for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_check(child != instance, ECS_INVALID_PARAMETER, "cycle detected in IsA relationship"); } #else /* Bit of boilerplate to ensure that we don't get warnings about the * error label not being used. */ ecs_check(true, ECS_INVALID_OPERATION, NULL); #endif /* Create children */ int32_t child_row; const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, &diff.added, child_count, component_data, false, &child_row, &diff); /* If children are slots, add slot relationships to parent */ if (slot_of) { for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_entity_t i_child = i_children[j]; flecs_instantiate_slot(world, base, instance, slot_of, child, i_child); } } /* If prefab child table has children itself, recursively instantiate */ for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; flecs_instantiate(world, child, i_table, child_row + j, 1); } } error: return; } void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count) { ecs_record_t *record = flecs_entities_get_any(world, base); ecs_table_t *base_table = record->table; if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { /* Don't instantiate children from base entities that aren't prefabs */ return; } ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_instantiate_children( world, base, table, row, count, tr->hdr.table); } } } static void flecs_sparse_on_add( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *added, bool construct) { int32_t i, j; for (i = 0; i < added->count; i ++) { ecs_id_t id = added->array[i]; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr && idr->flags & EcsIdIsSparse) { const ecs_type_info_t *ti = idr->type_info; ecs_xtor_t ctor = ti->hooks.ctor; ecs_iter_action_t on_add = ti->hooks.on_add; ecs_entity_t *entities = flecs_table_entities_array(table); for (j = 0; j < count; j ++) { ecs_entity_t e = entities[row + j]; void *ptr = flecs_sparse_ensure(idr->sparse, 0, e); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (construct && ctor) { ctor(ptr, 1, ti); } if (on_add) { flecs_invoke_hook(world, table, count, row, &entities[row], ptr, id, ti, EcsOnAdd, on_add); } } } } } static void flecs_sparse_on_remove( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *removed) { int32_t i, j; for (i = 0; i < removed->count; i ++) { ecs_id_t id = removed->array[i]; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr && idr->flags & EcsIdIsSparse) { const ecs_type_info_t *ti = idr->type_info; ecs_xtor_t dtor = ti->hooks.dtor; ecs_iter_action_t on_remove = ti->hooks.on_remove; ecs_entity_t *entities = flecs_table_entities_array(table); for (j = 0; j < count; j ++) { ecs_entity_t e = entities[row + j]; void *ptr = flecs_sparse_remove_fast(idr->sparse, 0, e); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (on_remove) { flecs_invoke_hook(world, table, count, row, &entities[row], ptr, id, ti, EcsOnRemove, on_remove); } if (dtor) { dtor(ptr, 1, ti); } } } } } static void flecs_union_on_add( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *added) { int32_t i, j; for (i = 0; i < added->count; i ++) { ecs_id_t id = added->array[i]; if (ECS_IS_PAIR(id)) { ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); ecs_id_record_t *idr = flecs_id_record_get(world, wc); if (idr && idr->flags & EcsIdIsUnion) { ecs_entity_t *entities = flecs_table_entities_array(table); for (j = 0; j < count; j ++) { ecs_entity_t e = entities[row + j]; flecs_switch_set( idr->sparse, (uint32_t)e, ecs_pair_second(world, id)); } } } } } static void flecs_union_on_remove( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *removed) { int32_t i, j; for (i = 0; i < removed->count; i ++) { ecs_id_t id = removed->array[i]; if (ECS_IS_PAIR(id)) { ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); ecs_id_record_t *idr = flecs_id_record_get(world, wc); if (idr && idr->flags & EcsIdIsUnion) { ecs_entity_t *entities = flecs_table_entities_array(table); for (j = 0; j < count; j ++) { ecs_entity_t e = entities[row + j]; flecs_switch_reset(idr->sparse, (uint32_t)e); } } } } } 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_flags64_t set_mask, bool construct, bool sparse) { ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); if (added->count) { ecs_flags32_t table_flags = table->flags; if (sparse && (table_flags & EcsTableHasSparse)) { flecs_sparse_on_add(world, table, row, count, added, construct); } if (table_flags & EcsTableHasUnion) { flecs_union_on_add(world, table, row, count, added); } if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasTraversable)) { flecs_emit(world, world, set_mask, &(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) { ecs_flags32_t table_flags = table->flags; if (table_flags & EcsTableHasSparse) { flecs_sparse_on_remove(world, table, row, count, removed); } if (table_flags & EcsTableHasUnion) { flecs_union_on_remove(world, table, row, count, removed); } if (table_flags & (EcsTableHasOnRemove|EcsTableHasIsA| EcsTableHasTraversable)) { flecs_emit(world, world, 0, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = removed, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world }); } } } static void flecs_update_name_index( ecs_world_t *world, ecs_table_t *src, ecs_table_t *dst, int32_t offset, int32_t count) { ecs_assert(src != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); if (!(dst->flags & EcsTableHasName)) { /* If destination table doesn't have a name, we don't need to update the * name index. Even if the src table had a name, the on_remove hook for * EcsIdentifier will remove the entity from the index. */ return; } ecs_hashmap_t *src_index = src->_->name_index; ecs_hashmap_t *dst_index = dst->_->name_index; if ((src_index == dst_index) || (!src_index && !dst_index)) { /* If the name index didn't change, the entity still has the same parent * so nothing needs to be done. */ return; } EcsIdentifier *names = ecs_table_get_pair(world, dst, EcsIdentifier, EcsName, offset); ecs_assert(names != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i; ecs_entity_t *entities = ecs_vec_get_t( &dst->data.entities, ecs_entity_t, offset); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; EcsIdentifier *name = &names[i]; uint64_t index_hash = name->index_hash; if (index_hash) { flecs_name_index_remove(src_index, e, index_hash); } const char *name_str = name->value; if (name_str) { ecs_assert(name->hash != 0, ECS_INTERNAL_ERROR, NULL); flecs_name_index_ensure( dst_index, e, name_str, name->length, name->hash); name->index = dst_index; } } } static ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append(world, table, entity, ctor, true); record->table = table; record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); ecs_assert(ecs_vec_count(&table->data.entities) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add( world, table, NULL, row, 1, &diff->added, evt_flags, 0, ctor, true); return record; } static void flecs_move_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_table_t *src_table = record->table; int32_t src_row = ECS_RECORD_TO_ROW(record->row); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&src_table->data.entities) > src_row, ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); /* Append new row to destination table */ int32_t dst_row = flecs_table_append(world, dst_table, entity, false, false); /* Invoke remove actions for removed components */ flecs_notify_on_remove( world, src_table, dst_table, src_row, 1, &diff->removed); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); /* Update entity index & delete old data after running remove actions */ record->table = dst_table; record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); flecs_table_delete(world, src_table, src_row, false); flecs_notify_on_add( world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags, 0, ctor, true); flecs_update_name_index(world, src_table, dst_table, dst_row, 1); error: return; } static void flecs_delete_entity( ecs_world_t *world, ecs_record_t *record, ecs_table_diff_t *diff) { ecs_table_t *table = record->table; int32_t row = ECS_RECORD_TO_ROW(record->row); /* Invoke remove actions before deleting */ flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); flecs_table_delete(world, table, row, true); } /* Updating component monitors is a relatively expensive operation that only * happens for entities that are monitored. The approach balances the amount of * processing between the operation on the entity vs the amount of work that * needs to be done to rematch queries, as a simple brute force approach does * not scale when there are many tables / queries. Therefore we need to do a bit * of bookkeeping that is more intelligent than simply flipping a flag */ static void flecs_update_component_monitor_w_array( ecs_world_t *world, ecs_type_t *ids) { if (!ids) { return; } int i; for (i = 0; i < ids->count; i ++) { ecs_entity_t id = ids->array[i]; if (ECS_HAS_ID_FLAG(id, PAIR)) { flecs_monitor_mark_dirty(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); } flecs_monitor_mark_dirty(world, id); } } static void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed) { flecs_update_component_monitor_w_array(world, added); flecs_update_component_monitor_w_array(world, removed); } static void flecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool construct, ecs_flags32_t evt_flags) { ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); flecs_journal_begin(world, EcsJournalMove, entity, &diff->added, &diff->removed); ecs_table_t *src_table = NULL; int is_trav = 0; if (record) { src_table = record->table; is_trav = (record->row & EcsEntityIsTraversable) != 0; } if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a * table, this suggests that a union relationship could have changed. */ if (src_table) { flecs_notify_on_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags, 0, construct, true); } flecs_journal_end(); return; } if (src_table) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_move_entity(world, entity, record, dst_table, diff, construct, evt_flags); } else { flecs_delete_entity(world, record, diff); record->table = NULL; } flecs_table_traversable_add(src_table, -is_trav); } else { flecs_table_traversable_add(dst_table, is_trav); if (dst_table->type.count) { flecs_new_entity(world, entity, record, dst_table, diff, construct, evt_flags); } } /* If the entity is being watched, it is being monitored for changes and * requires rematching systems when components are added or removed. This * ensures that systems that rely on components from containers or prefabs * update the matched tables when the application adds or removes a * component from, for example, a container. */ if (is_trav) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } if ((!src_table || !src_table->type.count) && world->range_check_enabled) { ecs_check(!world->info.max_id || entity <= world->info.max_id, ECS_OUT_OF_RANGE, 0); ecs_check(entity >= world->info.min_id, ECS_OUT_OF_RANGE, 0); } error: flecs_journal_end(); return; } static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **component_data, bool is_move, int32_t *row_out, ecs_table_diff_t *diff) { int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); } if (!table) { return entities; } ecs_type_t type = table->type; if (!type.count) { return entities; } ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; component_array.array = type.array; component_array.count = type.count; } ecs_data_t *data = &table->data; int32_t row = flecs_table_appendn(world, table, data, count, entities); /* Update entity index. */ int i; for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); r->table = table; r->row = ECS_ROW_TO_RECORD(row + i, 0); } flecs_defer_begin(world, world->stages[0]); flecs_notify_on_add(world, table, NULL, row, count, &diff->added, (component_data == NULL) ? 0 : EcsEventNoOnSet, 0, true, true); if (component_data) { int32_t c_i; for (c_i = 0; c_i < component_ids->count; c_i ++) { void *src_ptr = component_data[c_i]; if (!src_ptr) { continue; } /* Find component in storage type */ ecs_entity_t id = component_ids->array[c_i]; const ecs_table_record_t *tr = flecs_table_record_get( world, table, id); ecs_assert(tr != NULL, ECS_INVALID_PARAMETER, "id is not a component"); ecs_assert(tr->column != -1, ECS_INVALID_PARAMETER, "id is not a component"); ecs_assert(tr->count == 1, ECS_INVALID_PARAMETER, "ids cannot be wildcards"); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; ecs_type_info_t *ti = column->ti; int32_t size = column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(&column->data, size, row); ecs_copy_t copy; ecs_move_t move; if (is_move && (move = ti->hooks.move)) { move(ptr, src_ptr, count, ti); } else if (!is_move && (copy = ti->hooks.copy)) { copy(ptr, src_ptr, count, ti); } else { ecs_os_memcpy(ptr, src_ptr, size * count); } }; int32_t j, storage_count = table->column_count; for (j = 0; j < storage_count; j ++) { ecs_type_t set_type = { .array = &table->data.columns[j].id, .count = 1 }; flecs_notify_on_set(world, table, row, count, &set_type, true); } } flecs_defer_end(world, world->stages[0]); if (row_out) { *row_out = row; } if (sparse_count) { entities = flecs_entities_ids(world); return &entities[sparse_count]; } else { return entities; } } static void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_id_t id, bool construct) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = record->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, record, dst_table, &diff, construct, EcsEventNoOnSet); /* No OnSet, this function is only called from * functions that are about to set the component. */ } static void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *src_table = r->table; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = r->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static flecs_component_ptr_t flecs_ensure( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r) { flecs_component_ptr_t dst = {0}; flecs_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, "invalid component id specified for ensure"); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (r->table) { dst = flecs_get_component_ptr(r->table, ECS_RECORD_TO_ROW(r->row), idr); 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]); if (!idr) { idr = flecs_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); dst = flecs_get_component_ptr(r->table, ECS_RECORD_TO_ROW(r->row), idr); error: return dst; } void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook) { int32_t defer = world->stages[0]->defer; if (defer < 0) { world->stages[0]->defer *= -1; } ecs_iter_t it = { .field_count = 1}; it.entities = entities; flecs_iter_init(world, &it, flecs_iter_cache_all); it.world = world; it.real_world = world; it.table = table; it.ptrs[0] = ptr; it.sizes = ECS_CONST_CAST(ecs_size_t*, &ti->size); it.ids[0] = id; it.event = event; it.event_id = id; it.ctx = ti->hooks.ctx; it.callback_ctx = ti->hooks.binding_ctx; it.count = count; it.offset = row; it.flags = EcsIterIsValid; hook(&it); ecs_iter_fini(&it); world->stages[0]->defer = defer; } void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *ids, bool owned) { ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_data_t *data = &table->data; ecs_entity_t *entities = ecs_vec_get_t( &data->entities, ecs_entity_t, row); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((row + count) <= ecs_vec_count(&data->entities), ECS_INTERNAL_ERROR, NULL); if (owned) { int i; for (i = 0; i < ids->count; i ++) { ecs_id_t id = ids->array[i]; ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = idr->type_info; ecs_iter_action_t on_set = ti->hooks.on_set; if (!on_set) { continue; } if (idr->flags & EcsIdIsSparse) { int32_t j; for (j = 0; j < count; j ++) { void *ptr = flecs_sparse_get_any( idr->sparse, 0, entities[j]); flecs_invoke_hook(world, table, 1, row, &entities[j], ptr, id, ti, EcsOnSet, on_set); } } else { const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t index = tr->column; ecs_column_t *column = &table->data.columns[index]; if (on_set) { ecs_vec_t *c = &column->data; void *ptr = ecs_vec_get(c, column->size, row); flecs_invoke_hook(world, table, count, row, entities, ptr, id, ti, EcsOnSet, on_set); } } } } /* Run OnSet notifications */ if (table->flags & EcsTableHasOnSet && ids->count) { flecs_emit(world, world, 0, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = ids, .table = table, .offset = row, .count = count, .observable = world }); } } void flecs_record_add_flag( ecs_record_t *record, uint32_t flag) { if (flag == EcsEntityIsTraversable) { if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { flecs_table_traversable_add(table, 1); } } } record->row |= flag; } void flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) { ecs_record_t *record = flecs_entities_get_any(world, entity); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(record, flag); } /* -- Public functions -- */ bool ecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, const ecs_type_t *added, const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_is_deferred(world), ECS_INVALID_OPERATION, "commit cannot be called on stage or while world is deferred"); ecs_table_t *src_table = NULL; if (!record) { record = flecs_entities_get(world, entity); src_table = record->table; } ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; if (added) { diff.added = *added; } if (removed) { diff.removed = *removed; } ecs_defer_begin(world); flecs_commit(world, entity, record, table, &diff, true, 0); ecs_defer_end(world); return src_table != table; error: return false; } ecs_entity_t ecs_set_with( ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_id_t prev = stage->with; stage->with = id; return prev; error: return 0; } ecs_id_t ecs_get_with( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->with; error: return 0; } ecs_entity_t ecs_new( 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, * since it is thread safe (uses atomic inc when in threading mode) */ ecs_world_t *unsafe_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); ecs_entity_t entity; if (unsafe_world->flags & EcsWorldMultiThreaded) { /* When world is in multithreading mode, make sure OS API has threading * functions initialized */ ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, "thread safe id creation unavailable: threading API not available"); /* Can't atomically increase number above max int */ ecs_assert(flecs_entities_max_id(unsafe_world) < UINT_MAX, ECS_INVALID_OPERATION, "thread safe ids exhausted"); entity = (ecs_entity_t)ecs_os_ainc( (int32_t*)&flecs_entities_max_id(unsafe_world)); } else { entity = flecs_entities_new_id(unsafe_world); } ecs_assert(!unsafe_world->info.max_id || ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, ECS_OUT_OF_RANGE, NULL); flecs_journal(world, EcsJournalNew, entity, 0, 0); return entity; error: return 0; } ecs_entity_t ecs_new_low_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* It is possible that the world passed to this function is a stage, so * make sure we have the actual world. Cast away const since this is one of * the few functions that may modify the world while it is in readonly mode, * but only if single threaded. */ ecs_world_t *unsafe_world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); if (unsafe_world->flags & EcsWorldReadonly) { /* Can't issue new comp id while iterating when in multithreaded mode */ ecs_check(ecs_get_stage_count(world) <= 1, ECS_INVALID_WHILE_READONLY, NULL); } ecs_entity_t id = 0; if (unsafe_world->info.last_component_id < FLECS_HI_COMPONENT_ID) { do { id = unsafe_world->info.last_component_id ++; } while (ecs_exists(unsafe_world, id) && id <= FLECS_HI_COMPONENT_ID); } if (!id || id >= FLECS_HI_COMPONENT_ID) { /* If the low component ids are depleted, return a regular entity id */ id = ecs_new(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(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(world); if (flecs_defer_add(stage, entity, id)) { return entity; } ecs_table_diff_builder_t diff_builder = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff_builder); ecs_table_t *table = flecs_find_table_add( world, &world->store.root, id, &diff_builder); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff; flecs_table_diff_build_noalloc(&diff_builder, &diff); ecs_record_t *r = flecs_entities_get(world, entity); flecs_new_entity(world, entity, r, table, &diff, true, 0); flecs_table_diff_builder_fini(world, &diff_builder); flecs_defer_end(world, stage); return entity; error: return 0; } ecs_entity_t ecs_new_w_table( ecs_world_t *world, ecs_table_t *table) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_diff_t table_diff = { .added = table->type }; flecs_new_entity(world, entity, r, table, &table_diff, true, 0); return entity; error: return 0; } static void flecs_copy_id( ecs_world_t *world, ecs_record_t *r, ecs_id_t id, size_t size, void *dst_ptr, void *src_ptr, const ecs_type_info_t *ti) { ecs_check(dst_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst_ptr, src_ptr, 1, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, flecs_utosize(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); } error: return; } /* Traverse table graph by either adding or removing identifiers parsed from the * passed in expression. */ static int flecs_traverse_from_expr( ecs_world_t *world, const char *name, const char *expr, ecs_vec_t *ids) { #ifdef FLECS_SCRIPT const char *ptr = expr; if (ptr) { ecs_id_t id = 0; while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { if (!id) { break; } ecs_vec_append_t(&world->allocator, ids, ecs_id_t)[0] = id; } if (!ptr) { goto error; } } return 0; #else (void)world; (void)name; (void)expr; (void)ids; ecs_err("cannot parse component expression: script addon required"); goto error; #endif error: return -1; } /* 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) { #ifdef FLECS_SCRIPT const char *ptr = expr; if (ptr) { ecs_id_t id = 0; while (ptr[0] && (ptr = flecs_id_parse(world, name, ptr, &id))) { if (!id) { break; } ecs_add_id(world, entity, id); } } #else (void)world; (void)entity; (void)name; (void)expr; ecs_err("cannot parse component expression: script addon required"); #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 new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_vec_t ids; /* Add components from the 'add_expr' expression. Look up before naming * entity, so that expression can't resolve to self. */ ecs_vec_init_t(&world->allocator, &ids, ecs_id_t, 0); if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { if (flecs_traverse_from_expr(world, name, desc->add_expr, &ids)) { goto error; } } /* Set symbol */ if (desc->symbol && desc->symbol[0]) { const char *sym = ecs_get_symbol(world, result); if (sym) { ecs_assert(!ecs_os_strcmp(desc->symbol, sym), ECS_INCONSISTENT_NAME, desc->symbol); } else { ecs_set_symbol(world, result, desc->symbol); } } /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); } else if (new_entity && scope) { ecs_add_pair(world, result, EcsChildOf, scope); } /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* Add components from the 'add' array */ if (desc->add) { int32_t i = 0; ecs_id_t id; while ((id = desc->add[i ++])) { table = flecs_find_table_add(world, table, id, &diff); } } /* Add components from the 'set' array */ if (desc->set) { int32_t i = 0; ecs_id_t id; while ((id = desc->set[i ++].type)) { table = flecs_find_table_add(world, table, id, &diff); } } /* Add ids from .expr */ { int32_t i, count = ecs_vec_count(&ids); ecs_id_t *expr_ids = ecs_vec_first(&ids); for (i = 0; i < count; i ++) { table = flecs_find_table_add(world, table, expr_ids[i], &diff); } } /* Find destination table */ /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (new_entity) { if (new_entity && scope && !name && !name_assigned) { table = flecs_find_table_add( world, table, ecs_pair(EcsChildOf, scope), &diff); } if (with) { table = flecs_find_table_add(world, table, with, &diff); } } /* Commit entity to destination table */ if (src_table != table) { flecs_defer_begin(world, world->stages[0]); ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, result, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, world->stages[0]); } /* Set component values */ if (desc->set) { table = r->table; ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = 0, row = ECS_RECORD_TO_ROW(r->row); const ecs_value_t *v; flecs_defer_begin(world, world->stages[0]); while ((void)(v = &desc->set[i ++]), v->type) { if (!v->ptr) { continue; } ecs_assert(ECS_RECORD_TO_ROW(r->row) == row, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, v->type); flecs_component_ptr_t ptr = flecs_get_component_ptr(table, row, idr); ecs_check(ptr.ptr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = idr->type_info; flecs_copy_id(world, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } flecs_defer_end(world, world->stages[0]); } flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return 0; error: ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return -1; } /* When in deferred mode, we need to add/remove components one by one using * the regular operations. */ static void flecs_deferred_add_remove( ecs_world_t *world, ecs_entity_t entity, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool 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 */ if (desc->add) { int32_t i = 0; ecs_id_t id; while ((id = desc->add[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); } } } /* Set component values */ if (desc->set) { int32_t i = 0; const ecs_value_t *v; while ((void)(v = &desc->set[i ++]), v->type) { if (v->ptr) { ecs_set_id(world, entity, v->type, 0, v->ptr); } else { ecs_add_id(world, entity, v->type); } } } /* Add components from the 'add_expr' expression */ if (desc->add_expr) { flecs_defer_from_expr(world, entity, name, desc->add_expr); } int32_t thread_count = ecs_get_stage_count(world); /* Set symbol */ if (desc->symbol) { const char *sym = ecs_get_symbol(world, entity); if (!sym || ecs_os_strcmp(sym, desc->symbol)) { if (thread_count <= 1) { /* See above */ ecs_suspend_readonly_state_t state; ecs_world_t *real_world = flecs_suspend_readonly(world, &state); ecs_set_symbol(world, entity, desc->symbol); flecs_resume_readonly(real_world, &state); } else { ecs_set_symbol(world, entity, desc->symbol); } } } /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } } ecs_entity_t ecs_entity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_entity_desc_t was not initialized to zero"); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t scope = stage->scope; ecs_id_t with = ecs_get_with(world); ecs_entity_t result = desc->id; #ifdef FLECS_DEBUG if (desc->add) { ecs_id_t id; int32_t i = 0; while ((id = desc->add[i ++])) { if (ECS_HAS_ID_FLAG(id, PAIR) && (ECS_PAIR_FIRST(id) == EcsChildOf)) { if (desc->name) { ecs_check(false, ECS_INVALID_PARAMETER, "%s: cannot set parent in " "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent", desc->name); } else { ecs_check(false, ECS_INVALID_PARAMETER, "cannot set parent in " "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent"); } } } } #endif const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name) { if (!name[0]) { name = NULL; } else if (flecs_name_is_id(name)){ ecs_entity_t id = flecs_name_to_id(name); if (!id) { return 0; } if (result && (id != result)) { ecs_err("name id conflicts with provided id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool flecs_new_entity = false; bool name_assigned = false; /* Remove optional prefix from name. Entity names can be derived from * language identifiers, such as components (typenames) and systems * function names). Because C does not have namespaces, such identifiers * often encode the namespace as a prefix. * To ensure interoperability between C and C++ (and potentially other * languages with namespacing) the entity must be stored without this prefix * and with the proper namespace, which is what the name_prefix is for */ const char *prefix = world->info.name_prefix; if (name && prefix) { ecs_size_t len = ecs_os_strlen(prefix); if (!ecs_os_strncmp(name, prefix, len) && (isupper(name[len]) || name[len] == '_')) { if (name[len] == '_') { name = name + len + 1; } else { name = name + len; } } } /* Parent field takes precedence over scope */ if (desc->parent) { scope = desc->parent; } /* Find or create entity */ if (!result) { if (name) { /* If add array contains a ChildOf pair, use it as scope instead */ result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new(world); } flecs_new_entity = true; ecs_assert(ecs_get_type(world, result) == NULL, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure provided id is either alive or revivable */ ecs_make_alive(world, result); name_assigned = ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName); if (name && name_assigned) { /* If entity has name, verify that name matches. The name provided * to the function could either have been relative to the current * scope, or fully qualified. */ char *path; ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { /* Fully qualified name was provided, so make sure to * compare with fully qualified name */ path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); } else { /* Relative name was provided, so make sure to compare with * relative name */ if (!sep || sep[0]) { path = ecs_get_path_w_sep(world, scope, result, sep, ""); } else { /* Safe, only freed when sep is valid */ path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); } } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); if (!sep || sep[0]) { ecs_os_free(path); } return 0; } if (!sep || sep[0]) { ecs_os_free(path); } } } } ecs_assert(name_assigned == ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); if (stage->defer) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, flecs_new_entity, name_assigned)) { return 0; } } return result; error: return 0; } const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_bulk_desc_t was not initialized to zero"); const ecs_entity_t *entities = desc->entities; int32_t count = desc->count; int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { int i; for (i = 0; i < count; i ++) { ecs_make_alive(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &table_diff); flecs_table_diff_builder_fini(world, &diff); } else { ecs_table_diff_t diff = { .added.array = table->type.array, .added.count = table->type.count }; ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &diff); } if (!sparse_count) { return entities; } else { /* Refetch entity ids, in case the underlying array was reallocated */ entities = flecs_entities_ids(world); return &entities[sparse_count]; } error: return NULL; } static void flecs_check_component( ecs_world_t *world, ecs_entity_t result, const EcsComponent *ptr, ecs_size_t size, ecs_size_t alignment) { if (ptr->size != size) { char *path = ecs_get_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); ecs_os_free(path); } } ecs_entity_t ecs_component_init( ecs_world_t *world, const ecs_component_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_component_desc_t was not initialized to 0"); /* If existing entity is provided, check if it is already registered as a * component and matches the size/alignment. This can prevent having to * suspend readonly mode, and increases the number of scenarios in which * this function can be called in multithreaded mode. */ ecs_entity_t result = desc->entity; if (result && ecs_is_alive(world, result)) { const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); if (const_ptr) { flecs_check_component(world, result, const_ptr, desc->type.size, desc->type.alignment); return result; } } ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; if (!result) { result = ecs_new_low_id(world); } else { ecs_make_alive(world, result); new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_ensure(world, result, EcsComponent); if (!ptr->size) { ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; if (!new_component || ptr->size != desc->type.size) { if (!ptr->size) { ecs_trace("#[green]tag#[reset] %s created", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s created", ecs_get_name(world, result)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } if (desc->type.name && new_component) { ecs_entity(world, { .id = result, .name = desc->type.name }); } ecs_modified(world, result, EcsComponent); if (desc->type.size && !ecs_id_in_use(world, result) && !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) { ecs_set_hooks_id(world, result, &desc->type.hooks); } if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { world->info.last_component_id = result + 1; } flecs_resume_readonly(world, &readonly_state); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); return result; error: return 0; } const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, ecs_id_t id, int32_t count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { return ids; } ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); if (id) { table = flecs_find_table_add(world, table, id, &diff); } ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, stage); return ids; error: return NULL; } void ecs_clear( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, entity)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->table = NULL; if (r->row & EcsEntityIsTraversable) { flecs_table_traversable_add(table, -1); } } flecs_defer_end(world, stage); error: return; } static void flecs_throw_invalid_delete( ecs_world_t *world, ecs_id_t id) { char *id_str = NULL; if (!(world->flags & EcsWorldQuit)) { id_str = ecs_id_str(world, id); ecs_err("(OnDelete, Panic) constraint violated while deleting %s", id_str); ecs_os_free(id_str); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, ecs_entity_t action, bool delete_id) { ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, &world->store.marked_ids, ecs_marked_id_t); m->idr = idr; m->id = idr->id; m->action = action; m->delete_id = delete_id; flecs_id_record_claim(world, idr); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id); static void flecs_targets_mark_for_delete( ecs_world_t *world, ecs_table_t *table) { ecs_id_record_t *idr; ecs_entity_t *entities = ecs_vec_first(&table->data.entities); int32_t i, count = ecs_vec_count(&table->data.entities); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (!r) { continue; } /* If entity is not used as id or as relationship target, there won't * be any tables with a reference to it. */ ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; if (!(flags & (EcsEntityIsId|EcsEntityIsTarget))) { continue; } ecs_entity_t e = entities[i]; if (flags & EcsEntityIsId) { if ((idr = flecs_id_record_get(world, e))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(e, EcsWildcard)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE(idr->flags), true); } } if (flags & EcsEntityIsTarget) { if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_TARGET(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_TARGET(idr->flags), true); } } } } static bool flecs_id_is_delete_target( ecs_id_t id, ecs_entity_t action) { if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { /* If no explicit delete action is provided, and the id we're deleting * has the form (*, Target), use OnDeleteTarget action */ return true; } return false; } static ecs_entity_t flecs_get_delete_action( ecs_table_t *table, ecs_table_record_t *tr, ecs_entity_t action, bool delete_target) { ecs_entity_t result = action; if (!result && delete_target) { ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = idr->id; /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ int32_t i = tr->index, count = tr->count; do { ecs_type_t *type = &table->type; ecs_table_record_t *trr = &table->_->records[i]; ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; result = ECS_ID_ON_DELETE_TARGET(idrr->flags); if (result == EcsDelete) { /* Delete takes precedence over Remove */ break; } if (count > 1) { /* If table contains multiple pairs for target they are not * guaranteed to occupy consecutive elements in the table's type * vector, so a linear search is needed to find matches. */ for (++ i; i < type->count; i ++) { if (ecs_id_match(type->array[i], id)) { break; } } /* We should always have as many matching ids as tr->count */ ecs_assert(i < type->count, ECS_INTERNAL_ERROR, NULL); } } while (--count); } return result; } static void flecs_update_monitors_for_delete( ecs_world_t *world, ecs_id_t id) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){id}, .count = 1 }); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id) { if (idr->flags & EcsIdMarkedForDelete) { return; } idr->flags |= EcsIdMarkedForDelete; flecs_marked_id_push(world, idr, action, delete_id); ecs_id_t id = idr->id; bool delete_target = flecs_id_is_delete_target(id, action); /* Mark all tables with the id for delete */ ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableMarkedForDelete) { continue; } ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, delete_target); /* If this is a Delete action, recursively mark ids & tables */ if (cur_action == EcsDelete) { table->flags |= EcsTableMarkedForDelete; ecs_log_push_2(); flecs_targets_mark_for_delete(world, table); ecs_log_pop_2(); } else if (cur_action == EcsPanic) { flecs_throw_invalid_delete(world, id); } } } /* Same for empty tables */ if (flecs_table_cache_empty_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { tr->hdr.table->flags |= EcsTableMarkedForDelete; } } /* Signal query cache monitors */ flecs_update_monitors_for_delete(world, id); /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ if (ecs_id_is_wildcard(id)) { ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *cur = idr; if (ECS_PAIR_SECOND(id) == EcsWildcard) { while ((cur = cur->first.next)) { flecs_update_monitors_for_delete(world, cur->id); } } else { ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); while ((cur = cur->second.next)) { flecs_update_monitors_for_delete(world, cur->id); } } } } static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If there's no id record, there's nothing to delete */ return false; } if (!action) { /* If no explicit action is provided, derive it */ if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { /* Delete actions are determined by the component, or in the case * of a pair by the relationship. */ action = ECS_ID_ON_DELETE(idr->flags); } } if (action == EcsPanic) { /* This id is protected from deletion */ flecs_throw_invalid_delete(world, id); return false; } flecs_id_mark_for_delete(world, idr, action, delete_id); return true; } static void flecs_remove_from_table( ecs_world_t *world, ecs_table_t *table) { ecs_table_diff_t temp_diff = { .added = {0} }; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst_table = table; /* To find the dst table, remove all ids that are marked for deletion */ int32_t i, t, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); const ecs_table_record_t *tr; for (i = 0; i < count; i ++) { const ecs_id_record_t *idr = ids[i].idr; if (!(tr = flecs_id_record_get_table(idr, dst_table))) { continue; } t = tr->index; do { ecs_id_t id = dst_table->type.array[t]; ecs_table_t *tgt_table = flecs_table_traverse_remove( world, dst_table, &id, &temp_diff); ecs_assert(tgt_table != dst_table, ECS_INTERNAL_ERROR, NULL); dst_table = tgt_table; flecs_table_diff_build_append_table(world, &diff, &temp_diff); } while (dst_table->type.count && (t = ecs_search_offset( world, dst_table, t, idr->id, NULL)) != -1); } ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); if (!dst_table->type.count) { /* If this removes all components, clear table */ flecs_table_clear_entities(world, table); } else { /* Otherwise, merge table into dst_table */ if (dst_table != table) { int32_t table_count = ecs_table_count(table); if (diff.removed.count && table_count) { ecs_log_push_3(); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, table_count, &td.removed); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table); } } flecs_table_diff_builder_fini(world, &diff); } static bool flecs_on_delete_clear_tables( ecs_world_t *world) { /* Iterate in reverse order so that DAGs get deleted bottom to top */ int32_t i, last = ecs_vec_count(&world->store.marked_ids), first = 0; ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); do { for (i = last - 1; i >= first; i --) { ecs_id_record_t *idr = ids[i].idr; ecs_entity_t action = ids[i].action; /* Empty all tables for id */ { ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if ((action == EcsRemove) || !(table->flags & EcsTableMarkedForDelete)) { flecs_remove_from_table(world, table); } else { ecs_dbg_3( "#[red]delete#[reset] entities from table %u", (uint32_t)table->id); flecs_table_delete_entities(world, table); } } } } /* Run commands so children get notified before parent is deleted */ if (world->stages[0]->defer) { flecs_defer_end(world, world->stages[0]); flecs_defer_begin(world, world->stages[0]); } /* User code (from triggers) could have enqueued more ids to delete, * reobtain the array in case it got reallocated */ ids = ecs_vec_first(&world->store.marked_ids); } /* Check if new ids were marked since we started */ int32_t new_last = ecs_vec_count(&world->store.marked_ids); if (new_last != last) { /* Iterate remaining ids */ ecs_assert(new_last > last, ECS_INTERNAL_ERROR, NULL); first = last; last = new_last; } else { break; } } while (true); return true; } static bool flecs_on_delete_clear_ids( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->store.marked_ids); ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int twice = 2; do { for (i = 0; i < count; i ++) { /* Release normal ids before wildcard ids */ if (ecs_id_is_wildcard(ids[i].id)) { if (twice == 2) { continue; } } else { if (twice == 1) { continue; } } ecs_id_record_t *idr = ids[i].idr; bool delete_id = ids[i].delete_id; flecs_id_record_release_tables(world, idr); /* Release the claim taken by flecs_marked_id_push. This may delete the * id record as all other claims may have been released. */ int32_t rc = flecs_id_record_release(world, idr); ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); (void)rc; /* If rc is 0, the id was likely deleted by a nested delete_with call * made by an on_remove handler/OnRemove observer */ if (rc) { if (delete_id) { /* If id should be deleted, release initial claim. This happens when * a component, tag, or part of a pair is deleted. */ flecs_id_record_release(world, idr); } else { /* If id should not be deleted, unmark id record for deletion. This * happens when all instances *of* an id are deleted, for example * when calling ecs_remove_all or ecs_delete_with. */ idr->flags &= ~EcsIdMarkedForDelete; } } } } while (-- twice); return true; } static void flecs_on_delete( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { /* Cleanup can happen recursively. If a cleanup action is already in * progress, only append ids to the marked_ids. The topmost cleanup * frame will handle the actual cleanup. */ int32_t count = ecs_vec_count(&world->store.marked_ids); /* Make sure we're evaluating a consistent list of non-empty tables */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); /* Only perform cleanup if we're the first stack frame doing it */ if (!count && ecs_vec_count(&world->store.marked_ids)) { ecs_dbg_2("#[red]delete#[reset]"); ecs_log_push_2(); /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); /* All marked tables are empty, ensure they're in the right list */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); /* Verify deleted ids are no longer in use */ #ifdef FLECS_DEBUG ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int32_t i; count = ecs_vec_count(&world->store.marked_ids); for (i = 0; i < count; i ++) { ecs_assert(!ecs_id_in_use(world, ids[i].id), ECS_INTERNAL_ERROR, NULL); } #endif ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); /* Ids are deleted, clear stack */ ecs_vec_clear(&world->store.marked_ids); ecs_log_pop_2(); } } void ecs_delete_with( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { return; } flecs_on_delete(world, id, EcsDelete, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_remove_all( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { return; } flecs_on_delete(world, id, EcsRemove, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_delete( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_delete(stage, entity)) { return; } ecs_record_t *r = flecs_entities_try(world, entity); if (r) { flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); ecs_table_t *table; if (row_flags) { if (row_flags & EcsEntityIsTarget) { flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); r->idr = NULL; } if (row_flags & EcsEntityIsId) { flecs_on_delete(world, entity, 0, true); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); } if (row_flags & EcsEntityIsTraversable) { table = r->table; if (table) { flecs_table_traversable_add(table, -1); } } /* Merge operations before deleting entity */ flecs_defer_end(world, stage); flecs_defer_begin(world, stage); } table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->row = 0; r->table = NULL; } flecs_entities_remove(world, entity); flecs_journal_end(); } flecs_defer_end(world, stage); error: return; } void ecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); flecs_add_id(world, entity, id); error: return; } void ecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), ECS_INVALID_PARAMETER, NULL); flecs_remove_id(world, entity, id); error: return; } void ecs_auto_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_AUTO_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(world); } if (flecs_defer_clone(stage, dst, src, copy_value)) { return dst; } ecs_record_t *src_r = flecs_entities_get(world, src); ecs_assert(src_r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = src_r->table; if (!src_table) { goto done; } ecs_table_t *dst_table = src_table; if (src_table->flags & EcsTableHasName) { dst_table = ecs_table_remove_id(world, src_table, ecs_pair_t(EcsIdentifier, EcsName)); } ecs_type_t dst_type = dst_table->type; ecs_table_diff_t diff = { .added = dst_type }; ecs_record_t *dst_r = flecs_entities_get(world, dst); flecs_new_entity(world, dst, dst_r, dst_table, &diff, true, 0); int32_t row = ECS_RECORD_TO_ROW(dst_r->row); if (copy_value) { flecs_table_move(world, dst, src, dst_table, row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); int32_t i, count = dst_table->column_count; for (i = 0; i < count; i ++) { ecs_type_t type = { .array = &dst_table->data.columns[i].id, .count = 1 }; flecs_notify_on_set(world, dst_table, row, 1, &type, true); } } done: flecs_defer_end(world, stage); return dst; error: return 0; } const void* ecs_get_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = r->table; if (!table) { return NULL; } ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return NULL; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return flecs_get_base_component(world, table, id, idr, 0); } else { if (idr->flags & EcsIdIsSparse) { return flecs_sparse_get_any(idr->sparse, 0, entity); } ecs_check(tr->column != -1, ECS_NOT_A_COMPONENT, NULL); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_table_get_component(table, tr->column, row).ptr; error: return NULL; } void* ecs_get_mut_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); 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); int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_ptr(table, row, idr).ptr; error: return NULL; } void* ecs_ensure_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set( world, stage, EcsCmdEnsure, entity, id, 0, NULL, NULL); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_ensure(world, entity, id, r).ptr; ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return result; error: return NULL; } void* ecs_ensure_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(flecs_defer_cmd(stage), ECS_INVALID_PARAMETER, NULL); return flecs_defer_set(world, stage, EcsCmdSet, entity, id, 0, NULL, NULL); error: return NULL; } static ecs_record_t* flecs_access_begin( ecs_world_t *stage, ecs_entity_t entity, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); const ecs_world_t *world = ecs_get_world(stage); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table; if (!(table = r->table)) { return NULL; } int32_t count = ecs_os_ainc(&table->_->lock); (void)count; if (write) { ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); } return r; error: return NULL; } static void flecs_access_end( const ecs_record_t *r, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); int32_t count = ecs_os_adec(&r->table->_->lock); (void)count; if (write) { ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); } ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); error: return; } ecs_record_t* ecs_write_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, true); } void ecs_write_end( ecs_record_t *r) { flecs_access_end(r, true); } const ecs_record_t* ecs_read_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, false); } void ecs_read_end( const ecs_record_t *r) { flecs_access_end(r, false); } ecs_entity_t ecs_record_get_entity( const ecs_record_t *record) { ecs_check(record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = record->table; if (!table) { return 0; } return ecs_vec_get_t(&table->data.entities, ecs_entity_t, ECS_RECORD_TO_ROW(record->row))[0]; error: return 0; } const void* ecs_record_get_id( const ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); ecs_id_record_t *idr = flecs_id_record_get(world, id); return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); } bool ecs_record_has_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); if (r->table) { return ecs_table_has_id(world, r->table, id); } return false; } void* ecs_record_ensure_id( ecs_world_t *stage, ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); ecs_id_record_t *idr = flecs_id_record_get(world, id); return flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); } ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); ecs_check(record != NULL, ECS_INVALID_PARAMETER, "cannot create ref for empty entity"); ecs_ref_t result = { .entity = entity, .id = id, .record = record }; ecs_table_t *table = record->table; if (table) { result.tr = flecs_table_record_get(world, table, id); result.table_id = table->id; } return result; error: return (ecs_ref_t){0}; } static bool flecs_ref_needs_sync( ecs_ref_t *ref, ecs_table_record_t *tr, const ecs_table_t *table) { ecs_assert(ref != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return !tr || ref->table_id != table->id || tr->hdr.table != table; } void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return; } ecs_table_record_t *tr = ref->tr; if (flecs_ref_needs_sync(ref, tr, table)) { tr = ref->tr = flecs_table_record_get(world, table, ref->id); if (!tr) { return; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); ref->table_id = table->id; } error: return; } void* ecs_ref_get_id( const ecs_world_t *world, ecs_ref_t *ref, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, "ref not initialized"); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, "ref not initialized"); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, "ref not initialized"); ecs_check(id == ref->id, ECS_INVALID_PARAMETER, "ref not initialized"); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return NULL; } int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ref->tr; if (flecs_ref_needs_sync(ref, tr, table)) { tr = ref->tr = flecs_table_record_get(world, table, id); if (!tr) { return NULL; } ref->table_id = table->id; ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } int32_t column = tr->column; if (column == -1) { ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdIsSparse) { return flecs_sparse_get_any(idr->sparse, 0, ref->entity); } } return flecs_table_get_component(table, column, row).ptr; error: return NULL; } void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, bool *is_new) { 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, EcsCmdEmplace, entity, id, 0, NULL, is_new); } ecs_check(is_new || !ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, "cannot emplace a component the entity already has"); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); flecs_defer_end(world, stage); ecs_id_record_t *idr = flecs_id_record_get(world, id); void *ptr = flecs_get_component(r->table, ECS_RECORD_TO_ROW(r->row), idr); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, "emplaced component was removed during operation, make sure to not " "remove component T in on_add(T) hook/OnAdd(T) observer"); if (is_new) { *is_new = table != r->table; } return ptr; error: return NULL; } static void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, bool owned) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; if (!flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } /* If the entity does not have the component, calling ecs_modified is * invalid. The assert needs to happen after the defer statement, as the * entity may not have the component when this function is called while * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } static void flecs_set_id_copy( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, EcsCmdSet, entity, id, flecs_utosize(size), ptr, NULL); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); flecs_copy_id(world, r, id, size, dst.ptr, ptr, dst.ti); flecs_defer_end(world, stage); } static void flecs_set_id_move( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, cmd_kind, entity, id, flecs_utosize(size), ptr, NULL); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind != EcsCmdEmplace) { /* ctor will have happened by ensure */ move = ti->hooks.move_dtor; } else { move = ti->hooks.ctor_move_dtor; } if (move) { move(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, id); if (cmd_kind == EcsCmdSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } flecs_defer_end(world, stage); error: return; } void 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(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); /* Safe to cast away const: function won't modify if move arg is false */ flecs_set_id_copy(world, stage, entity, id, size, ECS_CONST_CAST(void*, ptr)); error: return; } #if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) static bool flecs_can_toggle( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return false; } return (idr->flags & EcsIdCanToggle) != 0; } #endif 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_stage_t *stage = flecs_stage_from_world(&world); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, "add CanToggle trait to component"); if (flecs_defer_enable(stage, entity, id, enable)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_entity_t bs_id = id | ECS_TOGGLE; ecs_table_t *table = r->table; int32_t index = -1; if (table) { index = ecs_table_get_type_index(world, table, bs_id); } if (index == -1) { ecs_add_id(world, entity, bs_id); flecs_defer_end(world, stage); ecs_enable_id(world, entity, id, enable); return; } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULL, since entity is stored in the table */ ecs_bitset_t *bs = &table->_->bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); flecs_defer_end(world, stage); error: return; } bool ecs_is_enabled_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_entity_t bs_id = id | ECS_TOGGLE; int32_t index = ecs_table_get_type_index(world, table, bs_id); if (index == -1) { /* If table does not have TOGGLE column for component, component is * always enabled, if the entity has it */ return ecs_has_id(world, entity, id); } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &table->_->bs_columns[index]; return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get_any(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_id_record_t *idr = flecs_id_record_get(world, id); int32_t column; if (idr) { ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr) { return true; } } if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { return false; } if (ECS_IS_PAIR(id) && (table->flags & EcsTableHasUnion)) { ecs_id_record_t *u_idr = flecs_id_record_get(world, ecs_pair(ECS_PAIR_FIRST(id), EcsUnion)); if (u_idr && u_idr->flags & EcsIdIsUnion) { uint64_t cur = flecs_switch_get(u_idr->sparse, (uint32_t)entity); return (uint32_t)cur == ECS_PAIR_SECOND(id); } } ecs_table_record_t *tr; column = ecs_search_relation(world, table, 0, id, EcsIsA, 0, 0, 0, &tr); if (column == -1) { return false; } return true; error: return false; } bool ecs_owns_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { return (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1); } ecs_entity_t ecs_get_target( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { goto not_found; } ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_id_record_t *idr = flecs_id_record_get(world, wc); const ecs_table_record_t *tr = NULL; if (idr) { tr = flecs_id_record_get_table(idr, table); } if (!tr) { if (!idr || (idr->flags & EcsIdOnInstantiateInherit)) { goto look_in_base; } else { return 0; } } if (index >= tr->count) { index -= tr->count; goto look_in_base; } ecs_entity_t result = ecs_pair_second(world, table->type.array[tr->index + index]); if (result == EcsUnion) { wc = ecs_pair(rel, EcsUnion); ecs_id_record_t *wc_idr = flecs_id_record_get(world, wc); ecs_assert(wc_idr != NULL, ECS_INTERNAL_ERROR, NULL); result = flecs_switch_get(wc_idr->sparse, (uint32_t)entity); } return result; look_in_base: if (table->flags & EcsTableHasIsA) { ecs_table_record_t *tr_isa = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_assert(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = table->type.array; int32_t i = tr_isa->index, end = (i + tr_isa->count); for (; i < end; i ++) { ecs_id_t isa_pair = ids[i]; ecs_entity_t base = ecs_pair_second(world, isa_pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t t = ecs_get_target(world, base, rel, index); if (t) { return t; } } } not_found: error: return 0; } ecs_entity_t ecs_get_parent( const ecs_world_t *world, ecs_entity_t entity) { return ecs_get_target(world, entity, EcsChildOf, 0); } ecs_entity_t ecs_get_target_for_id( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); if (!id) { return ecs_get_target(world, entity, rel, 0); } world = ecs_get_world(world); ecs_table_t *table = ecs_get_table(world, entity); ecs_entity_t subject = 0; if (rel) { int32_t column = ecs_search_relation( world, table, 0, id, rel, 0, &subject, 0, 0); if (column == -1) { return 0; } } else { entity = 0; /* Don't return entity if id was not found */ if (table) { ecs_id_t *ids = table->type.array; int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { ecs_id_t ent = ids[i]; if (ent & ECS_ID_FLAGS_MASK) { /* Skip ids with pairs, roles since 0 was provided for rel */ break; } if (ecs_has_id(world, ent, id)) { subject = ent; break; } } } } if (subject == 0) { return entity; } else { return subject; } error: return 0; } int32_t ecs_get_depth( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel) { ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, "cannot safely determine depth for relationship that is not acyclic " "(add Acyclic property to relationship)"); 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_stage_t *stage, ecs_entity_t entity, ecs_entity_t tag, const char *name) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { entity = ecs_new(world); } if (!name) { ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; } EcsIdentifier *ptr = ecs_ensure_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (tag == EcsName) { /* Insert command after ensure, but before the name is potentially * freed. Even though the name is a const char*, it is possible that the * application passed in the existing name of the entity which could * still cause it to be freed. */ flecs_defer_path(stage, 0, entity, name); } ecs_os_strset(&ptr->value, name); ecs_modified_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; error: return 0; } ecs_entity_t ecs_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { if (!entity) { return ecs_entity(world, { .name = name }); } ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_set_identifier(world, stage, entity, EcsName, name); return entity; } ecs_entity_t ecs_set_symbol( ecs_world_t *world, ecs_entity_t entity, const char *name) { return flecs_set_identifier(world, NULL, entity, EcsSymbol, name); } void ecs_set_alias( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_set_identifier(world, NULL, entity, EcsAlias, name); } ecs_id_t ecs_make_pair( ecs_entity_t relationship, ecs_entity_t target) { ecs_assert(!ECS_IS_PAIR(relationship) && !ECS_IS_PAIR(target), ECS_INVALID_PARAMETER, "cannot create nested pairs"); return ecs_pair(relationship, target); } bool ecs_is_valid( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* 0 is not a valid entity id */ if (!entity) { return false; } /* Entity identifiers should not contain flag bits */ if (entity & ECS_ID_FLAGS_MASK) { return false; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return false; } /* If id exists, it must be alive (the generation count must match) */ return ecs_is_alive(world, entity); error: return false; } ecs_id_t ecs_strip_generation( ecs_entity_t e) { /* If this is not a pair, erase the generation bits */ if (!(e & ECS_ID_FLAGS_MASK)) { e &= ~ECS_GENERATION_MASK; } return e; } bool ecs_is_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_is_alive(world, entity); error: return false; } ecs_entity_t ecs_get_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { return 0; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); if (flecs_entities_is_alive(world, entity)) { return entity; } /* Make sure id does not have generation. This guards against accidentally * "upcasting" a not alive identifier to an alive one. */ if ((uint32_t)entity != entity) { return 0; } ecs_entity_t current = flecs_entities_get_alive(world, entity); if (!current || !flecs_entities_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_make_alive( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(world)); /* The entity index can be mutated while in staged/readonly mode, as long as * the world is not multithreaded. */ ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, "cannot make entity alive while world is in multithreaded mode"); /* Check if a version of the provided id is alive */ ecs_entity_t any = ecs_get_alive(world, (uint32_t)entity); if (any == entity) { /* If alive and equal to the argument, there's nothing left to do */ return; } /* If the id is currently alive but did not match the argument, fail */ ecs_check(!any, ECS_INVALID_PARAMETER, "entity is alive with different generation"); /* Set generation if not alive. The sparse set checks if the provided * id matches its own generation which is necessary for alive ids. This * check would cause ecs_ensure to fail if the generation of the 'entity' * argument doesn't match with its generation. * * While this could've been addressed in the sparse set, this is a rare * scenario that can only be triggered by ecs_ensure. Implementing it here * allows the sparse set to not do this check, which is more efficient. */ flecs_entities_make_alive(world, entity); /* Ensure id exists. The underlying data structure will verify that the * generation count matches the provided one. */ flecs_entities_ensure(world, entity); error: return; } void ecs_make_alive_id( ecs_world_t *world, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); if (flecs_entities_get_alive(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); flecs_entities_ensure(world, r); } if (flecs_entities_get_alive(world, o) == 0) { ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, "second element of pair is not alive"); flecs_entities_ensure(world, o); } } else { flecs_entities_ensure(world, id & ECS_COMPONENT_MASK); } error: return; } bool ecs_exists( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_exists(world, entity); error: return false; } void ecs_set_version( ecs_world_t *world, ecs_entity_t entity_with_generation) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot change entity generation when world is in readonly mode"); ecs_assert(!(ecs_is_deferred(world)), ECS_INVALID_OPERATION, "cannot change entity generation while world is deferred"); flecs_entities_make_alive(world, entity_with_generation); ecs_record_t *r = flecs_entities_get(world, entity_with_generation); if (r && r->table) { int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_entity_t *entities = r->table->data.entities.array; entities[row] = entity_with_generation; } } ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); return record->table; error: return NULL; } const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity) { ecs_table_t *table = ecs_get_table(world, entity); if (table) { return &table->type; } return NULL; } const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr && ECS_IS_PAIR(id)) { idr = flecs_id_record_get(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); if (!idr || !idr->type_info) { idr = NULL; } if (!idr) { ecs_entity_t first = ecs_pair_first(world, id); if (!first || !ecs_has_id(world, first, EcsPairIsTag)) { idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); if (!idr || !idr->type_info) { idr = NULL; } } } } if (idr) { return idr->type_info; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_type_info_get(world, id); } error: return NULL; } ecs_entity_t ecs_get_typeid( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { ecs_assert(ti->component != 0, ECS_INTERNAL_ERROR, NULL); return ti->component; } error: return 0; } bool ecs_id_is_tag( const ecs_world_t *world, ecs_id_t id) { if (ecs_id_is_wildcard(id)) { /* If id is a wildcard, we can't tell if it's a tag or not, except * when the relationship part of a pair has the Tag property */ if (ECS_HAS_ID_FLAG(id, PAIR)) { if (ECS_PAIR_FIRST(id) != EcsWildcard) { ecs_entity_t rel = ecs_pair_first(world, id); if (ecs_is_valid(world, rel)) { if (ecs_has_id(world, rel, EcsPairIsTag)) { return true; } } else { /* During bootstrap it's possible that not all ids are valid * yet. Using ecs_get_typeid will ensure correct values are * returned for only those components initialized during * bootstrap, while still asserting if another invalid id * is provided. */ if (ecs_get_typeid(world, id) == 0) { return true; } } } else { /* If relationship is wildcard id is not guaranteed to be a tag */ } } } else { if (ecs_get_typeid(world, id) == 0) { return true; } } return false; } int32_t ecs_count_id( const ecs_world_t *world, ecs_entity_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!id) { return 0; } int32_t count = 0; ecs_iter_t it = ecs_each_id(world, id); while (ecs_each_next(&it)) { count += it.count; } 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) & EcsIdOnInstantiateDontInherit)){ 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, "world/stage must be deferred before it can be suspended"); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, "world/stage is already suspended"); stage->defer = -stage->defer; error: return; } void ecs_defer_resume( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, "world/stage must be suspended before it can be resumed"); stage->defer = -stage->defer; error: return; } 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, AUTO_OVERRIDE)) { return "AUTO_OVERRIDE"; } else { return "UNKNOWN"; } } void ecs_id_str_buf( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AUTO_OVERRIDE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t obj = ECS_PAIR_SECOND(id); ecs_entity_t e; if ((e = ecs_get_alive(world, rel))) { rel = e; } if ((e = ecs_get_alive(world, obj))) { obj = e; } ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_entity_t e = id & ECS_COMPONENT_MASK; ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); } error: return; } char* ecs_id_str( const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_id_str_buf(world, id, &buf); return ecs_strbuf_get(&buf); } static void ecs_type_str_buf( const ecs_world_t *world, const ecs_type_t *type, ecs_strbuf_t *buf) { ecs_entity_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; if (i) { ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, ' '); } if (id == 1) { ecs_strbuf_appendlit(buf, "Component"); } else { ecs_id_str_buf(world, id, buf); } } } char* ecs_type_str( const ecs_world_t *world, const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_type_str_buf(world, type, &buf); return ecs_strbuf_get(&buf); } char* ecs_table_str( const ecs_world_t *world, const ecs_table_t *table) { if (table) { return ecs_type_str(world, &table->type); } else { return NULL; } } char* ecs_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); ecs_strbuf_appendlit(&buf, " ["); const ecs_type_t *type = ecs_get_type(world, entity); if (type) { ecs_type_str_buf(world, type, &buf); } ecs_strbuf_appendch(&buf, ']'); return ecs_strbuf_get(&buf); error: return NULL; } static void flecs_flush_bulk_new( ecs_world_t *world, ecs_cmd_t *cmd) { ecs_entity_t *entities = cmd->is._n.entities; if (cmd->id) { int i, count = cmd->is._n.count; for (i = 0; i < count; i ++) { flecs_entities_ensure(world, entities[i]); flecs_add_id(world, entities[i], cmd->id); } } ecs_os_free(entities); } static void flecs_dtor_value( ecs_world_t *world, ecs_id_t id, void *value, int32_t count) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { ecs_size_t size = ti->size; void *ptr; int i; for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { dtor(ptr, 1, ti); } } } static void flecs_free_cmd_event( ecs_world_t *world, ecs_event_desc_t *desc) { if (desc->ids) { flecs_stack_free_n(desc->ids->array, ecs_id_t, desc->ids->count); } /* Safe const cast, command makes a copy of type object */ flecs_stack_free_t(ECS_CONST_CAST(ecs_type_t*, desc->ids), ecs_type_t); if (desc->param) { flecs_dtor_value(world, desc->event, /* Safe const cast, command makes copy of value */ ECS_CONST_CAST(void*, desc->param), 1); } } static void flecs_discard_cmd( ecs_world_t *world, ecs_cmd_t *cmd) { if (cmd->kind == EcsCmdBulkNew) { ecs_os_free(cmd->is._n.entities); } else if (cmd->kind == EcsCmdEvent) { flecs_free_cmd_event(world, cmd->is._1.value); } else { ecs_assert(cmd->kind != EcsCmdEvent, ECS_INTERNAL_ERROR, NULL); void *value = cmd->is._1.value; if (value) { flecs_dtor_value(world, cmd->id, value, 1); flecs_stack_free(value, cmd->is._1.size); } } } static bool flecs_remove_invalid( ecs_world_t *world, ecs_id_t id, ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); if (!flecs_entities_is_valid(world, rel)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } else { ecs_entity_t obj = ECS_PAIR_SECOND(id); if (!flecs_entities_is_valid(world, obj)) { /* Check the relationship's policy for deleted objects */ ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(rel, EcsWildcard)); if (idr) { ecs_entity_t action = ECS_ID_ON_DELETE_TARGET(idr->flags); if (action == EcsDelete) { /* Entity should be deleted, don't bother checking * other ids */ return false; } else if (action == EcsPanic) { /* If policy is throw this object should not have * been deleted */ flecs_throw_invalid_delete(world, id); } else { *id_out = 0; return true; } } else { *id_out = 0; return true; } } } } else { id &= ECS_COMPONENT_MASK; if (!flecs_entities_is_valid(world, id)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } } return true; } static ecs_table_t* flecs_cmd_batch_add_diff( ecs_world_t *world, ecs_table_t *dst, ecs_table_t *cur, ecs_table_t *prev) { int32_t p = 0, p_count = prev->type.count; int32_t c = 0, c_count = cur->type.count; ecs_id_t *p_ids = prev->type.array; ecs_id_t *c_ids = cur->type.array; for (; c < c_count;) { ecs_id_t c_id = c_ids[c]; ecs_id_t p_id = p_ids[p]; if (p_id < c_id) { /* Previous id no longer exists in new table, so it was removed */ dst = ecs_table_remove_id(world, dst, p_id); } if (c_id < p_id) { /* Current id didn't exist in previous table, so it was added */ dst = ecs_table_add_id(world, dst, c_id); } c += c_id <= p_id; p += p_id <= c_id; if (p == p_count) { break; } } /* Remainder */ for (; p < p_count; p ++) { ecs_id_t p_id = p_ids[p]; dst = ecs_table_remove_id(world, dst, p_id); } for (; c < c_count; c ++) { ecs_id_t c_id = c_ids[c]; dst = ecs_table_add_id(world, dst, c_id); } return dst; } static void flecs_cmd_batch_for_entity( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_entity_t entity, ecs_cmd_t *cmds, int32_t start) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = NULL; if (r) { table = r->table; } world->info.cmd.batched_entity_count ++; ecs_table_t *start_table = table; ecs_cmd_t *cmd; int32_t next_for_entity; ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ int32_t cur = start; ecs_id_t id; /* Mask to let observable implementation know which components were set. * This prevents the code from overwriting components with an override if * the batch also contains an IsA pair. */ ecs_flags64_t set_mask = 0; do { cmd = &cmds[cur]; id = cmd->id; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { /* First command for an entity has a negative index, flip sign */ next_for_entity *= -1; } /* Check if added id is still valid (like is the parent of a ChildOf * pair still alive), if not run cleanup actions for entity */ if (id) { if (flecs_remove_invalid(world, id, &id)) { if (!id) { /* Entity should remain alive but id should not be added */ cmd->kind = EcsCmdSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsCmdSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsCmdAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsCmdModified; /* fall through */ case EcsCmdAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsCmdModified: if (start_table) { /* If a modified was inserted for an existing component, the value * of the component could have been changed. If this is the case, * call on_set hooks before the OnAdd/OnRemove observers are invoked * when moving the entity to a different table. * This ensures that if OnAdd/OnRemove observers access the modified * component value, the on_set hook has had the opportunity to * run first to set any computed values of the component. */ int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_id_record_t *idr = flecs_id_record_get(world, cmd->id); flecs_component_ptr_t ptr = flecs_get_component_ptr( start_table, row, idr); if (ptr.ptr) { const ecs_type_info_t *ti = ptr.ti; ecs_iter_action_t on_set; if ((on_set = ti->hooks.on_set)) { ecs_table_t *prev_table = r->table; ecs_defer_begin(world); flecs_invoke_hook(world, start_table, 1, row, &entity, ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); ecs_defer_end(world); /* Don't run on_set hook twice, but make sure to still * invoke observers. */ cmd->kind = EcsCmdModifiedNoHook; /* If entity changed tables in hook, add difference to * destination table, so we don't lose the side effects * of the hook. */ if (r->table != prev_table) { table = flecs_cmd_batch_add_diff( world, table, r->table, prev_table); } } } } break; case EcsCmdSet: case EcsCmdEnsure: { ecs_id_t *ids = diff->added.array; uint8_t added_index = flecs_ito(uint8_t, diff->added.count); table = flecs_find_table_add(world, table, id, diff); if (diff->added.count == (added_index + 1)) { /* Single id was added, must be at the end of the array */ ecs_assert(ids[added_index] == id, ECS_INTERNAL_ERROR, NULL); set_mask |= (1llu << added_index); } else { /* Id was already added or multiple ids got added. Do a linear * search to find the index we need to set the set_mask. */ int32_t i; for (i = 0; i < diff->added.count; i ++) { if (ids[i] == id) { break; } } ecs_assert(i != diff->added.count, ECS_INTERNAL_ERROR, NULL); set_mask |= (1llu << i); } world->info.cmd.batched_command_count ++; break; } case EcsCmdEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsCmdRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsCmdClear: if (table) { ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, &diff->removed, ecs_id_t, table->type.count); ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, table->type.count); } table = &world->store.root; world->info.cmd.batched_command_count ++; break; case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdPath: case EcsCmdDelete: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: case EcsCmdModifiedNoHook: break; } /* Add, remove and clear operations can be skipped since they have no * side effects besides adding/removing components */ if (kind == EcsCmdAdd || kind == EcsCmdRemove || kind == EcsCmdClear) { cmd->kind = EcsCmdSkip; } } while ((cur = next_for_entity)); /* Invoke OnAdd handlers after commit. This ensures that observers with * mixed OnAdd/OnSet events won't get called with uninitialized values for * an OnSet field. */ ecs_type_t added = { diff->added.array, diff->added.count }; diff->added.array = NULL; diff->added.count = 0; /* Move entity to destination table in single operation */ flecs_table_diff_build_noalloc(diff, &table_diff); flecs_defer_begin(world, world->stages[0]); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, world->stages[0]); /* If destination table has new sparse components, make sure they're created * for the entity. */ if (table && (table->flags & EcsTableHasSparse) && added.count) { flecs_sparse_on_add( world, table, ECS_RECORD_TO_ROW(r->row), 1, &added, true); } /* If the batch contains set commands, copy the component value from the * temporary command storage to the actual component storage before OnSet * observers are invoked. This ensures that for multi-component OnSet * observers all components are assigned a valid value before the observer * is invoked. * This only happens for entities that didn't have the assigned component * yet, as for entities that did have the component already the value will * have been assigned directly to the component storage. */ if (set_mask) { cur = start; do { cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsCmdSet: case EcsCmdEnsure: { flecs_component_ptr_t ptr = {0}; if (r->table) { ecs_id_record_t *idr = flecs_id_record_get(world, cmd->id); ptr = flecs_get_component_ptr( r->table, ECS_RECORD_TO_ROW(r->row), idr); } /* It's possible that even though the component was set, the * command queue also contained a remove command, so before we * do anything ensure the entity actually has the component. */ if (ptr.ptr) { const ecs_type_info_t *ti = ptr.ti; ecs_move_t move = ti->hooks.move; if (move) { move(ptr.ptr, cmd->is._1.value, 1, ti); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { dtor(cmd->is._1.value, 1, ti); cmd->is._1.value = NULL; } } else { ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); } if (cmd->kind == EcsCmdSet) { /* A set operation is add + copy + modified. We just did * the add the copy, so the only thing that's left is a * modified command, which will call the OnSet * observers. */ cmd->kind = EcsCmdModified; } else { /* If this was an ensure, nothing's left to be done */ cmd->kind = EcsCmdSkip; } } else { /* The entity no longer has the component which means that * there was a remove command for the component in the * command queue. In that case skip the command. */ cmd->kind = EcsCmdSkip; } break; } case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdRemove: case EcsCmdEmplace: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdPath: case EcsCmdDelete: case EcsCmdClear: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: break; } } while ((cur = next_for_entity)); } if (added.count) { flecs_defer_begin(world, world->stages[0]); flecs_notify_on_add(world, table, start_table, ECS_RECORD_TO_ROW(r->row), 1, &added, 0, set_mask, true, false); flecs_defer_end(world, world->stages[0]); if (r->row & EcsEntityIsTraversable) { /* Update monitors since we didn't do this in flecs_commit. */ flecs_update_component_monitors(world, &added, NULL); } } diff->added.array = added.array; diff->added.count = added.count; flecs_table_diff_builder_clear(diff); } /* Leave safe section. Run all deferred commands. */ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); 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 (flecs_poly_is(world, ecs_world_t)) { merge_to_world = world->stages[0]->defer == 0; } ecs_stage_t *dst_stage = flecs_stage_from_world(&world); ecs_commands_t *commands = stage->cmd; ecs_vec_t *queue = &commands->queue; if (ecs_vec_count(queue)) { /* Internal callback for capturing commands */ if (world->on_commands_active) { world->on_commands_active(stage, queue, world->on_commands_ctx_active); } ecs_cmd_t *cmds = ecs_vec_first(queue); int32_t i, count = ecs_vec_count(queue); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_commands_push(stage); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_alive(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* Invalidate entry */ if (cmd->entry) { cmd->entry->first = -1; } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsCmdAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsCmdRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsCmdClone: ecs_clone(world, e, id, cmd->is._1.clone_value); world->info.cmd.other_count ++; break; case EcsCmdSet: flecs_set_id_move(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsCmdEmplace: if (merge_to_world) { bool is_new; ecs_emplace_id(world, e, id, &is_new); if (!is_new) { kind = EcsCmdEnsure; } } flecs_set_id_move(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.ensure_count ++; break; case EcsCmdEnsure: flecs_set_id_move(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.ensure_count ++; break; case EcsCmdModified: flecs_modified_id_if(world, e, id, true); world->info.cmd.modified_count ++; break; case EcsCmdModifiedNoHook: flecs_modified_id_if(world, e, id, false); world->info.cmd.modified_count ++; break; case EcsCmdAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id, true); world->info.cmd.set_count ++; break; case EcsCmdDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsCmdClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsCmdOnDeleteAction: ecs_defer_begin(world); flecs_on_delete(world, id, e, false); ecs_defer_end(world); world->info.cmd.other_count ++; break; case EcsCmdEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsCmdDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsCmdBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsCmdPath: { bool keep_alive = true; ecs_make_alive(world, e); if (cmd->id) { if (ecs_is_alive(world, cmd->id)) { ecs_add_pair(world, e, EcsChildOf, cmd->id); } else { ecs_delete(world, e); keep_alive = false; } } if (keep_alive) { ecs_set_name(world, e, cmd->is._1.value); } ecs_os_free(cmd->is._1.value); cmd->is._1.value = NULL; world->info.cmd.other_count ++; break; } case EcsCmdEvent: { ecs_event_desc_t *desc = cmd->is._1.value; ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_emit((ecs_world_t*)stage, desc); flecs_free_cmd_event(world, desc); world->info.cmd.event_count ++; break; } case EcsCmdSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); flecs_commands_pop(stage); flecs_table_diff_builder_fini(world, &diff); /* Internal callback for capturing commands, signal queue is done */ if (world->on_commands_active) { world->on_commands_active(stage, NULL, world->on_commands_ctx_active); } } return true; } return false; } /* Delete operations from queue without executing them. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); if (!--stage->defer) { ecs_vec_t commands = stage->cmd->queue; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->cmd->stack); flecs_sparse_clear(&stage->cmd->entries); } return true; } error: return false; } /** * @file entity_name.c * @brief Functions for working with named entities. */ #include #define ECS_NAME_BUFFER_LENGTH (64) static bool flecs_path_append( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix, ecs_strbuf_t *buf) { flecs_poly_assert(world, ecs_world_t); ecs_assert(sep[0] != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t cur = 0; const char *name = NULL; ecs_size_t name_len = 0; if (child && ecs_is_alive(world, child)) { ecs_record_t *r = flecs_entities_get(world, child); bool hasName = false; if (r && r->table) { hasName = r->table->flags & EcsTableHasName; } if (hasName) { cur = ecs_get_target(world, child, EcsChildOf, 0); if (cur) { ecs_assert(cur != child, ECS_CYCLE_DETECTED, NULL); if (cur != parent && (cur != EcsFlecsCore || prefix != NULL)) { flecs_path_append(world, parent, cur, sep, prefix, buf); 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_appendch(buf, '#'); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); } return cur != 0; } bool flecs_name_is_id( const char *name) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (name[0] == '#') { /* If name is not just digits it's not an id */ const char *ptr; char ch; for (ptr = name + 1; (ch = ptr[0]); ptr ++) { if (!isdigit(ch)) { return false; } } return true; } return false; } ecs_entity_t flecs_name_to_id( const char *name) { ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); return flecs_ito(uint64_t, atoll(name + 1)); } static ecs_entity_t flecs_get_builtin( const char *name) { if (name[0] == '.' && name[1] == '\0') { return EcsThis; } else if (name[0] == '*' && name[1] == '\0') { return EcsWildcard; } else if (name[0] == '_' && name[1] == '\0') { return EcsAny; } else if (name[0] == '$' && name[1] == '\0') { return EcsVariable; } return 0; } static bool flecs_is_sep( const char **ptr, const char *sep) { ecs_size_t len = ecs_os_strlen(sep); if (!ecs_os_strncmp(*ptr, sep, len)) { *ptr += len; return true; } else { return false; } } static const char* flecs_path_elem( const char *path, const char *sep, char **buffer_out, ecs_size_t *size_out) { char *buffer = NULL; if (buffer_out) { buffer = *buffer_out; } const char *ptr; char ch; int32_t template_nesting = 0; int32_t pos = 0; ecs_size_t size = size_out ? *size_out : 0; for (ptr = path; (ch = *ptr); ptr ++) { if (ch == '<') { template_nesting ++; } else if (ch == '>') { template_nesting --; } else if (ch == '\\') { ptr ++; if (buffer) { buffer[pos] = ptr[0]; } pos ++; continue; } ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); if (!template_nesting && flecs_is_sep(&ptr, sep)) { break; } if (buffer) { if (pos == (size - 1)) { if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ char *new_buffer = ecs_os_malloc(size * 2 + 1); ecs_os_memcpy(new_buffer, buffer, size); buffer = new_buffer; } else { /* heap buffer */ buffer = ecs_os_realloc(buffer, size * 2 + 1); } size *= 2; } buffer[pos] = ch; } pos ++; } if (buffer) { buffer[pos] = '\0'; *buffer_out = buffer; *size_out = size; } if (pos) { return ptr; } else { return NULL; } error: return NULL; } static bool flecs_is_root_path( const char *path, const char *prefix) { if (prefix) { return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); } else { return false; } } static ecs_entity_t flecs_get_parent_from_path( const ecs_world_t *world, ecs_entity_t parent, const char **path_ptr, const char *sep, const char *prefix, bool new_entity) { bool start_from_root = false; const char *path = *path_ptr; if (flecs_is_root_path(path, prefix)) { path += ecs_os_strlen(prefix); parent = 0; start_from_root = true; } if (path[0] == '#') { parent = flecs_name_to_id(path); path ++; while (path[0] && isdigit(path[0])) { path ++; /* Skip id part of path */ } /* Skip next separator so that the returned path points to the next * name element. */ ecs_size_t sep_len = ecs_os_strlen(sep); if (!ecs_os_strncmp(path, sep, ecs_os_strlen(sep))) { path += sep_len; } start_from_root = true; } if (!start_from_root && !parent && new_entity) { parent = ecs_get_scope(world); } *path_ptr = path; return parent; } static void flecs_on_set_symbol(ecs_iter_t *it) { EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); ecs_world_t *world = it->real_world; int i; for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; flecs_name_index_ensure( &world->symbols, e, n[i].value, n[i].length, n[i].hash); } } void flecs_bootstrap_hierarchy(ecs_world_t *world) { ecs_observer(world, { .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), .query.terms[0] = { .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol), .src.id = EcsSelf }, .callback = flecs_on_set_symbol, .events = {EcsOnSet}, .yield_existing = true }); } /* Public functions */ void ecs_get_path_w_sep_buf( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (child == EcsWildcard) { ecs_strbuf_appendch(buf, '*'); return; } if (child == EcsAny) { ecs_strbuf_appendch(buf, '_'); return; } if (!sep) { sep = "."; } if (!child || parent != child) { flecs_path_append(world, parent, child, sep, prefix, buf); } else { ecs_strbuf_appendstrn(buf, "", 0); } error: return; } char* ecs_get_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf); return ecs_strbuf_get(&buf); } ecs_entity_t ecs_lookup_child( const ecs_world_t *world, ecs_entity_t parent, const char *name) { ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); if (flecs_name_is_id(name)) { ecs_entity_t result = flecs_name_to_id(name); if (result && ecs_is_alive(world, result)) { if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { return 0; } return result; } } ecs_id_t pair = ecs_childof(parent); ecs_hashmap_t *index = flecs_id_name_index_get(world, pair); if (index) { return flecs_name_index_find(index, name, 0, 0); } else { return 0; } error: return 0; } ecs_entity_t ecs_lookup( const ecs_world_t *world, const char *path) { return ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true); } ecs_entity_t ecs_lookup_symbol( const ecs_world_t *world, const char *name, bool lookup_as_path, bool recursive) { if (!name) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); ecs_entity_t e = 0; if (lookup_as_path) { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, recursive); } if (!e) { e = flecs_name_index_find(&world->symbols, name, 0, 0); } return e; error: return 0; } ecs_entity_t ecs_lookup_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, const char *path, const char *sep, const char *prefix, bool recursive) { if (!path) { return 0; } ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_world_t *stage = world; world = ecs_get_world(world); ecs_entity_t e = flecs_get_builtin(path); if (e) { return e; } e = flecs_name_index_find(&world->aliases, path, 0, 0); if (e) { return e; } char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; const char *ptr; int32_t size = ECS_NAME_BUFFER_LENGTH; ecs_entity_t cur; bool lookup_path_search = false; const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); const ecs_entity_t *lookup_path_cur = lookup_path; while (lookup_path_cur && *lookup_path_cur) { lookup_path_cur ++; } if (!sep) { sep = "."; } parent = flecs_get_parent_from_path( stage, parent, &path, sep, prefix, true); if (parent && !(parent = ecs_get_alive(world, parent))) { return 0; } if (!path[0]) { return parent; } if (!sep[0]) { return ecs_lookup_child(world, parent, path); } retry: cur = parent; ptr = path; while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { cur = ecs_lookup_child(world, cur, elem); if (!cur) { goto tail; } } tail: if (!cur && recursive) { if (!lookup_path_search) { if (parent) { parent = ecs_get_target(world, parent, EcsChildOf, 0); goto retry; } else { lookup_path_search = true; } } if (lookup_path_search) { if (lookup_path_cur != lookup_path) { lookup_path_cur --; parent = lookup_path_cur[0]; goto retry; } } } if (elem != buff) { ecs_os_free(elem); } return cur; error: return 0; } ecs_entity_t ecs_set_scope( ecs_world_t *world, ecs_entity_t scope) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t cur = stage->scope; stage->scope = scope; return cur; error: return 0; } ecs_entity_t ecs_get_scope( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->scope; error: return 0; } ecs_entity_t* ecs_set_lookup_path( ecs_world_t *world, const ecs_entity_t *lookup_path) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Safe: application owns lookup path */ ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); stage->lookup_path = lookup_path; return cur; error: return NULL; } ecs_entity_t* ecs_get_lookup_path( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); /* Safe: application owns lookup path */ return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); error: return NULL; } const char* ecs_set_name_prefix( ecs_world_t *world, const char *prefix) { flecs_poly_assert(world, ecs_world_t); const char *old_prefix = world->info.name_prefix; world->info.name_prefix = prefix; return old_prefix; } static void flecs_add_path( ecs_world_t *world, bool defer_suspend, ecs_entity_t parent, ecs_entity_t entity, const char *name) { ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (defer_suspend) { real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, parent); } ecs_assert(name[0] != '#', ECS_INVALID_PARAMETER, "path should not contain identifier with #"); ecs_set_name(world, entity, name); if (defer_suspend) { ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_resume_readonly(real_world, &srs); flecs_defer_path(stage, parent, entity, name); } } ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!sep) { sep = "."; } if (!path) { if (!entity) { entity = ecs_new(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, sep, prefix, !entity); char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr = path; char *elem = buff; int32_t size = ECS_NAME_BUFFER_LENGTH; /* If we're in deferred/readonly mode suspend it, so that the name index is * immediately updated. Without this, we could create multiple entities for * the same name in a single command queue. */ bool suspend_defer = ecs_is_deferred(world) && (ecs_get_stage_count(world) <= 1); ecs_entity_t cur = parent; char *name = NULL; if (sep[0]) { while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { ecs_entity_t e = ecs_lookup_child(world, cur, elem); if (!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, NULL)) { e = entity; last_elem = true; } if (!e) { if (last_elem) { ecs_entity_t prev = ecs_set_scope(world, 0); e = ecs_entity(world, {0}); ecs_set_scope(world, prev); } else { e = ecs_new(world); } } if (!cur && last_elem && root_path) { ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); } flecs_add_path(world, suspend_defer, cur, e, name); } cur = e; } if (entity && (cur != entity)) { ecs_throw(ECS_ALREADY_DEFINED, name); } if (name) { ecs_os_free(name); } if (elem != buff) { ecs_os_free(elem); } } else { flecs_add_path(world, suspend_defer, parent, entity, path); } return cur; error: return 0; } ecs_entity_t ecs_new_from_path_w_sep( ecs_world_t *world, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } /** * @file id.c * @brief Id utilities. */ bool ecs_id_match( ecs_id_t id, ecs_id_t pattern) { if (id == pattern) { return true; } if (ECS_HAS_ID_FLAG(pattern, PAIR)) { if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } ecs_entity_t id_first = ECS_PAIR_FIRST(id); ecs_entity_t id_second = ECS_PAIR_SECOND(id); ecs_entity_t pattern_first = ECS_PAIR_FIRST(pattern); ecs_entity_t pattern_second = ECS_PAIR_SECOND(pattern); ecs_check(id_first != 0, ECS_INVALID_PARAMETER, "first element of pair cannot be 0"); ecs_check(id_second != 0, ECS_INVALID_PARAMETER, "second element of pair cannot be 0"); ecs_check(pattern_first != 0, ECS_INVALID_PARAMETER, "first element of pair cannot be 0"); ecs_check(pattern_second != 0, ECS_INVALID_PARAMETER, "second element of pair cannot be 0"); if (pattern_first == EcsWildcard) { if (pattern_second == EcsWildcard || pattern_second == id_second) { return true; } } else if (pattern_first == EcsFlag) { /* Used for internals, helps to keep track of which ids are used in * pairs that have additional flags (like OVERRIDE and TOGGLE) */ if (ECS_HAS_ID_FLAG(id, PAIR) && !ECS_IS_PAIR(id)) { if (ECS_PAIR_FIRST(id) == pattern_second) { return true; } if (ECS_PAIR_SECOND(id) == pattern_second) { return true; } } } else if (pattern_second == EcsWildcard) { if (pattern_first == id_first) { return true; } } } else { if ((id & ECS_ID_FLAGS_MASK) != (pattern & ECS_ID_FLAGS_MASK)) { return false; } if ((ECS_COMPONENT_MASK & pattern) == EcsWildcard) { return true; } } error: return false; } bool ecs_id_is_pair( ecs_id_t id) { return ECS_HAS_ID_FLAG(id, PAIR); } bool ecs_id_is_wildcard( ecs_id_t id) { if ((id == EcsWildcard) || (id == EcsAny)) { return true; } bool is_pair = ECS_IS_PAIR(id); if (!is_pair) { return false; } ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_entity_t second = ECS_PAIR_SECOND(id); return (first == EcsWildcard) || (second == EcsWildcard) || (first == EcsAny) || (second == EcsAny); } bool ecs_id_is_valid( const ecs_world_t *world, ecs_id_t id) { if (!id) { return false; } if (ecs_id_is_wildcard(id)) { return false; } if (ECS_HAS_ID_FLAG(id, PAIR)) { if (!ECS_PAIR_FIRST(id)) { return false; } if (!ECS_PAIR_SECOND(id)) { return false; } } else if (id & ECS_ID_FLAGS_MASK) { if (!ecs_is_valid(world, id & ECS_COMPONENT_MASK)) { return false; } } return true; } ecs_flags32_t ecs_id_get_flags( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { return idr->flags; } else { return 0; } } /** * @file iter.c * @brief Iterator API. * * The iterator API contains functions that apply to all iterators, such as * resource management, or fetching resources for a matched table. The API also * contains functions for generic iterators, which make it possible to iterate * an iterator without needing to know what created the iterator. */ #include /* Utility macros to enforce consistency when initializing iterator fields */ /* If term count is smaller than cache size, initialize with inline array, * otherwise allocate. */ #define INIT_CACHE(it, stack, fields, f, T, count)\ if (!it->f && (fields & flecs_iter_cache_##f) && count) {\ it->f = flecs_stack_calloc_n(stack, T, count);\ it->priv_.cache.used |= flecs_iter_cache_##f;\ } /* If array is allocated, free it when finalizing the iterator */ #define FINI_CACHE(it, f, T, count)\ if (it->priv_.cache.used & flecs_iter_cache_##f) {\ flecs_stack_free_n((void*)it->f, T, count);\ } void* flecs_iter_calloc( ecs_iter_t *it, ecs_size_t size, ecs_size_t align) { ecs_world_t *world = it->world; ecs_stage_t *stage = flecs_stage_from_world((ecs_world_t**)&world); ecs_stack_t *stack = &stage->allocators.iter_stack; return flecs_stack_calloc(stack, size, align); } void flecs_iter_free( void *ptr, ecs_size_t size) { flecs_stack_free(ptr, size); } void flecs_iter_init( const ecs_world_t *world, ecs_iter_t *it, ecs_flags8_t fields) { ecs_assert(!ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INTERNAL_ERROR, NULL); ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); ecs_stack_t *stack = &stage->allocators.iter_stack; it->priv_.cache.used = 0; it->priv_.cache.allocated = 0; it->priv_.cache.stack_cursor = flecs_stack_get_cursor(stack); 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, columns, int32_t, it->field_count); INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_count); INIT_CACHE(it, stack, fields, ptrs, void*, it->field_count); } void 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, columns, int32_t, it->field_count); FINI_CACHE(it, variables, ecs_var_t, it->variable_count); FINI_CACHE(it, ptrs, void*, it->field_count); ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_stack_restore_cursor(&stage->allocators.iter_stack, it->priv_.cache.stack_cursor); } bool flecs_iter_next_row( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); if (!is_instanced) { int32_t instance_count = it->instance_count; int32_t count = it->count; int32_t offset = it->offset; if (instance_count > count && offset < (instance_count - 1)) { ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); int t, field_count = it->field_count; for (t = 0; t < field_count; t ++) { ecs_entity_t src = it->sources[t]; if (!src) { void *ptr = it->ptrs[t]; if (ptr) { it->ptrs[t] = ECS_OFFSET(ptr, it->sizes[t]); } } } if (it->entities) { it->entities ++; } it->offset ++; return true; } } return false; } bool flecs_iter_next_instanced( ecs_iter_t *it, bool result) { it->instance_count = it->count; bool is_instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); if (result && !is_instanced && it->count && it->shared_fields) { it->count = 1; } return result; } /* --- Public API --- */ void* ecs_field_w_size( const ecs_iter_t *it, size_t size, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); ecs_check(!size || ecs_field_size(it, index) == size || (!ecs_field_size(it, index) && (!it->ptrs[index])), ECS_INVALID_PARAMETER, "mismatching size for field %d", index); (void)size; return it->ptrs[index]; error: return NULL; } bool ecs_field_is_readonly( const ecs_iter_t *it, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, "operation only valid for query iterators"); ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); const ecs_term_t *term = &it->query->terms[index]; if (term->inout == EcsIn) { return true; } else if (term->inout == EcsInOutDefault) { if (!ecs_term_match_this(term)) { return true; } const ecs_term_ref_t *src = &term->src; if (!(src->id & EcsSelf)) { return true; } } error: return false; } bool ecs_field_is_writeonly( const ecs_iter_t *it, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, "operation only valid for query iterators"); ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); const ecs_term_t *term = &it->query->terms[index]; return term->inout == EcsOut; error: return false; } bool ecs_field_is_set( const ecs_iter_t *it, int32_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->set_fields & (1llu << (index)); error: return false; } bool ecs_field_is_self( const ecs_iter_t *it, int32_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->sources == NULL || it->sources[index] == 0; error: return false; } ecs_id_t ecs_field_id( const ecs_iter_t *it, int32_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->ids[index]; error: return 0; } int32_t ecs_field_column( const ecs_iter_t *it, int32_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->columns[index]; error: return 0; } ecs_entity_t ecs_field_src( const ecs_iter_t *it, int32_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); if (it->sources) { return it->sources[index]; } else { return 0; } error: return 0; } size_t ecs_field_size( const ecs_iter_t *it, int32_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return (size_t)it->sizes[index]; error: return 0; } char* ecs_iter_str( const ecs_iter_t *it) { if (!(it->flags & EcsIterIsValid)) { return NULL; } ecs_world_t *world = it->world; ecs_strbuf_t buf = ECS_STRBUF_INIT; int i; if (it->field_count) { ecs_strbuf_list_push(&buf, "id: ", ","); for (i = 0; i < it->field_count; i ++) { ecs_id_t id = ecs_field_id(it, i); char *str = ecs_id_str(world, id); ecs_strbuf_list_appendstr(&buf, str); ecs_os_free(str); } ecs_strbuf_list_pop(&buf, "\n"); ecs_strbuf_list_push(&buf, "src: ", ","); for (i = 0; i < it->field_count; i ++) { ecs_entity_t subj = ecs_field_src(it, i); char *str = ecs_get_path(world, subj); ecs_strbuf_list_appendstr(&buf, str); ecs_os_free(str); } ecs_strbuf_list_pop(&buf, "\n"); ecs_strbuf_list_push(&buf, "set: ", ","); for (i = 0; i < it->field_count; i ++) { if (ecs_field_is_set(it, i)) { ecs_strbuf_list_appendlit(&buf, "true"); } else { ecs_strbuf_list_appendlit(&buf, "false"); } } ecs_strbuf_list_pop(&buf, "\n"); } if (it->variable_count && it->variable_names) { int32_t actual_count = 0; for (i = 0; i < it->variable_count; i ++) { const char *var_name = it->variable_names[i]; if (!var_name || var_name[0] == '_' || !strcmp(var_name, "this")) { /* Skip anonymous variables */ continue; } ecs_var_t var = it->variables[i]; if (!var.entity) { /* Skip table variables */ continue; } if (!actual_count) { ecs_strbuf_list_push(&buf, "var: ", ","); } char *str = ecs_get_path(world, var.entity); ecs_strbuf_list_append(&buf, "%s=%s", var_name, str); ecs_os_free(str); actual_count ++; } if (actual_count) { ecs_strbuf_list_pop(&buf, "\n"); } } if (it->count) { ecs_strbuf_appendlit(&buf, "this:\n"); for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; char *str = ecs_get_path(world, e); ecs_strbuf_appendlit(&buf, " - "); ecs_strbuf_appendstr(&buf, str); ecs_strbuf_appendch(&buf, '\n'); ecs_os_free(str); } } return ecs_strbuf_get(&buf); } bool ecs_iter_next( ecs_iter_t *iter) { ecs_check(iter != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(iter->next != NULL, ECS_INVALID_PARAMETER, NULL); return iter->next(iter); error: return false; } int32_t ecs_iter_count( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ECS_BIT_SET(it->flags, EcsIterIsInstanced); int32_t count = 0; while (ecs_iter_next(it)) { count += it->count; } return count; error: return 0; } ecs_entity_t ecs_iter_first( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ECS_BIT_SET(it->flags, EcsIterIsInstanced); ecs_entity_t result = 0; if (ecs_iter_next(it)) { result = it->entities[0]; ecs_iter_fini(it); } return result; error: return 0; } bool ecs_iter_is_true( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ECS_BIT_SET(it->flags, EcsIterIsInstanced); bool result = ecs_iter_next(it); if (result) { ecs_iter_fini(it); } return result; error: return false; } ecs_entity_t ecs_iter_get_var( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &it->variables[var_id]; ecs_entity_t e = var->entity; if (!e) { ecs_table_t *table = var->range.table; if (!table && !var_id) { table = it->table; } if (table) { if ((var->range.count == 1) || (ecs_table_count(table) == 1)) { ecs_assert(ecs_table_count(table) > var->range.offset, ECS_INTERNAL_ERROR, NULL); e = ecs_vec_get_t(&table->data.entities, ecs_entity_t, var->range.offset)[0]; } } } else { ecs_assert(ecs_is_valid(it->real_world, e), ECS_INTERNAL_ERROR, NULL); } return e; error: return 0; } ecs_table_t* ecs_iter_get_var_as_table( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_var_t *var = &it->variables[var_id]; ecs_table_t *table = var->range.table; if (!table && !var_id) { table = it->table; } if (!table) { /* If table is not set, try to get table from entity */ ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { table = r->table; if (ecs_table_count(table) != 1) { /* If table contains more than the entity, make sure not to * return a partial table. */ return NULL; } } } } if (table) { if (var->range.offset) { /* Don't return whole table if only partial table is matched */ return NULL; } if (!var->range.count || ecs_table_count(table) == var->range.count) { /* Return table if count matches */ return table; } } error: return NULL; } ecs_table_range_t ecs_iter_get_var_as_range( ecs_iter_t *it, int32_t var_id) { ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(it->variables != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_range_t result = { 0 }; ecs_var_t *var = &it->variables[var_id]; ecs_table_t *table = var->range.table; if (!table && !var_id) { table = it->table; } if (!table) { ecs_entity_t e = var->entity; if (e) { ecs_record_t *r = flecs_entities_get(it->real_world, e); if (r) { result.table = r->table; result.offset = ECS_RECORD_TO_ROW(r->row); result.count = 1; } } } else { result.table = table; result.offset = var->range.offset; result.count = var->range.count; if (!result.count) { result.count = ecs_table_count(table); } } return result; error: return (ecs_table_range_t){0}; } void ecs_iter_set_var( ecs_iter_t *it, int32_t var_id, ecs_entity_t entity) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < FLECS_QUERY_VARIABLE_COUNT_MAX, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, "cannot constrain variable while iterating"); ecs_check(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); /* Update iterator for constrained iterator */ flecs_query_iter_constrain(it); error: return; } void ecs_iter_set_var_as_table( ecs_iter_t *it, int32_t var_id, const ecs_table_t *table) { ecs_table_range_t range = { .table = ECS_CONST_CAST(ecs_table_t*, table) }; ecs_iter_set_var_as_range(it, var_id, &range); } void ecs_iter_set_var_as_range( ecs_iter_t *it, int32_t var_id, const ecs_table_range_t *range) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(var_id >= 0, ECS_INVALID_PARAMETER, "invalid variable index %d", var_id); ecs_check(var_id < it->variable_count, ECS_INVALID_PARAMETER, "variable index %d out of bounds", var_id); ecs_check(range != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(range->table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!range->offset || range->offset < ecs_table_count(range->table), ECS_INVALID_PARAMETER, NULL); ecs_check((range->offset + range->count) <= ecs_table_count(range->table), ECS_INVALID_PARAMETER, NULL); ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_OPERATION, "cannot set query variables while iterating"); ecs_var_t *var = &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); /* Update iterator for constrained iterator */ flecs_query_iter_constrain(it); error: return; } bool ecs_iter_var_is_constrained( ecs_iter_t *it, int32_t var_id) { return (it->constrained_vars & (flecs_uto(uint64_t, 1 << var_id))) != 0; } static void ecs_chained_iter_fini( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_fini(it->chain_it); it->chain_it = NULL; } ecs_iter_t ecs_page_iter( const ecs_iter_t *it, int32_t offset, int32_t limit) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = *it; result.priv_.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ result.priv_.iter.page = (ecs_page_iter_t){ .offset = offset, .limit = limit, .remaining = limit }; result.next = ecs_page_next; result.fini = ecs_chained_iter_fini; result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); return result; error: return (ecs_iter_t){ 0 }; } static void flecs_offset_iter( ecs_iter_t *it, int32_t offset) { it->entities = &it->entities[offset]; int32_t t, field_count = it->field_count; void **it_ptrs = it->ptrs; if (it_ptrs) { for (t = 0; t < field_count; t ++) { void *ptrs = it_ptrs[t]; if (!ptrs) { continue; } if (it->sources[t]) { continue; } it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); } } } static bool ecs_page_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); ecs_iter_t *chain_it = it->chain_it; bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); do { if (!ecs_iter_next(chain_it)) { goto depleted; } ecs_page_iter_t *iter = &it->priv_.iter.page; /* Copy everything up to the private iterator data */ ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); /* Keep instancing setting from original iterator */ ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); if (!chain_it->table) { goto yield; /* Task query */ } int32_t offset = iter->offset; int32_t limit = iter->limit; if (!(offset || limit)) { if (it->count) { goto yield; } else { goto depleted; } } int32_t count = it->count; int32_t remaining = iter->remaining; if (offset) { if (offset > count) { /* No entities to iterate in current table */ iter->offset -= count; it->count = 0; continue; } else { it->offset += offset; count = it->count -= offset; iter->offset = 0; flecs_offset_iter(it, offset); } } if (remaining) { if (remaining > count) { iter->remaining -= count; } else { it->count = remaining; iter->remaining = 0; } } else if (limit) { /* Limit hit: no more entities left to iterate */ goto done; } } while (it->count == 0); yield: if (!ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { it->offset = 0; } return true; done: /* Cleanup iterator resources if it wasn't yet depleted */ ecs_iter_fini(chain_it); depleted: error: return false; } bool ecs_page_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_page_next_instanced(it)); error: return false; } ecs_iter_t ecs_worker_iter( const ecs_iter_t *it, int32_t index, int32_t count) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); ecs_iter_t result = *it; result.priv_.cache.stack_cursor = NULL; /* Don't copy allocator cursor */ result.priv_.iter.worker = (ecs_worker_iter_t){ .index = index, .count = count }; result.next = ecs_worker_next; result.fini = ecs_chained_iter_fini; result.chain_it = ECS_CONST_CAST(ecs_iter_t*, it); return result; error: return (ecs_iter_t){ 0 }; } static bool ecs_worker_next_instanced( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); bool instanced = ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced); ecs_iter_t *chain_it = it->chain_it; ecs_worker_iter_t *iter = &it->priv_.iter.worker; int32_t res_count = iter->count, res_index = iter->index; int32_t per_worker, instances_per_worker, first; do { if (!ecs_iter_next(chain_it)) { return false; } /* Copy everything up to the private iterator data */ ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); /* Keep instancing setting from original iterator */ ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); int32_t count = it->count; int32_t instance_count = it->instance_count; per_worker = count / res_count; instances_per_worker = instance_count / res_count; first = per_worker * res_index; count -= per_worker * res_count; if (count) { if (res_index < count) { per_worker ++; first += res_index; } else { first += count; } } if (!per_worker && it->table == NULL) { if (res_index == 0) { return true; } else { // chained iterator was not yet cleaned up // since it returned true from ecs_iter_next, so clean it up here. ecs_iter_fini(chain_it); return false; } } } while (!per_worker); it->instance_count = instances_per_worker; it->frame_offset += first; flecs_offset_iter(it, it->offset + first); it->count = per_worker; if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { it->offset += first; } else { it->offset = 0; } return true; error: return false; } bool ecs_worker_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->chain_it->flags, EcsIterIsInstanced); if (flecs_iter_next_row(it)) { return true; } return flecs_iter_next_instanced(it, ecs_worker_next_instanced(it)); error: return false; } /** * @file misc.c * @brief Miscellaneous functions. */ #include #include #ifndef FLECS_NDEBUG static int64_t flecs_s_min[] = { [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; static int64_t flecs_s_max[] = { [1] = INT8_MAX, [2] = INT16_MAX, [4] = INT32_MAX, [8] = INT64_MAX }; static uint64_t flecs_u_max[] = { [1] = UINT8_MAX, [2] = UINT16_MAX, [4] = UINT32_MAX, [8] = UINT64_MAX }; uint64_t flecs_ito_( size_t size, bool is_signed, bool lt_zero, uint64_t u, const char *err) { union { uint64_t u; int64_t s; } v; v.u = u; if (is_signed) { ecs_assert(v.s >= flecs_s_min[size], ECS_INVALID_CONVERSION, err); ecs_assert(v.s <= flecs_s_max[size], ECS_INVALID_CONVERSION, err); } else { ecs_assert(lt_zero == false, ECS_INVALID_CONVERSION, err); ecs_assert(u <= flecs_u_max[size], ECS_INVALID_CONVERSION, err); } return u; } #endif int32_t flecs_next_pow_of_2( int32_t n) { n --; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n ++; return n; } /** Convert time to double */ double ecs_time_to_double( ecs_time_t t) { double result; result = t.sec; return result + (double)t.nanosec / (double)1000000000; } ecs_time_t ecs_time_sub( ecs_time_t t1, ecs_time_t t2) { ecs_time_t result; if (t1.nanosec >= t2.nanosec) { result.nanosec = t1.nanosec - t2.nanosec; result.sec = t1.sec - t2.sec; } else { result.nanosec = t1.nanosec - t2.nanosec + 1000000000; result.sec = t1.sec - t2.sec - 1; } return result; } void ecs_sleepf( double t) { if (t > 0) { int sec = (int)t; int nsec = (int)((t - sec) * 1000000000); ecs_os_sleep(sec, nsec); } } double ecs_time_measure( ecs_time_t *start) { ecs_time_t stop, temp; ecs_os_get_time(&stop); temp = stop; stop = ecs_time_sub(stop, *start); *start = temp; return ecs_time_to_double(stop); } void* ecs_os_memdup( const void *src, ecs_size_t size) { if (!src) { return NULL; } void *dst = ecs_os_malloc(size); ecs_assert(dst != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_memcpy(dst, src, size); return dst; } int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2) { (void)ptr1; (void)ptr2; return (e1 > e2) - (e1 < e2); } int flecs_id_qsort_cmp(const void *a, const void *b) { ecs_id_t id_a = *(const ecs_id_t*)a; ecs_id_t id_b = *(const ecs_id_t*)b; return (id_a > id_b) - (id_a < id_b); } uint64_t flecs_string_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } char* flecs_vasprintf( const char *fmt, va_list args) { ecs_size_t size = 0; char *result = NULL; va_list tmpa; va_copy(tmpa, args); size = vsnprintf(result, 0, fmt, tmpa); va_end(tmpa); if ((int32_t)size < 0) { return NULL; } result = (char *) ecs_os_malloc(size + 1); if (!result) { return NULL; } ecs_os_vsnprintf(result, size + 1, fmt, args); return result; } char* flecs_asprintf( const char *fmt, ...) { va_list args; va_start(args, fmt); char *result = flecs_vasprintf(fmt, args); va_end(args); return result; } char* flecs_to_snake_case(const char *str) { int32_t upper_count = 0, len = 1; const char *ptr = str; char ch, *out, *out_ptr; for (ptr = &str[1]; (ch = *ptr); ptr ++) { if (isupper(ch)) { upper_count ++; } len ++; } out = out_ptr = ecs_os_malloc_n(char, len + upper_count + 1); for (ptr = str; (ch = *ptr); ptr ++) { if (isupper(ch)) { if ((ptr != str) && (out_ptr[-1] != '_')) { out_ptr[0] = '_'; out_ptr ++; } out_ptr[0] = (char)tolower(ch); out_ptr ++; } else { out_ptr[0] = ch; out_ptr ++; } } out_ptr[0] = '\0'; return out; } char* flecs_load_from_file( const char *filename) { FILE* file; char* content = NULL; int32_t bytes; size_t size; /* Open file for reading */ ecs_os_fopen(&file, filename, "r"); if (!file) { ecs_err("%s (%s)", ecs_os_strerror(errno), filename); goto error; } /* Determine file size */ fseek(file, 0, SEEK_END); bytes = (int32_t)ftell(file); if (bytes == -1) { goto error; } fseek(file, 0, SEEK_SET); /* Load contents in memory */ content = ecs_os_malloc(bytes + 1); size = (size_t)bytes; if (!(size = fread(content, 1, size, file)) && bytes) { ecs_err("%s: read zero bytes instead of %d", filename, size); ecs_os_free(content); content = NULL; goto error; } else { content[size] = '\0'; } fclose(file); return content; error: ecs_os_free(content); return NULL; } char* flecs_chresc( char *out, char in, char delimiter) { char *bptr = out; switch(in) { case '\a': *bptr++ = '\\'; *bptr = 'a'; break; case '\b': *bptr++ = '\\'; *bptr = 'b'; break; case '\f': *bptr++ = '\\'; *bptr = 'f'; break; case '\n': *bptr++ = '\\'; *bptr = 'n'; break; case '\r': *bptr++ = '\\'; *bptr = 'r'; break; case '\t': *bptr++ = '\\'; *bptr = 't'; break; case '\v': *bptr++ = '\\'; *bptr = 'v'; break; case '\\': *bptr++ = '\\'; *bptr = '\\'; break; case '\033': *bptr = '['; /* Used for terminal colors */ break; default: if (in == delimiter) { *bptr++ = '\\'; *bptr = delimiter; } else { *bptr = in; } break; } *(++bptr) = '\0'; return bptr; } const char* flecs_chrparse( const char *in, char *out) { const char *result = in + 1; char ch; if (in[0] == '\\') { result ++; switch(in[1]) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '\\': ch = '\\'; break; case '"': ch = '"'; break; case '0': ch = '\0'; break; case ' ': ch = ' '; break; case '$': ch = '$'; break; default: goto error; } } else { ch = in[0]; } if (out) { *out = ch; } return result; error: return NULL; } ecs_size_t flecs_stresc( char *out, ecs_size_t n, char delimiter, const char *in) { const char *ptr = in; char ch, *bptr = out, buff[3]; ecs_size_t written = 0; while ((ch = *ptr++)) { if ((written += (ecs_size_t)(flecs_chresc( buff, ch, delimiter) - buff)) <= n) { /* If size != 0, an out buffer must be provided. */ ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); *bptr++ = buff[0]; if ((ch = buff[1])) { *bptr = ch; bptr++; } } } if (bptr) { while (written < n) { *bptr = '\0'; bptr++; written++; } } return written; error: return 0; } char* flecs_astresc( char delimiter, const char *in) { if (!in) { return NULL; } ecs_size_t len = flecs_stresc(NULL, 0, delimiter, in); char *out = ecs_os_malloc_n(char, len + 1); flecs_stresc(out, len, delimiter, in); out[len] = '\0'; return out; } const char* flecs_parse_digit( const char *ptr, char *token) { char *tptr = token; char ch = ptr[0]; if (!isdigit(ch) && ch != '-') { ecs_parser_error(NULL, NULL, 0, "invalid start of number '%s'", ptr); return NULL; } tptr[0] = ch; tptr ++; ptr ++; for (; (ch = *ptr); ptr ++) { if (!isdigit(ch) && (ch != '.') && (ch != 'e')) { break; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; return ptr; } const char* flecs_parse_ws_eol( const char *ptr) { while (isspace(*ptr)) { ptr ++; } return ptr; } /** * @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; } 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_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 observers should've unregistered by now */ ecs_assert(!ecs_map_is_init(&er->event_ids), ECS_INTERNAL_ERROR, NULL); } flecs_sparse_fini(&observable->events); } ecs_event_record_t* flecs_event_record_get( const ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); /* Builtin events*/ if (event == EcsOnAdd) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_add); else if (event == EcsOnRemove) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_remove); else if (event == EcsOnSet) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_set); else if (event == EcsWildcard) return ECS_CONST_CAST(ecs_event_record_t*, &o->on_wildcard); /* User events */ return flecs_sparse_try_t(&o->events, ecs_event_record_t, event); } ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { return er; } er = flecs_sparse_ensure_t(&o->events, ecs_event_record_t, event); er->event = event; return er; } static const ecs_event_record_t* flecs_event_record_get_if( const ecs_observable_t *o, ecs_entity_t event) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_event_record_t *er = flecs_event_record_get(o, event); if (er) { if (ecs_map_is_init(&er->event_ids)) { return er; } if (er->any) { return er; } if (er->wildcard) { return er; } if (er->wildcard_pair) { return er; } } return NULL; } ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id) { if (!er) { return NULL; } if (id == EcsAny) return er->any; else if (id == EcsWildcard) return er->wildcard; else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; else { if (ecs_map_is_init(&er->event_ids)) { return ecs_map_get_deref(&er->event_ids, ecs_event_id_record_t, id); } return NULL; } } static ecs_event_id_record_t* flecs_event_id_record_get_if( const ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (!ider) { return NULL; } if (ider->observer_count) { return ider; } return NULL; } ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id) { ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); if (ider) { return ider; } ider = ecs_os_calloc_t(ecs_event_id_record_t); if (id == EcsAny) { return er->any = ider; } else if (id == EcsWildcard) { return er->wildcard = ider; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { return er->wildcard_pair = ider; } ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); ecs_map_insert_ptr(&er->event_ids, id, ider); return ider; } void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id) { if (id == EcsAny) { er->any = NULL; } else if (id == EcsWildcard) { er->wildcard = NULL; } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { er->wildcard_pair = NULL; } else { ecs_map_remove(&er->event_ids, id); if (!ecs_map_count(&er->event_ids)) { ecs_map_fini(&er->event_ids); } } } static int32_t flecs_event_observers_get( const ecs_event_record_t *er, ecs_id_t id, ecs_event_id_record_t **iders) { if (!er) { return 0; } /* Populate array with observer sets matching the id */ int32_t count = 0; if (id != EcsAny) { iders[0] = flecs_event_id_record_get_if(er, EcsAny); count += iders[count] != 0; } iders[count] = flecs_event_id_record_get_if(er, id); count += iders[count] != 0; if (id != EcsAny) { if (ECS_IS_PAIR(id)) { ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); iders[count] = flecs_event_id_record_get_if(er, id_fwc); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id_swc); count += iders[count] != 0; iders[count] = flecs_event_id_record_get_if(er, id_pwc); count += iders[count] != 0; } else { iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); count += iders[count] != 0; } } return count; } bool flecs_observers_exist( ecs_observable_t *observable, ecs_id_t id, ecs_entity_t event) { const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); if (!er) { return false; } return flecs_event_id_record_get_if(er, id) != NULL; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *tgt_idr, ecs_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count); static void flecs_emit_propagate_id( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *cur, ecs_entity_t trav, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { return; } const ecs_table_record_t *tr; int32_t event_cur = it->event_cur; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!ecs_table_count(table)) { continue; } bool owned = flecs_id_record_get_table(idr, table) != NULL; int32_t e, entity_count = ecs_table_count(table); it->table = table; it->other_table = NULL; it->offset = 0; it->count = entity_count; if (entity_count) { it->entities = ecs_vec_first(&table->data.entities); } /* Treat as new event as this could invoke observers again for * different tables. */ it->event_cur = ++ world->event_id; int32_t ider_i; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); if (!owned) { /* Owned takes precedence */ flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } if (!table->_->traversable_count) { continue; } ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (e = 0; e < entity_count; e ++) { ecs_record_t *r = flecs_entities_get(world, entities[e]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *idr_t = r->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate(world, it, idr, idr_t, trav, iders, ider_count); } } } it->event_cur = event_cur; } static void flecs_emit_propagate( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_id_record_t *tgt_idr, ecs_entity_t propagate_trav, ecs_event_id_record_t **iders, int32_t ider_count) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("propagate events/invalidate cache for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Propagate to records of traversable relationships */ ecs_id_record_t *cur = tgt_idr; while ((cur = cur->trav.next)) { cur->reachable.generation ++; /* Invalidate cache */ /* Get traversed relationship */ ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); if (propagate_trav && propagate_trav != trav) { if (propagate_trav != EcsIsA) { continue; } } flecs_emit_propagate_id( world, it, idr, cur, trav, iders, ider_count); } ecs_log_pop_3(); } static void flecs_emit_propagate_invalidate_tables( ecs_world_t *world, ecs_id_record_t *tgt_idr) { ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("invalidate reachable cache for %s", idstr); ecs_os_free(idstr); } /* Invalidate records of traversable relationships */ ecs_id_record_t *cur = tgt_idr; while ((cur = cur->trav.next)) { ecs_reachable_cache_t *rc = &cur->reachable; if (rc->current != rc->generation) { /* Subtree is already marked invalid */ continue; } rc->generation ++; ecs_table_cache_iter_t idt; if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { continue; } const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } int32_t e, entity_count = ecs_table_count(table); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (e = 0; e < entity_count; e ++) { ecs_record_t *r = flecs_entities_get(world, entities[e]); ecs_id_record_t *idr_t = r->idr; if (idr_t) { /* Only notify for entities that are used in pairs with * traversable relationships */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } } } void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count) { ecs_entity_t *entities = ecs_vec_get_t(&table->data.entities, ecs_entity_t, offset); int32_t i; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Event is used as target in traversable relationship, propagate */ flecs_emit_propagate_invalidate_tables(world, idr_t); } } } static void flecs_propagate_entities( ecs_world_t *world, ecs_iter_t *it, ecs_id_record_t *idr, ecs_entity_t *entities, int32_t count, ecs_entity_t src, ecs_event_id_record_t **iders, int32_t ider_count) { if (!count) { return; } ecs_entity_t old_src = it->sources[0]; ecs_table_t *old_table = it->table; ecs_table_t *old_other_table = it->other_table; ecs_entity_t *old_entities = it->entities; int32_t old_count = it->count; int32_t old_offset = it->offset; int32_t i; for (i = 0; i < count; i ++) { ecs_record_t *record = flecs_entities_get(world, entities[i]); if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } ecs_id_record_t *idr_t = record->idr; if (idr_t) { /* Entity is used as target in traversable pairs, propagate */ ecs_entity_t e = src ? src : entities[i]; it->sources[0] = e; flecs_emit_propagate( world, it, idr, idr_t, 0, iders, ider_count); } } it->table = old_table; it->other_table = old_other_table; it->entities = old_entities; it->count = old_count; it->offset = old_offset; it->sources[0] = old_src; } static void flecs_override_copy( ecs_world_t *world, ecs_table_t *table, const ecs_type_info_t *ti, void *dst, const void *src, int32_t offset, int32_t count) { void *ptr = dst; ecs_copy_t copy = ti->hooks.copy; ecs_size_t size = ti->size; int32_t i; if (copy) { for (i = 0; i < count; i ++) { copy(ptr, src, 1, ti); ptr = ECS_OFFSET(ptr, size); } } else { for (i = 0; i < count; i ++) { ecs_os_memcpy(ptr, src, size); ptr = ECS_OFFSET(ptr, size); } } ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_entity_t *entities = ecs_vec_get_t( &table->data.entities, ecs_entity_t, offset); flecs_invoke_hook(world, table, count, offset, entities, dst, ti->component, ti, EcsOnSet, on_set); } } static void* flecs_override( ecs_iter_t *it, const ecs_type_t *emit_ids, ecs_id_t id, ecs_table_t *table, ecs_id_record_t *idr) { if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { return NULL; } int32_t i = 0, count = emit_ids->count; ecs_id_t *ids = emit_ids->array; for (i = 0; i < count; i ++) { if (ids[i] == id) { /* If an id was both inherited and overridden in the same event * (like what happens during an auto override), we need to copy the * value of the inherited component to the new component. * Also flag to the callee that this component was overridden, so * that an OnSet event can be emitted for it. * Note that this is different from a component that was overridden * after it was inherited, as this does not change the actual value * of the component for the entity (it is copied from the existing * overridden component), and does not require an OnSet event. */ ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { continue; } int32_t index = tr->column; ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); ecs_column_t *column = &table->data.columns[index]; ecs_size_t size = column->ti->size; return ecs_vec_get(&column->data, size, it->offset); } } return NULL; } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids); static void flecs_emit_forward_id( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_entity_t tgt, ecs_table_t *tgt_table, int32_t column, int32_t offset, ecs_entity_t trav) { ecs_id_t id = idr->id; ecs_entity_t event = er ? er->event : 0; bool inherit = trav == EcsIsA; bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); ecs_event_id_record_t *iders[5]; ecs_event_id_record_t *iders_onset[5]; /* Skip id if there are no observers for it */ int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); int32_t ider_onset_i, ider_onset_count = 0; if (er_onset) { ider_onset_count = flecs_event_observers_get( er_onset, id, iders_onset); } if (!may_override && (!ider_count && !ider_onset_count)) { return; } it->ids[0] = id; it->sources[0] = tgt; it->event_id = id; it->ptrs[0] = NULL; ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; /* safe, owned by observer */ int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); if (storage_i != -1) { ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *c = &tgt_table->data.columns[storage_i]; it->ptrs[0] = ecs_vec_get(&c->data, c->ti->size, offset); ECS_CONST_CAST(int32_t*, it->sizes)[0] = c->ti->size; /* safe, see above */ } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); bool owned = tr != NULL; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } /* Emit OnSet events for newly inherited components */ if (storage_i != -1) { bool override = false; /* If component was added together with IsA relationship, still emit * OnSet event, as it's a new value for the entity. */ void *base_ptr = it->ptrs[0]; void *ptr = flecs_override(it, emit_ids, id, table, idr); if (ptr) { override = true; it->ptrs[0] = ptr; } if (ider_onset_count) { it->event = er_onset->event; for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); /* Owned takes precedence */ if (!owned) { flecs_observers_invoke( world, &ider->self_up, it, table, trav); } else if (override) { ecs_entity_t src = it->sources[0]; it->sources[0] = 0; flecs_observers_invoke(world, &ider->self, it, table, 0); flecs_observers_invoke(world, &ider->self_up, it, table, 0); it->sources[0] = src; } } it->event = event; it->ptrs[0] = base_ptr; } } } static void flecs_emit_forward_and_cache_id( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_entity_t tgt, ecs_record_t *tgt_record, ecs_table_t *tgt_table, const ecs_table_record_t *tgt_tr, int32_t column, int32_t offset, ecs_vec_t *reachable_ids, ecs_entity_t trav) { /* Cache forwarded id for (rel, tgt) pair */ ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator, reachable_ids, ecs_reachable_elem_t); elem->tr = tgt_tr; elem->record = tgt_record; elem->src = tgt; elem->id = idr->id; #ifndef NDEBUG elem->table = tgt_table; #endif ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, tgt, tgt_table, column, offset, trav); } static int32_t flecs_emit_stack_at( ecs_vec_t *stack, ecs_id_record_t *idr) { int32_t sp = 0, stack_count = ecs_vec_count(stack); ecs_table_t **stack_elems = ecs_vec_first(stack); for (sp = 0; sp < stack_count; sp ++) { ecs_table_t *elem = stack_elems[sp]; if (flecs_id_record_get_table(idr, elem)) { break; } } return sp; } static bool flecs_emit_stack_has( ecs_vec_t *stack, ecs_id_record_t *idr) { return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); } static void flecs_emit_forward_cached_ids( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_reachable_cache_t *rc, ecs_vec_t *reachable_ids, ecs_vec_t *stack, ecs_entity_t trav) { ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t i, count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *rc_elem = &elems[i]; const ecs_table_record_t *rc_tr = rc_elem->tr; ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache; ecs_record_t *rc_record = rc_elem->record; ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, rc_elem->src) == rc_record, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(rc_record->table == rc_elem->table, ECS_INTERNAL_ERROR, NULL); if (flecs_emit_stack_has(stack, rc_idr)) { continue; } int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row); flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, rc_idr, rc_elem->src, rc_record, rc_record->table, rc_tr, rc_tr->index, rc_offset, reachable_ids, trav); } } static void flecs_emit_dump_cache( ecs_world_t *world, const ecs_vec_t *vec) { ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); for (int i = 0; i < ecs_vec_count(vec); i ++) { ecs_reachable_elem_t *elem = &elems[i]; char *idstr = ecs_id_str(world, elem->id); char *estr = ecs_id_str(world, elem->src); ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", idstr, (uint32_t)elem->id, estr, (uint32_t)elem->src, elem->table); ecs_os_free(idstr); ecs_os_free(estr); } if (!ecs_vec_count(vec)) { ecs_dbg_3("- no entries"); } } static void flecs_emit_forward_table_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t tgt, ecs_table_t *tgt_table, ecs_record_t *tgt_record, ecs_id_record_t *tgt_idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids) { ecs_allocator_t *a = &world->allocator; int32_t i, id_count = tgt_table->type.count; ecs_id_t *ids = tgt_table->type.array; int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row); int32_t rc_child_offset = ecs_vec_count(reachable_ids); int32_t stack_count = ecs_vec_count(stack); /* If tgt_idr is out of sync but is not the current id record being updated, * keep track so that we can update two records for the cost of one. */ ecs_reachable_cache_t *rc = &tgt_idr->reachable; bool parent_revalidate = (reachable_ids != &rc->ids) && (rc->current != rc->generation); if (parent_revalidate) { ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); } if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("forward events from %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); /* Function may have to copy values from overridden components if an IsA * relationship was added together with other components. */ ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id); bool inherit = trav == EcsIsA; for (i = 0; i < id_count; i ++) { ecs_id_t id = ids[i]; ecs_table_record_t *tgt_tr = &tgt_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; if (inherit && !(idr->flags & EcsIdOnInstantiateInherit)) { continue; } /* Id has the same relationship, traverse to find ids for forwarding */ if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, ecs_table_t*); t[0] = tgt_table; ecs_reachable_cache_t *idr_rc = &idr->reachable; if (idr_rc->current == idr_rc->generation) { /* Cache hit, use cached ids to prevent traversing the same * hierarchy multiple times. This especially speeds up code * where (deep) hierarchies are created. */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, id); ecs_dbg_3("forward cached for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, table, idr_rc, reachable_ids, stack, trav); ecs_log_pop_3(); } else { /* Cache is dirty, traverse upwards */ do { flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, idr, stack, reachable_ids); if (++i >= id_count) { break; } id = ids[i]; if (ECS_PAIR_FIRST(id) != trav) { break; } } while (true); } ecs_vec_remove_last(stack); continue; } int32_t stack_at = flecs_emit_stack_at(stack, idr); if (parent_revalidate && (stack_at == (stack_count - 1))) { /* If parent id record needs to be revalidated, add id */ ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, ecs_reachable_elem_t); elem->tr = tgt_tr; elem->record = tgt_record; elem->src = tgt; elem->id = idr->id; #ifndef NDEBUG elem->table = tgt_table; #endif } /* Skip id if it's masked by a lower table in the tree */ if (stack_at != stack_count) { continue; } flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, offset, reachable_ids, trav); } if (parent_revalidate) { /* If this is not the current cache being updated, but it's marked * as out of date, use intermediate results to populate cache. */ int32_t rc_parent_offset = ecs_vec_count(&rc->ids); /* Only add ids that were added for this table */ int32_t count = ecs_vec_count(reachable_ids); count -= rc_child_offset; /* Append ids to any ids that already were added /*/ if (count) { ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, ecs_reachable_elem_t, rc_parent_offset); ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, ecs_reachable_elem_t, rc_child_offset); ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); } rc->current = rc->generation; if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("cache revalidated for %s:", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } } ecs_log_pop_3(); } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids) { ecs_id_t id = idr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *tgt_record = flecs_entities_try(world, tgt); ecs_table_t *tgt_table; if (!tgt_record || !(tgt_table = tgt_record->table)) { return; } flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, tgt, tgt_table, tgt_record, idr, stack, reachable_ids); } static void flecs_emit_forward( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr) { ecs_reachable_cache_t *rc = &idr->reachable; if (rc->current != rc->generation) { /* Cache miss, iterate the tree to find ids to forward */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, idr->id); ecs_dbg_3("reachable cache miss for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); ecs_vec_t stack; ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, idr, &stack, &rc->ids); it->sources[0] = 0; ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); if (it->event == EcsOnAdd || it->event == EcsOnRemove) { /* Only OnAdd/OnRemove events can validate top-level cache, which * is for the id for which the event is emitted. * The reason for this is that we don't want to validate the cache * while the administration for the mutated entity isn't up to * date yet. */ rc->current = rc->generation; } if (ecs_should_log_3()) { ecs_dbg_3("cache after rebuild:"); flecs_emit_dump_cache(world, &rc->ids); } ecs_log_pop_3(); } else { /* Cache hit, use cached values instead of walking the tree */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, idr->id); ecs_dbg_3("reachable cache hit for %s", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } ecs_entity_t trav = ECS_PAIR_FIRST(idr->id); ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t i, count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *elem = &elems[i]; const ecs_table_record_t *tr = elem->tr; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; ecs_record_t *r = elem->record; ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, elem->src) == r, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); int32_t offset = ECS_RECORD_TO_ROW(r->row); flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, rc_idr, elem->src, r->table, tr->index, offset, trav); } } /* Propagate events for new reachable ids downwards */ if (table->_->traversable_count) { int32_t i; ecs_entity_t *entities = flecs_table_entities_array(table); entities = ECS_ELEM_T(entities, ecs_entity_t, it->offset); for (i = 0; i < it->count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->idr) { break; } } if (i != it->count) { ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *elem = &elems[i]; const ecs_table_record_t *tr = elem->tr; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; ecs_record_t *r = elem->record; ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, elem->src) == r, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); (void)r; ecs_event_id_record_t *iders[5] = {0}; int32_t ider_count = flecs_event_observers_get( er, rc_idr->id, iders); flecs_propagate_entities(world, it, rc_idr, it->entities, it->count, elem->src, iders, ider_count); } } } } /* The emit function is responsible for finding and invoking the observers * matching the emitted event. The function is also capable of forwarding events * for newly reachable ids (after adding a relationship) and propagating events * downwards. Both capabilities are not just useful in application logic, but * are also an important building block for keeping query caches in sync. */ void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_flags64_t set_mask, ecs_event_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->event != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->event != EcsWildcard, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->ids != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->ids->count != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); ecs_time_t t = {0}; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&t); } const ecs_type_t *ids = desc->ids; ecs_entity_t event = desc->event; ecs_table_t *table = desc->table, *other_table = desc->other_table; int32_t offset = desc->offset; int32_t i, count = desc->count; ecs_flags32_t table_flags = table->flags; /* Deferring cannot be suspended for observers */ int32_t defer = world->stages[0]->defer; if (defer < 0) { world->stages[0]->defer *= -1; } /* Table events are emitted for internal table operations only, and do not * provide component data and/or entity ids. */ bool table_event = desc->flags & EcsEventTableOnly; if (!count && !table_event) { /* If no count is provided, forward event for all entities in table */ count = ecs_table_count(table) - offset; } /* The world event id is used to determine if an observer has already been * triggered for an event. Observers for multiple components are split up * into multiple observers for a single component, and this counter is used * to make sure a multi observer only triggers once, even if multiple of its * single-component observers trigger. */ int32_t evtx = ++world->event_id; ecs_id_t ids_cache = 0; void *ptrs_cache = NULL; ecs_size_t sizes_cache = 0; int32_t columns_cache = 0; ecs_entity_t sources_cache = 0; ecs_iter_t it = { .world = stage, .real_world = world, .event = event, .event_cur = evtx, .table = table, .field_count = 1, .ids = &ids_cache, .ptrs = &ptrs_cache, .sizes = &sizes_cache, .columns = &columns_cache, .sources = &sources_cache, .other_table = other_table, .offset = offset, .count = count, .param = ECS_CONST_CAST(void*, desc->param), .flags = desc->flags | EcsIterIsValid }; ecs_observable_t *observable = ecs_get_observable(desc->observable); ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); /* Event records contain all observers for a specific event. In addition to * the emitted event, also request data for the Wildcard event (for * observers subscribing to the wildcard event), OnSet events. The * latter to are used for automatically emitting OnSet events for * inherited components, for example when an IsA relationship is added to an * entity. This doesn't add much overhead, as fetching records is cheap for * builtin event types. */ const ecs_event_record_t *er = flecs_event_record_get_if(observable, event); const ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); const ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); ecs_data_t *storage = NULL; ecs_column_t *columns = NULL; if (count) { storage = &table->data; columns = storage->columns; it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset); } int32_t id_count = ids->count; ecs_id_t *id_array = ids->array; /* If a table has IsA relationships, OnAdd/OnRemove events can trigger * (un)overriding a component. When a component is overridden its value is * initialized with the value of the overridden component. */ bool can_override = count && (table_flags & EcsTableHasIsA) && ( (event == EcsOnAdd) || (event == EcsOnRemove)); /* When a new (traversable) relationship is added (emitting an OnAdd/OnRemove * event) this will cause the components of the target entity to be * propagated to the source entity. This makes it possible for observers to * get notified of any new reachable components though the relationship. */ bool can_forward = event != EcsOnSet; /* Does table has observed entities */ bool has_observed = table_flags & EcsTableHasTraversable; ecs_event_id_record_t *iders[5] = {0}; if (count && can_forward && has_observed) { flecs_emit_propagate_invalidate(world, table, offset, count); } repeat_event: /* This is the core event logic, which is executed for each event. By * default this is just the event kind from the ecs_event_desc_t struct, but * can also include the Wildcard and UnSet events. The latter is emitted as * counterpart to OnSet, for any removed ids associated with data. */ for (i = 0; i < id_count; i ++) { /* Emit event for each id passed to the function. In most cases this * will just be one id, like a component that was added, removed or set. * In some cases events are emitted for multiple ids. * * One example is when an id was added with a "With" property, or * inheriting from a prefab with overrides. In these cases an entity is * moved directly to the archetype with the additional components. */ ecs_id_record_t *idr = NULL; const ecs_type_info_t *ti = NULL; ecs_id_t id = id_array[i]; int32_t ider_i, ider_count = 0; bool is_pair = ECS_IS_PAIR(id); void *override_ptr = NULL; ecs_entity_t base = 0; bool id_can_override = can_override; ecs_flags64_t id_bit = 1llu << i; if (id_bit & set_mask) { /* Component is already set, so don't override with prefab value */ id_can_override = false; } /* Check if this id is a pair of an traversable relationship. If so, we * may have to forward ids from the pair's target. */ if ((can_forward && is_pair) || id_can_override) { idr = flecs_id_record_get(world, id); if (!idr) { /* Possible for union ids */ continue; } ecs_flags32_t idr_flags = idr->flags; if (is_pair && (idr_flags & EcsIdTraversable)) { const ecs_event_record_t *er_fwd = NULL; if (ECS_PAIR_FIRST(id) == EcsIsA) { if (event == EcsOnAdd) { if (!world->stages[0]->base) { /* Adding an IsA relationship can trigger prefab * instantiation, which can instantiate prefab * hierarchies for the entity to which the * relationship was added. */ ecs_entity_t tgt = ECS_PAIR_SECOND(id); /* Setting this value prevents flecs_instantiate * from being called recursively, in case prefab * children also have IsA relationships. */ world->stages[0]->base = tgt; flecs_instantiate(world, tgt, table, offset, count); world->stages[0]->base = 0; } /* Adding an IsA relationship will emit OnSet events for * any new reachable components. */ er_fwd = er_onset; } } /* Forward events for components from pair target */ flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } if (id_can_override && !(idr_flags & EcsIdOnInstantiateDontInherit)) { /* 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; if (idr->flags & EcsIdIsSparse) { override_ptr = flecs_sparse_get_any( idr->sparse, 0, base); } else { base_column = base_tr->column; ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL); ecs_record_t *base_r = flecs_entities_get(world, base); ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL); int32_t base_row = ECS_RECORD_TO_ROW(base_r->row); ecs_vec_t *base_v = &base_table->data.columns[base_column].data; override_ptr = ecs_vec_get(base_v, ti->size, base_row); } } } } } if (er) { /* Get observer sets for id. There can be multiple sets of matching * observers, in case an observer matches for wildcard ids. For * example, both observers for (ChildOf, p) and (ChildOf, *) would * match an event for (ChildOf, p). */ ider_count = flecs_event_observers_get(er, id, iders); idr = idr ? idr : flecs_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); } if (!ider_count && !override_ptr) { /* If nothing more to do for this id, early out */ continue; } ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr == NULL) { /* When a single batch contains multiple add's for an exclusive * relationship, it's possible that an id was in the added list * that is no longer available for the entity. */ continue; } int32_t column = tr->index, storage_i; it.columns[0] = column + 1; it.ptrs[0] = NULL; ECS_CONST_CAST(int32_t*, it.sizes)[0] = 0; /* safe, owned by observer */ it.event_id = id; it.ids[0] = id; if (count) { storage_i = tr->column; bool is_sparse = idr->flags & EcsIdIsSparse; if (storage_i != -1 || is_sparse) { void *ptr; ecs_size_t size = idr->type_info->size; if (is_sparse) { ecs_assert(count == 1, ECS_UNSUPPORTED, "events for multiple entities are currently unsupported" " for sparse components"); ecs_entity_t e = flecs_table_entities_array(table)[offset]; ptr = flecs_sparse_get(idr->sparse, 0, e); } else{ ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_column_t *c = &columns[storage_i]; ptr = ecs_vec_get(&c->data, size, offset); } /* safe, owned by observer */ ECS_CONST_CAST(int32_t*, it.sizes)[0] = size; if (override_ptr) { if (event == EcsOnAdd) { /* If this is a new override, initialize the component * with the value of the overridden component. */ flecs_override_copy( world, table, ti, ptr, override_ptr, offset, count); } else if (er_onset) { /* If an override was removed, this re-exposes the * overridden component. Because this causes the actual * (now inherited) value of the component to change, an * OnSet event must be emitted for the base component.*/ ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); ecs_event_id_record_t *iders_set[5] = {0}; int32_t ider_set_i, ider_set_count = flecs_event_observers_get(er_onset, id, iders_set); if (ider_set_count) { /* Set the source temporarily to the base and base * component pointer. */ it.sources[0] = base; it.ptrs[0] = ptr; for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { ecs_event_id_record_t *ider = iders_set[ider_set_i]; flecs_observers_invoke( world, &ider->self_up, &it, table, EcsIsA); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); flecs_observers_invoke( world, &ider->up, &it, table, EcsIsA); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } it.sources[0] = 0; } } } it.ptrs[0] = ptr; } } /* Actually invoke observers for this event/id */ for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->self, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); flecs_observers_invoke(world, &ider->self_up, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } if (!ider_count || !count || !has_observed) { continue; } /* The table->traversable_count value indicates if the table contains any * entities that are used as targets of traversable relationships. If the * entity/entities for which the event was generated is used as such a * target, events must be propagated downwards. */ flecs_propagate_entities( world, &it, idr, it.entities, count, 0, iders, ider_count); } can_override = false; /* Don't override twice */ can_forward = false; /* Don't forward twice */ if (wcer && er != wcer) { /* Repeat event loop for Wildcard event */ er = wcer; it.event = event; goto repeat_event; } error: world->stages[0]->defer = defer; if (measure_time) { world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); } return; } void ecs_emit( ecs_world_t *stage, ecs_event_desc_t *desc) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!(desc->param && desc->const_param), ECS_INVALID_PARAMETER, "cannot set param and const_param at the same time"); if (desc->entity) { ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, desc->entity); ecs_table_t *table; if (!r || !(table = r->table)) { /* Empty entities can't trigger observers */ return; } desc->table = table; desc->offset = ECS_RECORD_TO_ROW(r->row); desc->count = 1; } if (!desc->observable) { desc->observable = world; } ecs_type_t default_ids = (ecs_type_t){ .count = 1, .array = (ecs_id_t[]){ EcsAny } }; if (!desc->ids || !desc->ids->count) { desc->ids = &default_ids; } if (desc->const_param) { desc->param = ECS_CONST_CAST(void*, desc->const_param); desc->const_param = NULL; } ecs_defer_begin(world); flecs_emit(world, stage, 0, desc); ecs_defer_end(world); if (desc->ids == &default_ids) { desc->ids = NULL; } error: return; } void ecs_enqueue( ecs_world_t *world, ecs_event_desc_t *desc) { if (!ecs_is_deferred(world)) { ecs_emit(world, desc); return; } ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_enqueue(world, stage, desc); } /** * @file observer.c * @brief Observer implementation. * * The observer implementation contains functions for creating, deleting and * invoking observers. The code is split up into single-term observers and * multi-term observers. Multi-term observers are created from multiple single- * term observers. */ #include static ecs_entity_t flecs_get_observer_event( ecs_term_t *term, ecs_entity_t event) { /* If operator is Not, reverse the event */ if (term->oper == EcsNot) { if (event == EcsOnAdd || event == EcsOnSet) { event = EcsOnRemove; } else if (event == EcsOnRemove) { event = EcsOnAdd; } } return event; } static ecs_flags32_t flecs_id_flag_for_event( ecs_entity_t e) { if (e == EcsOnAdd) { return EcsIdHasOnAdd; } if (e == EcsOnRemove) { return EcsIdHasOnRemove; } if (e == EcsOnSet) { return EcsIdHasOnSet; } if (e == EcsOnTableFill) { return EcsIdHasOnTableFill; } if (e == EcsOnTableEmpty) { return EcsIdHasOnTableEmpty; } if (e == EcsOnTableCreate) { return EcsIdHasOnTableCreate; } if (e == EcsOnTableDelete) { return EcsIdHasOnTableDelete; } if (e == EcsWildcard) { return EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet| EcsIdHasOnTableFill|EcsIdHasOnTableEmpty| EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; } return 0; } static void flecs_inc_observer_count( ecs_world_t *world, ecs_entity_t event, ecs_event_record_t *evt, ecs_id_t id, int32_t value) { ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); int32_t result = idt->observer_count += value; if (result == 1) { /* Notify framework that there are observers for the event/id. This * allows parts of the code to skip event evaluation early */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags |= flags; } } } else if (result == 0) { /* Ditto, but the reverse */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableNoTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags &= ~flags; } } flecs_event_id_record_remove(evt, id); ecs_os_free(idt); } } static ecs_id_t flecs_observer_id( ecs_id_t id) { if (ECS_IS_PAIR(id)) { if (ECS_PAIR_FIRST(id) == EcsAny) { id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); } if (ECS_PAIR_SECOND(id) == EcsAny) { id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); } } return id; } static void flecs_register_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_id_t term_id = flecs_observer_id(impl->register_id); ecs_term_t *term = &o->query->terms[0]; ecs_entity_t trav = term->trav; int i; for (i = 0; i < o->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, o->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, impl->id, o); 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 *o) { ecs_term_t *term = &o->query->terms[0]; ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { ecs_assert(term->trav != 0, ECS_INTERNAL_ERROR, NULL); flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, up)); } } static void flecs_unregister_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_id_t term_id = flecs_observer_id(impl->register_id); ecs_term_t *term = &o->query->terms[0]; ecs_entity_t trav = term->trav; int i; for (i = 0; i < o->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, o->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, impl->id); if (!ecs_map_count(id_observers)) { ecs_map_fini(id_observers); } flecs_inc_observer_count(world, event, er, term_id, -1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), -1); } } } static void flecs_unregister_observer( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); if (o->query->term_count == 0) { return; } ecs_term_t *term = &o->query->terms[0]; ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, up)); } } static bool flecs_ignore_observer( ecs_observer_t *o, ecs_table_t *table, ecs_iter_t *it) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_observer_impl_t *impl = flecs_observer_impl(o); int32_t *last_event_id = impl->last_event_id; if (last_event_id && last_event_id[0] == it->event_cur) { return true; } if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { return true; } ecs_flags32_t table_flags = table->flags, query_flags = o->query->flags; bool result = (table_flags & EcsTableIsPrefab) && !(query_flags & EcsQueryMatchPrefab); result = result || ((table_flags & EcsTableIsDisabled) && !(query_flags & EcsQueryMatchDisabled)); 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 *o, 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_path(world, it->system); ecs_dbg_3("observer: invoke %s", path); ecs_os_free(path); } ecs_log_push_3(); ecs_entity_t old_system = flecs_stage_set_system( world->stages[0], o->entity); world->info.observers_ran_frame ++; ecs_query_t *query = o->query; ecs_assert(term_index < query->term_count, ECS_INTERNAL_ERROR, NULL); ecs_term_t *term = &query->terms[term_index]; if (it->table && (term->oper != EcsNot)) { ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), ECS_INTERNAL_ERROR, NULL); } bool instanced = query->flags & EcsQueryIsInstanced; bool match_this = query->flags & EcsQueryMatchThis; bool table_only = it->flags & EcsIterTableOnly; if (match_this && (simple_result || instanced || table_only)) { callback(it); query->eval_count ++; } else { ecs_entity_t observer_src = ECS_TERM_REF_ID(&term->src); if (observer_src && !(term->src.id & 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); query->eval_count ++; } else if (observer_src == e) { ecs_entity_t dummy = 0; it->entities = &dummy; if (!src) { it->sources[0] = e; } callback(it); query->eval_count ++; it->sources[0] = src; break; } } it->entities = entities; it->count = count; } flecs_stage_set_system(world->stages[0], old_system); ecs_log_pop_3(); } static void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; it->ctx = o->ctx; it->callback = o->callback; if (ecs_should_log_3()) { char *path = ecs_get_path(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 *o, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, bool simple_result) { ecs_query_t *query = o->query; ecs_term_t *term = &query->terms[0]; if (flecs_ignore_observer(o, table, it)) { return; } ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); if (trav && term->trav != trav) { return; } ecs_observer_impl_t *impl = flecs_observer_impl(o); bool is_filter = term->inout == EcsInOutNone; ECS_BIT_COND(it->flags, EcsIterNoData, is_filter); it->system = o->entity; it->ctx = o->ctx; it->callback_ctx = o->callback_ctx; it->run_ctx = o->run_ctx; it->term_index = impl->term_index; it->query = o->query; ecs_entity_t event = it->event; int32_t event_cur = it->event_cur; it->event = flecs_get_observer_event(term, event); if (o->run) { it->next = flecs_default_next_callback; it->callback = flecs_default_uni_observer_run_callback; it->ctx = o; o->run(it); } else { ecs_iter_action_t callback = o->callback; it->callback = callback; flecs_observer_invoke(world, it, o, callback, 0, simple_result); } it->event = event; it->event_cur = event_cur; } void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav) { if (ecs_map_is_init(observers)) { ecs_table_lock(it->world, table); bool simple_result = flecs_is_simple_result(it); ecs_map_iter_t oit = ecs_map_iter(observers); while (ecs_map_next(&oit)) { ecs_observer_t *o = ecs_map_ptr(&oit); ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); flecs_uni_observer_invoke( world, o, it, table, trav, simple_result); } ecs_table_unlock(it->world, table); } } static void flecs_multi_observer_invoke( ecs_iter_t *it) { ecs_observer_t *o = it->ctx; flecs_poly_assert(o, ecs_observer_t); ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_world_t *world = it->real_world; if (impl->last_event_id[0] == it->event_cur) { /* Already handled this event */ return; } impl->last_event_id[0] = world->event_id; 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->query->terms[pivot_term]; bool is_not = term->oper == EcsNot; if (is_not) { table = it->other_table; prev_table = it->table; } table = table ? table : &world->store.root; prev_table = prev_table ? prev_table : &world->store.root; ecs_iter_t user_it; bool match; if (is_not) { match = ecs_query_has_table(o->query, table, &user_it); if (match) { /* The target table matches but the entity hasn't moved to it yet. * Now match the not_query, which will populate the iterator with * data from the table the entity is still stored in. */ ecs_iter_fini(&user_it); match = ecs_query_has_table(impl->not_query, prev_table, &user_it); /* A not query replaces Not terms with Optional terms, so if the * regular query matches, the not_query should also match. */ ecs_assert(match, ECS_INTERNAL_ERROR, NULL); } } else { ecs_table_range_t range = { .table = table, .offset = it->offset, .count = it->count }; match = ecs_query_has_range(o->query, &range, &user_it); } if (match) { /* Monitor observers only invoke when the query matches for the first * time with an entity */ if (impl->flags & EcsObserverIsMonitor) { ecs_iter_t table_it; if (ecs_query_has_table(o->query, prev_table, &table_it)) { ecs_iter_fini(&table_it); ecs_iter_fini(&user_it); goto done; } } ECS_BIT_COND(user_it.flags, EcsIterNoData, ECS_BIT_IS_SET(o->query->flags, EcsQueryNoData)); /* Patch data from original iterator. If the observer query has * wildcards which triggered the original event, the component id that * got matched by ecs_query_has_range may not be the same as the one * that caused the event. We need to make sure to communicate the * component id that actually triggered the observer. */ int32_t pivot_field = term->field_index; ecs_assert(pivot_field >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(pivot_field < user_it.field_count, ECS_INTERNAL_ERROR, NULL); user_it.ids[pivot_field] = it->event_id; user_it.ptrs[pivot_field] = it->ptrs[0]; user_it.columns[pivot_field] = it->columns[0]; user_it.term_index = pivot_term; user_it.ctx = o->ctx; user_it.callback_ctx = o->callback_ctx; user_it.run_ctx = o->run_ctx; user_it.param = it->param; user_it.callback = o->callback; user_it.system = o->entity; user_it.event = it->event; user_it.event_id = it->event_id; user_it.other_table = it->other_table; ecs_entity_t old_system = flecs_stage_set_system( world->stages[0], o->entity); ecs_table_lock(it->world, table); if (o->run) { user_it.next = flecs_default_next_callback; o->run(&user_it); } else { user_it.callback(&user_it); } ecs_iter_fini(&user_it); ecs_table_unlock(it->world, table); flecs_stage_set_system(world->stages[0], old_system); return; } else { /* While the observer query was strictly speaking evaluated, it's more * useful to measure how often the observer was actually invoked. */ o->query->eval_count --; } done: return; } /* For convenience, so applications can (in theory) use a single run callback * that uses ecs_iter_next to iterate results */ bool flecs_default_next_callback(ecs_iter_t *it) { if (it->interrupted_by) { return false; } else { /* Use interrupted_by to signal the next iteration must return false */ ecs_assert(it->system != 0, ECS_INTERNAL_ERROR, NULL); it->interrupted_by = it->system; return true; } } /* Run action for children of multi observer */ static void flecs_multi_observer_builtin_run(ecs_iter_t *it) { ecs_observer_t *o = it->ctx; ecs_run_action_t run = o->run; if (run) { if (flecs_observer_impl(o)->flags & EcsObserverBypassQuery) { it->next = flecs_default_next_callback; it->callback = flecs_multi_observer_invoke; it->interrupted_by = 0; it->run_ctx = o->run_ctx; run(it); return; } } flecs_multi_observer_invoke(it); } static void flecs_observer_yield_existing( ecs_world_t *world, ecs_observer_t *o) { ecs_run_action_t run = o->run; if (!run) { run = flecs_multi_observer_invoke; } ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_defer_begin(world); /* If yield existing is enabled, invoke for each thing that matches * the event, if the event is iterable. */ int i, count = o->event_count; for (i = 0; i < count; i ++) { ecs_iter_t it = ecs_query_iter(world, o->query); it.system = o->entity; it.ctx = o; it.callback = flecs_default_uni_observer_run_callback; it.callback_ctx = o->callback_ctx; it.run_ctx = o->run_ctx; it.event = o->events[i]; while (ecs_query_next(&it)) { it.event_id = it.ids[0]; it.event_cur = ++ world->event_id; ecs_iter_next_action_t next = it.next; it.next = flecs_default_next_callback; run(&it); it.next = next; it.interrupted_by = 0; } } ecs_defer_end(world); } static int flecs_uni_observer_init( ecs_world_t *world, ecs_observer_t *o, const ecs_observer_desc_t *desc) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_term_t *term = &o->query->terms[0]; impl->last_event_id = desc->last_event_id; if (!impl->last_event_id) { impl->last_event_id = &impl->last_event_id_storage; } impl->register_id = term->id; term->field_index = flecs_ito(int16_t, desc->term_index_); if (ecs_id_is_tag(world, term->id)) { /* If id is a tag, downgrade OnSet to OnAdd. */ int32_t e, count = o->event_count; bool has_on_add = false; for (e = 0; e < count; e ++) { if (o->events[e] == EcsOnAdd) { has_on_add = true; } } for (e = 0; e < count; e ++) { if (o->events[e] == EcsOnSet) { if (has_on_add) { /* Already registered */ o->events[e] = 0; } else { o->events[e] = EcsOnAdd; } } } } flecs_uni_observer_register(world, o->observable, o); return 0; } static int flecs_observer_add_child( ecs_world_t *world, ecs_observer_t *o, const ecs_observer_desc_t *child_desc) { ecs_observer_t *child_observer = flecs_observer_init( world, 0, child_desc); if (!child_observer) { return -1; } ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_vec_append_t(&world->allocator, &impl->children, ecs_observer_t*)[0] = child_observer; child_observer->entity = o->entity; return 0; } static int flecs_multi_observer_init( ecs_world_t *world, ecs_observer_t *o, const ecs_observer_desc_t *desc) { ecs_observer_impl_t *impl = flecs_observer_impl(o); /* Create last event id for filtering out the same event that arrives from * more than one term */ impl->last_event_id = ecs_os_calloc_t(int32_t); /* Mark observer as multi observer */ impl->flags |= EcsObserverIsMulti; /* Vector that stores a single-component observer for each query term */ ecs_vec_init_t(&world->allocator, &impl->children, ecs_observer_t*, 2); /* Create a child observer for each term in the query */ ecs_query_t *query = o->query; ecs_observer_desc_t child_desc = *desc; child_desc.last_event_id = impl->last_event_id; child_desc.run = NULL; child_desc.callback = flecs_multi_observer_builtin_run; child_desc.ctx = o; child_desc.ctx_free = NULL; child_desc.query.expr = NULL; child_desc.callback_ctx = NULL; child_desc.callback_ctx_free = NULL; child_desc.run_ctx = NULL; child_desc.run_ctx_free = NULL; child_desc.yield_existing = false; ecs_os_zeromem(&child_desc.entity); ecs_os_zeromem(&child_desc.query.terms); ecs_os_zeromem(&child_desc.query); ecs_os_memcpy_n(child_desc.events, o->events, ecs_entity_t, o->event_count); int i, term_count = query->term_count; bool optional_only = query->flags & EcsQueryMatchThis; bool has_not = false; for (i = 0; i < term_count; i ++) { if (query->terms[i].oper != EcsOptional) { if (ecs_term_match_this(&query->terms[i])) { optional_only = false; } } if ((query->terms[i].oper == EcsNot) && (query->terms[i].inout != EcsInOutFilter)) { has_not = true; } } if (query->flags & EcsQueryMatchPrefab) { child_desc.query.flags |= EcsQueryMatchPrefab; } if (query->flags & EcsQueryMatchDisabled) { child_desc.query.flags |= EcsQueryMatchDisabled; } for (i = 0; i < term_count; i ++) { if (query->terms[i].inout == EcsInOutFilter) { continue; } ecs_term_t *term = &child_desc.query.terms[0]; child_desc.term_index_ = query->terms[i].field_index; *term = query->terms[i]; int16_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); if (!type) { continue; } int32_t ti, ti_count = type->count; ecs_id_t *ti_ids = type->array; /* Correct operator will be applied when an event occurs, and * the observer is evaluated on the observer source */ term->oper = EcsAnd; for (ti = 0; ti < ti_count; ti ++) { ecs_id_t ti_id = ti_ids[ti]; ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); if (idr->flags & EcsIdOnInstantiateDontInherit) { continue; } term->first.name = NULL; term->first.id = ti_ids[ti]; term->id = ti_ids[ti]; if (flecs_observer_add_child(world, o, &child_desc)) { goto error; } } continue; } /* Single component observers never use OR */ if (oper == EcsOr) { term->oper = EcsAnd; } /* If observer only contains optional terms, match everything */ if (optional_only) { term->id = EcsAny; term->first.id = EcsAny; term->src.id = EcsThis | EcsIsVariable | EcsSelf; term->second.id = 0; } else if (term->oper == EcsOptional) { continue; } if (flecs_observer_add_child(world, o, &child_desc)) { goto error; } if (optional_only) { break; } } /* If observer has Not terms, we need to create a query that replaces Not * with Optional which we can use to populate the observer data for the * table that the entity moved away from (or to, if it's an OnRemove * observer). */ if (has_not) { ecs_query_desc_t not_desc = desc->query; not_desc.expr = NULL; ecs_os_memcpy_n(not_desc.terms, o->query->terms, ecs_term_t, term_count); /* cast suppresses warning */ for (i = 0; i < term_count; i ++) { if (not_desc.terms[i].oper == EcsNot) { not_desc.terms[i].oper = EcsOptional; } } flecs_observer_impl(o)->not_query = ecs_query_init(world, ¬_desc); } return 0; error: return -1; } static void flecs_observer_poly_fini(void *ptr) { flecs_observer_fini(ptr); } ecs_observer_t* flecs_observer_init( ecs_world_t *world, ecs_entity_t entity, const ecs_observer_desc_t *desc) { ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_check(desc->callback != NULL || desc->run != NULL, ECS_INVALID_OPERATION, "cannot create observer: must at least specify callback or run"); ecs_observer_impl_t *impl = flecs_sparse_add_t( &world->store.observers, ecs_observer_impl_t); ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_init(impl, ecs_observer_t); ecs_observer_t *o = &impl->pub; impl->id = flecs_sparse_last_id(&world->store.observers); impl->dtor = flecs_observer_poly_fini; /* Make writeable copy of query desc so that we can set name. This will * make debugging easier, as any error messages related to creating the * query will have the name of the observer. */ ecs_query_desc_t query_desc = desc->query; query_desc.entity = 0; query_desc.cache_kind = EcsQueryCacheNone; /* Create query */ ecs_query_t *query = o->query = ecs_query_init( world, &query_desc); if (query == NULL) { flecs_observer_fini(o); return 0; } flecs_poly_assert(query, ecs_query_t); ecs_check(o->query->term_count > 0, ECS_INVALID_PARAMETER, "observer must have at least one term"); ecs_observable_t *observable = desc->observable; if (!observable) { observable = ecs_get_observable(world); } o->run = desc->run; o->callback = desc->callback; o->ctx = desc->ctx; o->callback_ctx = desc->callback_ctx; o->run_ctx = desc->run_ctx; o->ctx_free = desc->ctx_free; o->callback_ctx_free = desc->callback_ctx_free; o->run_ctx_free = desc->run_ctx_free; o->observable = observable; o->entity = entity; impl->term_index = desc->term_index_; impl->flags = desc->flags_; /* Check if observer is monitor. Monitors are created as multi observers * since they require pre/post checking of the filter to test if the * entity is entering/leaving the monitor. */ int i; for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) { ecs_entity_t event = desc->events[i]; if (!event) { break; } if (event == EcsMonitor) { ecs_check(i == 0, ECS_INVALID_PARAMETER, "monitor observers can only have a single Monitor event"); o->events[0] = EcsOnAdd; o->events[1] = EcsOnRemove; o->event_count ++; impl->flags |= EcsObserverIsMonitor; } else { o->events[i] = event; } o->event_count ++; } /* Observer must have at least one event */ ecs_check(o->event_count != 0, ECS_INVALID_PARAMETER, "observer must have at least one event"); bool multi = false; if (query->term_count == 1 && !desc->last_event_id) { ecs_term_t *term = &query->terms[0]; /* If the query has a single term but it is a *From operator, we * need to create a multi observer */ multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom); /* An observer with only optional terms is a special case that is * only handled by multi observers */ multi |= term->oper == EcsOptional; } bool is_monitor = impl->flags & EcsObserverIsMonitor; if (query->term_count == 1 && !is_monitor && !multi) { if (flecs_uni_observer_init(world, o, desc)) { goto error; } } else { if (flecs_multi_observer_init(world, o, desc)) { goto error; } } if (desc->yield_existing) { flecs_observer_yield_existing(world, o); } return o; error: return NULL; } ecs_entity_t ecs_observer_init( ecs_world_t *world, const ecs_observer_desc_t *desc) { ecs_entity_t entity = 0; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_observer_desc_t was not initialized to zero"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot create observer while world is being deleted"); entity = desc->entity; if (!entity) { entity = ecs_entity(world, {0}); } EcsPoly *poly = flecs_poly_bind(world, entity, ecs_observer_t); if (!poly->poly) { ecs_observer_t *o = flecs_observer_init(world, entity, desc); ecs_assert(o->entity == entity, ECS_INTERNAL_ERROR, NULL); poly->poly = o; if (ecs_get_name(world, entity)) { ecs_trace("#[green]observer#[reset] %s created", ecs_get_name(world, entity)); } } else { flecs_poly_assert(poly->poly, ecs_observer_t); ecs_observer_t *o = (ecs_observer_t*)poly->poly; if (o->ctx_free) { if (o->ctx && o->ctx != desc->ctx) { o->ctx_free(o->ctx); } } if (o->callback_ctx_free) { if (o->callback_ctx && o->callback_ctx != desc->callback_ctx) { o->callback_ctx_free(o->callback_ctx); o->callback_ctx_free = NULL; o->callback_ctx = NULL; } } if (o->run_ctx_free) { if (o->run_ctx && o->run_ctx != desc->run_ctx) { o->run_ctx_free(o->run_ctx); o->run_ctx_free = NULL; o->run_ctx = NULL; } } if (desc->run) { o->run = desc->run; if (!desc->callback) { o->callback = NULL; } } if (desc->callback) { o->callback = desc->callback; if (!desc->run) { o->run = NULL; } } if (desc->ctx) { o->ctx = desc->ctx; } if (desc->callback_ctx) { o->callback_ctx = desc->callback_ctx; } if (desc->run_ctx) { o->run_ctx = desc->run_ctx; } if (desc->ctx_free) { o->ctx_free = desc->ctx_free; } if (desc->callback_ctx_free) { o->callback_ctx_free = desc->callback_ctx_free; } if (desc->run_ctx_free) { o->run_ctx_free = desc->run_ctx_free; } } flecs_poly_modified(world, entity, ecs_observer_t); return entity; error: if (entity) { ecs_delete(world, entity); } return 0; } const ecs_observer_t* ecs_observer_get( const ecs_world_t *world, ecs_entity_t observer) { return flecs_poly_get(world, observer, ecs_observer_t); } void flecs_observer_fini( ecs_observer_t *o) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = o->query->world; ecs_observer_impl_t *impl = flecs_observer_impl(o); if (impl->flags & EcsObserverIsMulti) { ecs_observer_t **children = ecs_vec_first(&impl->children); int32_t i, children_count = ecs_vec_count(&impl->children); for (i = 0; i < children_count; i ++) { flecs_observer_fini(children[i]); } ecs_os_free(impl->last_event_id); } else { if (o->query->term_count) { flecs_unregister_observer(world, o->observable, o); } else { /* Observer creation failed while creating query */ } } ecs_vec_fini_t(&world->allocator, &impl->children, ecs_observer_t*); /* Cleanup queries */ ecs_query_fini(o->query); if (impl->not_query) { ecs_query_fini(impl->not_query); } /* Cleanup context */ if (o->ctx_free) { o->ctx_free(o->ctx); } if (o->callback_ctx_free) { o->callback_ctx_free(o->callback_ctx); } if (o->run_ctx_free) { o->run_ctx_free(o->run_ctx); } flecs_poly_fini(o, ecs_observer_t); flecs_sparse_remove_t( &world->store.observers, ecs_observer_impl_t, impl->id); } void flecs_observer_set_disable_bit( ecs_world_t *world, ecs_entity_t e, ecs_flags32_t bit, bool cond) { const EcsPoly *poly = ecs_get_pair(world, e, EcsPoly, EcsObserver); if (!poly || !poly->poly) { return; } ecs_observer_t *o = poly->poly; ecs_observer_impl_t *impl = flecs_observer_impl(o); if (impl->flags & EcsObserverIsMulti) { ecs_observer_t **children = ecs_vec_first(&impl->children); int32_t i, children_count = ecs_vec_count(&impl->children); if (children_count) { for (i = 0; i < children_count; i ++) { ECS_BIT_COND(flecs_observer_impl(children[i])->flags, bit, cond); } } } else { flecs_poly_assert(o, ecs_observer_t); ECS_BIT_COND(impl->flags, bit, cond); } } /** * @file os_api.c * @brief Operating system abstraction API. * * The OS API implements an overridable interface for implementing functions * that are operating system specific, in addition to a number of hooks which * allow for customization by the user, like logging. */ #include #include void ecs_os_api_impl(ecs_os_api_t *api); static bool ecs_os_api_initialized = false; static bool ecs_os_api_initializing = false; static int ecs_os_api_init_count = 0; ecs_os_api_t ecs_os_api = { .flags_ = EcsOsApiHighResolutionTimer | EcsOsApiLogWithColors, .log_level_ = -1 /* Disable tracing by default, but log warnings/errors */ }; int64_t ecs_os_api_malloc_count = 0; int64_t ecs_os_api_realloc_count = 0; int64_t ecs_os_api_calloc_count = 0; int64_t ecs_os_api_free_count = 0; void ecs_os_set_api( ecs_os_api_t *os_api) { if (!ecs_os_api_initialized) { ecs_os_api = *os_api; ecs_os_api_initialized = true; } } ecs_os_api_t ecs_os_get_api(void) { return ecs_os_api; } void ecs_os_init(void) { if (!ecs_os_api_initialized) { ecs_os_set_api_defaults(); } if (!(ecs_os_api_init_count ++)) { if (ecs_os_api.init_) { ecs_os_api.init_(); } } } void ecs_os_fini(void) { if (!--ecs_os_api_init_count) { if (ecs_os_api.fini_) { ecs_os_api.fini_(); } } } /* Assume every non-glibc Linux target has no execinfo. This mainly fixes musl support, as musl doesn't define any preprocessor macro specifying its presence. */ #if defined(ECS_TARGET_LINUX) && !defined(__GLIBC__) #define HAVE_EXECINFO 0 #elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) #define HAVE_EXECINFO 1 #else #define HAVE_EXECINFO 0 #endif #if HAVE_EXECINFO #include #define ECS_BT_BUF_SIZE 100 void flecs_dump_backtrace( void *stream) { int nptrs; void *buffer[ECS_BT_BUF_SIZE]; char **strings; nptrs = backtrace(buffer, ECS_BT_BUF_SIZE); strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { return; } for (int j = 1; j < nptrs; j++) { fprintf(stream, "%s\n", strings[j]); } free(strings); } #else void flecs_dump_backtrace( void *stream) { (void)stream; } #endif #undef HAVE_EXECINFO_H static void flecs_log_msg( int32_t level, const char *file, int32_t line, const char *msg) { FILE *stream = ecs_os_api.log_out_; if (!stream) { stream = stdout; } bool use_colors = ecs_os_api.flags_ & EcsOsApiLogWithColors; bool timestamp = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; bool deltatime = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; time_t now = 0; if (deltatime) { now = time(NULL); int64_t delta = 0; if (ecs_os_api.log_last_timestamp_) { delta = now - ecs_os_api.log_last_timestamp_; } ecs_os_api.log_last_timestamp_ = (int64_t)now; if (delta) { if (delta < 10) { fputs(" ", stream); } if (delta < 100) { fputs(" ", stream); } char time_buf[20]; ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)delta); fputs("+", stream); fputs(time_buf, stream); fputs(" ", stream); } else { fputs(" ", stream); } } if (timestamp) { if (!now) { now = time(NULL); } char time_buf[20]; ecs_os_snprintf(time_buf, 20, "%u", (uint32_t)now); fputs(time_buf, stream); fputs(" ", stream); } if (level >= 4) { if (use_colors) fputs(ECS_NORMAL, stream); fputs("jrnl", stream); } else if (level >= 0) { if (level == 0) { if (use_colors) fputs(ECS_MAGENTA, stream); } else { if (use_colors) fputs(ECS_GREY, stream); } fputs("info", stream); } else if (level == -2) { if (use_colors) fputs(ECS_YELLOW, stream); fputs("warning", stream); } else if (level == -3) { if (use_colors) fputs(ECS_RED, stream); fputs("error", stream); } else if (level == -4) { if (use_colors) fputs(ECS_RED, stream); fputs("fatal", stream); } if (use_colors) fputs(ECS_NORMAL, stream); fputs(": ", stream); if (level >= 0) { if (ecs_os_api.log_indent_) { char indent[32]; int i, indent_count = ecs_os_api.log_indent_; if (indent_count > 15) indent_count = 15; for (i = 0; i < indent_count; i ++) { indent[i * 2] = '|'; indent[i * 2 + 1] = ' '; } if (ecs_os_api.log_indent_ != indent_count) { indent[i * 2 - 2] = '+'; } indent[i * 2] = '\0'; fputs(indent, stream); } } if (level < 0) { if (file) { const char *file_ptr = strrchr(file, '/'); if (!file_ptr) { file_ptr = strrchr(file, '\\'); } if (file_ptr) { file = file_ptr + 1; } fputs(file, stream); fputs(": ", stream); } if (line) { fprintf(stream, "%d: ", line); } } fputs(msg, stream); fputs("\n", stream); if (level == -4) { flecs_dump_backtrace(stream); } } void ecs_os_dbg( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(1, file, line, msg); } } void ecs_os_trace( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(0, file, line, msg); } } void ecs_os_warn( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-2, file, line, msg); } } void ecs_os_err( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-3, file, line, msg); } } void ecs_os_fatal( const char *file, int32_t line, const char *msg) { if (ecs_os_api.log_) { ecs_os_api.log_(-4, file, line, msg); } } static void ecs_os_gettime(ecs_time_t *time) { ecs_assert(ecs_os_has_time() == true, ECS_MISSING_OS_API, NULL); uint64_t now = ecs_os_now(); uint64_t sec = now / 1000000000; assert(sec < UINT32_MAX); assert((now - sec * 1000000000) < UINT32_MAX); time->sec = (uint32_t)sec; time->nanosec = (uint32_t)(now - sec * 1000000000); } static void* ecs_os_api_malloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_malloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); return malloc((size_t)size); } static void* ecs_os_api_calloc(ecs_size_t size) { ecs_os_linc(&ecs_os_api_calloc_count); ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); return calloc(1, (size_t)size); } static void* ecs_os_api_realloc(void *ptr, ecs_size_t size) { ecs_assert(size > 0, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_os_linc(&ecs_os_api_realloc_count); } else { /* If not actually reallocing, treat as malloc */ ecs_os_linc(&ecs_os_api_malloc_count); } return realloc(ptr, (size_t)size); } static void ecs_os_api_free(void *ptr) { if (ptr) { ecs_os_linc(&ecs_os_api_free_count); } free(ptr); } static char* ecs_os_api_strdup(const char *str) { if (str) { int len = ecs_os_strlen(str); char *result = ecs_os_malloc(len + 1); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcpy(result, str); return result; } else { return NULL; } } void ecs_os_strset(char **str, const char *value) { char *old = str[0]; str[0] = ecs_os_strdup(value); ecs_os_free(old); } /* Replace dots with underscores */ static char *module_file_base(const char *module, char sep) { char *base = ecs_os_strdup(module); ecs_size_t i, len = ecs_os_strlen(base); for (i = 0; i < len; i ++) { if (base[i] == '.') { base[i] = sep; } } return base; } static char* ecs_os_api_module_to_dl(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with underscores + OS library extension */ char *file_base = module_file_base(module, '_'); # if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".so"); # elif defined(ECS_TARGET_DARWIN) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dylib"); # elif defined(ECS_TARGET_WINDOWS) ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dll"); # endif ecs_os_free(file_base); return ecs_strbuf_get(&lib); } static char* ecs_os_api_module_to_etc(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with dashes + /etc */ char *file_base = module_file_base(module, '-'); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, "/etc"); ecs_os_free(file_base); return ecs_strbuf_get(&lib); } void ecs_os_set_api_defaults(void) { /* Don't overwrite if already initialized */ if (ecs_os_api_initialized != 0) { return; } if (ecs_os_api_initializing != 0) { return; } ecs_os_api_initializing = true; /* Memory management */ ecs_os_api.malloc_ = ecs_os_api_malloc; ecs_os_api.free_ = ecs_os_api_free; ecs_os_api.realloc_ = ecs_os_api_realloc; ecs_os_api.calloc_ = ecs_os_api_calloc; /* Strings */ ecs_os_api.strdup_ = ecs_os_api_strdup; /* Time */ ecs_os_api.get_time_ = ecs_os_gettime; /* Logging */ ecs_os_api.log_ = flecs_log_msg; /* Modules */ if (!ecs_os_api.module_to_dl_) { ecs_os_api.module_to_dl_ = ecs_os_api_module_to_dl; } if (!ecs_os_api.module_to_etc_) { ecs_os_api.module_to_etc_ = ecs_os_api_module_to_etc; } ecs_os_api.abort_ = abort; # ifdef FLECS_OS_API_IMPL /* Initialize defaults to OS API IMPL addon, but still allow for overriding * by the application */ ecs_set_os_api_impl(); ecs_os_api_initialized = false; # endif ecs_os_api_initializing = false; } bool ecs_os_has_heap(void) { return (ecs_os_api.malloc_ != NULL) && (ecs_os_api.calloc_ != NULL) && (ecs_os_api.realloc_ != NULL) && (ecs_os_api.free_ != NULL); } bool ecs_os_has_threading(void) { return (ecs_os_api.mutex_new_ != NULL) && (ecs_os_api.mutex_free_ != NULL) && (ecs_os_api.mutex_lock_ != NULL) && (ecs_os_api.mutex_unlock_ != NULL) && (ecs_os_api.cond_new_ != NULL) && (ecs_os_api.cond_free_ != NULL) && (ecs_os_api.cond_wait_ != NULL) && (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.thread_new_ != NULL) && (ecs_os_api.thread_join_ != NULL) && (ecs_os_api.thread_self_ != NULL); } bool ecs_os_has_task_support(void) { return (ecs_os_api.mutex_new_ != NULL) && (ecs_os_api.mutex_free_ != NULL) && (ecs_os_api.mutex_lock_ != NULL) && (ecs_os_api.mutex_unlock_ != NULL) && (ecs_os_api.cond_new_ != NULL) && (ecs_os_api.cond_free_ != NULL) && (ecs_os_api.cond_wait_ != NULL) && (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.task_new_ != NULL) && (ecs_os_api.task_join_ != NULL); } bool ecs_os_has_time(void) { return (ecs_os_api.get_time_ != NULL) && (ecs_os_api.sleep_ != NULL) && (ecs_os_api.now_ != NULL); } bool ecs_os_has_logging(void) { return (ecs_os_api.log_ != NULL); } bool ecs_os_has_dl(void) { return (ecs_os_api.dlopen_ != NULL) && (ecs_os_api.dlproc_ != NULL) && (ecs_os_api.dlclose_ != NULL); } bool ecs_os_has_modules(void) { return (ecs_os_api.module_to_dl_ != NULL) && (ecs_os_api.module_to_etc_ != NULL); } #if defined(ECS_TARGET_WINDOWS) static char error_str[255]; #endif const char* ecs_os_strerror(int err) { # if defined(ECS_TARGET_WINDOWS) strerror_s(error_str, 255, err); return error_str; # else return strerror(err); # endif } /** * @file poly.c * @brief Functions for managing poly objects. * * The poly framework makes it possible to generalize common functionality for * different kinds of API objects, as well as improved type safety checks. Poly * objects have a header that identifiers what kind of object it is. This can * then be used to discover a set of "mixins" implemented by the type. * * Mixins are like a vtable, but for members. Each type populates the table with * offsets to the members that correspond with the mixin. If an entry in the * mixin table is not set, the type does not support the mixin. */ static const char* mixin_kind_str[] = { [EcsMixinWorld] = "world", [EcsMixinEntity] = "entity", [EcsMixinObservable] = "observable", [EcsMixinDtor] = "dtor", [EcsMixinMax] = "max (should never be requested by application)" }; ecs_mixins_t ecs_world_t_mixins = { .type_name = "ecs_world_t", .elems = { [EcsMixinWorld] = offsetof(ecs_world_t, self), [EcsMixinObservable] = offsetof(ecs_world_t, observable), } }; ecs_mixins_t ecs_stage_t_mixins = { .type_name = "ecs_stage_t", .elems = { [EcsMixinWorld] = offsetof(ecs_stage_t, world) } }; ecs_mixins_t ecs_observer_t_mixins = { .type_name = "ecs_observer_t", .elems = { [EcsMixinWorld] = offsetof(ecs_observer_t, world), [EcsMixinEntity] = offsetof(ecs_observer_t, entity), [EcsMixinDtor] = offsetof(ecs_observer_impl_t, dtor) } }; static void* assert_mixin( const ecs_poly_t *poly, ecs_mixin_kind_t kind) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); const ecs_mixins_t *mixins = hdr->mixins; ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); ecs_size_t offset = mixins->elems[kind]; ecs_assert(offset != 0, ECS_INVALID_PARAMETER, "mixin %s not available for type %s", mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); (void)mixin_kind_str; /* Object has mixin, return its address */ return ECS_OFFSET(hdr, offset); } void* flecs_poly_init_( ecs_poly_t *poly, int32_t type, ecs_size_t size, ecs_mixins_t *mixins) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_os_memset(poly, 0, size); hdr->magic = ECS_OBJECT_MAGIC; hdr->type = type; hdr->refcount = 1; hdr->mixins = mixins; return poly; } void flecs_poly_fini_( ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); (void)type; ecs_header_t *hdr = poly; /* Don't deinit poly that wasn't initialized */ ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, "incorrect function called to free flecs object"); hdr->magic = 0; } int32_t flecs_poly_claim_( ecs_poly_t *poly) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); if (ecs_os_has_threading()) { return ecs_os_ainc(&hdr->refcount); } else { return ++hdr->refcount; } } int32_t flecs_poly_release_( ecs_poly_t *poly) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); if (ecs_os_has_threading()) { return ecs_os_adec(&hdr->refcount); } else { return --hdr->refcount; } } int32_t flecs_poly_refcount( ecs_poly_t *poly) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); return hdr->refcount; } EcsPoly* flecs_poly_bind_( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { /* Add tag to the entity for easy querying. This will make it possible to * query for `Query` instead of `(Poly, Query) */ if (!ecs_has_id(world, entity, tag)) { ecs_add_id(world, entity, tag); } /* Never defer creation of a poly object */ bool deferred = false; if (ecs_is_deferred(world)) { deferred = true; ecs_defer_suspend(world); } /* If this is a new poly, leave the actual creation up to the caller so they * call tell the difference between a create or an update */ EcsPoly *result = ecs_ensure_pair(world, entity, EcsPoly, tag); if (deferred) { ecs_defer_resume(world); } return result; } void flecs_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* flecs_poly_bind_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { return ecs_get_pair(world, entity, EcsPoly, tag); } ecs_poly_t* flecs_poly_get_( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { const EcsPoly *p = flecs_poly_bind_get_(world, entity, tag); if (p) { return p->poly; } return NULL; } bool flecs_poly_is_( const ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, "invalid/freed pointer to flecs object detected"); return hdr->type == type; } ecs_observable_t* ecs_get_observable( const ecs_poly_t *poly) { return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); } const ecs_world_t* ecs_get_world( const ecs_poly_t *poly) { if (((const ecs_header_t*)poly)->type == ecs_world_t_magic) { return poly; } return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); } ecs_entity_t ecs_get_entity( const ecs_poly_t *poly) { return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); } flecs_poly_dtor_t* ecs_get_dtor( const ecs_poly_t *poly) { return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); } /** * @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_record_t *idr, ecs_id_t *ids, ecs_id_t *id_out, ecs_table_record_t **tr_out) { ecs_table_record_t *tr = ecs_table_cache_get(&idr->cache, table); if (tr) { int32_t r = tr->index; if (tr_out) tr_out[0] = tr; if (id_out) { id_out[0] = 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] = type_id; } return offset - 1; } } return -1; } bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_id_record_t *idr, ecs_id_t id) { ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (idr->flags & EcsIdOnInstantiateDontInherit) { 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, 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, 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_flags64_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; flecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); flags = flags ? flags : (EcsSelf|EcsUp); if (!idr) { idr = flecs_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, 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_flags64_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out) { if (!table) return -1; flecs_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_id_record_get(world, id); if (!idr) { return -1; } int32_t result = flecs_type_search_relation(world, table, offset, id, idr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, id_out, tr_out); return result; } int32_t flecs_search_w_idr( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t *id_out, ecs_id_record_t *idr) { if (!table) return -1; flecs_poly_assert(world, ecs_world_t); (void)world; ecs_type_t type = table->type; ecs_id_t *ids = type.array; return flecs_type_search(table, idr, ids, id_out, 0); } int32_t ecs_search( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, ecs_id_t *id_out) { if (!table) return -1; flecs_poly_assert(world, ecs_world_t); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_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, 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) { flecs_poly_assert(world, ecs_world_t); return ecs_search(world, table, id, id_out); } if (!table) return -1; ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t count = type.count; return flecs_type_offset_search(offset, id, ids, count, id_out); } static int32_t flecs_relation_depth_walk( const ecs_world_t *world, const ecs_id_record_t *idr, const ecs_table_t *first, const ecs_table_t *table) { int32_t result = 0; ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return 0; } int32_t i = tr->index, end = i + tr->count; for (; i != end; i ++) { ecs_entity_t o = ecs_pair_second(world, table->type.array[i]); ecs_assert(o != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_t *ot = ecs_get_table(world, o); if (!ot) { continue; } ecs_assert(ot != first, ECS_CYCLE_DETECTED, NULL); int32_t cur = flecs_relation_depth_walk(world, idr, first, ot); if (cur > result) { result = cur; } } return result + 1; } int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); if (!idr) { return 0; } return flecs_relation_depth_walk(world, idr, table, table); } /** * @file stage.c * @brief Staging implementation. * * A stage is an object that can be used to temporarily store mutations to a * world while a world is in readonly mode. ECS operations that are invoked on * a stage are stored in a command buffer, which is flushed during sync points, * or manually by the user. * * Stages contain additional state to enable other API functionality without * having to mutate the world, such as setting the current scope, and allocators * that are local to a stage. * * In a multi threaded application, each thread has its own stage which allows * threads to insert mutations without having to lock administration. */ static ecs_cmd_t* flecs_cmd_new( ecs_stage_t *stage) { ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); cmd->is._1.value = NULL; cmd->next_for_entity = 0; cmd->entry = NULL; cmd->system = stage->system; return cmd; } static ecs_cmd_t* flecs_cmd_new_batched( ecs_stage_t *stage, ecs_entity_t e) { ecs_vec_t *cmds = &stage->cmd->queue; ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( &stage->cmd->entries, ecs_cmd_entry_t, e); int32_t cur = ecs_vec_count(cmds); ecs_cmd_t *cmd = flecs_cmd_new(stage); if (entry) { if (entry->first == -1) { /* Existing but invalidated entry */ entry->first = cur; cmd->entry = entry; } else { int32_t last = entry->last; ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *last_op = &arr[last]; last_op->next_for_entity = cur; if (last == entry->first) { /* Flip sign bit so flush logic can tell which command * is the first for an entity */ last_op->next_for_entity *= -1; } } } else { cmd->entry = entry = flecs_sparse_ensure_fast_t( &stage->cmd->entries, ecs_cmd_entry_t, e); entry->first = cur; } entry->last = cur; return cmd; } static void flecs_stage_merge( ecs_world_t *world) { bool is_stage = flecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); bool measure_frame_time = ECS_BIT_IS_SET(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. */ 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); flecs_poly_assert(s, ecs_stage_t); flecs_defer_end(world, s); } } flecs_eval_component_monitors(world); if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; /* If stage is unmanaged, deferring is always enabled */ if (stage->id == -1) { flecs_defer_begin(world, stage); } ecs_log_pop_3(); } bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage) { flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); (void)world; if (stage->defer < 0) return false; return (++ stage->defer) == 1; } bool flecs_defer_cmd( ecs_stage_t *stage) { if (stage->defer) { return (stage->defer > 0); } stage->defer ++; return false; } bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); if (cmd) { cmd->kind = EcsCmdModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; return true; } return false; } bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name) { if (stage->defer > 0) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdPath; cmd->entity = entity; cmd->id = parent; cmd->is._1.value = ecs_os_strdup(name); return true; } return false; } bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdDelete; cmd->entity = entity; return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdClear; cmd->entity = entity; return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; cmd->entity = entity; cmd->id = id; return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new_id as this is thread safe */ int i; for (i = 0; i < count; i ++) { ids[i] = ecs_new(world); } *ids_out = ids; /* Store data in op */ ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; cmd->entity = 0; return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdAdd; cmd->id = id; cmd->entity = entity; return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdRemove; cmd->id = id; cmd->entity = entity; return true; } return false; } void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t cmd_kind, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value, bool *is_new) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); /* Find type info for id */ const ecs_type_info_t *ti = NULL; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If idr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (world->flags & EcsWorldMultiThreaded) { ti = ecs_get_type_info(world, id); } 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, "provided component is not a type"); /* Make sure the size of the value equals the type size */ ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, "mismatching size specified for component in ensure/emplace/set"); size = ti->size; /* Find existing component. Make sure it's owned, so that we won't use the * component of a prefab. */ void *existing = NULL; ecs_table_t *table = NULL; if (idr) { /* Entity can only have existing component if id record exists */ ecs_record_t *r = flecs_entities_get(world, entity); table = r->table; if (r && table) { const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); if (tr) { if (tr->column != -1) { /* Entity has the component */ ecs_vec_t *column = &table->data.columns[tr->column].data; existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); } else { ecs_assert(idr->flags & EcsIdIsSparse, ECS_NOT_A_COMPONENT, NULL); existing = flecs_sparse_get_any(idr->sparse, 0, entity); } } } } /* Get existing value from storage */ void *cmd_value = existing; bool emplace = cmd_kind == EcsCmdEmplace; /* If the component does not yet exist, create a temporary value. This is * necessary so we can store a component value in the deferred command, * without adding the component to the entity which is not allowed in * deferred mode. */ if (!existing) { ecs_stack_t *stack = &stage->cmd->stack; cmd_value = flecs_stack_alloc(stack, size, ti->alignment); /* If the component doesn't yet exist, construct it and move the * provided value into the component, if provided. Don't construct if * this is an emplace operation, in which case the application is * responsible for constructing. */ if (value) { if (emplace) { ecs_move_t move = ti->hooks.move_ctor; if (move) { move(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } } else if (!emplace) { /* If the command is not an emplace, construct the temp storage */ /* Check if entity inherits component */ void *base = NULL; if (table && (table->flags & EcsTableHasIsA)) { base = flecs_get_base_component(world, table, id, idr, 0); } if (!base) { /* Normal ctor */ ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(cmd_value, 1, ti); } } else { /* Override */ ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, base, 1, ti); } else { ecs_os_memcpy(cmd_value, base, size); } } } } else if (value) { /* If component exists and value is provided, copy */ ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(existing, value, 1, ti); } else { ecs_os_memcpy(existing, value, size); } } if (!cmd) { /* If cmd is NULL, entity was already deleted. Check if we need to * insert a command into the queue. */ if (!ti->hooks.dtor) { /* If temporary memory does not need to be destructed, it'll get * freed when the stack allocator is reset. This prevents us * from having to insert a command when the entity was * already deleted. */ return cmd_value; } cmd = flecs_cmd_new(stage); } if (!existing) { /* If component didn't exist yet, insert command that will create it */ cmd->kind = cmd_kind; cmd->id = id; cmd->idr = idr; cmd->entity = entity; cmd->is._1.size = size; cmd->is._1.value = cmd_value; if (is_new) { *is_new = true; } } else { /* If component already exists, still insert an Add command to ensure * that any preceding remove commands won't remove the component. If the * operation is a set, also insert a Modified command. */ if (cmd_kind == EcsCmdSet) { cmd->kind = EcsCmdAddModified; } else { cmd->kind = EcsCmdAdd; } cmd->id = id; cmd->entity = entity; if (is_new) { *is_new = false; } } return cmd_value; error: return NULL; } void flecs_enqueue( ecs_world_t *world, ecs_stage_t *stage, ecs_event_desc_t *desc) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdEvent; cmd->entity = desc->entity; ecs_stack_t *stack = &stage->cmd->stack; ecs_event_desc_t *desc_cmd = flecs_stack_alloc_t(stack, ecs_event_desc_t); ecs_os_memcpy_t(desc_cmd, desc, ecs_event_desc_t); if (desc->ids && desc->ids->count != 0) { ecs_type_t *type_cmd = flecs_stack_alloc_t(stack, ecs_type_t); int32_t id_count = desc->ids->count; type_cmd->count = id_count; type_cmd->array = flecs_stack_alloc_n(stack, ecs_id_t, id_count); ecs_os_memcpy_n(type_cmd->array, desc->ids->array, ecs_id_t, id_count); desc_cmd->ids = type_cmd; } else { desc_cmd->ids = NULL; } cmd->is._1.value = desc_cmd; cmd->is._1.size = ECS_SIZEOF(ecs_event_desc_t); if (desc->param || desc->const_param) { ecs_assert(!(desc->const_param && desc->param), ECS_INVALID_PARAMETER, "cannot set param and const_param at the same time"); const ecs_type_info_t *ti = ecs_get_type_info(world, desc->event); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, "can only enqueue events with data for events that are components"); void *param_cmd = flecs_stack_alloc(stack, ti->size, ti->alignment); ecs_assert(param_cmd != NULL, ECS_INTERNAL_ERROR, NULL); if (desc->param) { if (ti->hooks.move_ctor) { ti->hooks.move_ctor(param_cmd, desc->param, 1, ti); } else { ecs_os_memcpy(param_cmd, desc->param, ti->size); } } else { if (ti->hooks.copy_ctor) { ti->hooks.copy_ctor(param_cmd, desc->const_param, 1, ti); } else { ecs_os_memcpy(param_cmd, desc->const_param, ti->size); } } desc_cmd->param = param_cmd; desc_cmd->const_param = NULL; } } void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage) { /* Execute post frame actions */ int32_t i, count = ecs_vec_count(&stage->post_frame_actions); ecs_action_elem_t *elems = ecs_vec_first(&stage->post_frame_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_clear(&stage->post_frame_actions); } static void flecs_commands_init( ecs_stage_t *stage, ecs_commands_t *cmd) { flecs_stack_init(&cmd->stack); ecs_vec_init_t(&stage->allocator, &cmd->queue, ecs_cmd_t, 0); flecs_sparse_init_t(&cmd->entries, &stage->allocator, &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } static void flecs_commands_fini( ecs_stage_t *stage, ecs_commands_t *cmd) { /* Make sure stage has no unmerged data */ ecs_assert(ecs_vec_count(&stage->cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); flecs_stack_fini(&cmd->stack); ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); flecs_sparse_fini(&cmd->entries); } void flecs_commands_push( ecs_stage_t *stage) { int32_t sp = ++ stage->cmd_sp; ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); stage->cmd = &stage->cmd_stack[sp]; } void flecs_commands_pop( ecs_stage_t *stage) { int32_t sp = -- stage->cmd_sp; ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); stage->cmd = &stage->cmd_stack[sp]; } ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system) { ecs_entity_t old = stage->system; stage->system = system; return old; } static ecs_stage_t* flecs_stage_new( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); flecs_poly_init(stage, ecs_stage_t); stage->world = world; stage->thread_ctx = world; flecs_stack_init(&stage->allocators.iter_stack); flecs_stack_init(&stage->allocators.deser_stack); flecs_allocator_init(&stage->allocator); flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&stage->allocators.query_impl, ecs_query_impl_t); flecs_ballocator_init_t(&stage->allocators.query_cache, ecs_query_cache_t); ecs_allocator_t *a = &stage->allocator; ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); int32_t i; for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { flecs_commands_init(stage, &stage->cmd_stack[i]); } stage->cmd = &stage->cmd_stack[0]; return stage; } static void flecs_stage_free( ecs_world_t *world, ecs_stage_t *stage) { (void)world; flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); flecs_poly_fini(stage, ecs_stage_t); ecs_allocator_t *a = &stage->allocator; ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); ecs_vec_fini(NULL, &stage->variables, 0); ecs_vec_fini(NULL, &stage->operations, 0); int32_t i; for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { flecs_commands_fini(stage, &stage->cmd_stack[i]); } flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); flecs_ballocator_fini(&stage->allocators.query_impl); flecs_ballocator_fini(&stage->allocators.query_cache); flecs_allocator_fini(&stage->allocator); ecs_os_free(stage); } ecs_allocator_t* flecs_stage_get_allocator( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); return &stage->allocator; } ecs_stack_t* flecs_stage_get_stack_allocator( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); return &stage->allocators.iter_stack; } ecs_world_t* ecs_stage_new( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_new(world); stage->id = -1; flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } void ecs_stage_free( ecs_world_t *world) { flecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; ecs_check(stage->id == -1, ECS_INVALID_PARAMETER, "cannot free stage that's owned by world"); flecs_stage_free(stage->world, stage); error: return; } void ecs_set_stage_count( ecs_world_t *world, int32_t stage_count) { flecs_poly_assert(world, ecs_world_t); /* World must have at least one default stage */ ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), ECS_INTERNAL_ERROR, NULL); const ecs_entity_t *lookup_path = NULL; if (world->stage_count >= 1) { lookup_path = world->stages[0]->lookup_path; } int32_t i, count = world->stage_count; if (stage_count < count) { for (i = stage_count; i < count; i ++) { /* If stage contains a thread handle, ecs_set_threads was used to * create the stages. ecs_set_threads and ecs_set_stage_count should * not be mixed. */ ecs_stage_t *stage = world->stages[i]; flecs_poly_assert(stage, ecs_stage_t); ecs_check(stage->thread == 0, ECS_INVALID_OPERATION, "cannot mix using set_stage_count and set_threads"); flecs_stage_free(world, stage); } } if (stage_count) { world->stages = ecs_os_realloc_n( world->stages, ecs_stage_t*, stage_count); for (i = count; i < stage_count; i ++) { ecs_stage_t *stage = world->stages[i] = flecs_stage_new(world); stage->id = i; /* Set thread_ctx to stage, as this stage might be used in a * multithreaded context */ stage->thread_ctx = (ecs_world_t*)stage; stage->thread = 0; } } else { /* Set to NULL to prevent double frees */ ecs_os_free(world->stages); world->stages = NULL; } /* Regardless of whether the stage was just initialized or not, when the * ecs_set_stage_count function is called, all stages inherit the auto_merge * property from the world */ for (i = 0; i < stage_count; i ++) { world->stages[i]->lookup_path = lookup_path; } world->stage_count = stage_count; error: return; } int32_t ecs_get_stage_count( const ecs_world_t *world) { world = ecs_get_world(world); return world->stage_count; } int32_t ecs_stage_get_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (flecs_poly_is(world, ecs_stage_t)) { ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); /* Index 0 is reserved for main stage */ return stage->id; } else if (flecs_poly_is(world, ecs_world_t)) { return 0; } else { ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } ecs_world_t* ecs_get_stage( const ecs_world_t *world, int32_t stage_id) { flecs_poly_assert(world, ecs_world_t); ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); return (ecs_world_t*)world->stages[stage_id]; error: return NULL; } bool ecs_readonly_begin( ecs_world_t *world, bool multi_threaded) { flecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *stage = world->stages[i]; stage->lookup_path = world->stages[0]->lookup_path; ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, "deferred mode cannot be enabled when entering readonly mode"); flecs_defer_begin(world, stage); } bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); /* From this point on, the world is "locked" for mutations, and it is only * allowed to enqueue commands from stages */ ECS_BIT_SET(world->flags, EcsWorldReadonly); ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, multi_threaded); return is_readonly; } void ecs_readonly_end( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, "world is not in readonly mode"); /* After this it is safe again to mutate the world directly */ ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); ecs_log_pop_3(); flecs_stage_merge(world); error: return; } void ecs_merge( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_poly_is(world, ecs_world_t) || flecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); flecs_stage_merge(world); error: return; } bool ecs_stage_is_readonly( const ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); if (flecs_poly_is(stage, ecs_stage_t)) { if (((const ecs_stage_t*)stage)->id == -1) { /* Stage is not owned by world, so never readonly */ return false; } } if (world->flags & EcsWorldReadonly) { if (flecs_poly_is(stage, ecs_world_t)) { return true; } } else { if (flecs_poly_is(stage, ecs_stage_t)) { return true; } } return false; } bool ecs_is_deferred( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->defer > 0; error: return false; } /** * @file value.c * @brief Utility functions to work with non-trivial pointers of user types. */ int ecs_value_init_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void *ptr) { flecs_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) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_init_w_type_info(world, ti, ptr); error: return -1; } void* ecs_value_new_w_type_info( ecs_world_t *world, const ecs_type_info_t *ti) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; void *result = flecs_alloc(&world->allocator, ti->size); if (ecs_value_init_w_type_info(world, ti, result) != 0) { flecs_free(&world->allocator, ti->size, result); goto error; } return result; error: return NULL; } void* ecs_value_new( ecs_world_t *world, ecs_entity_t type) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_new_w_type_info(world, ti); error: return NULL; } int ecs_value_fini_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void *ptr) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; 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) { flecs_poly_assert(world, ecs_world_t); (void)world; const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_fini_w_type_info(world, ti, ptr); error: return -1; } int ecs_value_free( ecs_world_t *world, ecs_entity_t type, void* ptr) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { goto error; } flecs_free(&world->allocator, ti->size, ptr); return 0; error: return -1; } int ecs_value_copy_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, const void *src) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; 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) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_copy_w_type_info(world, ti, dst, src); error: return -1; } int ecs_value_move_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, void *src) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; 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) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_move_w_type_info(world, ti, dst, src); error: return -1; } int ecs_value_move_ctor_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, void *src) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; 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) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_move_w_type_info(world, ti, dst, src); error: return -1; } /** * @file world.c * @brief World-level API. */ /* Id flags */ const ecs_id_t ECS_PAIR = (1ull << 63); const ecs_id_t ECS_AUTO_OVERRIDE = (1ull << 62); const ecs_id_t ECS_TOGGLE = (1ull << 61); /** Builtin component ids */ const ecs_entity_t ecs_id(EcsComponent) = 1; const ecs_entity_t ecs_id(EcsIdentifier) = 2; const ecs_entity_t ecs_id(EcsPoly) = 3; /* Poly target components */ const ecs_entity_t EcsQuery = 5; const ecs_entity_t EcsObserver = 6; const ecs_entity_t EcsSystem = 7; /* Core scopes & entities */ const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; const ecs_entity_t EcsNotQueryable = FLECS_HI_COMPONENT_ID + 8; const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 9; const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 10; /* Marker entities for query encoding */ const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 11; const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 12; const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 13; const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 14; /* Traits */ const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 15; const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 17; const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 19; const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 21; const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 23; const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 24; const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 26; const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 29; const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 32; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 35; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 38; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 47; const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 48; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 50; const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 51; /* Actions */ const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 52; const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 53; const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 54; /* Storage */ const ecs_entity_t EcsSparse = FLECS_HI_COMPONENT_ID + 55; const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 56; /* Misc */ const ecs_entity_t ecs_id(EcsDefaultChildComponent) = FLECS_HI_COMPONENT_ID + 57; /* Builtin predicate ids (used by query engine) */ const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 58; const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 59; const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 60; const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 61; const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 62; /* Systems */ const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 63; const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 64; const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 65; const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 67; const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 68; const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 69; const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 70; const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 71; const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 72; const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 73; const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 74; const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 75; const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 76; const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 77; /* Meta primitive components (don't use low ids to save id space) */ #ifdef FLECS_META const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 80; const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 97; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsType) = FLECS_HI_COMPONENT_ID + 98; const ecs_entity_t ecs_id(EcsTypeSerializer) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; #endif /* Doc module components */ #ifdef FLECS_DOC const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; #endif /* REST module components */ #ifdef FLECS_REST const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 118; #endif /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; /* Declarations for addons. Located in world.c to avoid issues during linking of * static library */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(EcsAlert); ECS_COMPONENT_DECLARE(EcsAlertInstance); ECS_COMPONENT_DECLARE(EcsAlertsActive); ECS_TAG_DECLARE(EcsAlertInfo); ECS_TAG_DECLARE(EcsAlertWarning); ECS_TAG_DECLARE(EcsAlertError); ECS_TAG_DECLARE(EcsAlertCritical); #endif #ifdef FLECS_UNITS ECS_DECLARE(EcsUnitPrefixes); ECS_DECLARE(EcsYocto); ECS_DECLARE(EcsZepto); ECS_DECLARE(EcsAtto); ECS_DECLARE(EcsFemto); ECS_DECLARE(EcsPico); ECS_DECLARE(EcsNano); ECS_DECLARE(EcsMicro); ECS_DECLARE(EcsMilli); ECS_DECLARE(EcsCenti); ECS_DECLARE(EcsDeci); ECS_DECLARE(EcsDeca); ECS_DECLARE(EcsHecto); ECS_DECLARE(EcsKilo); ECS_DECLARE(EcsMega); ECS_DECLARE(EcsGiga); ECS_DECLARE(EcsTera); ECS_DECLARE(EcsPeta); ECS_DECLARE(EcsExa); ECS_DECLARE(EcsZetta); ECS_DECLARE(EcsYotta); ECS_DECLARE(EcsKibi); ECS_DECLARE(EcsMebi); ECS_DECLARE(EcsGibi); ECS_DECLARE(EcsTebi); ECS_DECLARE(EcsPebi); ECS_DECLARE(EcsExbi); ECS_DECLARE(EcsZebi); ECS_DECLARE(EcsYobi); ECS_DECLARE(EcsDuration); ECS_DECLARE(EcsPicoSeconds); ECS_DECLARE(EcsNanoSeconds); ECS_DECLARE(EcsMicroSeconds); ECS_DECLARE(EcsMilliSeconds); ECS_DECLARE(EcsSeconds); ECS_DECLARE(EcsMinutes); ECS_DECLARE(EcsHours); ECS_DECLARE(EcsDays); ECS_DECLARE(EcsTime); ECS_DECLARE(EcsDate); ECS_DECLARE(EcsMass); ECS_DECLARE(EcsGrams); ECS_DECLARE(EcsKiloGrams); ECS_DECLARE(EcsElectricCurrent); ECS_DECLARE(EcsAmpere); ECS_DECLARE(EcsAmount); ECS_DECLARE(EcsMole); ECS_DECLARE(EcsLuminousIntensity); ECS_DECLARE(EcsCandela); ECS_DECLARE(EcsForce); ECS_DECLARE(EcsNewton); ECS_DECLARE(EcsLength); ECS_DECLARE(EcsMeters); ECS_DECLARE(EcsPicoMeters); ECS_DECLARE(EcsNanoMeters); ECS_DECLARE(EcsMicroMeters); ECS_DECLARE(EcsMilliMeters); ECS_DECLARE(EcsCentiMeters); ECS_DECLARE(EcsKiloMeters); ECS_DECLARE(EcsMiles); ECS_DECLARE(EcsPixels); ECS_DECLARE(EcsPressure); ECS_DECLARE(EcsPascal); ECS_DECLARE(EcsBar); ECS_DECLARE(EcsSpeed); ECS_DECLARE(EcsMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerHour); ECS_DECLARE(EcsMilesPerHour); ECS_DECLARE(EcsAcceleration); ECS_DECLARE(EcsTemperature); ECS_DECLARE(EcsKelvin); ECS_DECLARE(EcsCelsius); ECS_DECLARE(EcsFahrenheit); ECS_DECLARE(EcsData); ECS_DECLARE(EcsBits); ECS_DECLARE(EcsKiloBits); ECS_DECLARE(EcsMegaBits); ECS_DECLARE(EcsGigaBits); ECS_DECLARE(EcsBytes); ECS_DECLARE(EcsKiloBytes); ECS_DECLARE(EcsMegaBytes); ECS_DECLARE(EcsGigaBytes); ECS_DECLARE(EcsKibiBytes); ECS_DECLARE(EcsGibiBytes); ECS_DECLARE(EcsMebiBytes); ECS_DECLARE(EcsDataRate); ECS_DECLARE(EcsBitsPerSecond); ECS_DECLARE(EcsKiloBitsPerSecond); ECS_DECLARE(EcsMegaBitsPerSecond); ECS_DECLARE(EcsGigaBitsPerSecond); ECS_DECLARE(EcsBytesPerSecond); ECS_DECLARE(EcsKiloBytesPerSecond); ECS_DECLARE(EcsMegaBytesPerSecond); ECS_DECLARE(EcsGigaBytesPerSecond); ECS_DECLARE(EcsPercentage); ECS_DECLARE(EcsAngle); ECS_DECLARE(EcsRadians); ECS_DECLARE(EcsDegrees); ECS_DECLARE(EcsColor); ECS_DECLARE(EcsColorRgb); ECS_DECLARE(EcsColorHsl); ECS_DECLARE(EcsColorCss); ECS_DECLARE(EcsBel); ECS_DECLARE(EcsDeciBel); ECS_DECLARE(EcsFrequency); ECS_DECLARE(EcsHertz); ECS_DECLARE(EcsKiloHertz); ECS_DECLARE(EcsMegaHertz); ECS_DECLARE(EcsGigaHertz); ECS_DECLARE(EcsUri); ECS_DECLARE(EcsUriHyperlink); ECS_DECLARE(EcsUriImage); ECS_DECLARE(EcsUriFile); #endif /* -- Private functions -- */ ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world) { ecs_assert(flecs_poly_is(world, ecs_world_t) || flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (flecs_poly_is(world, ecs_world_t)) { return ECS_CONST_CAST(ecs_stage_t*, world->stages[0]); } else if (flecs_poly_is(world, ecs_stage_t)) { return ECS_CONST_CAST(ecs_stage_t*, world); } return NULL; } ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr) { ecs_world_t *world = *world_ptr; ecs_assert(flecs_poly_is(world, ecs_world_t) || flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (flecs_poly_is(world, ecs_world_t)) { return world->stages[0]; } *world_ptr = ((ecs_stage_t*)world)->world; return ECS_CONST_CAST(ecs_stage_t*, world); } ecs_world_t* flecs_suspend_readonly( const ecs_world_t *stage_world, ecs_suspend_readonly_state_t *state) { ecs_assert(stage_world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (!is_readonly && !stage->defer) { state->is_readonly = false; state->is_deferred = false; return world; } ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ ecs_assert(!(world->flags & EcsWorldReadonly) || (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = stage->defer != 0; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; /* Hack around safety checks (this ought to look ugly) */ state->defer_count = stage->defer; state->commands = stage->cmd->queue; state->defer_stack = stage->cmd->stack; flecs_stack_init(&stage->cmd->stack); state->scope = stage->scope; state->with = stage->with; stage->defer = 0; ecs_vec_init_t(NULL, &stage->cmd->queue, ecs_cmd_t, 0); return world; } void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state) { flecs_poly_assert(world, ecs_world_t); ecs_assert(state != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *temp_world = world; ecs_stage_t *stage = flecs_stage_from_world(&temp_world); if (state->is_readonly || state->is_deferred) { ecs_dbg_3("resuming readonly mode"); ecs_run_aperiodic(world, 0); /* Restore readonly state / defer count */ ECS_BIT_COND(world->flags, EcsWorldReadonly, state->is_readonly); stage->defer = state->defer_count; ecs_vec_fini_t(&stage->allocator, &stage->cmd->queue, ecs_cmd_t); stage->cmd->queue = state->commands; flecs_stack_fini(&stage->cmd->stack); stage->cmd->stack = state->defer_stack; stage->scope = state->scope; stage->with = state->with; } } /* Evaluate component monitor. If a monitored entity changed it will have set a * flag in one of the world's component monitors. Queries can register * themselves with component monitors to determine whether they need to rematch * with tables. */ static void flecs_eval_component_monitor( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); if (!world->monitors.is_dirty) { return; } world->monitors.is_dirty = false; ecs_map_iter_t it = ecs_map_iter(&world->monitors.monitors); while (ecs_map_next(&it)) { ecs_monitor_t *m = ecs_map_ptr(&it); if (!m->is_dirty) { continue; } m->is_dirty = false; int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **elems = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { ecs_query_t *q = elems[i]; flecs_poly_assert(q, ecs_query_t); flecs_query_cache_notify(world, q, &(ecs_query_cache_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); flecs_poly_assert(query, ecs_query_t); ecs_map_t *monitors = &world->monitors.monitors; ecs_map_init_if(monitors, &world->allocator); ecs_monitor_t *m = ecs_map_ensure_alloc_t(monitors, ecs_monitor_t, id); ecs_vec_init_if_t(&m->queries, ecs_query_t*); ecs_query_t **q = ecs_vec_append_t( &world->allocator, &m->queries, ecs_query_t*); *q = query; } void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(query != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(query, ecs_query_t); ecs_map_t *monitors = &world->monitors.monitors; if (!ecs_map_is_init(monitors)) { return; } ecs_monitor_t *m = ecs_map_get_deref(monitors, ecs_monitor_t, id); if (!m) { return; } int32_t i, count = ecs_vec_count(&m->queries); ecs_query_t **queries = ecs_vec_first(&m->queries); for (i = 0; i < count; i ++) { if (queries[i] == query) { ecs_vec_remove_t(&m->queries, ecs_query_t*, i); count --; break; } } if (!count) { ecs_vec_fini_t(&world->allocator, &m->queries, ecs_query_t*); ecs_map_remove_free(monitors, id); } if (!ecs_map_count(monitors)) { ecs_map_fini(monitors); } } static void flecs_init_store( ecs_world_t *world) { ecs_os_memset(&world->store, 0, ECS_SIZEOF(ecs_store_t)); ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &world->store.records, ecs_table_record_t, 0); ecs_vec_init_t(a, &world->store.marked_ids, ecs_marked_id_t, 0); ecs_vec_init_t(a, &world->store.depth_ids, ecs_entity_t, 0); ecs_map_init(&world->store.entity_to_depth, &world->allocator); /* Initialize entity index */ flecs_entities_init(world); /* Initialize table sparse set */ flecs_sparse_init_t(&world->store.tables, a, &world->allocators.sparse_chunk, ecs_table_t); /* Initialize table map */ flecs_table_hashmap_init(world, &world->store.table_map); /* Initialize root table */ flecs_init_root_table(world); /* Initilaize observer sparse set */ flecs_sparse_init_t(&world->store.observers, a, &world->allocators.sparse_chunk, ecs_observer_impl_t); } static void flecs_clean_tables( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->store.tables); /* Ensure that first table in sparse set has id 0. This is a dummy table * that only exists so that there is no table with id 0 */ ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, 0); (void)first; for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_fini(world, t); } /* Free table types separately so that if application destructors rely on * a type it's still valid. */ for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_free_type(world, t); } /* Clear the root table */ if (count) { flecs_table_reset(world, &world->store.root); } } static void flecs_fini_root_tables( ecs_world_t *world, ecs_id_record_t *idr, bool fini_targets) { ecs_table_cache_iter_t it; bool has_roots = flecs_table_cache_iter(&idr->cache, &it); ecs_assert(has_roots == true, ECS_INTERNAL_ERROR, NULL); (void)has_roots; const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableHasBuiltins) { continue; /* Query out modules */ } int32_t i, count = table->data.entities.count; ecs_entity_t *entities = table->data.entities.array; if (fini_targets) { /* Only delete entities that are used as pair target. Iterate * backwards to minimize moving entities around in table. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { ecs_delete(world, entities[i]); } } } else { /* Delete remaining entities that are not in use (added to another * entity). This limits table moves during cleanup and delays * cleanup of tags. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { ecs_delete(world, entities[i]); } } } } } static void flecs_fini_roots( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, world->stages[0]); flecs_fini_root_tables(world, idr, true); flecs_defer_end(world, world->stages[0]); flecs_defer_begin(world, world->stages[0]); flecs_fini_root_tables(world, idr, false); flecs_defer_end(world, world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_fini(world, &world->store.root); flecs_entities_clear(world); flecs_hashmap_fini(&world->store.table_map); ecs_assert(flecs_sparse_count(&world->store.observers) == 0, ECS_INTERNAL_ERROR, NULL); flecs_sparse_fini(&world->store.observers); ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); ecs_vec_fini_t(a, &world->store.depth_ids, ecs_entity_t); ecs_map_fini(&world->store.entity_to_depth); } 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_cache_table_t); flecs_ballocator_init_t(&a->query_table_match, ecs_query_cache_table_match_t); flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, FLECS_HI_COMPONENT_ID); flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); flecs_ballocator_init_t(&a->id_record, ecs_id_record_t); flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); flecs_table_diff_builder_init(world, &world->allocators.diff_builder); } static void flecs_world_allocators_fini( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; ecs_map_params_fini(&a->ptr); ecs_map_params_fini(&a->query_table_list); flecs_ballocator_fini(&a->query_table); flecs_ballocator_fini(&a->query_table_match); flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->id_record); flecs_ballocator_fini(&a->id_record_chunk); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); flecs_ballocator_fini(&a->hashmap); flecs_table_diff_builder_fini(world, &world->allocators.diff_builder); flecs_allocator_fini(&world->allocator); } #define ECS_STRINGIFY_INNER(x) #x #define ECS_STRINGIFY(x) ECS_STRINGIFY_INNER(x) static const char flecs_compiler_info[] #if defined(__clang__) = "clang " __clang_version__; #elif defined(__GNUC__) = "gcc " ECS_STRINGIFY(__GNUC__) "." ECS_STRINGIFY(__GNUC_MINOR__); #elif defined(_MSC_VER) = "msvc " ECS_STRINGIFY(_MSC_VER); #elif defined(__TINYC__) = "tcc " ECS_STRINGIFY(__TINYC__); #else = "unknown compiler"; #endif static const char *flecs_addons_info[] = { #ifdef FLECS_CPP "FLECS_CPP", #endif #ifdef FLECS_MODULE "FLECS_MODULE", #endif #ifdef FLECS_SCRIPT "FLECS_SCRIPT", #endif #ifdef FLECS_STATS "FLECS_STATS", #endif #ifdef FLECS_METRICS "FLECS_METRICS", #endif #ifdef FLECS_ALERTS "FLECS_ALERTS", #endif #ifdef FLECS_SYSTEM "FLECS_SYSTEM", #endif #ifdef FLECS_PIPELINE "FLECS_PIPELINE", #endif #ifdef FLECS_TIMER "FLECS_TIMER", #endif #ifdef FLECS_META "FLECS_META", #endif #ifdef FLECS_UNITS "FLECS_UNITS", #endif #ifdef FLECS_JSON "FLECS_JSON", #endif #ifdef FLECS_DOC "FLECS_DOC", #endif #ifdef FLECS_LOG "FLECS_LOG", #endif #ifdef FLECS_JOURNAL "FLECS_JOURNAL", #endif #ifdef FLECS_APP "FLECS_APP", #endif #ifdef FLECS_OS_API_IMPL "FLECS_OS_API_IMPL", #endif #ifdef FLECS_SCRIPT "FLECS_SCRIPT", #endif #ifdef FLECS_HTTP "FLECS_HTTP", #endif #ifdef FLECS_REST "FLECS_REST", #endif NULL }; static const ecs_build_info_t flecs_build_info = { .compiler = flecs_compiler_info, .addons = flecs_addons_info, #ifdef FLECS_DEBUG .debug = true, #endif #ifdef FLECS_SANITIZE .sanitize = true, #endif #ifdef FLECS_PERF_TRACE .perf_trace = true, #endif .version = FLECS_VERSION, .version_major = FLECS_VERSION_MAJOR, .version_minor = FLECS_VERSION_MINOR, .version_patch = FLECS_VERSION_PATCH }; static void flecs_log_build_info(void) { const ecs_build_info_t *bi = ecs_get_build_info(); ecs_assert(bi != NULL, ECS_INTERNAL_ERROR, NULL); ecs_trace("flecs version %s", bi->version); ecs_trace("addons included in build:"); ecs_log_push(); const char **addon = bi->addons; do { ecs_trace(addon[0]); } while ((++ addon)[0]); ecs_log_pop(); if (bi->sanitize) { ecs_trace("sanitize build, rebuild without FLECS_SANITIZE for (much) " "improved performance"); } else if (bi->debug) { ecs_trace("debug build, rebuild with NDEBUG or FLECS_NDEBUG for " "improved performance"); } else { ecs_trace("#[green]release#[reset] build"); } ecs_trace("compiled with %s", bi->compiler); } /* -- Public functions -- */ const ecs_build_info_t* ecs_get_build_info(void) { return &flecs_build_info; } const ecs_world_info_t* ecs_get_world_info( const ecs_world_t *world) { world = ecs_get_world(world); return &world->info; } ecs_world_t *ecs_mini(void) { #ifdef FLECS_OS_API_IMPL ecs_set_os_api_impl(); #endif ecs_os_init(); ecs_trace("#[bold]bootstrapping world"); ecs_log_push(); ecs_trace("tracing enabled, call ecs_log_set_level(-1) to disable"); if (!ecs_os_has_heap()) { ecs_abort(ECS_MISSING_OS_API, NULL); } if (!ecs_os_has_threading()) { ecs_trace("threading unavailable, to use threads set OS API first (see examples)"); } if (!ecs_os_has_time()) { ecs_trace("time management not available"); } flecs_log_build_info(); ecs_world_t *world = ecs_os_calloc_t(ecs_world_t); ecs_assert(world != NULL, ECS_OUT_OF_MEMORY, NULL); flecs_poly_init(world, ecs_world_t); world->flags |= EcsWorldInit; flecs_world_allocators_init(world); ecs_allocator_t *a = &world->allocator; world->self = world; flecs_sparse_init_t(&world->type_info, a, &world->allocators.sparse_chunk, ecs_type_info_t); ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); world->id_index_lo = ecs_os_calloc_n(ecs_id_record_t, FLECS_HI_ID_RECORD_ID); flecs_observable_init(&world->observable); world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_tables, a, &world->allocators.sparse_chunk, ecs_table_t*); world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_buffer, a, &world->allocators.sparse_chunk, ecs_table_t*); flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); world->info.time_scale = 1.0; if (ecs_os_has_time()) { ecs_os_get_time(&world->world_start_time); } ecs_set_stage_count(world, 1); ecs_default_lookup_path[0] = EcsFlecsCore; ecs_set_lookup_path(world, ecs_default_lookup_path); flecs_init_store(world); flecs_bootstrap(world); world->flags &= ~EcsWorldInit; ecs_trace("world ready!"); ecs_log_pop(); return world; } ecs_world_t *ecs_init(void) { ecs_world_t *world = ecs_mini(); #ifdef FLECS_MODULE_H ecs_trace("#[bold]import addons"); ecs_log_push(); ecs_trace("use ecs_mini to create world without importing addons"); #ifdef FLECS_SYSTEM ECS_IMPORT(world, FlecsSystem); #endif #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); #endif #ifdef FLECS_TIMER ECS_IMPORT(world, FlecsTimer); #endif #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif #ifdef FLECS_SCRIPT ECS_IMPORT(world, FlecsScript); #endif #ifdef FLECS_REST ECS_IMPORT(world, FlecsRest); #endif #ifdef FLECS_UNITS ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); #endif ecs_trace("addons imported!"); ecs_log_pop(); #endif return world; } ecs_world_t* ecs_init_w_args( int argc, char *argv[]) { ecs_world_t *world = ecs_init(); (void)argc; (void)argv; #ifdef FLECS_DOC if (argc) { char *app = argv[0]; char *last_elem = strrchr(app, '/'); if (!last_elem) { last_elem = strrchr(app, '\\'); } if (last_elem) { app = last_elem + 1; } ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } #endif return world; } void ecs_quit( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); world->flags |= EcsWorldQuit; error: return; } bool ecs_should_quit( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return true; } void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event) { flecs_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 flecs_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, "provided entity is not a component"); ecs_check(component_ptr->size != 0, ECS_INVALID_PARAMETER, "cannot register type hooks for type with size 0"); 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 = flecs_default_ctor; } /* Set default copy ctor, move ctor and merge */ if (h->copy && !h->copy_ctor) { ti->hooks.copy_ctor = flecs_default_copy_ctor; } if (h->move && !h->move_ctor) { ti->hooks.move_ctor = flecs_default_move_ctor; } if (!h->ctor_move_dtor) { if (h->move) { if (h->dtor) { if (h->move_ctor) { /* If an explicit move ctor has been set, use callback * that uses the move ctor vs. using a ctor+move */ ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { /* If no explicit move_ctor has been set, use * combination of ctor + move + dtor */ ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; } } else { /* If no dtor has been set, this is just a move ctor */ ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } else { /* If move is not set but move_ctor and dtor is, we can still set * ctor_move_dtor. */ if (h->move_ctor) { if (h->dtor) { ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } } } if (!h->move_dtor) { if (h->move) { if (h->dtor) { ti->hooks.move_dtor = flecs_default_move_w_dtor; } else { ti->hooks.move_dtor = flecs_default_move; } } else { if (h->dtor) { ti->hooks.move_dtor = flecs_default_dtor; } } } error: return; } const ecs_type_hooks_t* ecs_get_hooks_id( const ecs_world_t *world, ecs_entity_t id) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return &ti->hooks; } return NULL; } void ecs_atfini( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { flecs_poly_assert(world, ecs_world_t); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_action_elem_t *elem = ecs_vec_append_t(NULL, &world->fini_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } void ecs_run_post_frame( ecs_world_t *world, ecs_fini_action_t action, void *ctx) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(action != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, &stage->post_frame_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } /* Unset data in tables */ static void flecs_fini_unset_tables( ecs_world_t *world) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_remove_actions(world, table); } } /* Invoke fini actions */ static void flecs_fini_actions( ecs_world_t *world) { int32_t i, count = ecs_vec_count(&world->fini_actions); ecs_action_elem_t *elems = ecs_vec_first(&world->fini_actions); for (i = 0; i < count; i ++) { elems[i].action(world, elems[i].ctx); } ecs_vec_fini_t(NULL, &world->fini_actions, ecs_action_elem_t); } /* Cleanup remaining type info elements */ static void flecs_fini_type_info( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->type_info); ecs_sparse_t *type_info = &world->type_info; for (i = 0; i < count; i ++) { ecs_type_info_t *ti = flecs_sparse_get_dense_t(type_info, ecs_type_info_t, i); flecs_type_info_fini(ti); } flecs_sparse_fini(&world->type_info); } ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e) { if (ecs_is_alive(world, e)) { if (ecs_has_id(world, e, EcsOneOf)) { return e; } else { return ecs_get_target(world, e, EcsOneOf, 0); } } else { return 0; } } /* The destroyer of worlds */ int ecs_fini( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot fini world while it is in readonly mode"); ecs_assert(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot fini world when it is already being deleted"); ecs_assert(world->stages[0]->defer == 0, ECS_INVALID_OPERATION, "call defer_end before destroying world"); ecs_trace("#[bold]shutting down world"); ecs_log_push(); world->flags |= EcsWorldQuit; /* 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 OnRemove/destructors are deferred and * will be discarded after world cleanup */ flecs_defer_begin(world, world->stages[0]); /* Run OnRemove actions for components while the store is still * unmodified by cleanup. */ flecs_fini_unset_tables(world); /* This will destroy all entities and components. */ flecs_fini_store(world); /* Purge deferred operations from the queue. This discards operations but * makes sure that any resources in the queue are freed */ flecs_defer_purge(world, world->stages[0]); ecs_log_pop_1(); /* All queries are cleaned up, so monitors should've been cleaned up too */ ecs_assert(!ecs_map_is_init(&world->monitors.monitors), ECS_INTERNAL_ERROR, NULL); /* Cleanup world ctx and binding_ctx */ if (world->ctx_free) { world->ctx_free(world->ctx); } if (world->binding_ctx_free) { world->binding_ctx_free(world->binding_ctx); } /* After this point no more user code is invoked */ ecs_dbg_1("#[bold]cleanup world data structures"); ecs_log_push_1(); flecs_entities_fini(world); flecs_sparse_fini(world->pending_tables); flecs_sparse_fini(world->pending_buffer); ecs_os_free(world->pending_tables); ecs_os_free(world->pending_buffer); flecs_fini_id_records(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); flecs_name_index_fini(&world->aliases); flecs_name_index_fini(&world->symbols); ecs_set_stage_count(world, 0); ecs_log_pop_1(); flecs_world_allocators_fini(world); /* End of the world */ flecs_poly_free(world, ecs_world_t); ecs_os_fini(); ecs_trace("world destroyed, bye!"); ecs_log_pop(); return 0; } bool ecs_is_fini( const ecs_world_t *world) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldFini); } void ecs_dim( ecs_world_t *world, int32_t entity_count) { flecs_poly_assert(world, ecs_world_t); flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); } void flecs_eval_component_monitors( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); flecs_eval_component_monitor(world); } void ecs_measure_frame_time( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); if (ECS_EQZERO(world->info.target_fps) || enable) { ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } error: return; } void ecs_measure_system_time( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); error: return; } void ecs_set_target_fps( ecs_world_t *world, ecs_ftime_t fps) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ecs_measure_frame_time(world, true); world->info.target_fps = fps; error: return; } void ecs_set_default_query_flags( ecs_world_t *world, ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); world->default_query_flags = flags; } void* ecs_get_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->ctx; error: return NULL; } void* ecs_get_binding_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->binding_ctx; error: return NULL; } void ecs_set_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { flecs_poly_assert(world, ecs_world_t); world->ctx = ctx; world->ctx_free = ctx_free; } void ecs_set_binding_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { flecs_poly_assert(world, ecs_world_t); world->binding_ctx = ctx; world->binding_ctx_free = ctx_free; } void ecs_set_entity_range( ecs_world_t *world, ecs_entity_t id_start, ecs_entity_t id_end) { flecs_poly_assert(world, ecs_world_t); ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); ecs_check(!id_end || id_end > flecs_entities_max_id(world), ECS_INVALID_PARAMETER, NULL); uint32_t start = (uint32_t)id_start; uint32_t end = (uint32_t)id_end; if (flecs_entities_max_id(world) < start) { flecs_entities_max_id(world) = start - 1; } world->info.min_id = start; world->info.max_id = end; error: return; } bool ecs_enable_range_check( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); bool old_value = world->range_check_enabled; world->range_check_enabled = enable; return old_value; } ecs_entity_t ecs_get_max_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_max_id(world); error: return 0; } const ecs_type_info_t* flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component) { flecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); return 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) { flecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = flecs_type_info_get(world, component); ecs_type_info_t *ti_mut = NULL; if (!ti) { ti_mut = flecs_sparse_ensure_t( &world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); ti_mut->component = component; } else { ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); } if (!ti_mut->name) { const char *sym = ecs_get_symbol(world, component); if (sym) { ti_mut->name = ecs_os_strdup(sym); } else { const char *name = ecs_get_name(world, component); if (name) { ti_mut->name = ecs_os_strdup(name); } } } return ti_mut; } bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li) { bool changed = false; flecs_entities_ensure(world, component); ecs_type_info_t *ti = NULL; if (!size || !alignment) { ecs_assert(size == 0 && alignment == 0, ECS_INVALID_COMPONENT_SIZE, NULL); ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); } else { ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); changed |= ti->size != size; changed |= ti->alignment != alignment; ti->size = size; ti->alignment = alignment; if (li) { ecs_set_hooks_id(world, component, li); } } /* Set type info for id record of component */ ecs_id_record_t *idr = flecs_id_record_ensure(world, component); changed |= flecs_id_record_set_type_info(world, idr, ti); bool is_tag = idr->flags & EcsIdTag; /* All id records with component as relationship inherit type info */ idr = flecs_id_record_ensure(world, ecs_pair(component, EcsWildcard)); do { if (is_tag) { changed |= flecs_id_record_set_type_info(world, idr, NULL); } else if (ti) { changed |= flecs_id_record_set_type_info(world, idr, ti); } else if ((idr->type_info != NULL) && (idr->type_info->component == component)) { changed |= flecs_id_record_set_type_info(world, idr, NULL); } } while ((idr = idr->first.next)); /* All non-tag id records with component as object inherit type info, * if relationship doesn't have type info */ idr = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, component)); do { if (!(idr->flags & EcsIdTag) && !idr->type_info) { changed |= flecs_id_record_set_type_info(world, idr, ti); } } while ((idr = idr->first.next)); /* Type info of (*, component) should always point to component */ ecs_assert(flecs_id_record_get(world, ecs_pair(EcsWildcard, component))-> type_info == ti, ECS_INTERNAL_ERROR, NULL); return changed; } void flecs_type_info_fini( ecs_type_info_t *ti) { if (ti->hooks.ctx_free) { ti->hooks.ctx_free(ti->hooks.ctx); } if (ti->hooks.binding_ctx_free) { ti->hooks.binding_ctx_free(ti->hooks.binding_ctx); } if (ti->name) { /* Safe to cast away const, world has ownership over string */ ecs_os_free(ECS_CONST_CAST(char*, ti->name)); ti->name = NULL; } } void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not * deleted before entities that use it. The remaining type info elements * will be deleted after the store is finalized. */ return; } ecs_type_info_t *ti = flecs_sparse_try_t(&world->type_info, ecs_type_info_t, component); if (ti) { flecs_type_info_fini(ti); flecs_sparse_remove_t(&world->type_info, ecs_type_info_t, component); } } static ecs_ftime_t flecs_insert_sleep( ecs_world_t *world, ecs_time_t *stop) { flecs_poly_assert(world, ecs_world_t); ecs_time_t start = *stop, now = start; ecs_ftime_t delta_time = (ecs_ftime_t)ecs_time_measure(stop); if (ECS_EQZERO(world->info.target_fps)) { return delta_time; } ecs_ftime_t target_delta_time = ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); /* Calculate the time we need to sleep by taking the measured delta from the * previous frame, and subtracting it from target_delta_time. */ ecs_ftime_t sleep = target_delta_time - delta_time; /* Pick a sleep interval that is 4 times smaller than the time one frame * should take. */ ecs_ftime_t sleep_time = sleep / (ecs_ftime_t)4.0; do { /* Only call sleep when sleep_time is not 0. On some platforms, even * a sleep with a timeout of 0 can cause stutter. */ if (ECS_NEQZERO(sleep_time)) { ecs_sleepf((double)sleep_time); } now = start; delta_time = (ecs_ftime_t)ecs_time_measure(&now); } while ((target_delta_time - delta_time) > (sleep_time / (ecs_ftime_t)2.0)); *stop = now; return delta_time; } static ecs_ftime_t flecs_start_measure_frame( ecs_world_t *world, ecs_ftime_t user_delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_ftime_t delta_time = 0; if ((world->flags & EcsWorldMeasureFrameTime) || (ECS_EQZERO(user_delta_time))) { ecs_time_t t = world->frame_start_time; do { if (world->frame_start_time.nanosec || world->frame_start_time.sec){ delta_time = flecs_insert_sleep(world, &t); } else { ecs_time_measure(&t); if (ECS_NEQZERO(world->info.target_fps)) { delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; } else { /* Best guess */ delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; if (ECS_EQZERO(delta_time)) { delta_time = user_delta_time; break; } } } /* Keep trying while delta_time is zero */ } while (ECS_EQZERO(delta_time)); world->frame_start_time = t; /* Keep track of total time passed in world */ world->info.world_time_total_raw += (double)delta_time; } return (ecs_ftime_t)delta_time; } static void flecs_stop_measure_frame( ecs_world_t* world) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_t t = world->frame_start_time; world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } ecs_ftime_t ecs_frame_begin( ecs_world_t *world, ecs_ftime_t user_delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot begin frame while world is in readonly mode"); ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); /* Start measuring total frame time */ ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); if (ECS_EQZERO(user_delta_time)) { user_delta_time = delta_time; } world->info.delta_time_raw = user_delta_time; world->info.delta_time = user_delta_time * world->info.time_scale; /* Keep track of total scaled time passed in world */ world->info.world_time_total += (double)world->info.delta_time; /* Command buffer capturing */ world->on_commands_active = world->on_commands; world->on_commands = NULL; world->on_commands_ctx_active = world->on_commands_ctx; world->on_commands_ctx = NULL; ecs_run_aperiodic(world, 0); return world->info.delta_time; error: return (ecs_ftime_t)0; } void ecs_frame_end( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot end frame while world is in readonly mode"); world->info.frame_count_total ++; int32_t i, count = world->stage_count; for (i = 0; i < count; i ++) { flecs_stage_merge_post_frame(world, world->stages[i]); } flecs_stop_measure_frame(world); /* Reset command handler each frame */ world->on_commands_active = NULL; world->on_commands_ctx_active = NULL; error: return; } void flecs_delete_table( ecs_world_t *world, ecs_table_t *table) { flecs_poly_assert(world, ecs_world_t); flecs_table_fini(world, table); } static void flecs_process_empty_queries( ecs_world_t *world) { flecs_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_is_true(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) { flecs_poly_assert(world_r, ecs_world_t); /* We can't update the administration while in readonly mode, but we can * ensure that when this function is called there are no pending events. */ if (world_r->flags & EcsWorldReadonly) { ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, ECS_INTERNAL_ERROR, NULL); return; } /* Safe to cast, world is not readonly */ ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); /* If pending buffer is NULL there already is a stackframe that's iterating * the table list. This can happen when an observer for a table event results * in a mutation that causes another table to change state. A typical * example of this is a system that becomes active/inactive as the result of * a query (and as a result, its matched tables) becoming empty/non empty */ if (!world->pending_buffer) { return; } /* Swap buffer. The logic could in theory have been implemented with a * single sparse set, but that would've complicated (and slowed down) the * iteration. Additionally, by using a double buffer approach we can still * keep most of the original ordering of events intact, which is desirable * as it means that the ordering of tables in the internal data structures is * more predictable. */ int32_t i, count = flecs_sparse_count(world->pending_tables); if (!count) { return; } flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); do { ecs_sparse_t *pending_tables = world->pending_tables; world->pending_tables = world->pending_buffer; world->pending_buffer = NULL; /* Make sure that any ECS operations that occur while delivering the * events does not cause inconsistencies, like sending an Empty * notification for a table that just became non-empty. */ flecs_defer_begin(world, world->stages[0]); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( pending_tables, ecs_table_t*, i)[0]; if (!table->id) { /* Table is being deleted, ignore empty events */ continue; } /* For each id in the table, add it to the empty/non empty list * based on its current state */ if (flecs_table_records_update_empty(table)) { int32_t table_count = ecs_table_count(table); if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { /* Only emit an event when there was a change in the * administration. It is possible that a table ended up in the * pending_tables list by going from empty->non-empty, but then * became empty again. By the time we run this code, no changes * in the administration would actually be made. */ ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; if (ecs_should_log_3()) { ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id, table_count ? "non-empty" : "empty"); } ecs_log_push_3(); flecs_table_emit(world, table, evt); 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) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_count(table)) { table->_->generation = 0; } flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, (uint32_t)table->id)[0] = table; } bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return false; } return (flecs_table_cache_count(&idr->cache) != 0) || (flecs_table_cache_empty_count(&idr->cache) != 0); } void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags) { flecs_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) { flecs_poly_assert(world, ecs_world_t); /* Make sure empty tables are in the empty table lists */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0, clear_count = 0; bool time_budget = false; if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } if (ECS_NEQZERO(time_budget_seconds)) { time_budget = true; } if (!id) { id = EcsAny; /* Iterate all empty tables */ } ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { if (time_budget) { cur = start; if (ecs_time_measure(&cur) > time_budget_seconds) { goto done; } } ecs_table_t *table = tr->hdr.table; ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); if (table->type.count < min_id_count) { continue; } uint16_t gen = ++ table->_->generation; if (delete_generation && (gen > delete_generation)) { flecs_table_fini(world, table); delete_count ++; } else if (clear_generation && (gen > clear_generation)) { if (flecs_table_shrink(world, table)) { clear_count ++; } } } } done: if (ecs_should_log_1() && ecs_os_has_time()) { if (delete_count) { ecs_dbg_1("#[red]deleted#[normal] %d empty tables in %.2fs", delete_count, ecs_time_measure(&start)); } if (clear_count) { ecs_dbg_1("#[red]cleared#[normal] %d empty tables in %.2fs", clear_count, ecs_time_measure(&start)); } } return delete_count; } ecs_entities_t ecs_get_entities( const ecs_world_t *world) { ecs_entities_t result; result.ids = flecs_entities_ids(world); result.count = flecs_entities_size(world); result.alive_count = flecs_entities_count(world); return result; } /** * @file addons/alerts.c * @brief Alerts addon. */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(FlecsAlerts); typedef struct EcsAlert { char *message; ecs_map_t instances; /* Active instances for metric */ ecs_ftime_t retain_period; /* How long to retain the alert */ ecs_vec_t severity_filters; /* Severity filters */ /* Member range monitoring */ ecs_id_t id; /* (Component) id that contains to monitor member */ ecs_entity_t member; /* Member to monitor */ int32_t offset; /* Offset of member in component */ int32_t size; /* Size of component */ ecs_primitive_kind_t kind; /* Primitive type kind */ ecs_ref_t ranges; /* Reference to ranges component */ int32_t var_id; /* Variable from which to obtain data (0 = $this) */ } EcsAlert; typedef struct EcsAlertTimeout { ecs_ftime_t inactive_time; /* Time the alert has been inactive */ ecs_ftime_t expire_time; /* Expiration duration */ } EcsAlertTimeout; ECS_COMPONENT_DECLARE(EcsAlertTimeout); static ECS_CTOR(EcsAlert, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->instances, NULL); ecs_vec_init_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t, 0); }) static ECS_DTOR(EcsAlert, ptr, { ecs_os_free(ptr->message); ecs_map_fini(&ptr->instances); ecs_vec_fini_t(NULL, &ptr->severity_filters, ecs_alert_severity_filter_t); }) static ECS_MOVE(EcsAlert, dst, src, { ecs_os_free(dst->message); dst->message = src->message; src->message = NULL; ecs_map_fini(&dst->instances); dst->instances = src->instances; src->instances = (ecs_map_t){0}; ecs_vec_fini_t(NULL, &dst->severity_filters, ecs_alert_severity_filter_t); dst->severity_filters = src->severity_filters; src->severity_filters = (ecs_vec_t){0}; dst->retain_period = src->retain_period; dst->id = src->id; dst->member = src->member; dst->offset = src->offset; dst->size = src->size; dst->kind = src->kind; dst->ranges = src->ranges; dst->var_id = src->var_id; }) static ECS_CTOR(EcsAlertsActive, ptr, { ecs_map_init(&ptr->alerts, NULL); ptr->info_count = 0; ptr->warning_count = 0; ptr->error_count = 0; }) static ECS_DTOR(EcsAlertsActive, ptr, { ecs_map_fini(&ptr->alerts); }) static ECS_MOVE(EcsAlertsActive, dst, src, { ecs_map_fini(&dst->alerts); dst->alerts = src->alerts; dst->info_count = src->info_count; dst->warning_count = src->warning_count; dst->error_count = src->error_count; src->alerts = (ecs_map_t){0}; }) static ECS_DTOR(EcsAlertInstance, ptr, { ecs_os_free(ptr->message); }) static ECS_MOVE(EcsAlertInstance, dst, src, { ecs_os_free(dst->message); dst->message = src->message; src->message = NULL; }) static ECS_COPY(EcsAlertInstance, dst, src, { ecs_os_free(dst->message); dst->message = ecs_os_strdup(src->message); }) static void flecs_alerts_add_alert_to_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert, ecs_entity_t alert_instance) { EcsAlertsActive *active = ecs_ensure( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count ++; } else if (severity == EcsAlertWarning) { active->warning_count ++; } else if (severity == EcsAlertError) { active->error_count ++; } ecs_entity_t *ptr = ecs_map_ensure(&active->alerts, alert); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr[0] = alert_instance; ecs_modified(world, source, EcsAlertsActive); } static void flecs_alerts_remove_alert_from_src( ecs_world_t *world, ecs_entity_t source, ecs_entity_t alert) { EcsAlertsActive *active = ecs_ensure( world, source, EcsAlertsActive); ecs_assert(active != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_remove(&active->alerts, alert); ecs_entity_t severity = ecs_get_target(world, alert, ecs_id(EcsAlert), 0); if (severity == EcsAlertInfo) { active->info_count --; } else if (severity == EcsAlertWarning) { active->warning_count --; } else if (severity == EcsAlertError) { active->error_count --; } if (!ecs_map_count(&active->alerts)) { ecs_remove(world, source, EcsAlertsActive); } else { ecs_modified(world, source, EcsAlertsActive); } } static ecs_entity_t flecs_alert_get_severity( ecs_world_t *world, ecs_iter_t *it, EcsAlert *alert) { int32_t i, filter_count = ecs_vec_count(&alert->severity_filters); ecs_alert_severity_filter_t *filters = ecs_vec_first(&alert->severity_filters); for (i = 0; i < filter_count; i ++) { ecs_alert_severity_filter_t *filter = &filters[i]; if (!filter->var) { if (ecs_table_has_id(world, it->table, filters[i].with)) { return filters[i].severity; } } else { ecs_entity_t src = ecs_iter_get_var(it, filter->_var_index); if (src && src != EcsWildcard) { if (ecs_has_id(world, src, filters[i].with)) { return filters[i].severity; } } } } return 0; } static ecs_entity_t flecs_alert_out_of_range_kind( EcsAlert *alert, const EcsMemberRanges *ranges, const void *value_ptr) { double value = 0; switch(alert->kind) { case EcsU8: value = *(const uint8_t*)value_ptr; break; case EcsU16: value = *(const uint16_t*)value_ptr; break; case EcsU32: value = *(const uint32_t*)value_ptr; break; case EcsU64: value = (double)*(const uint64_t*)value_ptr; break; case EcsI8: value = *(const int8_t*)value_ptr; break; case EcsI16: value = *(const int16_t*)value_ptr; break; case EcsI32: value = *(const int32_t*)value_ptr; break; case EcsI64: value = (double)*(const int64_t*)value_ptr; break; case EcsF32: value = (double)*(const float*)value_ptr; break; case EcsF64: value = *(const double*)value_ptr; break; case EcsBool: case EcsChar: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: return 0; } bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); if (has_error && (value < ranges->error.min || value > ranges->error.max)) { return EcsAlertError; } else if (has_warning && (value < ranges->warning.min || value > ranges->warning.max)) { return EcsAlertWarning; } else { return 0; } } static void MonitorAlerts(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsAlert *alert = ecs_field(it, EcsAlert, 0); EcsPoly *poly = ecs_field(it, EcsPoly, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t a = it->entities[i]; /* Alert entity */ ecs_entity_t default_severity = ecs_get_target( world, a, ecs_id(EcsAlert), 0); ecs_query_t *q = poly[i].poly; if (!q) { continue; } flecs_poly_assert(q, ecs_query_t); ecs_id_t member_id = alert[i].id; const EcsMemberRanges *ranges = NULL; if (member_id) { ranges = ecs_ref_get(world, &alert[i].ranges, EcsMemberRanges); } ecs_iter_t rit = ecs_query_iter(world, q); rit.flags |= EcsIterNoData; rit.flags |= EcsIterIsInstanced; while (ecs_query_next(&rit)) { ecs_entity_t severity = flecs_alert_get_severity( world, &rit, &alert[i]); if (!severity) { severity = default_severity; } const void *member_data = NULL; ecs_entity_t member_src = 0; if (ranges) { if (alert[i].var_id) { member_src = ecs_iter_get_var(&rit, alert[i].var_id); if (!member_src || member_src == EcsWildcard) { continue; } } if (!member_src) { member_data = ecs_table_get_id( world, rit.table, member_id, rit.offset); } else { member_data = ecs_get_id(world, member_src, member_id); } if (!member_data) { continue; } member_data = ECS_OFFSET(member_data, alert[i].offset); } int32_t j, alert_src_count = rit.count; for (j = 0; j < alert_src_count; j ++) { ecs_entity_t src_severity = severity; ecs_entity_t e = rit.entities[j]; if (member_data) { ecs_entity_t range_severity = flecs_alert_out_of_range_kind( &alert[i], ranges, member_data); if (!member_src) { member_data = ECS_OFFSET(member_data, alert[i].size); } if (!range_severity) { continue; } if (range_severity < src_severity) { /* Range severity should not exceed alert severity */ src_severity = range_severity; } } ecs_entity_t *aptr = ecs_map_ensure(&alert[i].instances, e); ecs_assert(aptr != NULL, ECS_INTERNAL_ERROR, NULL); if (!aptr[0]) { /* Alert does not yet exist for entity */ ecs_entity_t ai = ecs_new_w_pair(world, EcsChildOf, a); ecs_set(world, ai, EcsAlertInstance, { .message = NULL }); ecs_set(world, ai, EcsMetricSource, { .entity = e }); ecs_set(world, ai, EcsMetricValue, { .value = 0 }); ecs_add_pair(world, ai, ecs_id(EcsAlert), src_severity); if (ECS_NEQZERO(alert[i].retain_period)) { ecs_set(world, ai, EcsAlertTimeout, { .inactive_time = 0, .expire_time = alert[i].retain_period }); } ecs_defer_suspend(it->world); flecs_alerts_add_alert_to_src(world, e, a, ai); ecs_defer_resume(it->world); aptr[0] = ai; } else { /* Make sure alert severity is up to date */ if (ecs_vec_count(&alert[i].severity_filters) || member_data) { ecs_entity_t cur_severity = ecs_get_target( world, aptr[0], ecs_id(EcsAlert), 0); if (cur_severity != src_severity) { ecs_add_pair(world, aptr[0], ecs_id(EcsAlert), src_severity); } } } } } } } static void MonitorAlertInstances(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsAlertInstance *alert_instance = ecs_field(it, EcsAlertInstance, 0); EcsMetricSource *source = ecs_field(it, EcsMetricSource, 1); EcsMetricValue *value = ecs_field(it, EcsMetricValue, 2); EcsAlertTimeout *timeout = ecs_field(it, EcsAlertTimeout, 3); /* Get alert component from alert instance parent (the alert) */ ecs_id_t childof_pair; if (ecs_search(world, it->table, ecs_childof(EcsWildcard), &childof_pair) == -1) { ecs_err("alert instances must be a child of an alert"); return; } ecs_entity_t parent = ecs_pair_second(world, childof_pair); ecs_assert(parent != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, parent, EcsAlert), ECS_INVALID_OPERATION, "alert entity does not have Alert component"); EcsAlert *alert = ecs_ensure(world, parent, EcsAlert); const EcsPoly *poly = ecs_get_pair(world, parent, EcsPoly, EcsQuery); ecs_assert(poly != NULL, ECS_INVALID_OPERATION, "alert entity does not have (Poly, Query) component"); ecs_query_t *query = poly->poly; if (!query) { return; } flecs_poly_assert(query, ecs_query_t); ecs_id_t member_id = alert->id; const EcsMemberRanges *ranges = NULL; if (member_id) { ranges = ecs_ref_get(world, &alert->ranges, EcsMemberRanges); } ecs_script_vars_t *vars = ecs_script_vars_init(it->world); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t ai = it->entities[i]; ecs_entity_t e = source[i].entity; /* If source of alert is no longer alive, delete alert instance even if * the alert has a retain period. */ if (!ecs_is_alive(world, e)) { ecs_delete(world, ai); continue; } /* Check if alert instance still matches query */ ecs_iter_t rit = ecs_query_iter(world, query); rit.flags |= EcsIterNoData; rit.flags |= EcsIterIsInstanced; ecs_iter_set_var(&rit, 0, e); if (ecs_query_next(&rit)) { bool match = true; /* If alert is monitoring member range, test value against range */ if (ranges) { ecs_entity_t member_src = e; if (alert->var_id) { member_src = ecs_iter_get_var(&rit, alert->var_id); } const void *member_data = ecs_get_id( world, member_src, member_id); if (!member_data) { match = false; } else { member_data = ECS_OFFSET(member_data, alert->offset); if (flecs_alert_out_of_range_kind( alert, ranges, member_data) == 0) { match = false; } } } if (match) { /* Only increase alert duration if the alert was active */ value[i].value += (double)it->delta_system_time; bool generate_message = alert->message; if (generate_message) { if (alert_instance[i].message) { /* If a message was already generated, only regenerate if * query has multiple variables. Variable values could have * changed, this ensures the message remains up to date. */ generate_message = rit.variable_count > 1; } } if (generate_message) { if (alert_instance[i].message) { ecs_os_free(alert_instance[i].message); } ecs_script_vars_from_iter(&rit, vars, 0); alert_instance[i].message = ecs_script_string_interpolate( world, alert->message, vars); } if (timeout) { if (ECS_NEQZERO(timeout[i].inactive_time)) { /* The alert just became active. Remove Disabled tag */ flecs_alerts_add_alert_to_src(world, e, parent, ai); ecs_remove_id(world, ai, EcsDisabled); } timeout[i].inactive_time = 0; } /* Alert instance still matches query, keep it alive */ ecs_iter_fini(&rit); continue; } ecs_iter_fini(&rit); } /* Alert instance is no longer active */ if (timeout) { if (ECS_EQZERO(timeout[i].inactive_time)) { /* The alert just became inactive. Add Disabled tag */ flecs_alerts_remove_alert_from_src(world, e, parent); ecs_add_id(world, ai, EcsDisabled); } ecs_ftime_t t = timeout[i].inactive_time; timeout[i].inactive_time += it->delta_system_time; if (t < timeout[i].expire_time) { /* Alert instance no longer matches query, but is still * within the timeout period. Keep it alive. */ continue; } } /* Alert instance no longer matches query, remove it */ flecs_alerts_remove_alert_from_src(world, e, parent); ecs_map_remove(&alert->instances, e); ecs_delete(world, ai); } ecs_script_vars_fini(vars); } ecs_entity_t ecs_alert_init( ecs_world_t *world, const ecs_alert_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_alert_desc_t was not initialized to zero"); ecs_check(!desc->query.entity || desc->entity == desc->query.entity, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); } ecs_query_desc_t private_desc = desc->query; private_desc.entity = result; ecs_query_t *q = ecs_query_init(world, &private_desc); if (!q) { ecs_err("failed to create alert filter"); return 0; } if (!(q->flags & EcsQueryMatchThis)) { ecs_err("alert filter must have at least one '$this' term"); ecs_query_fini(q); return 0; } /* Initialize Alert component which identifiers entity as alert */ EcsAlert *alert = ecs_ensure(world, result, EcsAlert); ecs_assert(alert != NULL, ECS_INTERNAL_ERROR, NULL); alert->message = ecs_os_strdup(desc->message); alert->retain_period = desc->retain_period; /* Initialize severity filters */ int32_t i; for (i = 0; i < 4; i ++) { if (desc->severity_filters[i].with) { if (!desc->severity_filters[i].severity) { ecs_err("severity filter must have severity"); goto error; } ecs_alert_severity_filter_t *sf = ecs_vec_append_t(NULL, &alert->severity_filters, ecs_alert_severity_filter_t); *sf = desc->severity_filters[i]; if (sf->var) { sf->_var_index = ecs_query_find_var(q, sf->var); if (sf->_var_index == -1) { ecs_err("unresolved variable '%s' in alert severity filter", sf->var); goto error; } } } } /* Fetch data for member monitoring */ if (desc->member) { alert->member = desc->member; if (!desc->id) { alert->id = ecs_get_parent(world, desc->member); if (!alert->id) { ecs_err("ecs_alert_desc_t::member is not a member"); goto error; } ecs_check(alert->id != 0, ECS_INVALID_PARAMETER, NULL); } else { alert->id = desc->id; } ecs_id_record_t *idr = flecs_id_record_ensure(world, alert->id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (!idr->type_info) { ecs_err("ecs_alert_desc_t::id must be a component"); goto error; } ecs_entity_t type = idr->type_info->component; if (type != ecs_get_parent(world, desc->member)) { char *type_name = ecs_get_path(world, type); ecs_err("member '%s' is not a member of '%s'", ecs_get_name(world, desc->member), type_name); ecs_os_free(type_name); goto error; } const EcsMember *member = ecs_get(world, alert->member, EcsMember); if (!member) { ecs_err("ecs_alert_desc_t::member is not a member"); goto error; } if (!member->type) { ecs_err("ecs_alert_desc_t::member must have a type"); goto error; } const EcsPrimitive *pr = ecs_get(world, member->type, EcsPrimitive); if (!pr) { ecs_err("ecs_alert_desc_t::member must be of a primitive type"); goto error; } if (!ecs_has(world, desc->member, EcsMemberRanges)) { ecs_err("ecs_alert_desc_t::member must have warning/error ranges"); goto error; } int32_t var_id = 0; if (desc->var) { var_id = ecs_query_find_var(q, desc->var); if (var_id == -1) { ecs_err("unresolved variable '%s' in alert member", desc->var); goto error; } } alert->offset = member->offset; alert->size = idr->type_info->size; alert->kind = pr->kind; alert->ranges = ecs_ref_init(world, desc->member, EcsMemberRanges); alert->var_id = var_id; } ecs_modified(world, result, EcsAlert); /* Register alert as metric */ ecs_add(world, result, EcsMetric); ecs_add_pair(world, result, EcsMetric, EcsCounter); /* Add severity to alert */ ecs_entity_t severity = desc->severity; if (!severity) { severity = EcsAlertError; } ecs_add_pair(world, result, ecs_id(EcsAlert), severity); if (desc->doc_name) { #ifdef FLECS_DOC ecs_doc_set_name(world, result, desc->doc_name); #else ecs_err("cannot set doc_name for alert, requires FLECS_DOC addon"); goto error; #endif } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_err("cannot set brief for alert, requires FLECS_DOC addon"); goto error; #endif } return result; error: if (result) { ecs_delete(world, result); } return 0; } int32_t ecs_get_alert_count( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t alert) { flecs_poly_assert(world, ecs_world_t); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(!alert || ecs_has(world, alert, EcsAlert), ECS_INVALID_PARAMETER, NULL); const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); if (!active) { return 0; } if (alert) { return ecs_map_get(&active->alerts, alert) != NULL; } return ecs_map_count(&active->alerts); error: return 0; } ecs_entity_t ecs_get_alert( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t alert) { flecs_poly_assert(world, ecs_world_t); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(alert != 0, ECS_INVALID_PARAMETER, NULL); const EcsAlertsActive *active = ecs_get(world, entity, EcsAlertsActive); if (!active) { return 0; } ecs_entity_t *ptr = ecs_map_get(&active->alerts, alert); if (ptr) { return ptr[0]; } error: return 0; } void FlecsAlertsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsAlerts); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); ECS_IMPORT(world, FlecsMetrics); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsAlert); ecs_remove_pair(world, ecs_id(EcsAlert), ecs_id(EcsIdentifier), EcsSymbol); ECS_COMPONENT_DEFINE(world, EcsAlertsActive); ecs_set_name_prefix(world, "EcsAlert"); ECS_COMPONENT_DEFINE(world, EcsAlertInstance); ECS_COMPONENT_DEFINE(world, EcsAlertTimeout); ECS_TAG_DEFINE(world, EcsAlertInfo); ECS_TAG_DEFINE(world, EcsAlertWarning); ECS_TAG_DEFINE(world, EcsAlertError); ECS_TAG_DEFINE(world, EcsAlertCritical); ecs_add_id(world, ecs_id(EcsAlert), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsAlert), EcsExclusive); ecs_add_id(world, ecs_id(EcsAlertsActive), EcsPrivate); ecs_struct(world, { .entity = ecs_id(EcsAlertInstance), .members = { { .name = "message", .type = ecs_id(ecs_string_t) } } }); ecs_set_hooks(world, EcsAlert, { .ctor = ecs_ctor(EcsAlert), .dtor = ecs_dtor(EcsAlert), .move = ecs_move(EcsAlert) }); ecs_set_hooks(world, EcsAlertsActive, { .ctor = ecs_ctor(EcsAlertsActive), .dtor = ecs_dtor(EcsAlertsActive), .move = ecs_move(EcsAlertsActive) }); ecs_set_hooks(world, EcsAlertInstance, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsAlertInstance), .move = ecs_move(EcsAlertInstance), .copy = ecs_copy(EcsAlertInstance) }); ecs_struct(world, { .entity = ecs_id(EcsAlertsActive), .members = { { .name = "info_count", .type = ecs_id(ecs_i32_t) }, { .name = "warning_count", .type = ecs_id(ecs_i32_t) }, { .name = "error_count", .type = ecs_id(ecs_i32_t) } } }); ECS_SYSTEM(world, MonitorAlerts, EcsPreStore, Alert, (Poly, Query)); ECS_SYSTEM(world, MonitorAlertInstances, EcsOnStore, Instance, flecs.metrics.Source, flecs.metrics.Value, ?Timeout, ?Disabled); ecs_system(world, { .entity = ecs_id(MonitorAlerts), .immediate = true, .interval = (ecs_ftime_t)0.5 }); ecs_system(world, { .entity = ecs_id(MonitorAlertInstances), .interval = (ecs_ftime_t)0.5 }); } #endif /** * @file addons/app.c * @brief App addon. */ #ifdef FLECS_APP static int flecs_default_run_action( ecs_world_t *world, ecs_app_desc_t *desc) { if (desc->init) { desc->init(world); } int result = 0; if (desc->frames) { int32_t i; for (i = 0; i < desc->frames; i ++) { if ((result = ecs_app_run_frame(world, desc)) != 0) { break; } } } else { while ((result = ecs_app_run_frame(world, desc)) == 0) { } } /* Ensure quit flag is set on world, which can be used to determine if * world needs to be cleaned up. */ #ifndef __EMSCRIPTEN__ ecs_quit(world); #endif if (result == 1) { return 0; /* Normal exit */ } else { return result; /* Error code */ } } static int flecs_default_frame_action( ecs_world_t *world, const ecs_app_desc_t *desc) { return !ecs_progress(world, desc->delta_time); } static ecs_app_run_action_t run_action = flecs_default_run_action; static ecs_app_frame_action_t frame_action = flecs_default_frame_action; static ecs_app_desc_t ecs_app_desc; /* Serve REST API from wasm image when running in emscripten */ #ifdef ECS_TARGET_EM #include ecs_http_server_t *flecs_wasm_rest_server; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); if (reply.code == 200) { return ecs_strbuf_get(&reply.body); } else { char *body = ecs_strbuf_get(&reply.body); if (body) { return body; } else { return flecs_asprintf( "{\"error\": \"bad request\", \"status\": %d}", reply.code); } } } #endif int ecs_app_run( ecs_world_t *world, ecs_app_desc_t *desc) { ecs_app_desc = *desc; /* Don't set FPS & threads if using emscripten */ #ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } #endif /* REST server enables connecting to app with explorer */ if (desc->enable_rest) { #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); #else ECS_IMPORT(world, FlecsRest); ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); #endif #else ecs_warn("cannot enable remote API, REST addon not available"); #endif } /* Monitoring periodically collects statistics */ if (desc->enable_stats) { #ifdef FLECS_STATS ECS_IMPORT(world, FlecsStats); #else ecs_warn("cannot enable monitoring, MONITOR addon not available"); #endif } return run_action(world, &ecs_app_desc); } int ecs_app_run_frame( ecs_world_t *world, const ecs_app_desc_t *desc) { return frame_action(world, desc); } int ecs_app_set_run_action( ecs_app_run_action_t callback) { if (run_action != flecs_default_run_action && run_action != callback) { ecs_err("run action already set"); return -1; } run_action = callback; return 0; } int ecs_app_set_frame_action( ecs_app_frame_action_t callback) { if (frame_action != flecs_default_frame_action && frame_action != callback) { ecs_err("frame action already set"); return -1; } frame_action = callback; return 0; } #endif /** * @file addons/doc.c * @brief Doc addon. */ #ifdef FLECS_DOC static ECS_COPY(EcsDocDescription, dst, src, { ecs_os_strset((char**)&dst->value, src->value); }) static ECS_MOVE(EcsDocDescription, dst, src, { ecs_os_free((char*)dst->value); dst->value = src->value; src->value = NULL; }) static ECS_DTOR(EcsDocDescription, ptr, { ecs_os_free((char*)ptr->value); }) static void flecs_doc_set( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t kind, const char *value) { if (value) { ecs_set_pair(world, entity, EcsDocDescription, kind, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, value) }); } else { ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); } } void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_doc_set(world, entity, EcsName, name); } void ecs_doc_set_brief( ecs_world_t *world, ecs_entity_t entity, const char *brief) { flecs_doc_set(world, entity, EcsDocBrief, brief); } void ecs_doc_set_detail( ecs_world_t *world, ecs_entity_t entity, const char *detail) { flecs_doc_set(world, entity, EcsDocDetail, detail); } void ecs_doc_set_link( ecs_world_t *world, ecs_entity_t entity, const char *link) { flecs_doc_set(world, entity, EcsDocLink, link); } void ecs_doc_set_color( ecs_world_t *world, ecs_entity_t entity, const char *color) { flecs_doc_set(world, entity, EcsDocColor, color); } const char* ecs_doc_get_name( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsName); if (ptr) { return ptr->value; } else { return ecs_get_name(world, entity); } } const char* ecs_doc_get_brief( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocBrief); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_detail( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocDetail); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_link( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocLink); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_color( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocColor); if (ptr) { return ptr->value; } else { return NULL; } } /* Doc definitions for core components */ static void flecs_doc_import_core_definitions( ecs_world_t *world) { ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); ecs_doc_set_brief(world, EcsFlecsCore, "Module with builtin components"); ecs_doc_set_brief(world, EcsFlecsInternals, "Module with internal entities"); ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to components"); ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); ecs_doc_set_brief(world, EcsPrivate, "Tag that is added to private components"); ecs_doc_set_brief(world, EcsFlag, "Internal tag for tracking ids with special id flags"); ecs_doc_set_brief(world, ecs_id(EcsPoly), "Internal component that stores pointer to poly objects"); ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to store entity name"); ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to store entity symbol"); ecs_doc_set_brief(world, EcsAlias, "Tag used with EcsIdentifier to store entity alias"); ecs_doc_set_brief(world, EcsQuery, "Tag added to query entities"); ecs_doc_set_brief(world, EcsObserver, "Tag added to observer entities"); ecs_doc_set_brief(world, EcsTransitive, "Trait that enables transitive evaluation of relationships"); ecs_doc_set_brief(world, EcsReflexive, "Trait that enables reflexive evaluation of relationships"); ecs_doc_set_brief(world, EcsFinal, "Trait that indicates an entity cannot be inherited from"); ecs_doc_set_brief(world, EcsDontInherit, "Trait that indicates it should not be inherited"); ecs_doc_set_brief(world, EcsPairIsTag, "Trait that ensures a pair cannot contain a value"); ecs_doc_set_brief(world, EcsAcyclic, "Trait that indicates a relationship is acyclic"); ecs_doc_set_brief(world, EcsTraversable, "Trait that indicates a relationship is traversable"); ecs_doc_set_brief(world, EcsExclusive, "Trait that ensures a relationship can only have one target"); ecs_doc_set_brief(world, EcsSymmetric, "Trait that causes a relationship to be two-way"); ecs_doc_set_brief(world, EcsWith, "Trait for adding additional components when a component is added"); ecs_doc_set_brief(world, EcsOneOf, "Trait that enforces target of relationship is a child of "); ecs_doc_set_brief(world, EcsOnDelete, "Cleanup trait for specifying what happens when component is deleted"); ecs_doc_set_brief(world, EcsOnDeleteTarget, "Cleanup trait for specifying what happens when pair target is deleted"); ecs_doc_set_brief(world, EcsRemove, "Cleanup action used with OnDelete/OnDeleteTarget"); ecs_doc_set_brief(world, EcsDelete, "Cleanup action used with OnDelete/OnDeleteTarget"); ecs_doc_set_brief(world, EcsPanic, "Cleanup action used with OnDelete/OnDeleteTarget"); ecs_doc_set_brief(world, ecs_id(EcsDefaultChildComponent), "Sets default component hint for children of entity"); ecs_doc_set_brief(world, EcsIsA, "Relationship used for expressing inheritance"); ecs_doc_set_brief(world, EcsChildOf, "Relationship used for expressing hierarchies"); ecs_doc_set_brief(world, EcsDependsOn, "Relationship used for expressing dependencies"); ecs_doc_set_brief(world, EcsSlotOf, "Relationship used for expressing prefab slots"); ecs_doc_set_brief(world, EcsOnAdd, "Event emitted when component is added"); ecs_doc_set_brief(world, EcsOnRemove, "Event emitted when component is removed"); ecs_doc_set_brief(world, EcsOnSet, "Event emitted when component is set"); ecs_doc_set_brief(world, EcsMonitor, "Marker used to create monitor observers"); ecs_doc_set_brief(world, EcsOnTableFill, "Event emitted when table becomes non-empty"); ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); ecs_doc_set_brief(world, EcsThis, "Query marker to express $this variable"); ecs_doc_set_brief(world, EcsWildcard, "Query marker to express match all wildcard"); ecs_doc_set_brief(world, EcsAny, "Query marker to express match at least one wildcard"); ecs_doc_set_brief(world, EcsPredEq, "Query marker to express == operator"); ecs_doc_set_brief(world, EcsPredMatch, "Query marker to express ~= operator"); ecs_doc_set_brief(world, EcsPredLookup, "Query marker to express by-name lookup"); ecs_doc_set_brief(world, EcsScopeOpen, "Query marker to express scope open"); ecs_doc_set_brief(world, EcsScopeClose, "Query marker to express scope close"); ecs_doc_set_brief(world, EcsEmpty, "Tag used to indicate a query has no results"); } /* Doc definitions for doc components */ static void flecs_doc_import_doc_definitions( ecs_world_t *world) { ecs_entity_t doc = ecs_lookup(world, "flecs.doc"); ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); ecs_doc_set_brief(world, EcsDocBrief, "Brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Link to additional documentation"); ecs_doc_set_brief(world, EcsDocColor, "Color hint for entity"); ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); } void FlecsDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsDoc); ecs_set_name_prefix(world, "EcsDoc"); flecs_bootstrap_component(world, EcsDocDescription); flecs_bootstrap_tag(world, EcsDocBrief); flecs_bootstrap_tag(world, EcsDocDetail); flecs_bootstrap_tag(world, EcsDocLink); flecs_bootstrap_tag(world, EcsDocColor); ecs_set_hooks(world, EcsDocDescription, { .ctor = flecs_default_ctor, .move = ecs_move(EcsDocDescription), .copy = ecs_copy(EcsDocDescription), .dtor = ecs_dtor(EcsDocDescription) }); ecs_add_pair(world, ecs_id(EcsDocDescription), EcsOnInstantiate, EcsDontInherit); ecs_add_id(world, ecs_id(EcsDocDescription), EcsPrivate); flecs_doc_import_core_definitions(world); flecs_doc_import_doc_definitions(world); } #endif /** * @file addons/flecs_cpp.c * @brief Utilities for C++ addon. */ #include /* Utilities for C++ API */ #ifdef FLECS_CPP /* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to * a uniform identifier */ #define ECS_CONST_PREFIX "const " #define ECS_STRUCT_PREFIX "struct " #define ECS_CLASS_PREFIX "class " #define ECS_ENUM_PREFIX "enum " #define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) #define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) #define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) #define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) static ecs_size_t ecs_cpp_strip_prefix( char *typeName, ecs_size_t len, const char *prefix, ecs_size_t prefix_len) { if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); typeName[len - prefix_len] = '\0'; len -= prefix_len; } return len; } static void ecs_cpp_trim_type_name( char *typeName) { ecs_size_t len = ecs_os_strlen(typeName); len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); while (typeName[len - 1] == ' ' || typeName[len - 1] == '&' || typeName[len - 1] == '*') { len --; typeName[len] = '\0'; } /* Remove const at end of string */ if (len > ECS_CONST_LEN) { if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { typeName[len - ECS_CONST_LEN] = '\0'; } len -= ECS_CONST_LEN; } /* Check if there are any remaining "struct " strings, which can happen * if this is a template type on msvc. */ if (len > ECS_STRUCT_LEN) { char *ptr = typeName; while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { /* Make sure we're not matched with part of a longer identifier * that contains 'struct' */ if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); len -= ECS_STRUCT_LEN; } } } } char* ecs_cpp_get_type_name( char *type_name, const char *func_name, size_t len, size_t front_len) { memcpy(type_name, func_name + front_len, len); type_name[len] = '\0'; ecs_cpp_trim_type_name(type_name); return type_name; } char* ecs_cpp_get_symbol_name( char *symbol_name, const char *type_name, size_t len) { // Symbol is same as name, but with '::' replaced with '.' ecs_os_strcpy(symbol_name, type_name); char *ptr; size_t i; for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { if (*ptr == ':') { symbol_name[i] = '.'; ptr ++; } else { symbol_name[i] = *ptr; } } symbol_name[i] = '\0'; return symbol_name; } static const char* flecs_cpp_func_rchr( const char *func_name, ecs_size_t func_name_len, ecs_size_t func_back_len, char ch) { const char *r = strrchr(func_name, ch); if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, func_back_len))) { return NULL; } return r; } static const char* flecs_cpp_func_max( const char *a, const char *b) { if (a > b) return a; return b; } char* ecs_cpp_get_constant_name( char *constant_name, const char *func_name, size_t func_name_len, size_t func_back_len) { ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); ecs_size_t fb_len = flecs_uto(ecs_size_t, func_back_len); const char *start = flecs_cpp_func_rchr(func_name, f_len, fb_len, ' '); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ')')); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ':')); start = flecs_cpp_func_max(start, flecs_cpp_func_rchr( func_name, f_len, fb_len, ',')); ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); start ++; ecs_size_t len = flecs_uto(ecs_size_t, (f_len - (start - func_name) - fb_len)); ecs_os_memcpy_n(constant_name, start, char, len); constant_name[len] = '\0'; return constant_name; } // Names returned from the name_helper class do not start with :: // but are relative to the root. If the namespace of the type // overlaps with the namespace of the current module, strip it from // the implicit identifier. // This allows for registration of component types that are not in the // module namespace to still be registered under the module scope. const char* ecs_cpp_trim_module( ecs_world_t *world, const char *type_name) { ecs_entity_t scope = ecs_get_scope(world); if (!scope) { return type_name; } char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); if (path) { ecs_size_t len = ecs_os_strlen(path); if (!ecs_os_strncmp(path, type_name, len)) { // Type is a child of current parent, trim name of parent type_name += len; ecs_assert(type_name[0], ECS_INVALID_PARAMETER, "invalid C++ type name"); ecs_assert(type_name[0] == ':', ECS_INVALID_PARAMETER, "invalid C++ type name"); ecs_assert(type_name[1] == ':', ECS_INVALID_PARAMETER, "invalid C++ type name"); type_name += 2; } else { // Type is not a child of current parent, trim entire path char *ptr = strrchr(type_name, ':'); if (ptr) { type_name = ptr + 1; } } } ecs_os_free(path); return type_name; } // Validate registered component void ecs_cpp_component_validate( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, size_t size, size_t alignment, bool implicit_name) { /* If entity has a name check if it matches */ if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { if (!implicit_name && id >= EcsFirstUserComponentId) { #ifndef FLECS_NDEBUG char *path = ecs_get_path_w_sep( world, 0, id, "::", NULL); if (ecs_os_strcmp(path, name)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' already registered with name '%s'", name, path); } ecs_os_free(path); #endif } if (symbol) { const char *existing_symbol = ecs_get_symbol(world, id); if (existing_symbol) { if (ecs_os_strcmp(symbol, existing_symbol)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' with symbol '%s' already registered with symbol '%s'", name, symbol, existing_symbol); } } } } else { /* Ensure that the entity id valid */ if (!ecs_is_alive(world, id)) { ecs_make_alive(world, id); } /* Register name with entity, so that when the entity is created the * correct id will be resolved from the name. Only do this when the * entity is empty. */ ecs_add_path_w_sep(world, id, 0, name, "::", "::"); } /* If a component was already registered with this id but with a * different size, the ecs_component_init function will fail. */ /* We need to explicitly call ecs_component_init here again. Even though * the component was already registered, it may have been registered * with a different world. This ensures that the component is registered * with the same id for the current world. * If the component was registered already, nothing will change. */ ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ .entity = id, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); (void)ent; ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); } ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, ecs_size_t size, ecs_size_t alignment, bool implicit_name, bool *existing_out) { (void)size; (void)alignment; /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ bool existing = false; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t ent; if (id) { ent = id; } else { ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); existing = ent != 0 && ecs_has(world, ent, EcsComponent); } ecs_set_scope(world, prev_scope); /* If entity exists, compare symbol name to ensure that the component * we are trying to register under this name is the same */ if (ent) { const EcsComponent *component = ecs_get(world, ent, EcsComponent); if (component != NULL) { const char *sym = ecs_get_symbol(world, ent); if (sym && ecs_os_strcmp(sym, symbol)) { /* Application is trying to register a type with an entity that * was already associated with another type. In most cases this * is an error, with the exception of a scenario where the * application is wrapping a C type with a C++ type. * * In this case the C++ type typically inherits from the C type, * and adds convenience methods to the derived class without * changing anything that would change the size or layout. * * To meet this condition, the new type must have the same size * and alignment as the existing type, and the name of the type * type must be equal to the registered name (not symbol). * * The latter ensures that it was the intent of the application * to alias the type, vs. accidentally registering an unrelated * type with the same size/alignment. */ char *type_path = ecs_get_path(world, ent); if (ecs_os_strcmp(type_path, symbol) || component->size != size || component->alignment != alignment) { ecs_err( "component with name '%s' is already registered for"\ " type '%s' (trying to register for type '%s')", name, sym, symbol); ecs_abort(ECS_NAME_IN_USE, NULL); } ecs_os_free(type_path); } else if (!sym) { ecs_set_symbol(world, ent, symbol); } } /* If no entity is found, lookup symbol to check if the component was * registered under a different name. */ } else if (!implicit_name) { ent = ecs_lookup_symbol(world, symbol, false, false); ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); } if (existing_out) { *existing_out = existing; } return ent; } ecs_entity_t ecs_cpp_component_register_explicit( ecs_world_t *world, ecs_entity_t s_id, ecs_entity_t id, const char *name, const char *type_name, const char *symbol, size_t size, size_t alignment, bool is_component, bool *existing_out) { char *existing_name = NULL; if (existing_out) *existing_out = false; // If an explicit id is provided, it is possible that the symbol and // name differ from the actual type, as the application may alias // one type to another. if (!id) { if (!name) { // If no name was provided first check if a type with the provided // symbol was already registered. id = ecs_lookup_symbol(world, symbol, false, false); if (id) { existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); name = existing_name; if (existing_out) *existing_out = true; } else { // If type is not yet known, derive from type name name = ecs_cpp_trim_module(world, type_name); } } } else { // If an explicit id is provided but it has no name, inherit // the name from the type. if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { name = ecs_cpp_trim_module(world, type_name); } } ecs_entity_t entity; if (is_component || size != 0) { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); 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, "registration failed for component %s", name); } else { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); } ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); return entity; } void ecs_cpp_enum_init( ecs_world_t *world, ecs_entity_t id) { (void)world; (void)id; #ifdef FLECS_META ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_set(world, id, EcsEnum, {0}); flecs_resume_readonly(world, &readonly_state); #endif } ecs_entity_t ecs_cpp_enum_constant_register( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t id, const char *name, int value) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); const char *parent_name = ecs_get_name(world, parent); ecs_size_t parent_name_len = ecs_os_strlen(parent_name); if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { name += parent_name_len; if (name[0] == '_') { name ++; } } ecs_entity_t prev = ecs_set_scope(world, parent); id = ecs_entity(world, { .id = id, .name = name }); ecs_assert(id != 0, ECS_INVALID_OPERATION, name); ecs_set_scope(world, prev); #ifdef FLECS_DEBUG const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, "enum component must have 32bit size"); #endif #ifdef FLECS_META ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), sizeof(ecs_i32_t), &value); #endif flecs_resume_readonly(world, &readonly_state); ecs_trace("#[green]constant#[reset] %s.%s created with value %d", ecs_get_name(world, parent), name, value); return id; } static int32_t flecs_reset_count = 0; int32_t ecs_cpp_reset_count_get(void) { return flecs_reset_count; } int32_t ecs_cpp_reset_count_inc(void) { return ++flecs_reset_count; } #ifdef FLECS_META const ecs_member_t* ecs_cpp_last_member( const ecs_world_t *world, ecs_entity_t type) { const EcsStruct *st = ecs_get(world, type, EcsStruct); if (!st) { char *type_str = ecs_get_path(world, type); ecs_err("entity '%s' is not a struct", type_str); ecs_os_free(type_str); return 0; } ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, ecs_vec_count(&st->members) - 1); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); return m; } #endif #endif /** * @file addons/http.c * @brief HTTP addon. * * This is a heavily modified version of the EmbeddableWebServer (see copyright * below). This version has been stripped from everything not strictly necessary * for receiving/replying to simple HTTP requests, and has been modified to use * the Flecs OS API. * * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and * CONTRIBUTORS (see below) - All rights reserved. * * CONTRIBUTORS: * Martin Pulec - bug fixes, warning fixes, IPv6 support * Daniel Barry - bug fix (ifa_addr != NULL) * * Released under the BSD 2-clause license: * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. THIS SOFTWARE IS * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef FLECS_HTTP #ifdef ECS_TARGET_MSVC #pragma comment(lib, "Ws2_32.lib") #endif #if defined(ECS_TARGET_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include typedef SOCKET ecs_http_socket_t; #else #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif typedef int ecs_http_socket_t; #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL (0) #endif #endif /* Max length of request method */ #define ECS_HTTP_METHOD_LEN_MAX (8) /* Timeout (s) before connection purge */ #define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) /* Number of dequeues before purging */ #define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) /* Number of retries receiving request */ #define ECS_HTTP_REQUEST_RECV_RETRY (10) /* Minimum interval between dequeueing requests (ms) */ #define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) /* Minimum interval between printing statistics (ms) */ #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Receive buffer size */ #define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) /* Global statistics */ int64_t ecs_http_request_received_count = 0; int64_t ecs_http_request_invalid_count = 0; int64_t ecs_http_request_handled_ok_count = 0; int64_t ecs_http_request_handled_error_count = 0; int64_t ecs_http_request_not_handled_count = 0; int64_t ecs_http_request_preflight_count = 0; int64_t ecs_http_send_ok_count = 0; int64_t ecs_http_send_error_count = 0; int64_t ecs_http_busy_count = 0; /* Send request queue */ typedef struct ecs_http_send_request_t { ecs_http_socket_t sock; char *headers; int32_t header_length; char *content; int32_t content_length; } ecs_http_send_request_t; typedef struct ecs_http_send_queue_t { ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; int32_t head; int32_t tail; ecs_os_thread_t thread; int32_t wait_ms; } ecs_http_send_queue_t; typedef struct ecs_http_request_key_t { const char *array; ecs_size_t count; } ecs_http_request_key_t; typedef struct ecs_http_request_entry_t { char *content; int32_t content_length; int code; double time; } ecs_http_request_entry_t; /* HTTP server struct */ struct ecs_http_server_t { bool should_run; bool running; ecs_http_socket_t sock; ecs_os_mutex_t lock; ecs_os_thread_t thread; ecs_http_reply_action_t callback; void *ctx; double cache_timeout; double cache_purge_timeout; ecs_sparse_t connections; /* sparse */ ecs_sparse_t requests; /* sparse */ bool initialized; uint16_t port; const char *ipaddr; double dequeue_timeout; /* used to not lock request queue too often */ double stats_timeout; /* used for periodic reporting of statistics */ double request_time; /* time spent on requests in last stats interval */ double request_time_total; /* total time spent on requests */ int32_t requests_processed; /* requests processed in last stats interval */ int32_t requests_processed_total; /* total requests processed */ int32_t dequeue_count; /* number of dequeues in last stats interval */ ecs_http_send_queue_t send_queue; ecs_hashmap_t request_cache; }; /** Fragment state, used by HTTP request parser */ typedef enum { HttpFragStateBegin, HttpFragStateMethod, HttpFragStatePath, HttpFragStateVersion, HttpFragStateHeaderStart, HttpFragStateHeaderName, HttpFragStateHeaderValueStart, HttpFragStateHeaderValue, HttpFragStateCR, HttpFragStateCRLF, HttpFragStateCRLFCR, HttpFragStateBody, HttpFragStateDone } HttpFragState; /** A fragment is a partially received HTTP request */ typedef struct { HttpFragState state; ecs_strbuf_t buf; ecs_http_method_t method; int32_t body_offset; int32_t query_offset; int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; int32_t header_count; int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; int32_t param_count; int32_t content_length; char *header_buf_ptr; char header_buf[32]; bool parse_content_length; bool invalid; } ecs_http_fragment_t; /** Extend public connection type with fragment data */ typedef struct { ecs_http_connection_t pub; ecs_http_socket_t sock; /* Connection is purged after both timeout expires and connection has * exceeded retry count. This ensures that a connection does not immediately * timeout when a frame takes longer than usual */ double dequeue_timeout; int32_t dequeue_retries; } ecs_http_connection_impl_t; typedef struct { ecs_http_request_t pub; uint64_t conn_id; /* for sanity check */ char *res; int32_t req_len; } ecs_http_request_impl_t; static ecs_size_t http_send( ecs_http_socket_t sock, const void *buf, ecs_size_t size, int flags) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); #ifdef ECS_TARGET_POSIX ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags | MSG_NOSIGNAL); return flecs_itoi32(send_bytes); #else int send_bytes = send(sock, buf, size, flags); return flecs_itoi32(send_bytes); #endif } static ecs_size_t http_recv( ecs_http_socket_t sock, void *buf, ecs_size_t size, int flags) { ecs_size_t ret; #ifdef ECS_TARGET_POSIX ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); ret = flecs_itoi32(recv_bytes); #else int recv_bytes = recv(sock, buf, size, flags); ret = flecs_itoi32(recv_bytes); #endif if (ret == -1) { ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); } else if (ret == 0) { ecs_dbg("recv: received 0 bytes (sock = %d)", sock); } return ret; } static void http_sock_set_timeout( ecs_http_socket_t sock, int32_t timeout_ms) { int r; #ifdef ECS_TARGET_POSIX struct timeval tv; tv.tv_sec = timeout_ms * 1000; tv.tv_usec = 0; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); #else DWORD t = (DWORD)timeout_ms; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); #endif if (r) { ecs_warn("http: failed to set socket timeout: %s", ecs_os_strerror(errno)); } } static void http_sock_keep_alive( ecs_http_socket_t sock) { int v = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { ecs_warn("http: failed to set socket KEEPALIVE: %s", ecs_os_strerror(errno)); } } static void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { (void)sock; (void)enable; #ifdef ECS_TARGET_POSIX int flags; flags = fcntl(sock,F_GETFL,0); if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } if (enable) { flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); } else { flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); } if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } #endif } static int http_getnameinfo( const struct sockaddr* addr, ecs_size_t addr_len, char *host, ecs_size_t host_len, char *port, ecs_size_t port_len, int flags) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return getnameinfo(addr, addr_len, host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #else return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #endif } static int http_bind( ecs_http_socket_t sock, const struct sockaddr* addr, ecs_size_t addr_len) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return bind(sock, addr, addr_len); #else return bind(sock, addr, flecs_ito(uint32_t, addr_len)); #endif } static bool http_socket_is_valid( ecs_http_socket_t sock) { #if defined(ECS_TARGET_WINDOWS) return sock != INVALID_SOCKET; #else return sock >= 0; #endif } #if defined(ECS_TARGET_WINDOWS) #define HTTP_SOCKET_INVALID INVALID_SOCKET #else #define HTTP_SOCKET_INVALID (-1) #endif static void http_close( ecs_http_socket_t *sock) { ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) closesocket(*sock); #else ecs_dbg_2("http: closing socket %u", *sock); shutdown(*sock, SHUT_RDWR); close(*sock); #endif *sock = HTTP_SOCKET_INVALID; } static ecs_http_socket_t http_accept( ecs_http_socket_t sock, struct sockaddr* addr, ecs_size_t *addr_len) { socklen_t len = (socklen_t)addr_len[0]; ecs_http_socket_t result = accept(sock, addr, &len); addr_len[0] = (ecs_size_t)len; return result; } static void http_reply_fini(ecs_http_reply_t* reply) { ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_free(reply->body.content); } static void http_request_fini(ecs_http_request_impl_t *req) { ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); ecs_os_free(req->res); flecs_sparse_remove_t(&req->pub.conn->server->requests, ecs_http_request_impl_t, req->pub.id); } static void http_connection_free(ecs_http_connection_impl_t *conn) { ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); uint64_t conn_id = conn->pub.id; if (http_socket_is_valid(conn->sock)) { http_close(&conn->sock); } flecs_sparse_remove_t(&conn->pub.server->connections, ecs_http_connection_impl_t, conn_id); } // https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int static char http_hex_2_int(char a, char b){ a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); return (char)((a << 4) + b); } static void http_decode_url_str( char *str) { char ch, *ptr, *dst = str; for (ptr = str; (ch = *ptr); ptr++) { if (ch == '%') { dst[0] = http_hex_2_int(ptr[1], ptr[2]); dst ++; ptr += 2; } else { dst[0] = ptr[0]; dst ++; } } dst[0] = '\0'; } static void http_parse_method( ecs_http_fragment_t *frag) { char *method = ecs_strbuf_get_small(&frag->buf); if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; else { frag->method = EcsHttpMethodUnsupported; frag->invalid = true; } ecs_strbuf_reset(&frag->buf); } static bool http_header_writable( ecs_http_fragment_t *frag) { return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; } static void http_header_buf_reset( ecs_http_fragment_t *frag) { frag->header_buf[0] = '\0'; frag->header_buf_ptr = frag->header_buf; } static void http_header_buf_append( ecs_http_fragment_t *frag, char ch) { if ((frag->header_buf_ptr - frag->header_buf) < ECS_SIZEOF(frag->header_buf)) { frag->header_buf_ptr[0] = ch; frag->header_buf_ptr ++; } else { frag->header_buf_ptr[0] = '\0'; } } static uint64_t http_request_key_hash(const void *ptr) { const ecs_http_request_key_t *key = ptr; const char *array = key->array; int32_t count = key->count; return flecs_hash(array, count * ECS_SIZEOF(char)); } static int http_request_key_compare(const void *ptr_1, const void *ptr_2) { const ecs_http_request_key_t *type_1 = ptr_1; const ecs_http_request_key_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } return ecs_os_memcmp(type_1->array, type_2->array, count_1); } static ecs_http_request_entry_t* http_find_request_entry( ecs_http_server_t *srv, const char *array, int32_t count) { ecs_http_request_key_t key; key.array = array; key.count = count; ecs_time_t t = {0, 0}; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (entry) { double tf = ecs_time_measure(&t); if ((tf - entry->time) < srv->cache_timeout) { return entry; } } return NULL; } static void http_insert_request_entry( ecs_http_server_t *srv, ecs_http_request_impl_t *req, ecs_http_reply_t *reply) { int32_t content_length = ecs_strbuf_written(&reply->body); if (!content_length) { return; } ecs_http_request_key_t key; key.array = req->res; key.count = req->req_len; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (!entry) { flecs_hashmap_result_t elem = flecs_hashmap_ensure( &srv->request_cache, &key, ecs_http_request_entry_t); ecs_http_request_key_t *elem_key = elem.key; elem_key->array = ecs_os_memdup_n(key.array, char, key.count); entry = elem.value; } else { ecs_os_free(entry->content); } ecs_time_t t = {0, 0}; entry->time = ecs_time_measure(&t); entry->content_length = ecs_strbuf_written(&reply->body); entry->content = ecs_strbuf_get(&reply->body); entry->code = reply->code; ecs_strbuf_appendstrn(&reply->body, entry->content, entry->content_length); } static char* http_decode_request( ecs_http_request_impl_t *req, ecs_http_fragment_t *frag) { ecs_os_zeromem(req); ecs_size_t req_len = frag->buf.length; char *res = ecs_strbuf_get(&frag->buf); if (!res) { return NULL; } req->pub.method = frag->method; req->pub.path = res + 1; http_decode_url_str(req->pub.path); if (frag->body_offset) { req->pub.body = &res[frag->body_offset]; } int32_t i, count = frag->header_count; for (i = 0; i < count; i ++) { req->pub.headers[i].key = &res[frag->header_offsets[i]]; req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; } count = frag->param_count; for (i = 0; i < count; i ++) { req->pub.params[i].key = &res[frag->param_offsets[i]]; req->pub.params[i].value = &res[frag->param_value_offsets[i]]; /* Safe, member is only const so that end-user can't change it */ http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); } req->pub.header_count = frag->header_count; req->pub.param_count = frag->param_count; req->res = res; req->req_len = frag->header_offsets[0]; if (!req->req_len) { req->req_len = req_len; } return res; } static ecs_http_request_entry_t* http_enqueue_request( ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_fragment_t *frag) { ecs_http_server_t *srv = conn->pub.server; ecs_os_mutex_lock(srv->lock); bool is_alive = conn->pub.id == conn_id; if (!is_alive || frag->invalid) { /* Don't enqueue invalid requests or requests for purged connections */ ecs_strbuf_reset(&frag->buf); } else { ecs_http_request_impl_t req; char *res = http_decode_request(&req, frag); if (res) { req.pub.conn = (ecs_http_connection_t*)conn; /* Check cache for GET requests */ if (frag->method == EcsHttpGet) { ecs_http_request_entry_t *entry = http_find_request_entry(srv, res, frag->header_offsets[0]); if (entry) { /* If an entry is found, don't enqueue a request. Instead * return the cached response immediately. */ ecs_os_free(res); return entry; } } ecs_http_request_impl_t *req_ptr = flecs_sparse_add_t( &srv->requests, ecs_http_request_impl_t); *req_ptr = req; req_ptr->pub.id = flecs_sparse_last_id(&srv->requests); req_ptr->conn_id = conn->pub.id; ecs_os_linc(&ecs_http_request_received_count); } } ecs_os_mutex_unlock(srv->lock); return NULL; } static bool http_parse_request( ecs_http_fragment_t *frag, const char* req_frag, ecs_size_t req_frag_len) { int32_t i; for (i = 0; i < req_frag_len; i++) { char c = req_frag[i]; switch (frag->state) { case HttpFragStateBegin: ecs_os_memset_t(frag, 0, ecs_http_fragment_t); frag->state = HttpFragStateMethod; frag->header_buf_ptr = frag->header_buf; /* fall through */ case HttpFragStateMethod: if (c == ' ') { http_parse_method(frag); ecs_strbuf_reset(&frag->buf); frag->state = HttpFragStatePath; frag->buf.content = NULL; } else { ecs_strbuf_appendch(&frag->buf, c); } break; case HttpFragStatePath: if (c == ' ') { frag->state = HttpFragStateVersion; ecs_strbuf_appendch(&frag->buf, '\0'); } else { if (c == '?' || c == '=' || c == '&') { ecs_strbuf_appendch(&frag->buf, '\0'); int32_t offset = ecs_strbuf_written(&frag->buf); if (c == '?' || c == '&') { frag->param_offsets[frag->param_count] = offset; } else { frag->param_value_offsets[frag->param_count] = offset; frag->param_count ++; } } else { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateVersion: if (c == '\r') { frag->state = HttpFragStateCR; } /* version is not stored */ break; case HttpFragStateHeaderStart: if (http_header_writable(frag)) { frag->header_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } http_header_buf_reset(frag); frag->state = HttpFragStateHeaderName; /* fall through */ case HttpFragStateHeaderName: if (c == ':') { frag->state = HttpFragStateHeaderValueStart; http_header_buf_append(frag, '\0'); frag->parse_content_length = !ecs_os_strcmp( frag->header_buf, "Content-Length"); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_value_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } } else if (c == '\r') { frag->state = HttpFragStateCR; } else { http_header_buf_append(frag, c); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateHeaderValueStart: http_header_buf_reset(frag); frag->state = HttpFragStateHeaderValue; if (c == ' ') { /* skip first space */ break; } /* fall through */ case HttpFragStateHeaderValue: if (c == '\r') { if (frag->parse_content_length) { http_header_buf_append(frag, '\0'); int32_t len = atoi(frag->header_buf); if (len < 0) { frag->invalid = true; } else { frag->content_length = len; } frag->parse_content_length = false; } if (http_header_writable(frag)) { int32_t cur = ecs_strbuf_written(&frag->buf); if (frag->header_offsets[frag->header_count] < cur && frag->header_value_offsets[frag->header_count] < cur) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_count ++; } } frag->state = HttpFragStateCR; } else { if (frag->parse_content_length) { http_header_buf_append(frag, c); } if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateCR: if (c == '\n') { frag->state = HttpFragStateCRLF; } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateCRLF: if (c == '\r') { frag->state = HttpFragStateCRLFCR; } else { frag->state = HttpFragStateHeaderStart; i--; } break; case HttpFragStateCRLFCR: if (c == '\n') { if (frag->content_length != 0) { frag->body_offset = ecs_strbuf_written(&frag->buf); frag->state = HttpFragStateBody; } else { frag->state = HttpFragStateDone; } } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateBody: { ecs_strbuf_appendch(&frag->buf, c); if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == frag->content_length) { frag->state = HttpFragStateDone; } } break; case HttpFragStateDone: break; } } if (frag->state == HttpFragStateDone) { return true; } else { return false; } } static ecs_http_send_request_t* http_send_queue_post( ecs_http_server_t *srv) { /* This function should only be called while the server is locked. Before * the lock is released, the returned element should be populated. */ ecs_http_send_queue_t *sq = &srv->send_queue; int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; if (next == sq->tail) { return NULL; } /* Don't enqueue new requests if server is shutting down */ if (!srv->should_run) { return NULL; } /* Return element at end of the queue */ ecs_http_send_request_t *result = &sq->requests[sq->head]; sq->head = next; return result; } static ecs_http_send_request_t* http_send_queue_get( ecs_http_server_t *srv) { ecs_os_mutex_lock(srv->lock); ecs_http_send_queue_t *sq = &srv->send_queue; if (sq->tail == sq->head) { return NULL; } int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; ecs_http_send_request_t *result = &sq->requests[sq->tail]; sq->tail = next; return result; } static void* http_server_send_queue(void* arg) { ecs_http_server_t *srv = arg; int32_t wait_ms = srv->send_queue.wait_ms; /* Run for as long as the server is running or there are messages. When the * server is stopping, no new messages will be enqueued */ while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { ecs_http_send_request_t* r = http_send_queue_get(srv); if (!r) { ecs_os_mutex_unlock(srv->lock); /* If the queue is empty, wait so we don't run too fast */ if (srv->should_run) { ecs_os_sleep(0, wait_ms * 1000 * 1000); } } else { ecs_http_socket_t sock = r->sock; char *headers = r->headers; int32_t headers_length = r->header_length; char *content = r->content; int32_t content_length = r->content_length; ecs_os_mutex_unlock(srv->lock); if (http_socket_is_valid(sock)) { bool error = false; http_sock_nonblock(sock, false); /* Write headers */ ecs_size_t written = http_send(sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to write HTTP response headers: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } else if (content_length >= 0) { /* Write content */ written = http_send(sock, content, content_length, 0); if (written != content_length) { ecs_err("http: failed to write HTTP response body: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } } if (!error) { ecs_os_linc(&ecs_http_send_ok_count); } http_close(&sock); } else { ecs_err("http: invalid socket\n"); } ecs_os_free(content); ecs_os_free(headers); } } return NULL; } static void http_append_send_headers( ecs_strbuf_t *hdrs, int code, const char* status, const char* content_type, ecs_strbuf_t *extra_headers, ecs_size_t content_len, bool preflight) { ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); ecs_strbuf_appendint(hdrs, code); ecs_strbuf_appendch(hdrs, ' '); ecs_strbuf_appendstr(hdrs, status); ecs_strbuf_appendlit(hdrs, "\r\n"); if (content_type) { ecs_strbuf_appendlit(hdrs, "Content-Type: "); ecs_strbuf_appendstr(hdrs, content_type); ecs_strbuf_appendlit(hdrs, "\r\n"); } if (content_len >= 0) { ecs_strbuf_appendlit(hdrs, "Content-Length: "); ecs_strbuf_append(hdrs, "%d", content_len); ecs_strbuf_appendlit(hdrs, "\r\n"); } ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); if (preflight) { ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, DELETE, OPTIONS\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); } ecs_strbuf_mergebuff(hdrs, extra_headers); ecs_strbuf_appendlit(hdrs, "\r\n"); } static void http_send_reply( ecs_http_connection_impl_t* conn, ecs_http_reply_t* reply, bool preflight) { ecs_strbuf_t hdrs = ECS_STRBUF_INIT; int32_t content_length = reply->body.length; char *content = ecs_strbuf_get(&reply->body); /* Use asynchronous send queue for outgoing data so send operations won't * hold up main thread */ ecs_http_send_request_t *req = NULL; if (!preflight) { req = http_send_queue_post(conn->pub.server); if (!req) { reply->code = 503; /* queue full, server is busy */ ecs_os_linc(&ecs_http_busy_count); } } http_append_send_headers(&hdrs, reply->code, reply->status, reply->content_type, &reply->headers, content_length, preflight); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); char *headers = ecs_strbuf_get(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* Second, enqueue send request for response body */ req->sock = conn->sock; req->headers = headers; req->header_length = headers_length; req->content = content; req->content_length = content_length; /* Take ownership of values */ reply->body.content = NULL; conn->sock = HTTP_SOCKET_INVALID; } static void http_recv_connection( ecs_http_server_t *srv, ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_socket_t sock) { ecs_size_t bytes_read; char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; ecs_http_fragment_t frag = {0}; int32_t retries = 0; do { if ((bytes_read = http_recv( sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { /* Connection has been purged by main thread */ goto done; } if (http_parse_request(&frag, recv_buf, bytes_read)) { if (frag.method == EcsHttpOptions) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = 200; reply.content_type = NULL; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; http_send_reply(conn, &reply, true); ecs_os_linc(&ecs_http_request_preflight_count); } else { ecs_http_request_entry_t *entry = http_enqueue_request(conn, conn_id, &frag); if (entry) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = entry->code; reply.content_type = "application/json"; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; ecs_strbuf_appendstrn(&reply.body, entry->content, entry->content_length); http_send_reply(conn, &reply, false); http_connection_free(conn); /* Lock was transferred from enqueue_request */ ecs_os_mutex_unlock(srv->lock); } } } else { ecs_os_linc(&ecs_http_request_invalid_count); } } ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { http_close(&sock); } done: ecs_strbuf_reset(&frag.buf); } typedef struct { ecs_http_connection_impl_t *conn; uint64_t id; } http_conn_res_t; static http_conn_res_t http_init_connection( ecs_http_server_t *srv, ecs_http_socket_t sock_conn, struct sockaddr_storage *remote_addr, ecs_size_t remote_addr_len) { http_sock_set_timeout(sock_conn, 100); http_sock_keep_alive(sock_conn); http_sock_nonblock(sock_conn, true); /* Create new connection */ ecs_os_mutex_lock(srv->lock); ecs_http_connection_impl_t *conn = flecs_sparse_add_t( &srv->connections, ecs_http_connection_impl_t); uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); conn->pub.server = srv; conn->sock = sock_conn; ecs_os_mutex_unlock(srv->lock); char *remote_host = conn->pub.host; char *remote_port = conn->pub.port; /* Fetch name & port info */ if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, remote_host, ECS_SIZEOF(conn->pub.host), remote_port, ECS_SIZEOF(conn->pub.port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(remote_host, "unknown"); ecs_os_strcpy(remote_port, "unknown"); } ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", remote_host, remote_port, sock_conn); return (http_conn_res_t){ .conn = conn, .id = conn_id }; } static void http_accept_connections( ecs_http_server_t* srv, const struct sockaddr* addr, ecs_size_t addr_len) { #ifdef ECS_TARGET_WINDOWS /* If on Windows, test if winsock needs to be initialized */ SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ WSADATA data = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &data); if (result) { ecs_warn("http: WSAStartup failed with GetLastError = %d\n", GetLastError()); return; } } else { http_close(&testsocket); } #endif /* Resolve name + port (used for logging) */ char addr_host[256]; char addr_port[20]; ecs_http_socket_t sock = HTTP_SOCKET_INVALID; ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); if (http_getnameinfo( addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(addr_host, "unknown"); ecs_os_strcpy(addr_port, "unknown"); } ecs_os_mutex_lock(srv->lock); if (srv->should_run) { ecs_dbg_2("http: initializing connection socket"); sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); if (!http_socket_is_valid(sock)) { ecs_err("http: unable to create new connection socket: %s", ecs_os_strerror(errno)); ecs_os_mutex_unlock(srv->lock); goto done; } int reuse = 1, result; result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, ECS_SIZEOF(reuse)); if (result) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } if (addr->sa_family == AF_INET6) { int ipv6only = 0; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, ECS_SIZEOF(ipv6only))) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } } result = http_bind(sock, addr, addr_len); if (result) { ecs_err("http: failed to bind to '%s:%s': %s", addr_host, addr_port, ecs_os_strerror(errno)); ecs_os_mutex_unlock(srv->lock); goto done; } http_sock_set_timeout(sock, 1000); srv->sock = sock; result = listen(srv->sock, SOMAXCONN); if (result) { ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", SOMAXCONN, ecs_os_strerror(errno)); } ecs_trace("http: listening for incoming connections on '%s:%s'", addr_host, addr_port); } else { ecs_dbg_2("http: server shut down while initializing"); } ecs_os_mutex_unlock(srv->lock); struct sockaddr_storage remote_addr; ecs_size_t remote_addr_len = 0; while (srv->should_run) { remote_addr_len = ECS_SIZEOF(remote_addr); ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, &remote_addr_len); if (!http_socket_is_valid(sock_conn)) { if (srv->should_run) { ecs_dbg("http: connection attempt failed: %s", ecs_os_strerror(errno)); } continue; } http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); http_recv_connection(srv, conn.conn, conn.id, sock_conn); } done: ecs_os_mutex_lock(srv->lock); if (http_socket_is_valid(sock) && errno != EBADF) { http_close(&sock); srv->sock = sock; } ecs_os_mutex_unlock(srv->lock); ecs_trace("http: no longer accepting connections on '%s:%s'", addr_host, addr_port); } static void* http_server_thread(void* arg) { ecs_http_server_t *srv = arg; struct sockaddr_in addr; ecs_os_zeromem(&addr); addr.sin_family = AF_INET; addr.sin_port = htons(srv->port); if (!srv->ipaddr) { addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } http_accept_connections(srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)); return NULL; } static void http_do_request( ecs_http_server_t *srv, ecs_http_reply_t *reply, const ecs_http_request_impl_t *req) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, "missing request handler for server"); if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { reply->code = 404; reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply->code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } error: return; } static void http_handle_request( ecs_http_server_t *srv, ecs_http_request_impl_t *req) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; if (req->pub.method != EcsHttpOptions) { if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { reply.code = 404; reply.status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply.code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } if (req->pub.method == EcsHttpGet) { http_insert_request_entry(srv, req, &reply); } http_send_reply(conn, &reply, false); ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); } else { /* Already taken care of */ } http_reply_fini(&reply); http_request_fini(req); http_connection_free(conn); } static void http_purge_request_cache( ecs_http_server_t *srv, bool fini) { ecs_time_t t = {0, 0}; double time = ecs_time_measure(&t); ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&bucket->values); ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); for (i = count - 1; i >= 0; i --) { ecs_http_request_entry_t *entry = &entries[i]; if (fini || ((time - entry->time) > srv->cache_purge_timeout)) { ecs_http_request_key_t *key = &keys[i]; /* Safe, code owns the value */ ecs_os_free(ECS_CONST_CAST(char*, key->array)); ecs_os_free(entry->content); flecs_hm_bucket_remove(&srv->request_cache, bucket, ecs_map_key(&it), i); } } } if (fini) { flecs_hashmap_fini(&srv->request_cache); } } static int32_t http_dequeue_requests( ecs_http_server_t *srv, double delta_time) { ecs_os_mutex_lock(srv->lock); int32_t i, request_count = flecs_sparse_count(&srv->requests); for (i = request_count - 1; i >= 1; i --) { ecs_http_request_impl_t *req = flecs_sparse_get_dense_t( &srv->requests, ecs_http_request_impl_t, i); http_handle_request(srv, req); } int32_t connections_count = flecs_sparse_count(&srv->connections); for (i = connections_count - 1; i >= 1; i --) { ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i); conn->dequeue_timeout += delta_time; conn->dequeue_retries ++; if ((conn->dequeue_timeout > (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) { ecs_dbg("http: purging connection '%s:%s' (sock = %d)", conn->pub.host, conn->pub.port, conn->sock); http_connection_free(conn); } } http_purge_request_cache(srv, false); ecs_os_mutex_unlock(srv->lock); return request_count - 1; } const char* ecs_http_get_header( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->header_count; i++) { if (!ecs_os_strcmp(req->headers[i].key, name)) { return req->headers[i].value; } } return NULL; } const char* ecs_http_get_param( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->param_count; i++) { if (!ecs_os_strcmp(req->params[i].key, name)) { return req->params[i].value; } } return NULL; } ecs_http_server_t* ecs_http_server_init( const ecs_http_server_desc_t *desc) { ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); ecs_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); srv->lock = ecs_os_mutex_new(); srv->sock = HTTP_SOCKET_INVALID; srv->should_run = false; srv->initialized = true; srv->cache_timeout = desc->cache_timeout; srv->cache_purge_timeout = desc->cache_purge_timeout; if (!ECS_EQZERO(srv->cache_timeout) && ECS_EQZERO(srv->cache_purge_timeout)) { srv->cache_purge_timeout = srv->cache_timeout * 10; } srv->callback = desc->callback; srv->ctx = desc->ctx; srv->port = desc->port; srv->ipaddr = desc->ipaddr; srv->send_queue.wait_ms = desc->send_queue_wait_ms; if (!srv->send_queue.wait_ms) { srv->send_queue.wait_ms = 1; } flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); flecs_sparse_init_t(&srv->requests, NULL, NULL, ecs_http_request_impl_t); /* Start at id 1 */ flecs_sparse_new_id(&srv->connections); flecs_sparse_new_id(&srv->requests); /* Initialize request cache */ flecs_hashmap_init(&srv->request_cache, ecs_http_request_key_t, ecs_http_request_entry_t, http_request_key_hash, http_request_key_compare, NULL); #ifndef ECS_TARGET_WINDOWS /* Ignore pipe signal. SIGPIPE can occur when a message is sent to a client * but te client already disconnected. */ signal(SIGPIPE, SIG_IGN); #endif return srv; error: return NULL; } void ecs_http_server_fini( ecs_http_server_t* srv) { if (srv->should_run) { ecs_http_server_stop(srv); } ecs_os_mutex_free(srv->lock); http_purge_request_cache(srv, true); flecs_sparse_fini(&srv->requests); flecs_sparse_fini(&srv->connections); ecs_os_free(srv); } int ecs_http_server_start( ecs_http_server_t *srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(!srv->should_run, ECS_INVALID_PARAMETER, NULL); ecs_check(!srv->thread, ECS_INVALID_PARAMETER, NULL); srv->should_run = true; ecs_dbg("http: starting server thread"); srv->thread = ecs_os_thread_new(http_server_thread, srv); if (!srv->thread) { goto error; } srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); if (!srv->send_queue.thread) { goto error; } return 0; error: return -1; } void ecs_http_server_stop( ecs_http_server_t* srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_OPERATION, "cannot stop HTTP server: not initialized"); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, "cannot stop HTTP server: already stopped/stopping"); /* Stop server thread */ ecs_dbg("http: shutting down server thread"); ecs_os_mutex_lock(srv->lock); srv->should_run = false; if (http_socket_is_valid(srv->sock)) { http_close(&srv->sock); } ecs_os_mutex_unlock(srv->lock); ecs_os_thread_join(srv->thread); ecs_os_thread_join(srv->send_queue.thread); ecs_trace("http: server threads shut down"); /* Cleanup all outstanding requests */ int i, count = flecs_sparse_count(&srv->requests); for (i = count - 1; i >= 1; i --) { http_request_fini(flecs_sparse_get_dense_t( &srv->requests, ecs_http_request_impl_t, i)); } /* Close all connections */ count = flecs_sparse_count(&srv->connections); for (i = count - 1; i >= 1; i --) { http_connection_free(flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i)); } ecs_assert(flecs_sparse_count(&srv->connections) == 1, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_sparse_count(&srv->requests) == 1, ECS_INTERNAL_ERROR, NULL); srv->thread = 0; error: return; } void ecs_http_server_dequeue( ecs_http_server_t* srv, ecs_ftime_t delta_time) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); srv->dequeue_timeout += (double)delta_time; srv->stats_timeout += (double)delta_time; if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { srv->dequeue_timeout = 0; ecs_time_t t = {0}; ecs_time_measure(&t); int32_t request_count = http_dequeue_requests(srv, srv->dequeue_timeout); srv->requests_processed += request_count; srv->requests_processed_total += request_count; double time_spent = ecs_time_measure(&t); srv->request_time += time_spent; srv->request_time_total += time_spent; srv->dequeue_count ++; } if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { srv->stats_timeout = 0; ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", srv->requests_processed, srv->request_time, (srv->request_time / (double)srv->dequeue_count)); srv->requests_processed = 0; srv->request_time = 0; srv->dequeue_count = 0; } error: return; } int ecs_http_server_http_request( ecs_http_server_t* srv, const char *req, ecs_size_t len, ecs_http_reply_t *reply_out) { if (!len) { len = ecs_os_strlen(req); } ecs_http_fragment_t frag = {0}; if (!http_parse_request(&frag, req, len)) { ecs_strbuf_reset(&frag.buf); reply_out->code = 400; return -1; } ecs_http_request_impl_t request; char *res = http_decode_request(&request, &frag); if (!res) { reply_out->code = 400; return -1; } ecs_http_request_entry_t *entry = http_find_request_entry(srv, request.res, request.req_len); if (entry) { reply_out->body = ECS_STRBUF_INIT; reply_out->code = entry->code; reply_out->content_type = "application/json"; reply_out->headers = ECS_STRBUF_INIT; reply_out->status = "OK"; ecs_strbuf_appendstrn(&reply_out->body, entry->content, entry->content_length); } else { http_do_request(srv, reply_out, &request); if (request.pub.method == EcsHttpGet) { http_insert_request_entry(srv, &request, reply_out); } } ecs_os_free(res); http_purge_request_cache(srv, false); return (reply_out->code >= 400) ? -1 : 0; } int ecs_http_server_request( ecs_http_server_t* srv, const char *method, const char *req, ecs_http_reply_t *reply_out) { const char *http_ver = " HTTP/1.1\r\n\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); int32_t http_ver_len = ecs_os_strlen(http_ver); char reqbuf[1024], *reqstr = reqbuf; int32_t len = method_len + req_len + http_ver_len + 1; if (method_len + req_len + http_ver_len >= 1024) { reqstr = ecs_os_malloc(len + 1); } char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; ptr[0] = '\n'; int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); if (reqbuf != reqstr) { ecs_os_free(reqstr); } return result; } void* ecs_http_server_ctx( ecs_http_server_t* srv) { return srv->ctx; } #endif /** * @file addons/journal.c * @brief Journal addon. */ #ifdef FLECS_JOURNAL static char* flecs_journal_entitystr( ecs_world_t *world, ecs_entity_t entity) { char *path; const char *_path = ecs_get_symbol(world, entity); if (_path && !strchr(_path, '.')) { path = flecs_asprintf("#[blue]%s", _path); } else { uint32_t gen = entity >> 32; if (gen) { path = flecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); } else { path = flecs_asprintf("#[normal]_%u", (uint32_t)entity); } } return path; } static char* flecs_journal_idstr( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *second_path = flecs_journal_entitystr(world, ecs_pair_second(world, id)); char *result = flecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", first_path, second_path); ecs_os_free(first_path); ecs_os_free(second_path); return result; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_journal_entitystr(world, id); } else { return ecs_id_str(world, id); } } static int flecs_journal_sp = 0; void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove) { flecs_journal_sp ++; if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { return; } char *path = NULL; char *var_id = NULL; if (entity) { if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { path = ecs_get_path(world, entity); var_id = flecs_journal_entitystr(world, entity); } else { path = ecs_id_str(world, entity); var_id = flecs_journal_idstr(world, entity); } } if (kind == EcsJournalNew) { ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); ecs_print(4, "#[green]ecs_entity_t %s;", var_id); ecs_print(4, "#[magenta]#endif"); ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " "#[grey] // %s = new()", var_id, path); } if (add) { for (int i = 0; i < add->count; i ++) { char *jidstr = flecs_journal_idstr(world, add->array[i]); char *idstr = ecs_id_str(world, add->array[i]); ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " "#[grey] // add(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (remove) { for (int i = 0; i < remove->count; i ++) { char *jidstr = flecs_journal_idstr(world, remove->array[i]); char *idstr = ecs_id_str(world, remove->array[i]); ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " "#[grey] // remove(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (kind == EcsJournalClear) { ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " "#[grey] // clear(%s)", var_id, path); } else if (kind == EcsJournalDelete) { ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " "#[grey] // delete(%s)", var_id, path); } else if (kind == EcsJournalDeleteWith) { ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " "#[grey] // delete_with(%s)", var_id, path); } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); } else if (kind == EcsJournalTableEvents) { ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); ecs_log_push(); } void flecs_journal_end(void) { flecs_journal_sp --; ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } #endif /** * @file addons/log.c * @brief Log addon. */ #ifdef FLECS_LOG #include void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf) { ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); char *ptr, ch, prev = '\0'; bool isNum = false; char isStr = '\0'; bool isVar = false; bool overrideColor = false; bool autoColor = true; bool dontAppend = false; for (ptr = msg; (ch = *ptr); ptr++) { dontAppend = false; if (!overrideColor) { if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isNum = false; } if (isStr && (isStr == ch) && prev != '\\') { isStr = '\0'; } else if (((ch == '\'') || (ch == '"')) && !isStr && !isalpha(prev) && (prev != '\\')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isStr = ch; } if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && !isalpha(prev) && !isdigit(prev) && (prev != '_') && (prev != '.')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); isNum = true; } if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isVar = false; } if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isVar = true; } } if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { bool isColor = true; overrideColor = true; /* Custom colors */ if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { autoColor = false; } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { overrideColor = false; if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else { isColor = false; overrideColor = false; } if (isColor) { ptr += 2; while ((ch = *ptr) != ']') ptr ++; dontAppend = true; } if (!autoColor) { overrideColor = true; } } if (ch == '\n') { if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); overrideColor = false; isNum = false; isStr = false; isVar = false; } } if (!dontAppend) { ecs_strbuf_appendstrn(buf, ptr, 1); } if (!overrideColor) { if (((ch == '\'') || (ch == '"')) && !isStr) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } prev = ch; } if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } void ecs_printv_( int level, const char *file, int32_t line, const char *fmt, va_list args) { (void)level; (void)line; ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; /* Apply color. Even if we don't want color, we still need to call the * colorize function to get rid of the color tags (e.g. #[green]) */ char *msg_nocolor = flecs_vasprintf(fmt, args); flecs_colorize_buf(msg_nocolor, ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); ecs_os_free(msg_nocolor); char *msg = ecs_strbuf_get(&msg_buf); if (msg) { ecs_os_api.log_(level, file, line, msg); ecs_os_free(msg); } else { ecs_os_api.log_(level, file, line, ""); } } void ecs_print_( 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; /* Count number of newlines up until column_arg */ int32_t i, line = 1; if (expr) { for (i = 0; i < column; i ++) { if (expr[i] == '\n') { line ++; } } ecs_strbuf_append(&msg_buf, "%d: ", line); } ecs_strbuf_vappend(&msg_buf, fmt, args); if (expr) { ecs_strbuf_appendch(&msg_buf, '\n'); /* Find start of line by taking column and looking for the * last occurring newline */ if (column != -1) { const char *ptr = &expr[column]; if (ptr[0] == '\n') { ptr --; } while (ptr[0] != '\n' && ptr > expr) { ptr --; } if (ptr[0] == '\n') { ptr ++; } if (ptr == expr) { /* ptr is already at start of line */ } else { column -= (int32_t)(ptr - expr); expr = ptr; } } /* Strip newlines from current statement, if any */ char *newline_ptr = strchr(expr, '\n'); if (newline_ptr) { /* Strip newline from expr */ ecs_strbuf_appendstrn(&msg_buf, expr, (int32_t)(newline_ptr - expr)); } else { ecs_strbuf_appendstr(&msg_buf, expr); } ecs_strbuf_appendch(&msg_buf, '\n'); if (column != -1) { int32_t c; for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } ecs_strbuf_appendch(&msg_buf, '^'); } } char *msg = ecs_strbuf_get(&msg_buf); 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 = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "%s (%s)", msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "%s", ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } void ecs_assert_log_( int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "assert: %s %s (%s)", cond_str, msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "assert: %s %s", cond_str, ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } void ecs_deprecated_( const char *file, int32_t line, const char *msg) { ecs_err_(file, line, "%s", msg); } bool ecs_should_log(int32_t level) { # if !defined(FLECS_LOG_3) if (level == 3) { return false; } # endif # if !defined(FLECS_LOG_2) if (level == 2) { return false; } # endif # if !defined(FLECS_LOG_1) if (level == 1) { return false; } # endif return level <= ecs_os_api.log_level_; } #define ECS_ERR_STR(code) case code: return &(#code[4]) const char* ecs_strerror( int32_t error_code) { switch (error_code) { ECS_ERR_STR(ECS_INVALID_PARAMETER); ECS_ERR_STR(ECS_NOT_A_COMPONENT); ECS_ERR_STR(ECS_INTERNAL_ERROR); ECS_ERR_STR(ECS_ALREADY_DEFINED); ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); ECS_ERR_STR(ECS_NAME_IN_USE); ECS_ERR_STR(ECS_OUT_OF_MEMORY); ECS_ERR_STR(ECS_DOUBLE_FREE); ECS_ERR_STR(ECS_OPERATION_FAILED); ECS_ERR_STR(ECS_INVALID_CONVERSION); ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_LEAK_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); ECS_ERR_STR(ECS_INVALID_FROM_WORKER); ECS_ERR_STR(ECS_OUT_OF_RANGE); ECS_ERR_STR(ECS_MISSING_OS_API); ECS_ERR_STR(ECS_UNSUPPORTED); ECS_ERR_STR(ECS_ACCESS_VIOLATION); ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); ECS_ERR_STR(ECS_INCONSISTENT_NAME); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); ECS_ERR_STR(ECS_INVALID_OPERATION); ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); ECS_ERR_STR(ECS_LOCKED_STORAGE); ECS_ERR_STR(ECS_ID_IN_USE); } return "unknown error code"; } #else /* Empty bodies for when logging is disabled */ void ecs_log_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_abort_( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } void ecs_assert_log_( int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; } #endif int ecs_log_get_level(void) { return ecs_os_api.log_level_; } int ecs_log_set_level( int level) { int prev = level; ecs_os_api.log_level_ = level; return prev; } bool ecs_log_enable_colors( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); return prev; } bool ecs_log_enable_timestamp( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); return prev; } bool ecs_log_enable_timedelta( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); return prev; } int ecs_log_last_error(void) { int result = ecs_os_api.log_last_error_; ecs_os_api.log_last_error_ = 0; return result; } /** * @file addons/metrics.c * @brief Metrics addon. */ #ifdef FLECS_METRICS /* Public components */ ECS_COMPONENT_DECLARE(FlecsMetrics); ECS_TAG_DECLARE(EcsMetricInstance); ECS_COMPONENT_DECLARE(EcsMetricValue); ECS_COMPONENT_DECLARE(EcsMetricSource); ECS_TAG_DECLARE(EcsMetric); ECS_TAG_DECLARE(EcsCounter); ECS_TAG_DECLARE(EcsCounterIncrement); ECS_TAG_DECLARE(EcsCounterId); ECS_TAG_DECLARE(EcsGauge); /* Internal components */ static ECS_COMPONENT_DECLARE(EcsMetricMember); static ECS_COMPONENT_DECLARE(EcsMetricId); static ECS_COMPONENT_DECLARE(EcsMetricOneOf); static ECS_COMPONENT_DECLARE(EcsMetricCountIds); static ECS_COMPONENT_DECLARE(EcsMetricCountTargets); static ECS_COMPONENT_DECLARE(EcsMetricMemberInstance); static ECS_COMPONENT_DECLARE(EcsMetricIdInstance); static ECS_COMPONENT_DECLARE(EcsMetricOneOfInstance); /** Context for metric */ typedef struct { ecs_entity_t metric; /**< Metric entity */ ecs_entity_t kind; /**< Metric kind (gauge, counter) */ } ecs_metric_ctx_t; /** Context for metric that monitors member */ typedef struct { ecs_metric_ctx_t metric; ecs_primitive_kind_t type_kind; /**< Primitive type kind of member */ uint16_t offset; /**< Offset of member in component */ } ecs_member_metric_ctx_t; /** Context for metric that monitors whether entity has id */ typedef struct { ecs_metric_ctx_t metric; ecs_id_record_t *idr; /**< Id record for monitored component */ } ecs_id_metric_ctx_t; /** Context for metric that monitors whether entity has pair target */ typedef struct { ecs_metric_ctx_t metric; ecs_id_record_t *idr; /**< Id record for monitored component */ ecs_size_t size; /**< Size of metric type */ ecs_map_t target_offset; /**< Pair target to metric type offset */ } ecs_oneof_metric_ctx_t; /** Context for metric that monitors how many entities have a pair target */ typedef struct { ecs_metric_ctx_t metric; ecs_id_record_t *idr; /**< Id record for monitored component */ ecs_map_t targets; /**< Map of counters for each target */ } ecs_count_targets_metric_ctx_t; /** Stores context shared for all instances of member metric */ typedef struct { ecs_member_metric_ctx_t *ctx; } EcsMetricMember; /** Stores context shared for all instances of id metric */ typedef struct { ecs_id_metric_ctx_t *ctx; } EcsMetricId; /** Stores context shared for all instances of oneof metric */ typedef struct { ecs_oneof_metric_ctx_t *ctx; } EcsMetricOneOf; /** Stores context shared for all instances of id counter metric */ typedef struct { ecs_id_t id; } EcsMetricCountIds; /** Stores context shared for all instances of target counter metric */ typedef struct { ecs_count_targets_metric_ctx_t *ctx; } EcsMetricCountTargets; /** Instance of member metric */ typedef struct { ecs_ref_t ref; ecs_member_metric_ctx_t *ctx; } EcsMetricMemberInstance; /** Instance of id metric */ typedef struct { ecs_record_t *r; ecs_id_metric_ctx_t *ctx; } EcsMetricIdInstance; /** Instance of oneof metric */ typedef struct { ecs_record_t *r; ecs_oneof_metric_ctx_t *ctx; } EcsMetricOneOfInstance; /** Component lifecycle */ static ECS_DTOR(EcsMetricMember, ptr, { ecs_os_free(ptr->ctx); }) static ECS_MOVE(EcsMetricMember, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricId, ptr, { ecs_os_free(ptr->ctx); }) static ECS_MOVE(EcsMetricId, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricOneOf, ptr, { if (ptr->ctx) { ecs_map_fini(&ptr->ctx->target_offset); ecs_os_free(ptr->ctx); } }) static ECS_MOVE(EcsMetricOneOf, dst, src, { *dst = *src; src->ctx = NULL; }) static ECS_DTOR(EcsMetricCountTargets, ptr, { if (ptr->ctx) { ecs_map_fini(&ptr->ctx->targets); ecs_os_free(ptr->ctx); } }) static ECS_MOVE(EcsMetricCountTargets, dst, src, { *dst = *src; src->ctx = NULL; }) /** Observer used for creating new instances of member metric */ static void flecs_metrics_on_member_metric(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_member_metric_ctx_t *ctx = it->ctx; ecs_id_t id = ecs_field_id(it, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricMemberInstance *src = ecs_emplace( world, m, EcsMetricMemberInstance, NULL); src->ref = ecs_ref_init_id(world, e, id); src->ctx = ctx; ecs_modified(world, m, EcsMetricMemberInstance); ecs_set(world, m, EcsMetricValue, { 0 }); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Observer used for creating new instances of id metric */ static void flecs_metrics_on_id_metric(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_id_metric_ctx_t *ctx = it->ctx; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricIdInstance *src = ecs_emplace( world, m, EcsMetricIdInstance, NULL); src->r = ecs_record_find(world, e); src->ctx = ctx; ecs_modified(world, m, EcsMetricIdInstance); ecs_set(world, m, EcsMetricValue, { 0 }); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Observer used for creating new instances of oneof metric */ static void flecs_metrics_on_oneof_metric(ecs_iter_t *it) { if (it->event == EcsOnRemove) { return; } ecs_world_t *world = it->world; ecs_oneof_metric_ctx_t *ctx = it->ctx; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t m = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); EcsMetricOneOfInstance *src = ecs_emplace( world, m, EcsMetricOneOfInstance, NULL); src->r = ecs_record_find(world, e); src->ctx = ctx; ecs_modified(world, m, EcsMetricOneOfInstance); ecs_add_pair(world, m, ctx->metric.metric, ecs_id(EcsMetricValue)); ecs_set(world, m, EcsMetricSource, { e }); ecs_add(world, m, EcsMetricInstance); ecs_add_pair(world, m, EcsMetric, ctx->metric.kind); } } /** Set doc name of metric instance to name of source entity */ #ifdef FLECS_DOC static void SetMetricDocName(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMetricSource *src = ecs_field(it, EcsMetricSource, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t src_e = src[i].entity; const char *name = ecs_get_name(world, src_e); if (name) { ecs_doc_set_name(world, it->entities[i], name); } } } #endif /** Delete metric instances for entities that are no longer alive */ static void ClearMetricInstance(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMetricSource *src = ecs_field(it, EcsMetricSource, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t src_e = src[i].entity; if (!ecs_is_alive(world, src_e)) { ecs_delete(world, it->entities[i]); } } } /** Update member metric */ static void UpdateMemberInstance(ecs_iter_t *it, bool counter) { ecs_world_t *world = it->real_world; EcsMetricValue *m = ecs_field(it, EcsMetricValue, 0); EcsMetricMemberInstance *mi = ecs_field(it, EcsMetricMemberInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_member_metric_ctx_t *ctx = mi[i].ctx; ecs_ref_t *ref = &mi[i].ref; if (!ref->entity) { continue; } const void *ptr = ecs_ref_get_id(world, ref, ref->id); if (ptr) { ptr = ECS_OFFSET(ptr, ctx->offset); if (!counter) { m[i].value = ecs_meta_ptr_to_float(ctx->type_kind, ptr); } else { m[i].value += ecs_meta_ptr_to_float(ctx->type_kind, ptr) * (double)dt; } } else { ecs_delete(it->world, it->entities[i]); } } } static void UpdateGaugeMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, false); } static void UpdateCounterMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, false); } static void UpdateCounterIncrementMemberInstance(ecs_iter_t *it) { UpdateMemberInstance(it, true); } /** Update id metric */ static void UpdateIdInstance(ecs_iter_t *it, bool counter) { ecs_world_t *world = it->real_world; EcsMetricValue *m = ecs_field(it, EcsMetricValue, 0); EcsMetricIdInstance *mi = ecs_field(it, EcsMetricIdInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *table = r->table; if (!table) { ecs_delete(it->world, it->entities[i]); continue; } ecs_id_metric_ctx_t *ctx = mi[i].ctx; ecs_id_record_t *idr = ctx->idr; if (flecs_search_w_idr(world, table, NULL, idr) != -1) { if (!counter) { m[i].value = 1.0; } else { m[i].value += 1.0 * (double)dt; } } else { ecs_delete(it->world, it->entities[i]); } } } static void UpdateGaugeIdInstance(ecs_iter_t *it) { UpdateIdInstance(it, false); } static void UpdateCounterIdInstance(ecs_iter_t *it) { UpdateIdInstance(it, true); } /** Update oneof metric */ static void UpdateOneOfInstance(ecs_iter_t *it, bool counter) { ecs_world_t *world = it->real_world; ecs_table_t *table = it->table; void *m = ecs_table_get_column(table, ecs_table_type_to_column_index(table, it->columns[0]), it->offset); EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; ecs_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *mtable = r->table; double *value = ECS_ELEM(m, ctx->size, i); if (!counter) { ecs_os_memset(value, 0, ctx->size); } if (!mtable) { ecs_delete(it->world, it->entities[i]); continue; } ecs_id_record_t *idr = ctx->idr; ecs_id_t id; if (flecs_search_w_idr(world, mtable, &id, idr) == -1) { ecs_delete(it->world, it->entities[i]); continue; } ecs_entity_t tgt = ECS_PAIR_SECOND(id); uint64_t *offset = ecs_map_get(&ctx->target_offset, tgt); if (!offset) { ecs_err("unexpected relationship target for metric"); continue; } value = ECS_OFFSET(value, *offset); if (!counter) { *value = 1.0; } else { *value += 1.0 * (double)dt; } } } static void UpdateGaugeOneOfInstance(ecs_iter_t *it) { UpdateOneOfInstance(it, false); } static void UpdateCounterOneOfInstance(ecs_iter_t *it) { UpdateOneOfInstance(it, true); } static void UpdateCountTargets(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsMetricCountTargets *m = ecs_field(it, EcsMetricCountTargets, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_count_targets_metric_ctx_t *ctx = m[i].ctx; ecs_id_record_t *cur = ctx->idr; while ((cur = cur->first.next)) { ecs_id_t id = cur->id; ecs_entity_t *mi = ecs_map_ensure(&ctx->targets, id); if (!mi[0]) { mi[0] = ecs_new_w_pair(world, EcsChildOf, ctx->metric.metric); ecs_entity_t tgt = ecs_pair_second(world, cur->id); const char *name = ecs_get_name(world, tgt); if (name) { ecs_set_name(world, mi[0], name); } EcsMetricSource *source = ecs_ensure( world, mi[0], EcsMetricSource); source->entity = tgt; } EcsMetricValue *value = ecs_ensure(world, mi[0], EcsMetricValue); value->value += (double)ecs_count_id(world, cur->id) * (double)it->delta_system_time; } } } static void UpdateCountIds(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsMetricCountIds *m = ecs_field(it, EcsMetricCountIds, 0); EcsMetricValue *v = ecs_field(it, EcsMetricValue, 1); int32_t i, count = it->count; for (i = 0; i < count; i ++) { v[i].value += (double)ecs_count_id(world, m[i].id) * (double)it->delta_system_time; } } /** Initialize member metric */ static int flecs_member_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_entity_t type = 0, member_type = 0, member = 0, id = 0; uintptr_t offset = 0; if (desc->dotmember) { if (!desc->id) { char *metric_name = ecs_get_path(world, metric); ecs_err("missing id for metric '%s' with member '%s", metric_name, desc->dotmember); ecs_os_free(metric_name); goto error; } if (desc->member) { char *metric_name = ecs_get_path(world, metric); ecs_err("cannot set both member and dotmember for metric '%s'", metric_name); ecs_os_free(metric_name); goto error; } type = ecs_get_typeid(world, desc->id); ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); if (ecs_meta_push(&cur)) { char *metric_name = ecs_get_path(world, metric); ecs_err("invalid type for metric '%s'", metric_name); ecs_os_free(metric_name); goto error; } if (ecs_meta_dotmember(&cur, desc->dotmember)) { char *metric_name = ecs_get_path(world, metric); ecs_err("invalid dotmember '%s' for metric '%s'", desc->dotmember, metric_name); ecs_os_free(metric_name); goto error; } id = desc->id; member_type = ecs_meta_get_type(&cur); offset = (uintptr_t)ecs_meta_get_ptr(&cur); member = ecs_meta_get_member_id(&cur); } else { const EcsMember *m = ecs_get(world, desc->member, EcsMember); if (!m) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("entity '%s' provided for metric '%s' is not a member", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } type = ecs_get_parent(world, desc->member); if (!type) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("member '%s' provided for metric '%s' is not part of a type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } id = type; if (desc->id) { if (type != ecs_get_typeid(world, desc->id)) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); char *id_name = ecs_get_path(world, desc->id); ecs_err("member '%s' for metric '%s' is not of type '%s'", member_name, metric_name, id_name); ecs_os_free(id_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } id = desc->id; } member = desc->member; member_type = m->type; offset = flecs_ito(uintptr_t, m->offset); } const EcsPrimitive *p = ecs_get(world, member_type, EcsPrimitive); if (!p) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("member '%s' provided for metric '%s' must have primitive type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } const EcsType *mt = ecs_get(world, type, EcsType); if (!mt) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("parent of member '%s' for metric '%s' is not a type", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } if (mt->kind != EcsStructType) { char *metric_name = ecs_get_path(world, metric); char *member_name = ecs_get_path(world, desc->member); ecs_err("parent of member '%s' for metric '%s' is not a struct", member_name, metric_name); ecs_os_free(member_name); ecs_os_free(metric_name); goto error; } ecs_member_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_member_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->type_kind = p->kind; ctx->offset = flecs_uto(uint16_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .query.terms[0] = { .id = id, .src.id = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_member_metric, .yield_existing = true, .ctx = ctx }); ecs_set_pair(world, metric, EcsMetricMember, member, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** Update id metric */ static int flecs_id_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_id_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_id_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_observer(world, { .entity = metric, .events = { EcsOnAdd }, .query.terms[0] = { .id = desc->id, .src.id = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_id_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricId, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } /** Update oneof metric */ static int flecs_oneof_metric_init( ecs_world_t *world, ecs_entity_t metric, ecs_entity_t scope, const ecs_metric_desc_t *desc) { ecs_oneof_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_oneof_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->target_offset, NULL); /* Add member for each child of oneof to metric, so it can be used as metric * instance type that holds values for all targets */ ecs_iter_t it = ecs_children(world, scope); uint64_t offset = 0; while (ecs_children_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_entity_t tgt = it.entities[i]; const char *name = ecs_get_name(world, tgt); if (!name) { /* Member must have name */ continue; } char *to_snake_case = flecs_to_snake_case(name); ecs_entity_t mbr = ecs_entity(world, { .name = to_snake_case, .parent = ecs_childof(metric) }); ecs_os_free(to_snake_case); ecs_set(world, mbr, EcsMember, { .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }); /* Truncate upper 32 bits of target so we can lookup the offset * with the id we get from the pair */ ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; offset += sizeof(double); } } ctx->size = flecs_uto(ecs_size_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsMonitor }, .query.terms[0] = { .id = desc->id, .src.id = EcsSelf, .inout = EcsInOutNone }, .callback = flecs_metrics_on_oneof_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_id_targets_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->targets, NULL); ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_ids_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); ecs_set(world, metric, EcsMetricValue, { .value = 0 }); return 0; } ecs_entity_t ecs_metric_init( ecs_world_t *world, const ecs_metric_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_metric_desc_t was not initialized to zero"); flecs_poly_assert(world, ecs_world_t); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); } ecs_entity_t kind = desc->kind; if (!kind) { ecs_err("missing metric kind"); goto error; } if (kind != EcsGauge && kind != EcsCounter && kind != EcsCounterId && kind != EcsCounterIncrement) { ecs_err("invalid metric kind %s", ecs_get_path(world, kind)); goto error; } if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { ecs_err("CounterIncrement can only be used in combination with member"); goto error; } if (kind == EcsCounterId && (desc->member || desc->dotmember)) { ecs_err("CounterId cannot be used in combination with member"); goto error; } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); #endif } if (desc->member || desc->dotmember) { if (flecs_member_metric_init(world, result, desc)) { goto error; } } else if (desc->id) { if (desc->targets) { if (!ecs_id_is_pair(desc->id)) { ecs_err("cannot specify targets for id that is not a pair"); goto error; } if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { ecs_err("first element of pair cannot be wildcard with " " targets enabled"); goto error; } if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { ecs_err("second element of pair must be wildcard with " " targets enabled"); goto error; } if (kind == EcsCounterId) { if (flecs_count_id_targets_metric_init(world, result, desc)) { goto error; } } else { ecs_entity_t first = ecs_pair_first(world, desc->id); ecs_entity_t scope = flecs_get_oneof(world, first); if (!scope) { ecs_err("first element of pair must have OneOf with " " targets enabled"); goto error; } if (flecs_oneof_metric_init(world, result, scope, desc)) { goto error; } } } else { if (kind == EcsCounterId) { if (flecs_count_ids_metric_init(world, result, desc)) { goto error; } } else { if (flecs_id_metric_init(world, result, desc)) { goto error; } } } } else { ecs_err("missing source specified for metric"); goto error; } return result; error: if (result && result != desc->entity) { ecs_delete(world, result); } return 0; } void FlecsMetricsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMetrics); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); ECS_TAG_DEFINE(world, EcsMetric); ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); ECS_TAG_DEFINE(world, EcsCounter); ECS_TAG_DEFINE(world, EcsCounterIncrement); ECS_TAG_DEFINE(world, EcsCounterId); ECS_TAG_DEFINE(world, EcsGauge); ecs_set_scope(world, old_scope); ecs_set_name_prefix(world, "EcsMetric"); ECS_TAG_DEFINE(world, EcsMetricInstance); ECS_COMPONENT_DEFINE(world, EcsMetricValue); ECS_COMPONENT_DEFINE(world, EcsMetricSource); ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); ECS_COMPONENT_DEFINE(world, EcsMetricMember); ECS_COMPONENT_DEFINE(world, EcsMetricId); ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); ecs_struct(world, { .entity = ecs_id(EcsMetricValue), .members = { { .name = "value", .type = ecs_id(ecs_f64_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsMetricSource), .members = { { .name = "entity", .type = ecs_id(ecs_entity_t) } } }); ecs_set_hooks(world, EcsMetricMember, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricMember), .move = ecs_move(EcsMetricMember) }); ecs_set_hooks(world, EcsMetricId, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricId), .move = ecs_move(EcsMetricId) }); ecs_set_hooks(world, EcsMetricOneOf, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricOneOf), .move = ecs_move(EcsMetricOneOf) }); ecs_set_hooks(world, EcsMetricCountTargets, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricCountTargets), .move = ecs_move(EcsMetricCountTargets) }); ecs_add_id(world, EcsMetric, EcsOneOf); #ifdef FLECS_DOC ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, Source); #endif ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, [in] Source); ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, CounterIncrement)); ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, [out] Value, [in] IdInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, [inout] Value, [in] IdInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, [inout] CountIds, Value); ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, [inout] CountTargets); } #endif /** * @file addons/module.c * @brief Module addon. */ #ifdef FLECS_MODULE #include char* flecs_module_path_from_c( const char *c_name) { ecs_strbuf_t str = ECS_STRBUF_INIT; const char *ptr; char ch; for (ptr = c_name; (ch = *ptr); ptr++) { if (isupper(ch)) { ch = flecs_ito(char, tolower(ch)); if (ptr != c_name) { ecs_strbuf_appendstrn(&str, ".", 1); } } ecs_strbuf_appendstrn(&str, &ch, 1); } return ecs_strbuf_get(&str); } ecs_entity_t ecs_import( ecs_world_t *world, ecs_module_action_t module, const char *module_name) { ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = flecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup(world, path); ecs_os_free(path); if (!e) { ecs_trace("#[magenta]import#[reset] %s", module_name); ecs_log_push(); /* Load module */ module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup(world, module_name); ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); ecs_log_pop(); } /* Restore to previous state */ ecs_set_scope(world, old_scope); world->info.name_prefix = old_name_prefix; return e; error: return 0; } ecs_entity_t ecs_import_c( ecs_world_t *world, ecs_module_action_t module, const char *c_name) { char *name = flecs_module_path_from_c(c_name); ecs_entity_t e = ecs_import(world, module, name); ecs_os_free(name); return e; } ecs_entity_t ecs_import_from_library( ecs_world_t *world, const char *library_name, const char *module_name) { ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); char *import_func = ECS_CONST_CAST(char*, module_name); char *module = ECS_CONST_CAST(char*, module_name); if (!ecs_os_has_modules() || !ecs_os_has_dl()) { ecs_err( "library loading not supported, set module_to_dl, dlopen, dlclose " "and dlproc os API callbacks first"); return 0; } /* If no module name is specified, try default naming convention for loading * the main module from the library */ if (!import_func) { import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); const char *ptr; char ch, *bptr = import_func; bool capitalize = true; for (ptr = library_name; (ch = *ptr); ptr ++) { if (ch == '.') { capitalize = true; } else { if (capitalize) { *bptr = flecs_ito(char, toupper(ch)); bptr ++; capitalize = false; } else { *bptr = flecs_ito(char, tolower(ch)); bptr ++; } } } *bptr = '\0'; module = ecs_os_strdup(import_func); ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcat(bptr, "Import"); } char *library_filename = ecs_os_module_to_dl(library_name); if (!library_filename) { ecs_err("failed to find library file for '%s'", library_name); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("found file '%s' for library '%s'", library_filename, library_name); } ecs_os_dl_t dl = ecs_os_dlopen(library_filename); if (!dl) { ecs_err("failed to load library '%s' ('%s')", library_name, library_filename); ecs_os_free(library_filename); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("library '%s' ('%s') loaded", library_name, library_filename); } ecs_module_action_t action = (ecs_module_action_t) ecs_os_dlproc(dl, import_func); if (!action) { ecs_err("failed to load import function %s from library %s", import_func, library_name); ecs_os_free(library_filename); ecs_os_dlclose(dl); return 0; } else { ecs_trace("found import function '%s' in library '%s' for module '%s'", import_func, library_name, module); } /* 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); flecs_poly_assert(world, ecs_world_t); ecs_entity_t old_scope = ecs_set_scope(world, 0); ecs_entity_t e = desc->entity; if (!e) { char *module_path = flecs_module_path_from_c(c_name); e = ecs_entity(world, { .name = module_path }); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } else if (!ecs_exists(world, e)) { char *module_path = flecs_module_path_from_c(c_name); ecs_make_alive(world, e); ecs_add_fullpath(world, e, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } ecs_add_id(world, e, EcsModule); ecs_component_desc_t private_desc = *desc; private_desc.entity = e; if (desc->type.size) { ecs_entity_t result = ecs_component_init(world, &private_desc); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); (void)result; } ecs_set_scope(world, old_scope); return e; error: return 0; } #endif /** * @file addons/rest.c * @brief Rest addon. */ /** * @file addons/pipeline/pipeline.h * @brief Internal functions/types for pipeline addon. */ #ifndef FLECS_PIPELINE_PRIVATE_H #define FLECS_PIPELINE_PRIVATE_H /** Instruction data for pipeline. * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { int32_t offset; /* Offset in systems vector */ int32_t count; /* Number of systems to run before next op */ double time_spent; /* Time spent merging commands for sync point */ int64_t commands_enqueued; /* Number of commands enqueued for sync point */ bool multi_threaded; /* Whether systems can be ran multi threaded */ bool immediate; /* Whether systems are staged or not */ } ecs_pipeline_op_t; struct ecs_pipeline_state_t { ecs_query_t *query; /* Pipeline query */ ecs_vec_t ops; /* Pipeline schedule */ ecs_vec_t systems; /* Vector with system ids */ ecs_entity_t last_system; /* Last system ran by pipeline */ ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ int32_t match_count; /* Used to track of rebuild is necessary */ int32_t rebuild_count; /* Number of pipeline rebuilds */ ecs_iter_t *iters; /* Iterator for worker(s) */ int32_t iter_count; /* Members for continuing pipeline iteration after pipeline rebuild */ ecs_pipeline_op_t *cur_op; /* Current pipeline op */ int32_t cur_i; /* Index in current result */ int32_t ran_since_merge; /* Index in current op */ bool immediate; /* Is pipeline in readonly mode */ }; typedef struct EcsPipeline { /* Stable ptr so threads can safely access while entity/components move */ ecs_pipeline_state_t *state; } EcsPipeline; //////////////////////////////////////////////////////////////////////////////// //// Pipeline API //////////////////////////////////////////////////////////////////////////////// bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame); void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); void flecs_create_worker_threads( ecs_world_t *world); void flecs_join_worker_threads( ecs_world_t *world); void flecs_signal_workers( ecs_world_t *world); void flecs_wait_for_sync( ecs_world_t *world); #endif #ifdef FLECS_REST /* Retain captured commands for one minute at 60 FPS */ #define FLECS_REST_COMMAND_RETAIN_COUNT (60 * 60) static ECS_TAG_DECLARE(EcsRestPlecs); typedef struct { ecs_world_t *world; ecs_http_server_t *srv; int32_t rc; ecs_map_t cmd_captures; double last_time; } ecs_rest_ctx_t; typedef struct { char *cmds; ecs_time_t start_time; ecs_strbuf_t buf; } ecs_rest_cmd_sync_capture_t; typedef struct { ecs_vec_t syncs; } ecs_rest_cmd_capture_t; static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_rest_server_fini(impl->srv); } } ecs_os_free(ptr->ipaddr); }) static char *rest_last_err; static ecs_os_api_log_t rest_prev_log; static void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; #ifdef FLECS_DEBUG if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { ecs_log_enable_colors(true); rest_prev_log(level, file, line, msg); ecs_log_enable_colors(false); } } #endif if (!rest_last_err && level < 0) { rest_last_err = ecs_os_strdup(msg); } } static char* flecs_rest_get_captured_log(void) { char *result = rest_last_err; rest_last_err = NULL; return result; } static void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) { ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); ecs_strbuf_vappend(&reply->body, fmt, args); ecs_strbuf_appendlit(&reply->body, "\"}"); } static void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); flecs_reply_verror(reply, fmt, args); va_end(args); } static void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { if (!ecs_os_strcmp(value, "true")) { value_out[0] = true; } else { value_out[0] = false; } } } static void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = atoi(value); } } static void flecs_rest_string_param( const ecs_http_request_t *req, const char *name, char **value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = ECS_CONST_CAST(char*, value); } } static void flecs_rest_parse_json_ser_entity_params( ecs_world_t *world, ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "entity_id", &desc->serialize_entity_id); flecs_rest_bool_param(req, "doc", &desc->serialize_doc); flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "matches", &desc->serialize_matches); flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); char *rel = NULL; flecs_rest_string_param(req, "refs", &rel); if (rel) { desc->serialize_refs = ecs_lookup(world, rel); } } static void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); flecs_rest_bool_param(req, "doc", &desc->serialize_doc); flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); flecs_rest_bool_param(req, "query_profile", &desc->serialize_query_profile); flecs_rest_bool_param(req, "table", &desc->serialize_table); flecs_rest_bool_param(req, "fields", &desc->serialize_fields); bool results = true; flecs_rest_bool_param(req, "results", &results); desc->dont_serialize_results = !results; } static bool flecs_rest_get_entity( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *path = &req->path[7]; ecs_dbg_2("rest: request entity '%s'", path); ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { ecs_dbg_2("rest: entity '%s' not found", path); flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; return true; } ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; flecs_rest_parse_json_ser_entity_params(world, &desc, req); if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static bool flecs_rest_put_entity( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_dbg_2("rest: create entity '%s'", path); ecs_entity_t result = ecs_entity(world, { .name = path, .sep = "/" }); if (!result) { ecs_dbg_2("rest: failed to create entity '%s'", path); flecs_reply_error(reply, "failed to create entity '%s'", path); reply->code = 500; return true; } ecs_strbuf_appendlit(&reply->body, "{\"id\":\""); ecs_strbuf_appendint(&reply->body, (uint32_t)result); ecs_strbuf_appendlit(&reply->body, "\"}"); return true; } static bool flecs_rest_get_world( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static ecs_entity_t flecs_rest_entity_from_path( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; } return e; } static bool flecs_rest_get_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { flecs_reply_error(reply, "component '%s' is not a type", component); reply->code = 400; return true; } const void *ptr = ecs_get_id(world, e, id); if (!ptr) { flecs_reply_error(reply, "failed to get component '%s'", component); reply->code = 500; return true; } ecs_ptr_to_json_buf(world, type, ptr, &reply->body); return true; } static bool flecs_rest_put_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } const char *data = ecs_http_get_param(req, "value"); if (!data) { ecs_add_id(world, e, id); return true; } ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { flecs_reply_error(reply, "component '%s' is not a type", component); reply->code = 400; return true; } void *ptr = ecs_ensure_id(world, e, id); if (!ptr) { flecs_reply_error(reply, "failed to create component '%s'", component); reply->code = 500; return true; } if (!ecs_ptr_from_json(world, type, ptr, data, NULL)) { flecs_reply_error(reply, "invalid value for component '%s'", component); reply->code = 400; return true; } ecs_modified_id(world, e, id); return true; } static bool flecs_rest_delete_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_remove_id(world, e, id); return true; } static bool flecs_rest_delete_entity( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } ecs_delete(world, e); return true; } static bool flecs_rest_toggle( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } bool enable = true; flecs_rest_bool_param(req, "enable", &enable); const char *component = ecs_http_get_param(req, "component"); if (!component) { ecs_enable(world, e, enable); return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_entity_t rel = 0; if (ECS_IS_PAIR(id)) { rel = ecs_pair_first(world, id); } else { rel = id & ECS_COMPONENT_MASK; } if (!ecs_has_id(world, rel, EcsCanToggle)) { flecs_reply_error(reply, "cannot toggle component '%s'", component); reply->code = 400; return true; } ecs_enable_id(world, e, id, enable); return true; } static bool flecs_rest_script( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { (void)world; (void)req; (void)reply; #ifdef FLECS_SCRIPT ecs_entity_t script = flecs_rest_entity_from_path(world, reply, path); if (!script) { script = ecs_entity(world, { .name = path }); } const char *code = ecs_http_get_param(req, "code"); if (!code) { flecs_reply_error(reply, "missing data parameter"); return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; rest_prev_log = try ? NULL : prev_log; ecs_os_api.log_ = flecs_rest_capture_log; script = ecs_script(world, { .entity = script, .code = code }); if (!script) { char *err = flecs_rest_get_captured_log(); char *escaped_err = flecs_astresc('"', err); if (escaped_err) { flecs_reply_error(reply, escaped_err); } else { flecs_reply_error(reply, "error parsing script"); } if (!try) { reply->code = 400; /* bad request */ } ecs_os_free(escaped_err); ecs_os_free(err); } ecs_os_api.log_ = prev_log; ecs_log_enable_colors(prev_color); return true; #else return false; #endif } static void flecs_rest_reply_set_captured_log( ecs_http_reply_t *reply) { char *err = flecs_rest_get_captured_log(); if (err) { char *escaped_err = flecs_astresc('"', err); flecs_reply_error(reply, escaped_err); ecs_os_free(escaped_err); ecs_os_free(err); } reply->code = 400; } static void flecs_rest_iter_to_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_poly_t *query, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; flecs_rest_parse_json_ser_iter_params(&desc, req); desc.query = query; int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); if (offset < 0 || limit < 0) { flecs_reply_error(reply, "invalid offset/limit parameter"); return; } ecs_iter_t pit = ecs_page_iter(it, offset, limit); if (ecs_iter_to_json_buf(&pit, &reply->body, &desc)) { flecs_rest_reply_set_captured_log(reply); } flecs_rest_int_param(req, "offset", &offset); } static bool flecs_rest_reply_existing_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *name) { ecs_entity_t qe = ecs_lookup(world, name); if (!qe) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; return true; } ecs_query_t *q = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); if (!poly_comp) { poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsObserver); if (poly_comp) { q = ((ecs_observer_t*)poly_comp->poly)->query; } else { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } } else { q = poly_comp->poly; } if (!q) { flecs_reply_error(reply, "query '%s' is not initialized", name); reply->code = 400; return true; } ecs_iter_t it = ecs_query_iter(world, q); ecs_dbg_2("rest: request query '%s'", name); bool prev_color = ecs_log_enable_colors(false); rest_prev_log = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; const char *vars = ecs_http_get_param(req, "vars"); if (vars) { #ifdef FLECS_SCRIPT if (ecs_query_args_parse(q, &it, vars) == NULL) { flecs_rest_reply_set_captured_log(reply); return true; } #else flecs_reply_error(reply, "cannot parse query arg expression: script addon required"); reply->code = 400; return true; #endif } flecs_rest_iter_to_reply(req, reply, q, &it); ecs_os_api.log_ = rest_prev_log; ecs_log_enable_colors(prev_color); return true; } static bool flecs_rest_get_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { const char *q_name = ecs_http_get_param(req, "name"); if (q_name) { return flecs_rest_reply_existing_query(world, req, reply, q_name); } const char *expr = ecs_http_get_param(req, "expr"); if (!expr) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'expr'"); reply->code = 400; /* bad request */ return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); ecs_dbg_2("rest: request query '%s'", expr); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; rest_prev_log = try ? NULL : prev_log; ecs_os_api.log_ = flecs_rest_capture_log; ecs_query_t *q = ecs_query(world, { .expr = expr }); if (!q) { flecs_rest_reply_set_captured_log(reply); if (try) { /* If client is trying queries, don't spam console with errors */ reply->code = 200; } } else { ecs_iter_t it = ecs_query_iter(world, q); flecs_rest_iter_to_reply(req, reply, q, &it); ecs_query_fini(q); } ecs_os_api.log_ = prev_log; ecs_log_enable_colors(prev_color); return true; } #ifdef FLECS_STATS static void flecs_rest_array_append_( ecs_strbuf_t *reply, const char *field, int32_t field_len, const ecs_float_t *values, int32_t t) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "[", ","); int32_t i; for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { int32_t index = i % ECS_STAT_WINDOW; ecs_strbuf_list_next(reply); ecs_strbuf_appendflt(reply, (double)values[index], '"'); } ecs_strbuf_list_pop(reply, "]"); } #define flecs_rest_array_append(reply, field, values, t)\ flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) static void flecs_rest_gauge_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "{", ","); flecs_rest_array_append(reply, "avg", m->gauge.avg, t); flecs_rest_array_append(reply, "min", m->gauge.min, t); flecs_rest_array_append(reply, "max", m->gauge.max, t); if (brief) { ecs_strbuf_list_appendlit(reply, "\"brief\":\""); ecs_strbuf_appendstrn(reply, brief, brief_len); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_counter_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); } #define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_GAUGE_APPEND(reply, s, field, brief)\ ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) #define ECS_COUNTER_APPEND(reply, s, field, brief)\ ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) static void flecs_world_stats_to_json( ecs_strbuf_t *reply, const EcsWorldStats *monitor_stats) { const ecs_world_stats_t *stats = &monitor_stats->stats; ecs_strbuf_list_push(reply, "{", ","); ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.ensure_count, "Get_mut commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); ecs_strbuf_appendch(reply, '"'); bool disabled = ecs_has_id(world, system, EcsDisabled); ecs_strbuf_list_appendlit(reply, "\"disabled\":"); ecs_strbuf_appendstr(reply, disabled ? "true" : "false"); if (!stats->task) { ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); } ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); ecs_strbuf_list_pop(reply, "}"); } static void flecs_sync_stats_to_json( ecs_http_reply_t *reply, const ecs_pipeline_stats_t *pstats, const ecs_sync_stats_t *stats) { ecs_strbuf_list_push(&reply->body, "{", ","); ecs_strbuf_list_appendlit(&reply->body, "\"multi_threaded\":"); ecs_strbuf_appendbool(&reply->body, stats->multi_threaded); ecs_strbuf_list_appendlit(&reply->body, "\"immediate\":"); ecs_strbuf_appendbool(&reply->body, stats->immediate); ECS_GAUGE_APPEND_T(&reply->body, stats, time_spent, pstats->t, ""); ECS_GAUGE_APPEND_T(&reply->body, stats, commands_enqueued, pstats->t, ""); ecs_strbuf_list_pop(&reply->body, "}"); } static void flecs_all_systems_stats_to_json( ecs_world_t *world, ecs_http_reply_t *reply, ecs_entity_t period) { const EcsSystemStats *stats = ecs_get_pair(world, EcsWorld, EcsSystemStats, period); ecs_strbuf_list_push(&reply->body, "[", ","); if (stats) { ecs_map_iter_t it = ecs_map_iter(&stats->stats); while (ecs_map_next(&it)) { ecs_entity_t id = ecs_map_key(&it); ecs_system_stats_t *sys_stats = ecs_map_ptr(&it); if (!ecs_is_alive(world, id)) { continue; } ecs_strbuf_list_next(&reply->body); flecs_system_stats_to_json(world, &reply->body, id, sys_stats); } } ecs_strbuf_list_pop(&reply->body, "]"); } static void flecs_pipeline_stats_to_json( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_entity_t period) { char *pipeline_name = NULL; flecs_rest_string_param(req, "name", &pipeline_name); if (!pipeline_name || !ecs_os_strcmp(pipeline_name, "all")) { flecs_all_systems_stats_to_json(world, reply, period); return; } ecs_entity_t e = ecs_lookup(world, pipeline_name); if (!e) { flecs_reply_error(reply, "pipeline '%s' not found", pipeline_name); reply->code = 404; return; } const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); const EcsSystemStats *system_stats = ecs_get_pair(world, EcsWorld, EcsSystemStats, period); if (!stats || !system_stats) { goto noresults; } ecs_pipeline_stats_t *pstats = ecs_map_get_deref( &stats->stats, ecs_pipeline_stats_t, e); if (!pstats) { goto noresults; } const EcsPipeline *p = ecs_get(world, e, EcsPipeline); ecs_strbuf_list_push(&reply->body, "[", ","); ecs_pipeline_op_t *ops = ecs_vec_first_t(&p->state->ops, ecs_pipeline_op_t); ecs_entity_t *systems = ecs_vec_first_t(&p->state->systems, ecs_entity_t); ecs_sync_stats_t *syncs = ecs_vec_first_t( &pstats->sync_points, ecs_sync_stats_t); int32_t s, o, op_count = ecs_vec_count(&p->state->ops); for (o = 0; o < op_count; o ++) { ecs_pipeline_op_t *op = &ops[o]; for (s = op->offset; s < (op->offset + op->count); s ++) { ecs_entity_t system = systems[s]; if (!ecs_is_alive(world, system)) { continue; } ecs_system_stats_t *sys_stats = ecs_map_get_deref( &system_stats->stats, ecs_system_stats_t, system); ecs_strbuf_list_next(&reply->body); flecs_system_stats_to_json(world, &reply->body, system, sys_stats); } ecs_strbuf_list_next(&reply->body); flecs_sync_stats_to_json(reply, pstats, &syncs[o]); } ecs_strbuf_list_pop(&reply->body, "]"); return; noresults: ecs_strbuf_appendlit(&reply->body, "[]"); } static bool flecs_rest_get_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *period_str = NULL; flecs_rest_string_param(req, "period", &period_str); char *category = &req->path[6]; ecs_entity_t period = EcsPeriod1s; if (period_str) { char *period_name = flecs_asprintf("Period%s", period_str); period = ecs_lookup_child(world, ecs_id(FlecsStats), period_name); ecs_os_free(period_name); if (!period) { flecs_reply_error(reply, "bad request (invalid period string)"); reply->code = 400; return false; } } if (!ecs_os_strcmp(category, "world")) { const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, EcsWorldStats, period); flecs_world_stats_to_json(&reply->body, stats); return true; } else if (!ecs_os_strcmp(category, "pipeline")) { flecs_pipeline_stats_to_json(world, req, reply, period); return true; } else { flecs_reply_error(reply, "bad request (unsupported category)"); reply->code = 400; return false; } } #else static bool flecs_rest_get_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; return false; } #endif static void flecs_rest_reply_table_append_type( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = table->type.count; ecs_id_t *ids = table->type.array; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(reply); ecs_strbuf_appendch(reply, '"'); ecs_id_str_buf(world, ids[i], reply); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "]"); } static void flecs_rest_reply_table_append_memory( ecs_strbuf_t *reply, const ecs_table_t *table) { int32_t used = 0, allocated = 0; used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); int32_t i, storage_count = table->column_count; ecs_column_t *columns = table->data.columns; for (i = 0; i < storage_count; i ++) { used += columns[i].data.count * columns[i].ti->size; allocated += columns[i].data.size * columns[i].ti->size; } ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"used\":"); ecs_strbuf_appendint(reply, used); ecs_strbuf_list_appendlit(reply, "\"allocated\":"); ecs_strbuf_appendint(reply, 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_appendlit(reply, "\"id\":"); ecs_strbuf_appendint(reply, (uint32_t)table->id); ecs_strbuf_list_appendlit(reply, "\"type\":"); flecs_rest_reply_table_append_type(world, reply, table); ecs_strbuf_list_appendlit(reply, "\"count\":"); ecs_strbuf_appendint(reply, ecs_table_count(table)); ecs_strbuf_list_appendlit(reply, "\"memory\":"); flecs_rest_reply_table_append_memory(reply, table); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_get_tables( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_rest_reply_table_append(world, &reply->body, table); } ecs_strbuf_list_pop(&reply->body, "]"); return true; } static const char* flecs_rest_cmd_kind_to_str( ecs_cmd_kind_t kind) { switch(kind) { case EcsCmdClone: return "Clone"; case EcsCmdBulkNew: return "BulkNew"; case EcsCmdAdd: return "Add"; case EcsCmdRemove: return "Remove"; case EcsCmdSet: return "Set"; case EcsCmdEmplace: return "Emplace"; case EcsCmdEnsure: return "Ensure"; case EcsCmdModified: return "Modified"; case EcsCmdModifiedNoHook: return "ModifiedNoHook"; case EcsCmdAddModified: return "AddModified"; case EcsCmdPath: return "Path"; case EcsCmdDelete: return "Delete"; case EcsCmdClear: return "Clear"; case EcsCmdOnDeleteAction: return "OnDeleteAction"; case EcsCmdEnable: return "Enable"; case EcsCmdDisable: return "Disable"; case EcsCmdEvent: return "Event"; case EcsCmdSkip: return "Skip"; default: return "Unknown"; } } static bool flecs_rest_cmd_has_id( const ecs_cmd_t *cmd) { switch(cmd->kind) { case EcsCmdClear: case EcsCmdDelete: case EcsCmdClone: case EcsCmdDisable: case EcsCmdPath: return false; case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdRemove: case EcsCmdSet: case EcsCmdEmplace: case EcsCmdEnsure: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdEvent: case EcsCmdSkip: default: return true; } } static void flecs_rest_server_garbage_collect_all( ecs_rest_ctx_t *impl) { ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); while (ecs_map_next(&it)) { ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_os_free(sync->cmds); } ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_os_free(capture); } ecs_map_fini(&impl->cmd_captures); } static void flecs_rest_server_garbage_collect( ecs_world_t *world, ecs_rest_ctx_t *impl) { const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_map_iter_t it = ecs_map_iter(&impl->cmd_captures); ecs_vec_t removed_frames = {0}; while (ecs_map_next(&it)) { int64_t frame = flecs_uto(int64_t, ecs_map_key(&it)); if ((wi->frame_count_total - frame) > FLECS_REST_COMMAND_RETAIN_COUNT) { ecs_rest_cmd_capture_t *capture = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_os_free(sync->cmds); } ecs_vec_fini_t(NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_os_free(capture); ecs_vec_init_if_t(&removed_frames, int64_t); ecs_vec_append_t(NULL, &removed_frames, int64_t)[0] = frame; } } int32_t i, count = ecs_vec_count(&removed_frames); if (count) { int64_t *frames = ecs_vec_first(&removed_frames); if (count) { for (i = 0; i < count; i ++) { ecs_map_remove(&impl->cmd_captures, flecs_ito(uint64_t, frames[i])); } } ecs_vec_fini_t(NULL, &removed_frames, int64_t); } } static void flecs_rest_cmd_to_json( ecs_world_t *world, ecs_strbuf_t *buf, ecs_cmd_t *cmd) { ecs_strbuf_list_push(buf, "{", ","); ecs_strbuf_list_appendlit(buf, "\"kind\":\""); ecs_strbuf_appendstr(buf, flecs_rest_cmd_kind_to_str(cmd->kind)); ecs_strbuf_appendlit(buf, "\""); if (flecs_rest_cmd_has_id(cmd)) { ecs_strbuf_list_appendlit(buf, "\"id\":\""); char *idstr = ecs_id_str(world, cmd->id); ecs_strbuf_appendstr(buf, idstr); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(idstr); } if (cmd->system) { ecs_strbuf_list_appendlit(buf, "\"system\":\""); char *sysstr = ecs_get_path(world, cmd->system); ecs_strbuf_appendstr(buf, sysstr); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(sysstr); } if (cmd->kind == EcsCmdBulkNew) { /* Todo */ } else if (cmd->kind == EcsCmdEvent) { /* Todo */ } else { if (cmd->entity) { ecs_strbuf_list_appendlit(buf, "\"entity\":\""); char *path = ecs_get_path_w_sep(world, 0, cmd->entity, ".", ""); ecs_strbuf_appendstr(buf, path); ecs_strbuf_appendlit(buf, "\""); ecs_os_free(path); ecs_strbuf_list_appendlit(buf, "\"is_alive\":\""); if (ecs_is_alive(world, cmd->entity)) { ecs_strbuf_appendlit(buf, "true"); } else { ecs_strbuf_appendlit(buf, "false"); } ecs_strbuf_appendlit(buf, "\""); ecs_strbuf_list_appendlit(buf, "\"next_for_entity\":"); ecs_strbuf_appendint(buf, cmd->next_for_entity); } } ecs_strbuf_list_pop(buf, "}"); } static void flecs_rest_on_commands( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx) { ecs_world_t *world = stage->world; ecs_rest_cmd_capture_t *capture = ctx; ecs_assert(capture != NULL, ECS_INTERNAL_ERROR, NULL); if (commands) { ecs_vec_init_if_t(&capture->syncs, ecs_rest_cmd_sync_capture_t); ecs_rest_cmd_sync_capture_t *sync = ecs_vec_append_t( NULL, &capture->syncs, ecs_rest_cmd_sync_capture_t); int32_t i, count = ecs_vec_count(commands); ecs_cmd_t *cmds = ecs_vec_first(commands); sync->buf = ECS_STRBUF_INIT; ecs_strbuf_list_push(&sync->buf, "{", ","); ecs_strbuf_list_appendlit(&sync->buf, "\"commands\":"); ecs_strbuf_list_push(&sync->buf, "[", ","); for (i = 0; i < count; i ++) { ecs_strbuf_list_next(&sync->buf); flecs_rest_cmd_to_json(world, &sync->buf, &cmds[i]); } ecs_strbuf_list_pop(&sync->buf, "]"); /* Measure how long it takes to process queue */ sync->start_time = (ecs_time_t){0}; ecs_time_measure(&sync->start_time); } else { /* Finished processing queue, measure duration */ ecs_rest_cmd_sync_capture_t *sync = ecs_vec_last_t( &capture->syncs, ecs_rest_cmd_sync_capture_t); double duration = ecs_time_measure(&sync->start_time); ecs_strbuf_list_appendlit(&sync->buf, "\"duration\":"); ecs_strbuf_appendflt(&sync->buf, duration, '"'); ecs_strbuf_list_pop(&sync->buf, "}"); sync->cmds = ecs_strbuf_get(&sync->buf); } } static bool flecs_rest_get_commands_capture( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; const ecs_world_info_t *wi = ecs_get_world_info(world); ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_appendlit(&reply->body, "\"frame\":"); ecs_strbuf_appendint(&reply->body, wi->frame_count_total); ecs_strbuf_appendstr(&reply->body, "}"); ecs_map_init_if(&impl->cmd_captures, &world->allocator); ecs_rest_cmd_capture_t *capture = ecs_map_ensure_alloc_t( &impl->cmd_captures, ecs_rest_cmd_capture_t, flecs_ito(uint64_t, wi->frame_count_total)); world->on_commands = flecs_rest_on_commands; world->on_commands_ctx = capture; /* Run garbage collection so that requests don't linger */ flecs_rest_server_garbage_collect(world, impl); return true; } static bool flecs_rest_get_commands_request( ecs_world_t *world, ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; char *frame_str = &req->path[15]; int32_t frame = atoi(frame_str); ecs_map_init_if(&impl->cmd_captures, &world->allocator); const ecs_rest_cmd_capture_t *capture = ecs_map_get_deref( &impl->cmd_captures, ecs_rest_cmd_capture_t, flecs_ito(uint64_t, frame)); if (!capture) { ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_append(&reply->body, "\"error\": \"no capture for frame %u\"", frame); ecs_strbuf_appendstr(&reply->body, "}"); reply->code = 404; return true; } ecs_strbuf_appendstr(&reply->body, "{"); ecs_strbuf_list_append(&reply->body, "\"syncs\":"); ecs_strbuf_list_push(&reply->body, "[", ","); int32_t i, count = ecs_vec_count(&capture->syncs); ecs_rest_cmd_sync_capture_t *syncs = ecs_vec_first(&capture->syncs); for (i = 0; i < count; i ++) { ecs_rest_cmd_sync_capture_t *sync = &syncs[i]; ecs_strbuf_list_appendstr(&reply->body, sync->cmds); } ecs_strbuf_list_pop(&reply->body, "]"); ecs_strbuf_appendstr(&reply->body, "}"); return true; } static bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) { ecs_rest_ctx_t *impl = ctx; ecs_world_t *world = impl->world; if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } if (req->method == EcsHttpGet) { /* Entity endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_get_entity(world, req, reply); /* Component GET endpoint */ } else if (!ecs_os_strncmp(req->path, "component/", 10)) { return flecs_rest_get_component(world, req, reply, &req->path[10]); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_get_query(world, req, reply); /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_get_world(world, req, reply); /* Stats endpoint */ } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { return flecs_rest_get_stats(world, req, reply); /* Tables endpoint */ } else if (!ecs_os_strncmp(req->path, "tables", 6)) { return flecs_rest_get_tables(world, req, reply); /* Commands capture endpoint */ } else if (!ecs_os_strncmp(req->path, "commands/capture", 16)) { return flecs_rest_get_commands_capture(world, impl, req, reply); /* Commands request endpoint (request commands from specific frame) */ } else if (!ecs_os_strncmp(req->path, "commands/frame/", 15)) { return flecs_rest_get_commands_request(world, impl, req, reply); } } else if (req->method == EcsHttpPut) { /* Component PUT endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_put_entity(world, reply, &req->path[7]); /* Component PUT endpoint */ } else if (!ecs_os_strncmp(req->path, "component/", 10)) { return flecs_rest_put_component(world, req, reply, &req->path[10]); /* Enable endpoint */ } else if (!ecs_os_strncmp(req->path, "toggle/", 7)) { return flecs_rest_toggle(world, req, reply, &req->path[7]); /* Script endpoint */ } else if (!ecs_os_strncmp(req->path, "script/", 7)) { return flecs_rest_script(world, req, reply, &req->path[7]); } } else if (req->method == EcsHttpDelete) { /* Entity DELETE endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_delete_entity(world, reply, &req->path[7]); /* Component DELETE endpoint */ } else if (!ecs_os_strncmp(req->path, "component/", 10)) { return flecs_rest_delete_component(world, req, reply, &req->path[10]); } } return false; } ecs_http_server_t* ecs_rest_server_init( ecs_world_t *world, const ecs_http_server_desc_t *desc) { ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); ecs_http_server_desc_t private_desc = {0}; if (desc) { private_desc = *desc; } private_desc.callback = flecs_rest_reply; private_desc.ctx = srv_ctx; ecs_http_server_t *srv = ecs_http_server_init(&private_desc); if (!srv) { ecs_os_free(srv_ctx); return NULL; } srv_ctx->world = world; srv_ctx->srv = srv; srv_ctx->rc = 1; srv_ctx->srv = srv; return srv; } void ecs_rest_server_fini( ecs_http_server_t *srv) { ecs_rest_ctx_t *impl = ecs_http_server_ctx(srv); flecs_rest_server_garbage_collect_all(impl); ecs_os_free(impl); ecs_http_server_fini(srv); } static void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = it->ptrs[0]; int i; for(i = 0; i < it->count; i ++) { if (!rest[i].port) { rest[i].port = ECS_REST_DEFAULT_PORT; } ecs_http_server_t *srv = ecs_rest_server_init(it->real_world, &(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port, .cache_timeout = 0.2 }); if (!srv) { const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; ecs_err("failed to create REST server on %s:%u", ipaddr, rest[i].port); continue; } rest[i].impl = ecs_http_server_ctx(srv); ecs_http_server_start(srv); } } static void DequeueRest(ecs_iter_t *it) { EcsRest *rest = ecs_field(it, EcsRest, 0); if (it->delta_system_time > (ecs_ftime_t)1.0) { ecs_warn( "detected large progress interval (%.2fs), REST request may timeout", (double)it->delta_system_time); } const ecs_world_info_t *wi = ecs_get_world_info(it->world); int32_t i; for(i = 0; i < it->count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; if (ctx) { float elapsed = (float)(wi->world_time_total_raw - ctx->last_time); ecs_http_server_dequeue(ctx->srv, (ecs_ftime_t)elapsed); flecs_rest_server_garbage_collect(it->world, ctx); ctx->last_time = wi->world_time_total_raw; } } } static void DisableRest(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_iter_t rit = ecs_each_id(world, ecs_id(EcsRest)); if (it->event == EcsOnAdd) { /* REST module was disabled */ while (ecs_each_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 0); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_stop(ctx->srv); } } } else if (it->event == EcsOnRemove) { /* REST module was enabled */ while (ecs_each_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 0); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_start(ctx->srv); } } } } void FlecsRestImport( ecs_world_t *world) { ECS_MODULE(world, FlecsRest); ECS_IMPORT(world, FlecsPipeline); #ifdef FLECS_SCRIPT ECS_IMPORT(world, FlecsScript); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsRest), "Module that implements Flecs REST API"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsRest); ecs_set_hooks(world, EcsRest, { .ctor = flecs_default_ctor, .move = ecs_move(EcsRest), .copy = ecs_copy(EcsRest), .dtor = ecs_dtor(EcsRest), .on_set = flecs_on_set_rest }); ecs_system(world, { .entity = ecs_entity(world, {.name = "DequeueRest", .add = ecs_ids( ecs_dependson(EcsPostFrame))}), .query.terms = { { .id = ecs_id(EcsRest) }, }, .callback = DequeueRest, .immediate = true }); ecs_observer(world, { .query = { .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} }, .events = {EcsOnAdd, EcsOnRemove}, .callback = DisableRest }); ecs_set_name_prefix(world, "EcsRest"); ECS_TAG_DEFINE(world, EcsRestPlecs); /* Enable frame time measurements so we're guaranteed to have a delta time * value to pass into the HTTP server. */ if (ecs_os_has_time()) { ecs_measure_frame_time(world, true); } } #endif /** * @file addons/timer.c * @brief Timer addon. */ /** * @file addons/system/system.h * @brief Internal types and functions for system addon. */ #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #ifdef FLECS_SYSTEM #define ecs_system_t_magic (0x65637383) #define ecs_system_t_tag EcsSystem extern ecs_mixins_t ecs_system_t_mixins; /* 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 flecs_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, void *param); #endif #endif #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, 0); EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 1); ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); int i; for (i = 0; i < it->count; i ++) { tick_source[i].tick = false; if (!timer[i].active) { continue; } const ecs_world_info_t *info = ecs_get_world_info(it->world); ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; ecs_ftime_t timeout = timer[i].timeout; if (time_elapsed >= timeout) { ecs_ftime_t t = time_elapsed - timeout; if (t > timeout) { t = 0; } timer[i].time = t; /* Initialize with remainder */ tick_source[i].tick = true; tick_source[i].time_elapsed = time_elapsed - timer[i].overshoot; timer[i].overshoot = t; if (timer[i].single_shot) { timer[i].active = false; } } else { timer[i].time = time_elapsed; } } } static void ProgressRateFilters(ecs_iter_t *it) { EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 0); EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 1); int i; for (i = 0; i < it->count; i ++) { ecs_entity_t src = filter[i].src; bool inc = false; filter[i].time_elapsed += it->delta_time; if (src) { const EcsTickSource *tick_src = ecs_get( it->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, 0); /* If tick source has no filters, tick unconditionally */ int i; for (i = 0; i < it->count; i ++) { tick_src[i].tick = true; tick_src[i].time_elapsed = it->delta_time; } } ecs_entity_t ecs_set_timeout( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t timeout) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { timer = ecs_entity(world, {0}); } ecs_set(world, timer, EcsTimer, { .timeout = timeout, .single_shot = true, .active = true }); ecs_system_t *system_data = flecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_timeout( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } ecs_entity_t ecs_set_interval( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t interval) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { timer = ecs_new_w(world, EcsTimer); } EcsTimer *t = ecs_ensure(world, timer, EcsTimer); ecs_check(t != NULL, ECS_INTERNAL_ERROR, NULL); t->timeout = interval; t->active = true; ecs_modified(world, timer, EcsTimer); ecs_system_t *system_data = flecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_interval( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { return 0; } const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } void ecs_start_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr->active = true; ptr->time = 0; error: return; } void ecs_stop_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr->active = false; error: return; } void ecs_reset_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_ensure(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ptr->time = 0; error: return; } ecs_entity_t ecs_set_rate( ecs_world_t *world, ecs_entity_t filter, int32_t rate, ecs_entity_t source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!filter) { filter = ecs_entity(world, {0}); } ecs_set(world, filter, EcsRateFilter, { .rate = rate, .src = source }); ecs_system_t *system_data = flecs_poly_get(world, filter, ecs_system_t); if (system_data) { system_data->tick_source = filter; } error: return filter; } void ecs_set_tick_source( ecs_world_t *world, ecs_entity_t system, ecs_entity_t tick_source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); system_data->tick_source = tick_source; error: return; } static void RandomizeTimers(ecs_iter_t *it) { EcsTimer *timer = ecs_field(it, EcsTimer, 0); int32_t i; for (i = 0; i < it->count; i ++) { timer[i].time = ((ecs_ftime_t)rand() / (ecs_ftime_t)RAND_MAX) * timer[i].timeout; } } void ecs_randomize_timers( ecs_world_t *world) { ecs_observer(world, { .entity = ecs_entity(world, { .name = "flecs.timer.RandomizeTimers" }), .query.terms = {{ .id = ecs_id(EcsTimer) }}, .events = {EcsOnSet}, .yield_existing = true, .callback = RandomizeTimers }); } void FlecsTimerImport( ecs_world_t *world) { ECS_MODULE(world, FlecsTimer); ECS_IMPORT(world, FlecsPipeline); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsTimer), "Module that implements system timers (used by .interval)"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTimer); flecs_bootstrap_component(world, EcsRateFilter); ecs_set_hooks(world, EcsTimer, { .ctor = flecs_default_ctor }); /* Add EcsTickSource to timers and rate filters */ ecs_system(world, { .entity = ecs_entity(world, {.name = "AddTickSource", .add = ecs_ids( ecs_dependson(EcsPreFrame) )}), .query.terms = { { .id = ecs_id(EcsTimer), .oper = EcsOr }, { .id = ecs_id(EcsRateFilter), .oper = EcsAnd }, { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} }, .callback = AddTickSource }); /* Timer handling */ ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressTimers", .add = ecs_ids( ecs_dependson(EcsPreFrame))}), .query.terms = { { .id = ecs_id(EcsTimer) }, { .id = ecs_id(EcsTickSource) } }, .callback = ProgressTimers }); /* Rate filter handling */ ecs_system(world, { .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = ecs_ids( ecs_dependson(EcsPreFrame))}), .query.terms = { { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .inout = EcsOut } }, .callback = ProgressRateFilters }); /* TickSource without a timer or rate filter just increases each frame */ ecs_system(world, { .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = ecs_ids( ecs_dependson(EcsPreFrame))}), .query.terms = { { .id = ecs_id(EcsTickSource), .inout = EcsOut }, { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, { .id = ecs_id(EcsTimer), .oper = EcsNot } }, .callback = ProgressTickSource }); } #endif /** * @file addons/units.c * @brief Units addon. */ #ifdef FLECS_UNITS void FlecsUnitsImport( ecs_world_t *world) { ECS_MODULE(world, FlecsUnits); ECS_IMPORT(world, FlecsMeta); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsUnits), "Module with (amongst others) SI units for annotating component members"); #endif ecs_set_name_prefix(world, "Ecs"); EcsUnitPrefixes = ecs_entity(world, { .name = "prefixes", .add = ecs_ids( 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); /* Color */ EcsColor = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Color" }); prev_scope = ecs_set_scope(world, EcsColor); EcsColorRgb = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Rgb" }), .quantity = EcsColor }); EcsColorHsl = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hsl" }), .quantity = EcsColor }); EcsColorCss = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Css" }), .quantity = EcsColor }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsColorCss, .kind = EcsString }); 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 }); /* Frequency */ EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Frequency" }); prev_scope = ecs_set_scope(world, EcsFrequency); EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hertz" }), .quantity = EcsFrequency, .symbol = "Hz" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsHertz, .kind = EcsF32 }); EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloHertz" }), .prefix = EcsKilo, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloHertz, .kind = EcsF32 }); EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaHertz" }), .prefix = EcsMega, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaHertz, .kind = EcsF32 }); EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaHertz" }), .prefix = EcsGiga, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaHertz, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Uri" }); prev_scope = ecs_set_scope(world, EcsUri); EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hyperlink" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriHyperlink, .kind = EcsString }); EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Image" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriImage, .kind = EcsString }); EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "File" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriFile, .kind = EcsString }); ecs_set_scope(world, prev_scope); /* Documentation */ #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, EcsDuration, "Time amount (e.g. \"20 seconds\", \"2 hours\")"); ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); ecs_doc_set_brief(world, EcsHours, "60 minutes"); ecs_doc_set_brief(world, EcsDays, "24 hours"); ecs_doc_set_brief(world, EcsTime, "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); ecs_doc_set_brief(world, EcsDate, "Seconds passed since January 1st 1970"); ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); ecs_doc_set_brief(world, EcsElectricCurrent, "Units of electrical current (e.g. \"2 ampere\")"); ecs_doc_set_brief(world, EcsAmount, "Units of amount of substance (e.g. \"2 mole\")"); ecs_doc_set_brief(world, EcsLuminousIntensity, "Units of luminous intensity (e.g. \"1 candela\")"); ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); ecs_doc_set_brief(world, EcsLength, "Units of length (e.g. \"5 meters\", \"20 miles\")"); ecs_doc_set_brief(world, EcsPressure, "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); ecs_doc_set_brief(world, EcsSpeed, "Units of movement (e.g. \"5 meters/second\")"); ecs_doc_set_brief(world, EcsAcceleration, "Unit of speed increase (e.g. \"5 meters/second/second\")"); ecs_doc_set_brief(world, EcsTemperature, "Units of temperature (e.g. \"5 degrees Celsius\")"); ecs_doc_set_brief(world, EcsData, "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); ecs_doc_set_brief(world, EcsDataRate, "Units of data transmission (e.g. \"100 megabits/second\")"); ecs_doc_set_brief(world, EcsAngle, "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); ecs_doc_set_brief(world, EcsFrequency, "The number of occurrences of a repeating event per unit of time."); ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); #endif } #endif /** * @file datastructures/allocator.c * @brief Allocator for any size. * * Allocators create a block allocator for each requested size. */ static ecs_size_t flecs_allocator_size( ecs_size_t size) { return ECS_ALIGN(size, 16); } static ecs_size_t flecs_allocator_size_hash( ecs_size_t size) { return size >> 4; } void flecs_allocator_init( ecs_allocator_t *a) { flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, FLECS_SPARSE_PAGE_SIZE); flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); } void flecs_allocator_fini( ecs_allocator_t *a) { ecs_assert(a != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i = 0, count = flecs_sparse_count(&a->sizes); for (i = 0; i < count; i ++) { ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( &a->sizes, ecs_block_allocator_t, i); flecs_ballocator_fini(ba); } flecs_sparse_fini(&a->sizes); flecs_ballocator_fini(&a->chunks); } ecs_block_allocator_t* flecs_allocator_get( ecs_allocator_t *a, ecs_size_t size) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); if (!size) { return NULL; } ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); size = flecs_allocator_size(size); ecs_size_t hash = flecs_allocator_size_hash(size); ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes, ecs_block_allocator_t, (uint32_t)hash); if (!result) { result = flecs_sparse_ensure_fast_t(&a->sizes, ecs_block_allocator_t, (uint32_t)hash); flecs_ballocator_init(result, size); } ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); return result; } char* flecs_strdup( ecs_allocator_t *a, const char* str) { ecs_size_t len = ecs_os_strlen(str); char *result = flecs_alloc_n(a, char, len + 1); ecs_os_memcpy(result, str, len + 1); return result; } void flecs_strfree( ecs_allocator_t *a, char* str) { ecs_size_t len = ecs_os_strlen(str); flecs_free_n(a, char, len + 1, str); } void* flecs_dup( ecs_allocator_t *a, ecs_size_t size, const void *src) { ecs_block_allocator_t *ba = flecs_allocator_get(a, size); if (ba) { void *dst = flecs_balloc(ba); ecs_os_memcpy(dst, src, size); return dst; } else { return NULL; } } /** * @file datastructures/bitset.c * @brief Bitset data structure. * * Simple bitset implementation. The bitset allows for storage of arbitrary * numbers of bits. */ static void ensure( ecs_bitset_t *bs, ecs_size_t size) { if (!bs->size) { int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->size = ((size - 1) / 64 + 1) * 64; bs->data = ecs_os_calloc(new_size); } else if (size > bs->size) { int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->size = ((size - 1) / 64 + 1) * 64; int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->data = ecs_os_realloc(bs->data, new_size); ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); } } void flecs_bitset_init( ecs_bitset_t* bs) { bs->size = 0; bs->count = 0; bs->data = NULL; } void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count) { if (count > bs->count) { bs->count = count; ensure(bs, count); } } void flecs_bitset_fini( ecs_bitset_t *bs) { ecs_os_free(bs->data); bs->data = NULL; bs->count = 0; } void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count) { int32_t elem = bs->count += count; ensure(bs, elem); } void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); uint32_t hi = ((uint32_t)elem) >> 6; uint32_t lo = ((uint32_t)elem) & 0x3F; uint64_t v = bs->data[hi]; bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); error: return; } bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); error: return false; } int32_t flecs_bitset_count( const ecs_bitset_t *bs) { return bs->count; } void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); int32_t last = bs->count - 1; bool last_value = flecs_bitset_get(bs, last); flecs_bitset_set(bs, elem, last_value); flecs_bitset_set(bs, last, 0); bs->count --; error: return; } void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b) { ecs_check(elem_a < bs->count, ECS_INVALID_PARAMETER, NULL); ecs_check(elem_b < bs->count, ECS_INVALID_PARAMETER, NULL); bool a = flecs_bitset_get(bs, elem_a); bool b = flecs_bitset_get(bs, elem_b); flecs_bitset_set(bs, elem_a, b); flecs_bitset_set(bs, elem_b, a); error: return; } /** * @file datastructures/block_allocator.c * @brief Block allocator. * * A block allocator is an allocator for a fixed size that allocates blocks of * memory with N elements of the requested size. */ // #ifdef FLECS_SANITIZE // #define FLECS_MEMSET_UNINITIALIZED // #endif int64_t ecs_block_allocator_alloc_count = 0; int64_t ecs_block_allocator_free_count = 0; #ifndef FLECS_USE_OS_ALLOC static ecs_block_allocator_chunk_header_t* flecs_balloc_block( ecs_block_allocator_t *allocator) { if (!allocator->chunk_size) { return NULL; } ecs_block_allocator_block_t *block = ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + allocator->block_size); ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, ECS_SIZEOF(ecs_block_allocator_block_t)); block->memory = first_chunk; if (!allocator->block_tail) { ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); block->next = NULL; allocator->block_head = block; allocator->block_tail = block; } else { block->next = NULL; allocator->block_tail->next = block; allocator->block_tail = block; } ecs_block_allocator_chunk_header_t *chunk = first_chunk; int32_t i, end; for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); chunk = chunk->next; } ecs_os_linc(&ecs_block_allocator_alloc_count); chunk->next = NULL; return first_chunk; } #endif void flecs_ballocator_init( ecs_block_allocator_t *ba, ecs_size_t size) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ba->data_size = size; #ifdef FLECS_SANITIZE ba->alloc_count = 0; size += ECS_SIZEOF(int64_t); #endif ba->chunk_size = ECS_ALIGN(size, 16); ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); ba->block_size = ba->chunks_per_block * ba->chunk_size; ba->head = NULL; ba->block_head = NULL; ba->block_tail = NULL; } ecs_block_allocator_t* flecs_ballocator_new( ecs_size_t size) { ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); flecs_ballocator_init(result, size); return result; } void flecs_ballocator_fini( ecs_block_allocator_t *ba) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, "(size = %u)", (uint32_t)ba->data_size); #endif ecs_block_allocator_block_t *block; for (block = ba->block_head; block;) { ecs_block_allocator_block_t *next = block->next; ecs_os_free(block); ecs_os_linc(&ecs_block_allocator_free_count); block = next; } ba->block_head = NULL; } void flecs_ballocator_free( ecs_block_allocator_t *ba) { flecs_ballocator_fini(ba); ecs_os_free(ba); } void* flecs_balloc( ecs_block_allocator_t *ba) { void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_malloc(ba->data_size); #else if (!ba) return NULL; if (!ba->head) { ba->head = flecs_balloc_block(ba); ecs_assert(ba->head != NULL, ECS_INTERNAL_ERROR, NULL); } result = ba->head; ba->head = ba->head->next; #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); ba->alloc_count ++; *(int64_t*)result = ba->chunk_size; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #endif #endif #ifdef FLECS_MEMSET_UNINITIALIZED ecs_os_memset(result, 0xAA, ba->data_size); #endif return result; } void* flecs_bcalloc( ecs_block_allocator_t *ba) { #ifdef FLECS_USE_OS_ALLOC return ecs_os_calloc(ba->data_size); #else if (!ba) return NULL; void *result = flecs_balloc(ba); ecs_os_memset(result, 0, ba->data_size); return result; #endif } void flecs_bfree( ecs_block_allocator_t *ba, void *memory) { flecs_bfree_w_dbg_info(ba, memory, NULL); } FLECS_API void flecs_bfree_w_dbg_info( ecs_block_allocator_t *ba, void *memory, const char *type_name) { (void)type_name; #ifdef FLECS_USE_OS_ALLOC (void)ba; ecs_os_free(memory); return; #else if (!ba) { ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); return; } if (memory == NULL) { return; } #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); if (*(int64_t*)memory != ba->chunk_size) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", memory, *(int64_t*)memory, ba->chunk_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", memory, *(int64_t*)memory, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } ba->alloc_count --; #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); #endif } void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) { void *result; #ifdef FLECS_USE_OS_ALLOC (void)src; result = ecs_os_realloc(memory, dst->data_size); #else if (dst == src) { return memory; } result = flecs_balloc(dst); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { size = dst->data_size; } ecs_os_memcpy(result, memory, size); } flecs_bfree(src, memory); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, dst->data_size - src->data_size); } else if (dst && !src) { ecs_os_memset(result, 0xAA, dst->data_size); } #endif return result; } void* flecs_bdup( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC if (memory && ba->chunk_size) { return ecs_os_memdup(memory, ba->data_size); } else { return NULL; } #else void *result = flecs_balloc(ba); if (result) { ecs_os_memcpy(result, memory, ba->data_size); } return result; #endif } // This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) // main repo: https://github.com/wangyi-fudan/wyhash // author: 王一 Wang Yi // contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, // Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, // Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, // hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric /* quick example: string s="fjsakfdsjkf"; uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); */ #ifndef WYHASH_CONDOM //protections that produce different results: //1: normal valid behavior //2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" #define WYHASH_CONDOM 1 #endif #ifndef WYHASH_32BIT_MUM //0: normal version, slow on 32 bit systems //1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function #define WYHASH_32BIT_MUM 0 #endif //includes #include #include #if defined(_MSC_VER) && defined(_M_X64) #include #pragma intrinsic(_umul128) #endif //likely and unlikely macros #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) #define likely_(x) __builtin_expect(x,1) #define unlikely_(x) __builtin_expect(x,0) #else #define likely_(x) (x) #define unlikely_(x) (x) #endif //128bit multiply function static inline void wymum_(uint64_t *A, uint64_t *B){ #if(WYHASH_32BIT_MUM) uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; #if(WYHASH_CONDOM>1) *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; #else *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; #endif #elif defined(__SIZEOF_INT128__) __uint128_t r=*A; r*=*B; #if(WYHASH_CONDOM>1) *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); #else *A=(uint64_t)r; *B=(uint64_t)(r>>64); #endif #elif defined(_MSC_VER) && defined(_M_X64) #if(WYHASH_CONDOM>1) uint64_t a, b; a=_umul128(*A,*B,&b); *A^=a; *B^=b; #else *A=_umul128(*A,*B,B); #endif #else uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; #if(WYHASH_CONDOM>1) *A^=lo; *B^=hi; #else *A=lo; *B=hi; #endif #endif } //multiply and xor mix function, aka MUM static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } //endian macros #ifndef WYHASH_LITTLE_ENDIAN #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 1 #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 0 #else #warning could not determine endianness! Falling back to little endian. #define WYHASH_LITTLE_ENDIAN 1 #endif #endif //read functions #if (WYHASH_LITTLE_ENDIAN) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} #elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} #elif defined(_MSC_VER) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} #else static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); } static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); } #endif static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} //wyhash main function static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; if(likely_(len<=16)){ if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} else a=b=0; } else{ size_t i=len; if(unlikely_(i>48)){ uint64_t see1=seed, see2=seed; do{ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); p+=48; i-=48; }while(likely_(i>48)); seed^=see1^see2; } while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } a=wyr8_(p+i-16); b=wyr8_(p+i-8); } a^=secret[1]; b^=seed; wymum_(&a,&b); return wymix_(a^secret[0]^len,b^secret[1]); } //the default secret parameters static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; uint64_t flecs_hash( const void *data, ecs_size_t length) { return wyhash(data, flecs_ito(size_t, length), 0, wyp_); } /** * @file datastructures/hashmap.c * @brief Hashmap data structure. * * The hashmap data structure is built on top of the map data structure. Where * the map data structure can only work with 64bit key values, the hashmap can * hash keys of any size, and handles collisions between hashes. */ static int32_t flecs_hashmap_find_key( const ecs_hashmap_t *map, ecs_vec_t *keys, ecs_size_t key_size, const void *key) { int32_t i, count = ecs_vec_count(keys); void *key_array = ecs_vec_first(keys); for (i = 0; i < count; i ++) { void *key_ptr = ECS_OFFSET(key_array, key_size * i); if (map->compare(key_ptr, key) == 0) { return i; } } return -1; } void flecs_hashmap_init_( ecs_hashmap_t *map, ecs_size_t key_size, ecs_size_t value_size, ecs_hash_value_action_t hash, ecs_compare_action_t compare, ecs_allocator_t *allocator) { map->key_size = key_size; map->value_size = value_size; map->hash = hash; map->compare = compare; flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); ecs_map_init(&map->impl, allocator); } void flecs_hashmap_fini( ecs_hashmap_t *map) { ecs_allocator_t *a = map->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&map->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); #ifdef FLECS_SANITIZE flecs_bfree(&map->bucket_allocator, bucket); #endif } flecs_ballocator_fini(&map->bucket_allocator); ecs_map_fini(&map->impl); } void flecs_hashmap_copy( ecs_hashmap_t *dst, const ecs_hashmap_t *src) { ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, src->compare, src->impl.allocator); ecs_map_copy(&dst->impl, &src->impl); ecs_allocator_t *a = dst->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&dst->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); bucket_ptr[0] = dst_bucket; dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); } } void* flecs_hashmap_get_( const ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return NULL; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return NULL; } return ecs_vec_get(&bucket->values, value_size, index); } flecs_hashmap_result_t flecs_hashmap_ensure_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); ecs_hm_bucket_t *bucket = r[0]; if (!bucket) { bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); } ecs_allocator_t *a = map->impl.allocator; void *value_ptr, *key_ptr; ecs_vec_t *keys = &bucket->keys; ecs_vec_t *values = &bucket->values; if (!keys->array) { ecs_vec_init(a, &bucket->keys, key_size, 1); ecs_vec_init(a, &bucket->values, value_size, 1); keys = &bucket->keys; values = &bucket->values; key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); if (index == -1) { key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { key_ptr = ecs_vec_get(keys, key_size, index); value_ptr = ecs_vec_get(values, value_size, index); } } return (flecs_hashmap_result_t){ .key = key_ptr, .value = value_ptr, .hash = hash }; } void flecs_hashmap_set_( ecs_hashmap_t *map, ecs_size_t key_size, void *key, ecs_size_t value_size, const void *value) { void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy(value_ptr, value, value_size); } ecs_hm_bucket_t* flecs_hashmap_get_bucket( const ecs_hashmap_t *map, uint64_t hash) { ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); } void flecs_hm_bucket_remove( ecs_hashmap_t *map, ecs_hm_bucket_t *bucket, uint64_t hash, int32_t index) { ecs_vec_remove(&bucket->keys, map->key_size, index); ecs_vec_remove(&bucket->values, map->value_size, index); if (!ecs_vec_count(&bucket->keys)) { ecs_allocator_t *a = map->impl.allocator; ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; flecs_bfree(&map->bucket_allocator, bucket); } } void flecs_hashmap_remove_w_hash_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size, uint64_t hash) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); (void)value_size; ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return; } flecs_hm_bucket_remove(map, bucket, hash, index); } void flecs_hashmap_remove_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); } flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map) { return (flecs_hashmap_iter_t){ .it = ecs_map_iter(&map->impl) }; } void* flecs_hashmap_next_( flecs_hashmap_iter_t *it, ecs_size_t key_size, void *key_out, ecs_size_t value_size) { int32_t index = ++ it->index; ecs_hm_bucket_t *bucket = it->bucket; while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { ecs_map_next(&it->it); bucket = it->bucket = ecs_map_ptr(&it->it); if (!bucket) { return NULL; } index = it->index = 0; } if (key_out) { *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); } return ecs_vec_get(&bucket->values, value_size, index); } /** * @file datastructures/map.c * @brief Map data structure. * * Map data structure for 64bit keys and dynamic payload size. */ /* The ratio used to determine whether the map should flecs_map_rehash. If * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ #define ECS_LOAD_FACTOR (12) #define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) static uint8_t flecs_log2(uint32_t v) { static const uint8_t log2table[32] = {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } /* Get bucket count for number of elements */ static int32_t flecs_map_get_bucket_count( int32_t count) { return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); } /* Get bucket shift amount for a given bucket count */ static uint8_t flecs_map_get_bucket_shift ( int32_t bucket_count) { return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); } /* Get bucket index for provided map key */ static int32_t flecs_map_get_bucket_index( uint16_t bucket_shift, ecs_map_key_t key) { ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); return (int32_t)((11400714819323198485ull * key) >> bucket_shift); } /* Get bucket for key */ static ecs_bucket_t* flecs_map_get_bucket( const ecs_map_t *map, ecs_map_key_t key) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key); ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); return &map->buckets[bucket_id]; } /* Add element to bucket */ static ecs_map_val_t* flecs_map_bucket_add( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); new_entry->key = key; new_entry->next = bucket->first; bucket->first = new_entry; return &new_entry->value; } /* Remove element from bucket */ static ecs_map_val_t flecs_map_bucket_remove( ecs_map_t *map, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { ecs_map_val_t value = entry->value; ecs_bucket_entry_t **next_holder = &bucket->first; while(*next_holder != entry) { next_holder = &(*next_holder)->next; } *next_holder = entry->next; flecs_bfree(map->entry_allocator, entry); map->count --; return value; } } return 0; } /* Free contents of bucket */ static void flecs_map_bucket_clear( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket) { ecs_bucket_entry_t *entry = bucket->first; while(entry) { ecs_bucket_entry_t *next = entry->next; flecs_bfree(allocator, entry); entry = next; } } /* Get payload pointer for key from bucket */ static ecs_map_val_t* flecs_map_bucket_get( ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { return &entry->value; } } return NULL; } /* Grow number of buckets */ static void flecs_map_rehash( ecs_map_t *map, int32_t count) { count = flecs_next_pow_of_2(count); if (count < 2) { count = 2; } ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); int32_t old_count = map->bucket_count; ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); if (map->allocator) { map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); } else { map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); } map->bucket_count = count; map->bucket_shift = flecs_map_get_bucket_shift(count); /* Remap old bucket entries to new buckets */ for (b = buckets; b < end; b++) { ecs_bucket_entry_t* entry; for (entry = b->first; entry;) { ecs_bucket_entry_t* next = entry->next; int32_t bucket_index = flecs_map_get_bucket_index( map->bucket_shift, entry->key); ecs_bucket_t *bucket = &map->buckets[bucket_index]; entry->next = bucket->first; bucket->first = entry; entry = next; } } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); } else { ecs_os_free(buckets); } } void ecs_map_params_init( ecs_map_params_t *params, ecs_allocator_t *allocator) { params->allocator = allocator; flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); } void ecs_map_params_fini( ecs_map_params_t *params) { flecs_ballocator_fini(¶ms->entry_allocator); } void ecs_map_init_w_params( ecs_map_t *result, ecs_map_params_t *params) { ecs_os_zeromem(result); result->allocator = params->allocator; if (params->entry_allocator.chunk_size) { result->entry_allocator = ¶ms->entry_allocator; result->shared_allocator = true; } else { result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); } flecs_map_rehash(result, 0); } void ecs_map_init_w_params_if( ecs_map_t *result, ecs_map_params_t *params) { if (!ecs_map_is_init(result)) { ecs_map_init_w_params(result, params); } } void ecs_map_init( ecs_map_t *result, ecs_allocator_t *allocator) { ecs_map_init_w_params(result, &(ecs_map_params_t) { .allocator = allocator }); } void ecs_map_init_if( ecs_map_t *result, ecs_allocator_t *allocator) { if (!ecs_map_is_init(result)) { ecs_map_init(result, allocator); } } void ecs_map_fini( ecs_map_t *map) { if (!ecs_map_is_init(map)) { return; } bool sanitize = false; #ifdef FLECS_SANITIZE sanitize = true; #endif /* Free buckets in sanitized mode, so we can replace the allocator with * regular malloc/free and use asan/valgrind to find memory errors. */ ecs_allocator_t *a = map->allocator; ecs_block_allocator_t *ea = map->entry_allocator; if (map->shared_allocator || sanitize) { ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; while (bucket != end) { flecs_map_bucket_clear(ea, bucket); bucket ++; } } if (ea && !map->shared_allocator) { flecs_ballocator_free(ea); map->entry_allocator = NULL; } if (a) { flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); } else { ecs_os_free(map->buckets); } map->bucket_shift = 0; } ecs_map_val_t* ecs_map_get( const ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); } void* ecs_map_get_deref_( const ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t* ptr = flecs_map_bucket_get( flecs_map_get_bucket(map, key), key); if (ptr) { return (void*)(uintptr_t)ptr[0]; } return NULL; } void ecs_map_insert( ecs_map_t *map, ecs_map_key_t key, ecs_map_val_t value) { ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); } ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; } void* ecs_map_insert_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { void *elem = ecs_os_calloc(elem_size); ecs_map_insert_ptr(map, key, (uintptr_t)elem); return elem; } ecs_map_val_t* ecs_map_ensure( ecs_map_t *map, ecs_map_key_t key) { ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); if (result) { return result; } int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); bucket = flecs_map_get_bucket(map, key); } ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); *v = 0; return v; } void* ecs_map_ensure_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { ecs_map_val_t *val = ecs_map_ensure(map, key); if (!*val) { void *elem = ecs_os_calloc(elem_size); *val = (ecs_map_val_t)(uintptr_t)elem; return elem; } else { return (void*)(uintptr_t)*val; } } ecs_map_val_t ecs_map_remove( ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); } void ecs_map_remove_free( ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t val = ecs_map_remove(map, key); if (val) { ecs_os_free((void*)(uintptr_t)val); } } void ecs_map_clear( ecs_map_t *map) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = map->bucket_count; for (i = 0; i < count; i ++) { flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]); } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); } else { ecs_os_free(map->buckets); } map->buckets = NULL; map->bucket_count = 0; map->count = 0; flecs_map_rehash(map, 2); } ecs_map_iter_t ecs_map_iter( const ecs_map_t *map) { if (ecs_map_is_init(map)) { return (ecs_map_iter_t){ .map = map, .bucket = NULL, .entry = NULL }; } else { return (ecs_map_iter_t){ 0 }; } } bool ecs_map_next( ecs_map_iter_t *iter) { const ecs_map_t *map = iter->map; ecs_bucket_t *end; if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { return false; } ecs_bucket_entry_t *entry = NULL; if (!iter->bucket) { for (iter->bucket = map->buckets; iter->bucket != end; ++iter->bucket) { if (iter->bucket->first) { entry = iter->bucket->first; break; } } if (iter->bucket == end) { return false; } } else if ((entry = iter->entry) == NULL) { do { ++iter->bucket; if (iter->bucket == end) { return false; } } while(!iter->bucket->first); entry = iter->bucket->first; } ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); iter->entry = entry->next; iter->res = &entry->key; return true; } void ecs_map_copy( ecs_map_t *dst, const ecs_map_t *src) { if (ecs_map_is_init(dst)) { ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); ecs_map_fini(dst); } if (!ecs_map_is_init(src)) { return; } ecs_map_init(dst, src->allocator); ecs_map_iter_t it = ecs_map_iter(src); while (ecs_map_next(&it)) { ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); } } /** * @file datastructures/name_index.c * @brief Data structure for resolving 64bit keys by string (name). */ static uint64_t flecs_name_index_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } static int flecs_name_index_compare( const void *ptr1, const void *ptr2) { const ecs_hashed_string_t *str1 = ptr1; const ecs_hashed_string_t *str2 = ptr2; ecs_size_t len1 = str1->length; ecs_size_t len2 = str2->length; if (len1 != len2) { return (len1 > len2) - (len1 < len2); } return ecs_os_memcmp(str1->value, str2->value, len1); } void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { flecs_hashmap_init_(hm, ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), flecs_name_index_hash, flecs_name_index_compare, allocator); } void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { if (!hm->compare) { flecs_name_index_init(hm, allocator); } } bool flecs_name_index_is_init( const ecs_hashmap_t *hm) { return hm->compare != NULL; } ecs_hashmap_t* flecs_name_index_new( ecs_world_t *world, ecs_allocator_t *allocator) { ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); flecs_name_index_init(result, allocator); result->hashmap_allocator = &world->allocators.hashmap; return result; } void flecs_name_index_fini( ecs_hashmap_t *map) { flecs_hashmap_fini(map); } void flecs_name_index_free( ecs_hashmap_t *map) { if (map) { flecs_name_index_fini(map); flecs_bfree(map->hashmap_allocator, map); } } ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *map) { ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); result->hashmap_allocator = map->hashmap_allocator; flecs_hashmap_copy(result, map); return result; } ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash) { if (!length) { length = ecs_os_strlen(name); } else { ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); } if (!hash) { hash = flecs_hash(name, length); } else { ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); } return (ecs_hashed_string_t) { .value = ECS_CONST_CAST(char*, name), .length = length, .hash = hash }; } const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); if (!b) { return NULL; } ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); int32_t i, count = ecs_vec_count(&b->keys); for (i = 0; i < count; i ++) { ecs_hashed_string_t *key = &keys[i]; ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); if (hs.length != key->length) { continue; } if (!ecs_os_strcmp(name, key->value)) { uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); return e; } } return NULL; } uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); if (id) { return id[0]; } return 0; } void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t e, uint64_t hash) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { flecs_hm_bucket_remove(map, b, hash, i); break; } } } void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { ecs_hashed_string_t *key = ecs_vec_get_t( &b->keys, ecs_hashed_string_t, i); key->value = ECS_CONST_CAST(char*, name); ecs_assert(ecs_os_strlen(name) == key->length, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_hash(name, key->length) == key->hash, ECS_INTERNAL_ERROR, NULL); return; } } /* Record must already have been in the index */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash) { ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); uint64_t existing = flecs_name_index_find( map, name, key.length, key.hash); if (existing) { if (existing != id) { ecs_abort(ECS_ALREADY_DEFINED, "conflicting id registered with name '%s'", name); } } flecs_hashmap_result_t hmr = flecs_hashmap_ensure( map, &key, uint64_t); *((uint64_t*)hmr.value) = id; error: return; } /** * @file datastructures/sparse.c * @brief Sparse set data structure. */ /* Utility to get a pointer to the payload */ #define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) typedef struct ecs_page_t { int32_t *sparse; /* Sparse array with indices to dense array */ void *data; /* Store data in sparse array to reduce * indirection and provide stable pointers. */ } ecs_page_t; static ecs_page_t* flecs_sparse_page_new( ecs_sparse_t *sparse, int32_t page_index) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; int32_t count = ecs_vec_count(&sparse->pages); ecs_page_t *pages; if (count <= page_index) { ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); } else { pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); } ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); ecs_page_t *result = &pages[page_index]; ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); /* Initialize sparse array with zero's, as zero is used to indicate that the * sparse element has not been paired with a dense element. Use zero * as this means we can take advantage of calloc having a possibly better * performance than malloc + memset. */ result->sparse = ca ? flecs_bcalloc(ca) : ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); /* Initialize the data array with zero's to guarantee that data is * always initialized. When an entry is removed, data is reset back to * zero. Initialize now, as this can take advantage of calloc. */ result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); return result; } static void flecs_sparse_page_free( ecs_sparse_t *sparse, ecs_page_t *page) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; if (ca) { flecs_bfree(ca, page->sparse); } else { ecs_os_free(page->sparse); } if (a) { flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); } else { ecs_os_free(page->data); } } static ecs_page_t* flecs_sparse_get_page( const ecs_sparse_t *sparse, int32_t page_index) { ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); if (page_index >= ecs_vec_count(&sparse->pages)) { return NULL; } return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index); } static ecs_page_t* flecs_sparse_get_or_create_page( ecs_sparse_t *sparse, int32_t page_index) { ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); if (page && page->sparse) { return page; } return flecs_sparse_page_new(sparse, page_index); } static void flecs_sparse_grow_dense( ecs_sparse_t *sparse) { ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } static uint64_t flecs_sparse_strip_generation( uint64_t *index_out) { uint64_t index = *index_out; uint64_t gen = index & ECS_GENERATION_MASK; /* Make sure there's no junk in the id */ ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), ECS_INVALID_PARAMETER, NULL); *index_out -= gen; return gen; } static void flecs_sparse_assign_index( ecs_page_t * page, uint64_t * dense_array, uint64_t index, int32_t dense) { /* Initialize sparse-dense pair. This assigns the dense index to the sparse * array, and the sparse index to the dense array .*/ page->sparse[FLECS_SPARSE_OFFSET(index)] = dense; dense_array[dense] = index; } static uint64_t flecs_sparse_inc_gen( uint64_t index) { /* When an index is deleted, its generation is increased so that we can do * liveliness checking while recycling ids */ return ECS_GENERATION_INC(index); } static uint64_t flecs_sparse_inc_id( ecs_sparse_t *sparse) { /* Generate a new id. The last issued id could be stored in an external * variable, such as is the case with the last issued entity id, which is * stored on the world. */ return ++ sparse->max_id; } static uint64_t flecs_sparse_get_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); return sparse->max_id; } static void flecs_sparse_set_id( ecs_sparse_t *sparse, uint64_t value) { /* Sometimes the max id needs to be assigned directly, which typically * happens when the API calls get_or_create for an id that hasn't been * issued before. */ sparse->max_id = value; } /* Pair dense id with new sparse id */ static uint64_t flecs_sparse_create_id( ecs_sparse_t *sparse, int32_t dense) { uint64_t index = flecs_sparse_inc_id(sparse); flecs_sparse_grow_dense(sparse); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); ecs_assert(page->sparse[FLECS_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, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = FLECS_SPARSE_OFFSET(index); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); (void)dense; return DATA(page->data, sparse->size, offset); } /* Swap dense elements. A swap occurs when an element is removed, or when a * removed element is recycled. */ static void flecs_sparse_swap_dense( ecs_sparse_t * sparse, ecs_page_t * page_a, int32_t a, int32_t b) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t index_a = dense_array[a]; uint64_t index_b = dense_array[b]; ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index_b)); flecs_sparse_assign_index(page_a, dense_array, index_a, b); flecs_sparse_assign_index(page_b, dense_array, index_b, a); } void flecs_sparse_init( ecs_sparse_t *result, struct ecs_allocator_t *allocator, ecs_block_allocator_t *page_allocator, ecs_size_t size) { ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); result->size = size; result->max_id = UINT64_MAX; result->allocator = allocator; result->page_allocator = page_allocator; ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); result->dense.count = 1; /* Consume first value in dense array as 0 is used in the sparse array to * indicate that a sparse element hasn't been paired yet. */ ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; result->count = 1; } void flecs_sparse_clear( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { int32_t *indices = pages[i].sparse; if (indices) { ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); } } ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); sparse->count = 1; sparse->max_id = 0; } void flecs_sparse_fini( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { flecs_sparse_page_free(sparse, &pages[i]); } ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } uint64_t flecs_sparse_new_id( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_sparse_new_index(sparse); } void* flecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t size) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); uint64_t index = flecs_sparse_new_index(sparse); ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, size, FLECS_SPARSE_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, FLECS_SPARSE_PAGE(index)); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Check if element is alive. If element is not alive, update indices so * that the first unused dense element points to the sparse element. */ int32_t count = sparse->count; if (dense >= count) { /* If dense is not alive, swap it with the first unused element. */ flecs_sparse_swap_dense(sparse, page, dense, count); dense = count; /* First unused element is now last used element */ sparse->count ++; /* Set dense element to new generation */ ecs_vec_first_t(&sparse->dense, uint64_t)[dense] = index | gen; } 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, FLECS_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, FLECS_SPARSE_PAGE(index)); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; int32_t count = sparse->count; if (!dense) { /* Element is not paired yet. Must add a new element to dense array */ sparse->count = count + 1; if (count == ecs_vec_count(&sparse->dense)) { flecs_sparse_grow_dense(sparse); } uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, count); } return DATA(page->data, sparse->size, offset); } void* flecs_sparse_remove_fast( 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, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } flecs_sparse_strip_generation(&index); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { int32_t count = sparse->count; if (dense == (count - 1)) { /* If dense is the last used element, simply decrease count */ sparse->count --; } else if (dense < count) { /* If element is alive, move it to unused elements */ flecs_sparse_swap_dense(sparse, page, dense, count - 1); sparse->count --; } /* Reset memory to zero on remove */ return DATA(page->data, sparse->size, offset); } else { /* Element is not paired and thus not alive, nothing to be done */ return NULL; } } 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, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return; } uint64_t gen = flecs_sparse_strip_generation(&index); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (gen != cur_gen) { /* Generation doesn't match which means that the provided entity is * already not alive. */ return; } /* Increase generation */ dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen); int32_t count = sparse->count; if (dense == (count - 1)) { /* If dense is the last used element, simply decrease count */ sparse->count --; } else if (dense < count) { /* If element is alive, move it to unused elements */ flecs_sparse_swap_dense(sparse, page, dense, count - 1); sparse->count --; } else { /* Element is not alive, nothing to be done */ return; } /* Reset memory to zero on remove */ void *ptr = DATA(page->data, sparse->size, offset); ecs_os_memset(ptr, 0, size); } else { /* Element is not paired and thus not alive, nothing to be done */ return; } } void* flecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t size, int32_t dense_index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); (void)size; dense_index ++; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); } bool flecs_sparse_is_alive( const ecs_sparse_t *sparse, uint64_t index) { ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return false; } int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return false; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return false; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return true; } void* flecs_sparse_try( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = FLECS_SPARSE_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, FLECS_SPARSE_PAGE(index)); int32_t offset = FLECS_SPARSE_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, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; bool in_use = dense && (dense < sparse->count); if (!in_use) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } int32_t flecs_sparse_count( const ecs_sparse_t *sparse) { if (!sparse || !sparse->count) { return 0; } return sparse->count - 1; } const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); if (sparse->dense.array) { return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); } else { return NULL; } } void ecs_sparse_init( ecs_sparse_t *sparse, ecs_size_t elem_size) { flecs_sparse_init(sparse, NULL, NULL, elem_size); } void* ecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t elem_size) { return flecs_sparse_add(sparse, elem_size); } uint64_t ecs_sparse_last_id( const ecs_sparse_t *sparse) { return flecs_sparse_last_id(sparse); } int32_t ecs_sparse_count( const ecs_sparse_t *sparse) { return flecs_sparse_count(sparse); } void* ecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t elem_size, int32_t index) { return flecs_sparse_get_dense(sparse, elem_size, index); } void* ecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t elem_size, uint64_t id) { return flecs_sparse_get(sparse, elem_size, id); } /** * @file datastructures/stack_allocator.c * @brief Stack allocator. * * The stack allocator enables pushing and popping values to a stack, and has * a lower overhead when compared to block allocators. A stack allocator is a * good fit for small temporary allocations. * * The stack allocator allocates memory in pages. If the requested size of an * allocation exceeds the page size, a regular allocator is used instead. */ #define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) int64_t ecs_stack_allocator_alloc_count = 0; int64_t ecs_stack_allocator_free_count = 0; static ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { ecs_stack_page_t *result = ecs_os_malloc( FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); result->next = NULL; result->id = page_id + 1; ecs_os_linc(&ecs_stack_allocator_alloc_count); return result; } void* flecs_stack_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; if (page == &stack->first && !page->data) { page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); ecs_os_linc(&ecs_stack_allocator_alloc_count); } int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); void *result = NULL; if (next_sp > ECS_STACK_PAGE_SIZE) { if (size > ECS_STACK_PAGE_SIZE) { result = ecs_os_malloc(size); /* Too large for page */ goto done; } if (page->next) { page = page->next; } else { page = page->next = flecs_stack_page_new(page->id); } sp = 0; next_sp = flecs_ito(int16_t, size); stack->tail_page = page; } page->sp = next_sp; result = ECS_OFFSET(page->data, sp); done: #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif return result; } void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { void *ptr = flecs_stack_alloc(stack, size, align); ecs_os_memset(ptr, 0, size); return ptr; } void flecs_stack_free( void *ptr, ecs_size_t size) { if (size > ECS_STACK_PAGE_SIZE) { ecs_os_free(ptr); } } ecs_stack_cursor_t* flecs_stack_get_cursor( ecs_stack_t *stack) { ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; int16_t sp = stack->tail_page->sp; ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); result->page = page; result->sp = sp; result->is_free = false; #ifdef FLECS_DEBUG ++ stack->cursor_count; result->owner = stack; #endif result->prev = stack->tail_cursor; stack->tail_cursor = result; return result; } #define FLECS_STACK_LEAK_MSG \ "a stack allocator leak is most likely due to an unterminated " \ "iteration: call ecs_iter_fini to fix" void flecs_stack_restore_cursor( ecs_stack_t *stack, ecs_stack_cursor_t *cursor) { if (!cursor) { return; } ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, "attempting to restore a cursor for the wrong stack"); ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, "double free detected in stack allocator"); ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, "double free detected in stack allocator"); cursor->is_free = true; #ifdef FLECS_DEBUG -- stack->cursor_count; #endif /* If cursor is not the last on the stack no memory should be freed */ if (cursor != stack->tail_cursor) { return; } /* Iterate freed cursors to know how much memory we can free */ do { ecs_stack_cursor_t* prev = cursor->prev; if (!prev || !prev->is_free) { break; /* Found active cursor, free up until this point */ } cursor = prev; } while (cursor); stack->tail_cursor = cursor->prev; stack->tail_page = cursor->page; stack->tail_page->sp = cursor->sp; /* If the cursor count is zero, stack should be empty * if the cursor count is non-zero, stack should not be empty */ ecs_dbg_assert((stack->cursor_count == 0) == (stack->tail_page == &stack->first && stack->tail_page->sp == 0), ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); } void flecs_stack_reset( ecs_stack_t *stack) { ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); stack->tail_page = &stack->first; stack->first.sp = 0; stack->tail_cursor = NULL; } void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); stack->tail_page = &stack->first; stack->first.data = NULL; } void flecs_stack_fini( ecs_stack_t *stack) { ecs_stack_page_t *next, *cur = &stack->first; ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); do { next = cur->next; if (cur == &stack->first) { if (cur->data) { ecs_os_linc(&ecs_stack_allocator_free_count); } ecs_os_free(cur->data); } else { ecs_os_linc(&ecs_stack_allocator_free_count); ecs_os_free(cur); } } while ((cur = next)); } /** * @file datastructures/strbuf.c * @brief Utility for constructing strings. * * A buffer builds up a list of elements which individually can be up to N bytes * large. While appending, data is added to these elements. More elements are * added on the fly when needed. When an application calls ecs_strbuf_get, all * elements are combined in one string and the element administration is freed. * * This approach prevents reallocs of large blocks of memory, and therefore * copying large blocks of memory when appending to a large buffer. A buffer * preallocates some memory for the element overhead so that for small strings * there is hardly any overhead, while for large strings the overhead is offset * by the reduced time spent on copying memory. * * The functionality provided by strbuf is similar to std::stringstream. */ #include /** * stm32tpl -- STM32 C++ Template Peripheral Library * Visit https://github.com/antongus/stm32tpl for new versions * * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA */ #define MAX_PRECISION (10) #define EXP_THRESHOLD (3) #define INT64_MAX_F ((double)INT64_MAX) static const double rounders[MAX_PRECISION + 1] = { 0.5, // 0 0.05, // 1 0.005, // 2 0.0005, // 3 0.00005, // 4 0.000005, // 5 0.0000005, // 6 0.00000005, // 7 0.000000005, // 8 0.0000000005, // 9 0.00000000005 // 10 }; static char* flecs_strbuf_itoa( char *buf, int64_t v) { char *ptr = buf; char * p1; char c; if (!v) { *ptr++ = '0'; } else { if (v < 0) { ptr[0] = '-'; ptr ++; v *= -1; } char *p = ptr; while (v) { int64_t vdiv = v / 10; int64_t vmod = v - (vdiv * 10); p[0] = (char)('0' + vmod); p ++; v = vdiv; } p1 = p; while (p > ptr) { c = *--p; *p = *ptr; *ptr++ = c; } ptr = p1; } return ptr; } static void flecs_strbuf_ftoa( ecs_strbuf_t *out, double f, int precision, char nan_delim) { char buf[64]; char * ptr = buf; char c; int64_t intPart; int64_t exp = 0; if (ecs_os_isnan(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "NaN"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "NaN"); return; } } if (ecs_os_isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "Inf"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "Inf"); return; } } if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } if (f < 0) { f = -f; *ptr++ = '-'; } if (precision < 0) { if (f < 1.0) precision = 6; else if (f < 10.0) precision = 5; else if (f < 100.0) precision = 4; else if (f < 1000.0) precision = 3; else if (f < 10000.0) precision = 2; else if (f < 100000.0) precision = 1; else precision = 0; } if (precision) { f += rounders[precision]; } /* Make sure that number can be represented as 64bit int, increase exp */ while (f > INT64_MAX_F) { f /= 1000 * 1000 * 1000; exp += 9; } intPart = (int64_t)f; f -= (double)intPart; ptr = flecs_strbuf_itoa(ptr, intPart); if (precision) { *ptr++ = '.'; while (precision--) { f *= 10.0; c = (char)f; *ptr++ = (char)('0' + c); f -= c; } } *ptr = 0; /* Remove trailing 0s */ while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { ptr[-1] = '\0'; ptr --; } if (ptr != buf && ptr[-1] == '.') { ptr[-1] = '\0'; ptr --; } /* If 0s before . exceed threshold, convert to exponent to save space * without losing precision. */ char *cur = ptr; while ((&cur[-1] != buf) && (cur[-1] == '0')) { cur --; } if (exp || ((ptr - cur) > EXP_THRESHOLD)) { cur[0] = '\0'; exp += (ptr - cur); ptr = cur; } if (exp) { char *p1 = &buf[1]; if (nan_delim) { ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); buf[0] = nan_delim; p1 ++; } /* Make sure that exp starts after first character */ c = p1[0]; if (c) { p1[0] = '.'; do { char t = (++p1)[0]; if (t == '.') { exp ++; p1 --; break; } p1[0] = c; c = t; exp ++; } while (c); ptr = p1 + 1; } else { ptr = p1; } ptr[0] = 'e'; ptr = flecs_strbuf_itoa(ptr + 1, exp); if (nan_delim) { ptr[0] = nan_delim; ptr ++; } ptr[0] = '\0'; } ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } /* Add an extra element to the buffer */ static void flecs_strbuf_grow( ecs_strbuf_t *b) { if (!b->content) { b->content = b->small_string; b->size = ECS_STRBUF_SMALL_STRING_SIZE; } else if (b->content == b->small_string) { b->size *= 2; b->content = ecs_os_malloc_n(char, b->size); ecs_os_memcpy(b->content, b->small_string, b->length); } else { b->size *= 2; if (b->size < 16) b->size = 16; b->content = ecs_os_realloc_n(b->content, char, b->size); } } static char* flecs_strbuf_ptr( ecs_strbuf_t *b) { ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); return &b->content[b->length]; } /* Append a format string to a buffer */ static void flecs_strbuf_vappend( ecs_strbuf_t *b, const char* str, va_list args) { va_list arg_cpy; if (!str) { return; } /* Compute the memory required to add the string to the buffer. If user * provided buffer, use space left in buffer, otherwise use space left in * current element. */ int32_t mem_left = b->size - b->length; int32_t mem_required; va_copy(arg_cpy, args); if (b->content) { mem_required = ecs_os_vsnprintf( flecs_strbuf_ptr(b), flecs_itosize(mem_left), str, args); } else { mem_required = ecs_os_vsnprintf(NULL, 0, str, args); mem_left = 0; } ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); if ((mem_required + 1) >= mem_left) { while ((mem_required + 1) >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_vsnprintf(flecs_strbuf_ptr(b), flecs_itosize(mem_required + 1), str, arg_cpy); } b->length += mem_required; va_end(arg_cpy); } static void flecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str, int n) { int32_t mem_left = b->size - b->length; while (n >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); b->length += n; } static void flecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { if (b->size == b->length) { flecs_strbuf_grow(b); } flecs_strbuf_ptr(b)[0] = ch; b->length ++; } void ecs_strbuf_vappend( ecs_strbuf_t *b, const char* fmt, va_list args) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_vappend(b, fmt, args); } void ecs_strbuf_append( ecs_strbuf_t *b, const char* fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_appendstrn( ecs_strbuf_t *b, const char* str, int32_t len) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, len); } void ecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_appendint( ecs_strbuf_t *b, int64_t v) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char numbuf[32]; char *ptr = flecs_strbuf_itoa(numbuf, v); ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } void ecs_strbuf_appendflt( ecs_strbuf_t *b, double flt, char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_ftoa(b, flt, 10, nan_delim); } void ecs_strbuf_appendbool( ecs_strbuf_t *buffer, bool v) { ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); if (v) { ecs_strbuf_appendlit(buffer, "true"); } else { ecs_strbuf_appendlit(buffer, "false"); } } void ecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } void ecs_strbuf_mergebuff( ecs_strbuf_t *b, ecs_strbuf_t *src) { if (src->content) { ecs_strbuf_appendstr(b, src->content); } ecs_strbuf_reset(src); } char* ecs_strbuf_get( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; if (!result) { return NULL; } ecs_strbuf_appendch(b, '\0'); result = b->content; if (result == b->small_string) { result = ecs_os_memdup_n(result, char, b->length + 1); } b->length = 0; b->content = NULL; b->size = 0; b->list_sp = 0; return result; } char* ecs_strbuf_get_small( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; result[b->length] = '\0'; b->length = 0; b->content = NULL; b->size = 0; return result; } void ecs_strbuf_reset( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->content && b->content != b->small_string) { ecs_os_free(b->content); } *b = ECS_STRBUF_INIT; } void ecs_strbuf_list_push( ecs_strbuf_t *b, const char *list_open, const char *separator) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, "strbuf list is corrupt"); b->list_sp ++; ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, ECS_INVALID_OPERATION, "max depth for strbuf list stack exceeded"); b->list_stack[b->list_sp].count = 0; b->list_stack[b->list_sp].separator = separator; if (list_open) { char ch = list_open[0]; if (ch && !list_open[1]) { ecs_strbuf_appendch(b, ch); } else { ecs_strbuf_appendstr(b, list_open); } } } void ecs_strbuf_list_pop( ecs_strbuf_t *b, const char *list_close) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, "pop called more often than push for strbuf list"); b->list_sp --; if (list_close) { char ch = list_close[0]; if (ch && !list_close[1]) { ecs_strbuf_appendch(b, list_close[0]); } else { ecs_strbuf_appendstr(b, list_close); } } } void ecs_strbuf_list_next( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t list_sp = b->list_sp; if (b->list_stack[list_sp].count != 0) { const char *sep = b->list_stack[list_sp].separator; if (sep && !sep[1]) { ecs_strbuf_appendch(b, sep[0]); } else { ecs_strbuf_appendstr(b, sep); } } b->list_stack[list_sp].count ++; } void ecs_strbuf_list_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_list_append( ecs_strbuf_t *b, const char *fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_list_appendstr( ecs_strbuf_t *b, const char *str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstr(b, str); } void ecs_strbuf_list_appendstrn( ecs_strbuf_t *b, const char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstrn(b, str, n); } int32_t ecs_strbuf_written( const ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return b->length; } static ecs_switch_page_t* flecs_switch_page_ensure( ecs_switch_t* sw, uint32_t elem) { int32_t page_index = FLECS_SPARSE_PAGE(elem); ecs_vec_set_min_count_zeromem_t( sw->hdrs.allocator, &sw->pages, ecs_switch_page_t, page_index + 1); ecs_switch_page_t *page = ecs_vec_get_t( &sw->pages, ecs_switch_page_t, page_index); if (!ecs_vec_count(&page->nodes)) { ecs_vec_init_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); ecs_vec_init_t(sw->hdrs.allocator, &page->values, uint64_t, FLECS_SPARSE_PAGE_SIZE); ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->values, uint64_t, FLECS_SPARSE_PAGE_SIZE); } return page; } static ecs_switch_page_t* flecs_switch_page_get( const ecs_switch_t* sw, uint32_t elem) { int32_t page_index = FLECS_SPARSE_PAGE(elem); if (page_index >= ecs_vec_count(&sw->pages)) { return NULL; } ecs_switch_page_t *page = ecs_vec_get_t( &sw->pages, ecs_switch_page_t, page_index); if (!ecs_vec_count(&page->nodes)) { return NULL; } return page; } static void flecs_switch_page_fini( ecs_switch_t* sw, ecs_switch_page_t *page) { if (ecs_vec_count(&page->nodes)) { ecs_vec_fini_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t); ecs_vec_fini_t(sw->hdrs.allocator, &page->values, uint64_t); } } static ecs_switch_node_t* flecs_switch_get_node( ecs_switch_t* sw, uint32_t element) { if (!element) { return NULL; } ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); int32_t page_offset = FLECS_SPARSE_OFFSET(element); return ecs_vec_get_t(&page->nodes, ecs_switch_node_t, page_offset); } void flecs_switch_init( ecs_switch_t* sw, ecs_allocator_t *allocator) { ecs_map_init(&sw->hdrs, allocator); ecs_vec_init_t(allocator, &sw->pages, ecs_switch_page_t, 0); } void flecs_switch_fini( ecs_switch_t* sw) { int32_t i, count = ecs_vec_count(&sw->pages); ecs_switch_page_t *pages = ecs_vec_first(&sw->pages); for (i = 0; i < count; i ++) { flecs_switch_page_fini(sw, &pages[i]); } ecs_vec_fini_t(sw->hdrs.allocator, &sw->pages, ecs_switch_page_t); ecs_map_fini(&sw->hdrs); } bool flecs_switch_set( ecs_switch_t *sw, uint32_t element, uint64_t value) { ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); int32_t page_offset = FLECS_SPARSE_OFFSET(element); uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); if (elem[0] == value) { return false; } ecs_switch_node_t *node = ecs_vec_get_t( &page->nodes, ecs_switch_node_t, page_offset); uint64_t prev_value = elem[0]; if (prev_value) { ecs_switch_node_t *prev = flecs_switch_get_node(sw, node->prev); if (prev) { prev->next = node->next; } ecs_switch_node_t *next = flecs_switch_get_node(sw, node->next); if (next) { next->prev = node->prev; } if (!prev) { uint64_t *hdr = ecs_map_get(&sw->hdrs, prev_value); ecs_assert(hdr[0] == (uint64_t)element, ECS_INTERNAL_ERROR, NULL); hdr[0] = (uint64_t)node->next; } } elem[0] = value; if (value) { uint64_t *hdr = ecs_map_ensure(&sw->hdrs, value); if (!hdr[0]) { hdr[0] = (uint64_t)element; } else { ecs_switch_node_t *head = flecs_switch_get_node(sw, (uint32_t)hdr[0]); ecs_assert(head->prev == 0, ECS_INTERNAL_ERROR, NULL); head->prev = element; node->next = (uint32_t)hdr[0]; hdr[0] = (uint64_t)element; } node->prev = 0; } return true; } bool flecs_switch_reset( ecs_switch_t *sw, uint32_t element) { return flecs_switch_set(sw, element, 0); } uint64_t flecs_switch_get( const ecs_switch_t *sw, uint32_t element) { ecs_switch_page_t *page = flecs_switch_page_get(sw, element); if (!page) { return 0; } int32_t page_offset = FLECS_SPARSE_OFFSET(element); uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); return elem[0]; } uint32_t flecs_switch_first( const ecs_switch_t *sw, uint64_t value) { uint64_t *hdr = ecs_map_get(&sw->hdrs, value); if (!hdr) { return 0; } return (uint32_t)hdr[0]; } FLECS_DBG_API uint32_t flecs_switch_next( const ecs_switch_t *sw, uint32_t previous) { ecs_switch_page_t *page = flecs_switch_page_get(sw, previous); if (!page) { return 0; } int32_t offset = FLECS_SPARSE_OFFSET(previous); ecs_switch_node_t *elem = ecs_vec_get_t( &page->nodes, ecs_switch_node_t, offset); return elem->next; } ecs_map_iter_t flecs_switch_targets( const ecs_switch_t *sw) { return ecs_map_iter(&sw->hdrs); } /** * @file datastructures/vec.c * @brief Vector with allocator support. */ void ecs_vec_init( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { v->array = flecs_alloc(allocator, size * elem_count); } else { v->array = ecs_os_malloc(size * elem_count); } } v->size = elem_count; #ifdef FLECS_SANITIZE v->elem_size = size; #endif } void ecs_vec_init_if( ecs_vec_t *vec, ecs_size_t size) { ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); (void)vec; (void)size; #ifdef FLECS_SANITIZE if (!vec->elem_size) { ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); vec->elem_size = size; } #endif } void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (v->array) { ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (allocator) { flecs_free(allocator, size * v->size, v->array); } else { ecs_os_free(v->array); } v->array = NULL; v->count = 0; v->size = 0; } } ecs_vec_t* ecs_vec_reset( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (!v->size) { ecs_vec_init(allocator, v, size, 0); } else { ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); ecs_vec_clear(v); } return v; } void ecs_vec_clear( ecs_vec_t *vec) { vec->count = 0; } ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); void *array; if (allocator) { array = flecs_dup(allocator, size * v->size, v->array); } else { array = ecs_os_memdup(v->array, size * v->size); } return (ecs_vec_t) { .count = v->count, .size = v->size, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } ecs_vec_t ecs_vec_copy_shrink( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; void *array = NULL; if (count) { if (allocator) { array = flecs_dup(allocator, size * count, v->array); } else { array = ecs_os_memdup(v->array, size * count); } } return (ecs_vec_t) { .count = count, .size = count, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (count < v->size) { if (count) { if (allocator) { v->array = flecs_realloc( allocator, size * count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * count); } v->size = count; } else { ecs_vec_fini(allocator, v, size); } } } void ecs_vec_set_size( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->size != elem_count) { if (elem_count < v->count) { elem_count = v->count; } elem_count = flecs_next_pow_of_2(elem_count); if (elem_count < 2) { elem_count = 2; } if (elem_count != v->size) { if (allocator) { v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * elem_count); } v->size = elem_count; } } } void ecs_vec_set_min_size( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { if (elem_count > vec->size) { ecs_vec_set_size(allocator, vec, size, elem_count); } } void ecs_vec_set_min_count( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { ecs_vec_set_min_size(allocator, vec, size, elem_count); if (vec->count < elem_count) { vec->count = elem_count; } } void ecs_vec_set_min_count_zeromem( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { int32_t count = vec->count; if (count < elem_count) { ecs_vec_set_min_count(allocator, vec, size, elem_count); ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, size * (elem_count - count)); } } void ecs_vec_set_count( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->count != elem_count) { if (v->size < elem_count) { ecs_vec_set_size(allocator, v, size, elem_count); } v->count = elem_count; } } void* ecs_vec_grow( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); int32_t count = v->count; ecs_vec_set_count(allocator, v, size, count + elem_count); return ECS_ELEM(v->array, size, count); } void* ecs_vec_append( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (v->size == count) { ecs_vec_set_size(allocator, v, size, count + 1); } v->count = count + 1; return ECS_ELEM(v->array, size, count); } void ecs_vec_remove( ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); if (index == --v->count) { return; } ecs_os_memcpy( ECS_ELEM(v->array, size, index), ECS_ELEM(v->array, size, v->count), size); } void ecs_vec_remove_last( ecs_vec_t *v) { v->count --; } int32_t ecs_vec_count( const ecs_vec_t *v) { return v->count; } int32_t ecs_vec_size( const ecs_vec_t *v) { return v->size; } void* ecs_vec_get( const ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, ECS_OUT_OF_RANGE, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); return ECS_ELEM(v->array, size, index); } void* ecs_vec_last( const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(!v->elem_size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(v->array, size, v->count - 1); } void* ecs_vec_first( const ecs_vec_t *v) { return v->array; } /** * @file queries/api.c * @brief User facing API for rules. */ #include ecs_mixins_t ecs_query_t_mixins = { .type_name = "ecs_query_t", .elems = { [EcsMixinWorld] = offsetof(ecs_query_impl_t, pub.real_world), [EcsMixinEntity] = offsetof(ecs_query_impl_t, pub.entity), [EcsMixinDtor] = offsetof(ecs_query_impl_t, dtor) } }; int32_t ecs_query_find_var( const ecs_query_t *q, const char *name) { flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); ecs_var_id_t var_id = flecs_query_find_var_id(impl, name, EcsVarEntity); if (var_id == EcsVarNone) { if (q->flags & EcsQueryMatchThis) { if (!ecs_os_strcmp(name, EcsThisName)) { var_id = 0; } } if (var_id == EcsVarNone) { return -1; } } return (int32_t)var_id; } const char* ecs_query_var_name( const ecs_query_t *q, int32_t var_id) { flecs_poly_assert(q, ecs_query_t); if (var_id) { ecs_assert(var_id < flecs_query_impl(q)->var_count, ECS_INVALID_PARAMETER, NULL); return flecs_query_impl(q)->vars[var_id].name; } else { return EcsThisName; } } bool ecs_query_var_is_entity( const ecs_query_t *q, int32_t var_id) { flecs_poly_assert(q, ecs_query_t); return flecs_query_impl(q)->vars[var_id].kind == EcsVarEntity; } static int flecs_query_set_caching_policy( ecs_query_impl_t *impl, const ecs_query_desc_t *desc) { ecs_query_cache_kind_t kind = desc->cache_kind; /* If caching policy is default, try to pick a policy that does the right * thing in most cases. */ if (kind == EcsQueryCacheDefault) { if (desc->entity || desc->group_by || desc->group_by_callback || desc->order_by || desc->order_by_callback) { /* If the query is created with an entity handle (typically * indicating that the query is named or belongs to a system) the * chance is very high that the query will be reused, so enable * caching. * Additionally, if the query uses features that require a cache * such as group_by/order_by, also enable caching. */ kind = EcsQueryCacheAuto; } else { /* Be conservative in other scenario's, as caching adds significant * overhead to the cost of query creation which doesn't offset the * benefit of faster iteration if it's only used once. */ kind = EcsQueryCacheNone; } } /* Don't cache query, even if it has cacheable terms */ if (kind == EcsQueryCacheNone) { impl->pub.cache_kind = EcsQueryCacheNone; if (desc->group_by || desc->order_by) { ecs_err("cannot create uncached query with group_by/order_by"); return -1; } return 0; } /* Entire query must be cached */ if (desc->cache_kind == EcsQueryCacheAll) { if (impl->pub.flags & EcsQueryIsCacheable) { impl->pub.cache_kind = EcsQueryCacheAll; return 0; } else { ecs_err("cannot enforce QueryCacheAll, " "query contains uncacheable terms"); return -1; } } /* Only cache terms that are cacheable */ if (kind == EcsQueryCacheAuto) { if (impl->pub.flags & EcsQueryIsCacheable) { /* If all terms of the query are cacheable, just set the policy to * All which simplifies work for the compiler. */ impl->pub.cache_kind = EcsQueryCacheAll; } else if (!(impl->pub.flags & EcsQueryHasCacheable)) { /* Same for when the query has no cacheable terms */ impl->pub.cache_kind = EcsQueryCacheNone; } else { /* Part of the query is cacheable */ impl->pub.cache_kind = EcsQueryCacheAuto; } } return 0; } static int flecs_query_create_cache( ecs_query_impl_t *impl, ecs_query_desc_t *desc) { ecs_query_t *q = &impl->pub; if (flecs_query_set_caching_policy(impl, desc)) { return -1; } if ((q->cache_kind != EcsQueryCacheNone) && !q->entity) { /* Cached queries need an entity handle for observer components */ q->entity = ecs_new(q->world); desc->entity = q->entity; } if (q->cache_kind == EcsQueryCacheAll) { /* Create query cache for all terms */ if (!flecs_query_cache_init(impl, desc)) { goto error; } } else if (q->cache_kind == EcsQueryCacheAuto) { /* Query is partially cached */ ecs_query_desc_t cache_desc = *desc; ecs_os_memset_n(&cache_desc.terms, 0, ecs_term_t, FLECS_TERM_COUNT_MAX); cache_desc.expr = NULL; /* Maps field indices from cache to query */ int8_t field_map[FLECS_TERM_COUNT_MAX]; int32_t i, count = q->term_count, dst_count = 0, dst_field = 0; ecs_term_t *terms = q->terms; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (term->flags_ & EcsTermIsCacheable) { cache_desc.terms[dst_count] = *term; field_map[dst_field] = flecs_ito(int8_t, term->field_index); dst_count ++; if (i) { dst_field += term->field_index != term[-1].field_index; } else { dst_field ++; } } } if (dst_count) { if (!flecs_query_cache_init(impl, &cache_desc)) { goto error; } impl->field_map = flecs_alloc_n(&impl->stage->allocator, int8_t, FLECS_TERM_COUNT_MAX); ecs_os_memcpy_n(impl->field_map, field_map, int8_t, dst_count); } } return 0; error: return -1; } static void flecs_query_fini( ecs_query_impl_t *impl) { ecs_stage_t *stage = impl->stage; ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &stage->allocator; if (impl->ctx_free) { impl->ctx_free(impl->pub.ctx); } if (impl->binding_ctx_free) { impl->binding_ctx_free(impl->pub.binding_ctx); } if (impl->vars != &impl->vars_cache.var) { flecs_free(a, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * impl->var_size, impl->vars); } flecs_free_n(a, ecs_query_op_t, impl->op_count, impl->ops); flecs_free_n(a, ecs_var_id_t, impl->pub.field_count, impl->src_vars); flecs_free_n(a, int32_t, impl->pub.field_count, impl->monitor); flecs_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->field_map); flecs_name_index_fini(&impl->tvar_index); flecs_name_index_fini(&impl->evar_index); ecs_query_t *q = &impl->pub; int i, count = q->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &q->terms[i]; if (!(term->flags_ & EcsTermKeepAlive)) { continue; } ecs_id_record_t *idr = flecs_id_record_get(q->real_world, term->id); if (idr) { if (!(q->world->flags & EcsWorldQuit)) { if (ecs_os_has_threading()) { int32_t idr_keep_alive = ecs_os_adec(&idr->keep_alive); ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); (void)idr_keep_alive; } else { idr->keep_alive --; ecs_assert(idr->keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); } } } } if (impl->tokens) { flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); } if (impl->cache) { flecs_query_cache_fini(impl); } flecs_poly_fini(impl, ecs_query_t); flecs_bfree(&stage->allocators.query_impl, impl); } static void flecs_query_poly_fini(void *ptr) { flecs_query_fini(ptr); } static char* flecs_query_append_token( char *dst, const char *src) { int32_t len = ecs_os_strlen(src); ecs_os_memcpy(dst, src, len + 1); return dst + len + 1; } static void flecs_query_populate_tokens( ecs_query_impl_t *impl) { ecs_query_t *q = &impl->pub; int32_t i, term_count = q->term_count; char *old_tokens = impl->tokens; int32_t old_tokens_len = impl->tokens_len; impl->tokens = NULL; impl->tokens_len = 0; /* Step 1: determine size of token buffer */ int32_t len = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &q->terms[i]; if (term->first.name) { len += ecs_os_strlen(term->first.name) + 1; } if (term->second.name) { len += ecs_os_strlen(term->second.name) + 1; } if (term->src.name) { len += ecs_os_strlen(term->src.name) + 1; } } /* Step 2: reassign term tokens to buffer */ if (len) { impl->tokens = flecs_alloc(&impl->stage->allocator, len); impl->tokens_len = flecs_ito(int16_t, len); char *token = impl->tokens, *next; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &q->terms[i]; if (term->first.name) { next = flecs_query_append_token(token, term->first.name); term->first.name = token; token = next; } if (term->second.name) { next = flecs_query_append_token(token, term->second.name); term->second.name = token; token = next; } if (term->src.name) { next = flecs_query_append_token(token, term->src.name); term->src.name = token; token = next; } } } if (old_tokens) { flecs_free(&impl->stage->allocator, old_tokens_len, old_tokens); } } static void flecs_query_add_self_ref( ecs_query_t *q) { if (q->entity) { int32_t t, term_count = q->term_count; ecs_term_t *terms = q->terms; for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (ECS_TERM_REF_ID(&term->src) == q->entity) { ecs_add_id(q->world, q->entity, term->id); } } } } void ecs_query_fini( ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); if (q->entity) { /* If query is associated with entity, use poly dtor path */ ecs_delete(q->world, q->entity); } else { flecs_query_fini(flecs_query_impl(q)); } } ecs_query_t* ecs_query_init( ecs_world_t *world, const ecs_query_desc_t *const_desc) { ecs_world_t *world_arg = world; ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_query_impl_t *result = flecs_bcalloc(&stage->allocators.query_impl); flecs_poly_init(result, ecs_query_t); ecs_query_desc_t desc = *const_desc; ecs_entity_t entity = const_desc->entity; if (entity) { /* Remove existing query if entity has one */ bool deferred = false; if (ecs_is_deferred(world)) { deferred = true; /* Ensures that remove operation doesn't get applied after bind */ ecs_defer_suspend(world); } ecs_remove_pair(world, entity, ecs_id(EcsPoly), EcsQuery); if (deferred) { ecs_defer_resume(world); } } /* Initialize the query */ result->pub.entity = entity; result->pub.real_world = world; result->pub.world = world_arg; result->stage = stage; ecs_assert(flecs_poly_is(result->pub.real_world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_poly_is(result->stage, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); if (flecs_query_finalize_query(world, &result->pub, &desc)) { goto error; } /* If query terms have itself as source, add term ids to self */ flecs_query_add_self_ref(&result->pub); /* Store remaining string tokens in terms (after entity lookups) in single * token buffer which simplifies memory management & reduces allocations. */ flecs_query_populate_tokens(result); /* Initialize static context */ result->pub.ctx = const_desc->ctx; result->pub.binding_ctx = const_desc->binding_ctx; result->ctx_free = const_desc->ctx_free; result->binding_ctx_free = const_desc->binding_ctx_free; result->dtor = flecs_query_poly_fini; result->cache = NULL; /* Initialize query cache if necessary */ if (flecs_query_create_cache(result, &desc)) { goto error; } /* Compile query to operations */ if (flecs_query_compile(world, stage, result)) { goto error; } /* Entity could've been set by finalize query if query is cached */ entity = result->pub.entity; if (entity) { EcsPoly *poly = flecs_poly_bind(world, entity, ecs_query_t); poly->poly = result; flecs_poly_modified(world, entity, ecs_query_t); } return &result->pub; error: result->pub.entity = 0; ecs_query_fini(&result->pub); return NULL; } bool ecs_query_has( ecs_query_t *q, ecs_entity_t entity, ecs_iter_t *it) { flecs_poly_assert(q, ecs_query_t); ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); *it = ecs_query_iter(q->world, q); ecs_iter_set_var(it, 0, entity); return ecs_query_next(it); error: return false; } bool ecs_query_has_table( ecs_query_t *q, ecs_table_t *table, ecs_iter_t *it) { flecs_poly_assert(q, ecs_query_t); ecs_check(q->flags & EcsQueryMatchThis, ECS_INVALID_PARAMETER, NULL); *it = ecs_query_iter(q->world, q); it->flags |= EcsIterNoData; ecs_iter_set_var_as_table(it, 0, table); return ecs_query_next(it); error: return false; } bool ecs_query_has_range( ecs_query_t *q, ecs_table_range_t *range, ecs_iter_t *it) { flecs_poly_assert(q, ecs_query_t); if (q->flags & EcsQueryMatchThis) { if (range->table) { if ((range->offset + range->count) > ecs_table_count(range->table)) { return false; } } } *it = ecs_query_iter(q->world, q); it->flags |= EcsIterNoData; if (q->flags & EcsQueryMatchThis) { ecs_iter_set_var_as_range(it, 0, range); } return ecs_query_next(it); } ecs_query_count_t ecs_query_count( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_query_count_t result = {0}; if (!(q->flags & EcsQueryMatchThis)) { return result; } ecs_run_aperiodic(q->world, EcsAperiodicEmptyTables); ecs_query_impl_t *impl = flecs_query_impl(q); if (impl->cache && q->flags & EcsQueryIsCacheable) { result.results = flecs_query_cache_table_count(impl->cache); result.entities = flecs_query_cache_entity_count(impl->cache); result.tables = flecs_query_cache_table_count(impl->cache); result.empty_tables = flecs_query_cache_empty_table_count(impl->cache); } else { ecs_iter_t it = flecs_query_iter(q->world, q); it.flags |= EcsIterIsInstanced; it.flags |= EcsIterNoData; while (flecs_query_next_instanced(&it)) { result.results ++; result.entities += it.count; ecs_iter_skip(&it); } } return result; } bool ecs_query_is_true( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_run_aperiodic(q->world, EcsAperiodicEmptyTables); ecs_query_impl_t *impl = flecs_query_impl(q); if (impl->cache && q->flags & EcsQueryIsCacheable) { return flecs_query_cache_table_count(impl->cache) != 0; } else { ecs_iter_t it = flecs_query_iter(q->world, q); return ecs_iter_is_true(&it); } } int32_t ecs_query_match_count( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); if (!impl->cache) { return 0; } else { return impl->cache->match_count; } } /** * @file query/util.c * @brief Query utilities. */ ecs_query_lbl_t flecs_itolbl(int64_t val) { return flecs_ito(int16_t, val); } ecs_var_id_t flecs_itovar(int64_t val) { return flecs_ito(uint8_t, val); } ecs_var_id_t flecs_utovar(uint64_t val) { return flecs_uto(uint8_t, val); } bool flecs_term_is_builtin_pred( ecs_term_t *term) { if (term->first.id & EcsIsEntity) { ecs_entity_t id = ECS_TERM_REF_ID(&term->first); if (id == EcsPredEq || id == EcsPredMatch || id == EcsPredLookup) { return true; } } return false; } const char* flecs_term_ref_var_name( ecs_term_ref_t *ref) { if (!(ref->id & EcsIsVariable)) { return NULL; } if (ECS_TERM_REF_ID(ref) == EcsThis) { return EcsThisName; } return ref->name; } bool flecs_term_ref_is_wildcard( ecs_term_ref_t *ref) { if ((ref->id & EcsIsVariable) && ((ECS_TERM_REF_ID(ref) == EcsWildcard) || (ECS_TERM_REF_ID(ref) == EcsAny))) { return true; } return false; } bool flecs_term_is_fixed_id( ecs_query_t *q, ecs_term_t *term) { /* Transitive/inherited terms have variable ids */ if (term->flags_ & (EcsTermTransitive|EcsTermIdInherited)) { return false; } /* Or terms can match different ids */ if (term->oper == EcsOr) { return false; } if ((term != q->terms) && term[-1].oper == EcsOr) { return false; } /* Wildcards can assume different ids */ if (ecs_id_is_wildcard(term->id)) { return false; } /* Any terms can have fixed ids, but they require special handling */ if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { return false; } /* First terms that are Not or Optional require special handling */ if (term->oper == EcsNot || term->oper == EcsOptional) { if (term == q->terms) { return false; } } return true; } bool flecs_term_is_or( const ecs_query_t *q, const ecs_term_t *term) { bool first_term = term == q->terms; return (term->oper == EcsOr) || (!first_term && term[-1].oper == EcsOr); } ecs_flags16_t flecs_query_ref_flags( ecs_flags16_t flags, ecs_flags16_t kind) { return (flags >> kind) & (EcsQueryIsVar | EcsQueryIsEntity); } bool flecs_query_is_written( ecs_var_id_t var_id, uint64_t written) { if (var_id == EcsVarNone) { return true; } ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); return (written & (1ull << var_id)) != 0; } void flecs_query_write( ecs_var_id_t var_id, uint64_t *written) { ecs_assert(var_id < EcsQueryMaxVarCount, ECS_INTERNAL_ERROR, NULL); *written |= (1ull << var_id); } void flecs_query_write_ctx( ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx, bool cond_write) { bool is_written = flecs_query_is_written(var_id, ctx->written); flecs_query_write(var_id, &ctx->written); if (!is_written) { if (cond_write) { flecs_query_write(var_id, &ctx->cond_written); } } } bool flecs_ref_is_written( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t kind, uint64_t written) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, kind); if (flags & EcsQueryIsEntity) { ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); if (ref->entity) { return true; } } else if (flags & EcsQueryIsVar) { return flecs_query_is_written(ref->var, written); } return false; } ecs_allocator_t* flecs_query_get_allocator( const ecs_iter_t *it) { ecs_world_t *world = it->world; if (flecs_poly_is(world, ecs_world_t)) { return &world->allocator; } else { ecs_assert(flecs_poly_is(world, ecs_stage_t), ECS_INTERNAL_ERROR, NULL); return &((ecs_stage_t*)world)->allocator; } } const char* flecs_query_op_str( uint16_t kind) { switch(kind) { case EcsQueryAnd: return "and "; case EcsQueryAndId: return "andid "; case EcsQueryAndAny: return "andany "; case EcsQueryTriv: return "triv "; case EcsQueryTrivData: return "trivpop "; case EcsQueryTrivWildcard: return "trivwc "; case EcsQueryCache: return "cache "; case EcsQueryCacheData: return "cachepop "; case EcsQueryIsCache: return "xcache "; case EcsQueryIsCacheData: return "xcachepop "; case EcsQueryOnlyAny: return "any "; case EcsQueryUp: return "up "; case EcsQueryUpId: return "upid "; case EcsQuerySelfUp: return "selfup "; case EcsQuerySelfUpId: return "selfupid "; case EcsQueryWith: return "with "; case EcsQueryTrav: return "trav "; case EcsQueryAndFrom: return "andfrom "; case EcsQueryOrFrom: return "orfrom "; case EcsQueryNotFrom: return "notfrom "; case EcsQueryIds: return "ids "; case EcsQueryIdsRight: return "idsr "; case EcsQueryIdsLeft: return "idsl "; case EcsQueryEach: return "each "; case EcsQueryStore: return "store "; case EcsQueryReset: return "reset "; case EcsQueryOr: return "or "; case EcsQueryOptional: return "option "; case EcsQueryIfVar: return "ifvar "; case EcsQueryIfSet: return "ifset "; case EcsQueryEnd: return "end "; case EcsQueryNot: return "not "; case EcsQueryPredEq: return "eq "; case EcsQueryPredNeq: return "neq "; case EcsQueryPredEqName: return "eq_nm "; case EcsQueryPredNeqName: return "neq_nm "; case EcsQueryPredEqMatch: return "eq_m "; case EcsQueryPredNeqMatch: return "neq_m "; case EcsQueryMemberEq: return "membereq "; case EcsQueryMemberNeq: return "memberneq "; case EcsQueryToggle: return "toggle "; case EcsQueryToggleOption: return "togglopt "; case EcsQueryUnionEq: return "union "; case EcsQueryUnionEqWith: return "union_w "; case EcsQueryUnionNeq: return "unionneq "; case EcsQueryUnionEqUp: return "union_up "; case EcsQueryUnionEqSelfUp: return "union_sup "; case EcsQueryLookup: return "lookup "; case EcsQuerySetVars: return "setvars "; case EcsQuerySetThis: return "setthis "; case EcsQuerySetFixed: return "setfix "; case EcsQuerySetIds: return "setids "; case EcsQuerySetId: return "setid "; case EcsQueryContain: return "contain "; case EcsQueryPairEq: return "pair_eq "; case EcsQueryPopulate: return "pop "; case EcsQueryPopulateSelf: return "popself "; case EcsQueryPopulateSparse: return "popsparse "; case EcsQueryYield: return "yield "; case EcsQueryNothing: return "nothing "; default: return "!invalid "; } } static int32_t flecs_query_op_ref_str( const ecs_query_impl_t *query, ecs_query_ref_t *ref, ecs_flags16_t flags, ecs_strbuf_t *buf) { int32_t color_chars = 0; if (flags & EcsQueryIsVar) { ecs_assert(ref->var < query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_query_var_t *var = &query->vars[ref->var]; ecs_strbuf_appendlit(buf, "#[green]$#[reset]"); if (var->kind == EcsVarTable) { ecs_strbuf_appendch(buf, '['); } ecs_strbuf_appendlit(buf, "#[green]"); if (var->name) { ecs_strbuf_appendstr(buf, var->name); } else { if (var->id) { #ifdef FLECS_DEBUG if (var->label) { ecs_strbuf_appendstr(buf, var->label); ecs_strbuf_appendch(buf, '\''); } #endif ecs_strbuf_append(buf, "%d", var->id); } else { ecs_strbuf_appendlit(buf, "this"); } } ecs_strbuf_appendlit(buf, "#[reset]"); if (var->kind == EcsVarTable) { ecs_strbuf_appendch(buf, ']'); } color_chars = ecs_os_strlen("#[green]#[reset]#[green]#[reset]"); } else if (flags & EcsQueryIsEntity) { char *path = ecs_get_path(query->pub.world, ref->entity); ecs_strbuf_appendlit(buf, "#[blue]"); ecs_strbuf_appendstr(buf, path); ecs_strbuf_appendlit(buf, "#[reset]"); ecs_os_free(path); color_chars = ecs_os_strlen("#[blue]#[reset]"); } return color_chars; } static void flecs_query_str_append_bitset( ecs_strbuf_t *buf, ecs_flags64_t bitset) { ecs_strbuf_list_push(buf, "{", ","); int8_t b; for (b = 0; b < 64; b ++) { if (bitset & (1llu << b)) { ecs_strbuf_list_append(buf, "%d", b); } } ecs_strbuf_list_pop(buf, "}"); } static void flecs_query_plan_w_profile( const ecs_query_t *q, const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_op_t *ops = impl->ops; int32_t i, count = impl->op_count, indent = 0; for (i = 0; i < count; i ++) { ecs_query_op_t *op = &ops[i]; ecs_flags16_t flags = op->flags; ecs_flags16_t src_flags = flecs_query_ref_flags(flags, EcsQuerySrc); ecs_flags16_t first_flags = flecs_query_ref_flags(flags, EcsQueryFirst); ecs_flags16_t second_flags = flecs_query_ref_flags(flags, EcsQuerySecond); if (it) { #ifdef FLECS_DEBUG const ecs_query_iter_t *rit = &it->priv_.iter.query; ecs_strbuf_append(buf, "#[green]%4d -> #[red]%4d <- #[grey] | ", rit->profile[i].count[0], rit->profile[i].count[1]); #endif } ecs_strbuf_append(buf, "#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", i, op->prev, op->next); int32_t hidden_chars, start = ecs_strbuf_written(buf); if (op->kind == EcsQueryEnd) { indent --; } ecs_strbuf_append(buf, "%*s", indent, ""); ecs_strbuf_appendstr(buf, flecs_query_op_str(op->kind)); ecs_strbuf_appendstr(buf, " "); int32_t written = ecs_strbuf_written(buf); for (int32_t j = 0; j < (12 - (written - start)); j ++) { ecs_strbuf_appendch(buf, ' '); } hidden_chars = flecs_query_op_ref_str(impl, &op->src, src_flags, buf); if (op->kind == EcsQueryNot || op->kind == EcsQueryOr || op->kind == EcsQueryOptional || op->kind == EcsQueryIfVar || op->kind == EcsQueryIfSet) { indent ++; } if (op->kind == EcsQueryPopulate || op->kind == EcsQueryPopulateSelf || op->kind == EcsQueryPopulateSparse || op->kind == EcsQueryTriv || op->kind == EcsQueryTrivData || op->kind == EcsQueryTrivWildcard) { flecs_query_str_append_bitset(buf, op->src.entity); } if (op->kind == EcsQueryIfSet) { ecs_strbuf_append(buf, "[%d]\n", op->other); continue; } bool is_toggle = op->kind == EcsQueryToggle || op->kind == EcsQueryToggleOption; if (!first_flags && !second_flags && !is_toggle) { ecs_strbuf_appendstr(buf, "\n"); continue; } written = ecs_strbuf_written(buf) - hidden_chars; for (int32_t j = 0; j < (30 - (written - start)); j ++) { ecs_strbuf_appendch(buf, ' '); } if (is_toggle) { if (op->first.entity) { flecs_query_str_append_bitset(buf, op->first.entity); } if (op->second.entity) { if (op->first.entity) { ecs_strbuf_appendlit(buf, ", !"); } flecs_query_str_append_bitset(buf, op->second.entity); } ecs_strbuf_appendstr(buf, "\n"); continue; } ecs_strbuf_appendstr(buf, "("); if (op->kind == EcsQueryMemberEq || op->kind == EcsQueryMemberNeq) { uint32_t offset = (uint32_t)op->first.entity; uint32_t size = (uint32_t)(op->first.entity >> 32); ecs_strbuf_append(buf, "#[yellow]elem#[reset]([%d], 0x%x, 0x%x)", op->field_index, size, offset); } else { flecs_query_op_ref_str(impl, &op->first, first_flags, buf); } if (second_flags) { ecs_strbuf_appendstr(buf, ", "); flecs_query_op_ref_str(impl, &op->second, second_flags, buf); } else { switch (op->kind) { case EcsQueryPredEqName: case EcsQueryPredNeqName: case EcsQueryPredEqMatch: case EcsQueryPredNeqMatch: { int8_t term_index = op->term_index; ecs_strbuf_appendstr(buf, ", #[yellow]\""); ecs_strbuf_appendstr(buf, q->terms[term_index].second.name); ecs_strbuf_appendstr(buf, "\"#[reset]"); break; } case EcsQueryLookup: { ecs_var_id_t src_id = op->src.var; ecs_strbuf_appendstr(buf, ", #[yellow]\""); ecs_strbuf_appendstr(buf, impl->vars[src_id].lookup); ecs_strbuf_appendstr(buf, "\"#[reset]"); break; } default: break; } } ecs_strbuf_appendch(buf, ')'); ecs_strbuf_appendch(buf, '\n'); } } char* ecs_query_plan_w_profile( const ecs_query_t *q, const ecs_iter_t *it) { flecs_poly_assert(q, ecs_query_t); ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_query_plan_w_profile(q, it, &buf); // ecs_query_impl_t *impl = flecs_query_impl(q); // if (impl->cache) { // ecs_strbuf_appendch(&buf, '\n'); // flecs_query_plan_w_profile(impl->cache->query, it, &buf); // } #ifdef FLECS_LOG char *str = ecs_strbuf_get(&buf); flecs_colorize_buf(str, ecs_os_api.flags_ & EcsOsApiLogWithColors, &buf); ecs_os_free(str); #endif return ecs_strbuf_get(&buf); } char* ecs_query_plan( const ecs_query_t *q) { return ecs_query_plan_w_profile(q, NULL); } static void flecs_query_str_add_id( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_term_t *term, const ecs_term_ref_t *ref, bool is_src) { bool is_added = false; ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); if (ref->id & EcsIsVariable && !ecs_id_is_wildcard(ref_id)){ ecs_strbuf_appendlit(buf, "$"); } if (ref_id) { char *path = ecs_get_path(world, ref_id); ecs_strbuf_appendstr(buf, path); ecs_os_free(path); } else if (ref->name) { ecs_strbuf_appendstr(buf, ref->name); } else { ecs_strbuf_appendlit(buf, "0"); } is_added = true; ecs_flags64_t flags = ECS_TERM_REF_FLAGS(ref); if (!(flags & EcsTraverseFlags)) { /* If flags haven't been set yet, initialize with defaults. This can * happen if an error is thrown while the term is being finalized */ flags |= EcsSelf; } if ((flags & EcsTraverseFlags) != EcsSelf) { if (is_added) { ecs_strbuf_list_push(buf, "|", "|"); } else { ecs_strbuf_list_push(buf, "", "|"); } if (is_src) { if (flags & EcsSelf) { ecs_strbuf_list_appendstr(buf, "self"); } if (flags & EcsCascade) { ecs_strbuf_list_appendstr(buf, "cascade"); } else if (flags & EcsUp) { ecs_strbuf_list_appendstr(buf, "up"); } if (flags & EcsDesc) { ecs_strbuf_list_appendstr(buf, "desc"); } if (term->trav) { char *rel_path = ecs_get_path(world, term->trav); ecs_strbuf_appendlit(buf, " "); ecs_strbuf_appendstr(buf, rel_path); ecs_os_free(rel_path); } } ecs_strbuf_list_pop(buf, ""); } } void flecs_term_to_buf( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf, int32_t t) { const ecs_term_ref_t *src = &term->src; const ecs_term_ref_t *first = &term->first; const ecs_term_ref_t *second = &term->second; ecs_entity_t src_id = ECS_TERM_REF_ID(src); ecs_entity_t first_id = ECS_TERM_REF_ID(first); bool src_set = !ecs_term_match_0(term); bool second_set = ecs_term_ref_is_set(second); if (first_id == EcsScopeOpen) { ecs_strbuf_appendlit(buf, "{"); return; } else if (first_id == EcsScopeClose) { ecs_strbuf_appendlit(buf, "}"); return; } if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || (ECS_TERM_REF_ID(&term->first) == EcsPredMatch)) && (term->first.id & EcsIsEntity)) { ecs_strbuf_appendlit(buf, "$"); if (ECS_TERM_REF_ID(&term->src) == EcsThis && (term->src.id & EcsIsVariable)) { ecs_strbuf_appendlit(buf, "this"); } else if (term->src.id & EcsIsVariable) { ecs_strbuf_appendstr(buf, term->src.name); } else { /* Shouldn't happen */ } if (ECS_TERM_REF_ID(&term->first) == EcsPredEq) { if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, " != "); } else { ecs_strbuf_appendlit(buf, " == "); } } else if (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) { ecs_strbuf_appendlit(buf, " ~= "); } if (term->second.id & EcsIsEntity) { if (term->second.id != 0) { ecs_get_path_w_sep_buf( world, 0, ECS_TERM_REF_ID(&term->second), ".", NULL, buf); } } else { if (term->second.id & EcsIsVariable) { ecs_strbuf_appendlit(buf, "$"); if (term->second.name) { ecs_strbuf_appendstr(buf, term->second.name); } else if (ECS_TERM_REF_ID(&term->second) == EcsThis) { ecs_strbuf_appendlit(buf, "this"); } } else if (term->second.id & EcsIsName) { ecs_strbuf_appendlit(buf, "\""); if ((ECS_TERM_REF_ID(&term->first) == EcsPredMatch) && (term->oper == EcsNot)) { ecs_strbuf_appendlit(buf, "!"); } ecs_strbuf_appendstr(buf, term->second.name); ecs_strbuf_appendlit(buf, "\""); } } return; } if (!t || !(term[-1].oper == EcsOr)) { if (term->inout == EcsIn) { ecs_strbuf_appendlit(buf, "[in] "); } else if (term->inout == EcsInOut) { ecs_strbuf_appendlit(buf, "[inout] "); } else if (term->inout == EcsOut) { ecs_strbuf_appendlit(buf, "[out] "); } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { ecs_strbuf_appendlit(buf, "[none] "); } } if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, "!"); } else if (term->oper == EcsOptional) { ecs_strbuf_appendlit(buf, "?"); } if (!src_set) { flecs_query_str_add_id(world, buf, term, &term->first, false); if (!second_set) { ecs_strbuf_appendlit(buf, "()"); } else { ecs_strbuf_appendlit(buf, "(#0,"); flecs_query_str_add_id(world, buf, term, &term->second, false); ecs_strbuf_appendlit(buf, ")"); } } else { ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; if (flags && !ECS_HAS_ID_FLAG(flags, PAIR)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(flags)); ecs_strbuf_appendch(buf, '|'); } flecs_query_str_add_id(world, buf, term, &term->first, false); ecs_strbuf_appendlit(buf, "("); if (term->src.id & EcsIsEntity && src_id == first_id) { ecs_strbuf_appendlit(buf, "$"); } else { flecs_query_str_add_id(world, buf, term, &term->src, true); } if (second_set) { ecs_strbuf_appendlit(buf, ","); flecs_query_str_add_id(world, buf, term, &term->second, false); } ecs_strbuf_appendlit(buf, ")"); } } char* ecs_term_str( const ecs_world_t *world, const ecs_term_t *term) { ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_term_to_buf(world, term, &buf, 0); return ecs_strbuf_get(&buf); } char* ecs_query_str( const ecs_query_t *q) { ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); ecs_world_t *world = q->world; ecs_strbuf_t buf = ECS_STRBUF_INIT; const ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = 0; i < count; i ++) { const ecs_term_t *term = &terms[i]; flecs_term_to_buf(world, term, &buf, i); if (i != (count - 1)) { if (term->oper == EcsOr) { ecs_strbuf_appendlit(&buf, " || "); } else { if (ECS_TERM_REF_ID(&term->first) != EcsScopeOpen) { if (ECS_TERM_REF_ID(&term[1].first) != EcsScopeClose) { ecs_strbuf_appendlit(&buf, ", "); } } } } } return ecs_strbuf_get(&buf); error: return NULL; } int32_t flecs_query_pivot_term( const ecs_world_t *world, const ecs_query_t *query) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_term_t *terms = query->terms; int32_t i, term_count = query->term_count; int32_t pivot_term = -1, min_count = -1, self_pivot_term = -1; for (i = 0; i < term_count; i ++) { const ecs_term_t *term = &terms[i]; ecs_id_t id = term->id; if ((term->oper != EcsAnd) || (i && (term[-1].oper == EcsOr))) { continue; } if (!ecs_term_match_this(term)) { continue; } ecs_id_record_t *idr = flecs_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 query 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.id & EcsTraverseFlags) == EcsSelf) { self_pivot_term = i; } } } if (self_pivot_term != -1) { pivot_term = self_pivot_term; } return pivot_term; error: return -2; } void flecs_query_apply_iter_flags( ecs_iter_t *it, const ecs_query_t *query) { ECS_BIT_COND(it->flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->flags, EcsQueryIsInstanced)); ECS_BIT_COND(it->flags, EcsIterNoData, ECS_BIT_IS_SET(query->flags, EcsQueryNoData)); ECS_BIT_COND(it->flags, EcsIterHasCondSet, ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); } /** * @file query/validator.c * @brief Validate and finalize queries. */ #include #ifdef FLECS_SCRIPT #endif static void flecs_query_validator_error( const ecs_query_validator_ctx_t *ctx, const char *fmt, ...) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ctx->desc && ctx->desc->expr) { ecs_strbuf_appendlit(&buf, "expr: "); ecs_strbuf_appendstr(&buf, ctx->desc->expr); ecs_strbuf_appendlit(&buf, "\n"); } if (ctx->query) { ecs_query_t *query = ctx->query; const ecs_term_t *terms = query->terms; int32_t i, count = query->term_count; for (i = 0; i < count; i ++) { const ecs_term_t *term = &terms[i]; if (ctx->term_index == i) { ecs_strbuf_appendlit(&buf, " > "); } else { ecs_strbuf_appendlit(&buf, " "); } flecs_term_to_buf(ctx->world, term, &buf, i); if (term->oper == EcsOr) { ecs_strbuf_appendlit(&buf, " ||"); } else if (i != (count - 1)) { ecs_strbuf_appendlit(&buf, ","); } ecs_strbuf_appendlit(&buf, "\n"); } } else { ecs_strbuf_appendlit(&buf, " > "); flecs_term_to_buf(ctx->world, ctx->term, &buf, 0); ecs_strbuf_appendlit(&buf, "\n"); } char *expr = ecs_strbuf_get(&buf); const char *name = NULL; if (ctx->query && ctx->query->entity) { name = ecs_get_name(ctx->query->world, ctx->query->entity); } va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); ecs_parser_error(name, NULL, 0, "%s\n%s", msg, expr); ecs_os_free(msg); ecs_os_free(expr); va_end(args); } static int flecs_term_ref_finalize_flags( ecs_term_ref_t *ref, ecs_query_validator_ctx_t *ctx) { if ((ref->id & EcsIsEntity) && (ref->id & EcsIsVariable)) { flecs_query_validator_error(ctx, "cannot set both IsEntity and IsVariable"); return -1; } if (ref->name && ref->name[0] == '$') { if (!ref->name[1]) { if (!(ref->id & EcsIsName)) { if (ref->id & ~EcsTermRefFlags) { flecs_query_validator_error(ctx, "conflicting values for .name and .id"); return -1; } ref->id |= EcsVariable; ref->id |= EcsIsVariable; } } else { ref->name = &ref->name[1]; ref->id |= EcsIsVariable; } } if (!(ref->id & (EcsIsEntity|EcsIsVariable|EcsIsName))) { if (ECS_TERM_REF_ID(ref) || ref->name) { if (ECS_TERM_REF_ID(ref) == EcsThis || ECS_TERM_REF_ID(ref) == EcsWildcard || ECS_TERM_REF_ID(ref) == EcsAny || ECS_TERM_REF_ID(ref) == EcsVariable) { /* Builtin variable ids default to variable */ ref->id |= EcsIsVariable; } else { ref->id |= EcsIsEntity; } } } return 0; } static int flecs_term_ref_lookup( const ecs_world_t *world, ecs_entity_t scope, ecs_term_ref_t *ref, ecs_query_validator_ctx_t *ctx) { const char *name = ref->name; if (!name) { return 0; } if (ref->id & EcsIsVariable) { if (!ecs_os_strcmp(name, "this")) { ref->id = EcsThis | ECS_TERM_REF_FLAGS(ref); ref->name = NULL; return 0; } return 0; } else if (ref->id & EcsIsName) { if (ref->name == NULL) { flecs_query_validator_error(ctx, "IsName flag specified without name"); return -1; } return 0; } ecs_assert(ref->id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); if (flecs_identifier_is_0(name)) { if (ECS_TERM_REF_ID(ref)) { flecs_query_validator_error( ctx, "name '0' does not match entity id"); return -1; } ref->name = NULL; return 0; } ecs_entity_t e = 0; if (scope) { e = ecs_lookup_child(world, scope, name); } if (!e) { e = ecs_lookup(world, name); } if (!e) { if (ctx->query && (ctx->query->flags & EcsQueryAllowUnresolvedByName)) { ref->id |= EcsIsName; ref->id &= ~EcsIsEntity; return 0; } else { flecs_query_validator_error(ctx, "unresolved identifier '%s'", name); return -1; } } ecs_entity_t ref_id = ECS_TERM_REF_ID(ref); if (ref_id && ref_id != e) { char *e_str = ecs_get_path(world, ref_id); flecs_query_validator_error(ctx, "name '%s' does not match term.id '%s'", name, e_str); ecs_os_free(e_str); return -1; } ref->id = e | ECS_TERM_REF_FLAGS(ref); ref_id = ECS_TERM_REF_ID(ref); if (!ecs_os_strcmp(name, "*") || !ecs_os_strcmp(name, "_") || !ecs_os_strcmp(name, "$")) { ref->id &= ~EcsIsEntity; ref->id |= EcsIsVariable; } /* Check if looked up id is alive (relevant for numerical ids) */ if (!(ref->id & EcsIsName) && ref_id) { if (!ecs_is_alive(world, ref_id)) { flecs_query_validator_error(ctx, "identifier '%s' is not alive", ref->name); return -1; } ref->name = NULL; return 0; } return 0; } static ecs_id_t flecs_wildcard_to_any(ecs_id_t id) { ecs_id_t flags = id & EcsTermRefFlags; if (ECS_IS_PAIR(id)) { ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_entity_t second = ECS_PAIR_SECOND(id); if (first == EcsWildcard) id = ecs_pair(EcsAny, second); if (second == EcsWildcard) id = ecs_pair(ECS_PAIR_FIRST(id), EcsAny); } else if ((id & ~EcsTermRefFlags) == EcsWildcard) { id = EcsAny; } return id | flags; } static int flecs_term_refs_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { ecs_term_ref_t *src = &term->src; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *second = &term->second; /* Include subsets for component by default, to support inheritance */ if (!(first->id & EcsTraverseFlags)) { first->id |= EcsSelf; } /* Traverse Self by default for pair target */ if (!(second->id & EcsTraverseFlags)) { if (ECS_TERM_REF_ID(second) || second->name || (second->id & EcsIsEntity)) { second->id |= EcsSelf; } } /* Source defaults to This */ if (!ECS_TERM_REF_ID(src) && (src->name == NULL) && !(src->id & EcsIsEntity)) { src->id = EcsThis | ECS_TERM_REF_FLAGS(src); src->id |= EcsIsVariable; } /* Initialize term identifier flags */ if (flecs_term_ref_finalize_flags(src, ctx)) { return -1; } if (flecs_term_ref_finalize_flags(first, ctx)) { return -1; } if (flecs_term_ref_finalize_flags(second, ctx)) { return -1; } /* Lookup term identifiers by name */ if (flecs_term_ref_lookup(world, 0, src, ctx)) { return -1; } if (flecs_term_ref_lookup(world, 0, first, ctx)) { return -1; } ecs_entity_t first_id = 0; ecs_entity_t oneof = 0; if (first->id & EcsIsEntity) { first_id = ECS_TERM_REF_ID(first); if (!first_id) { flecs_query_validator_error(ctx, "invalid \"0\" for first.name"); return -1; } /* If first element of pair has OneOf property, lookup second element of * pair in the value of the OneOf property */ oneof = flecs_get_oneof(world, first_id); } if (flecs_term_ref_lookup(world, oneof, &term->second, ctx)) { return -1; } /* If source is 0, reset traversal flags */ if (ECS_TERM_REF_ID(src) == 0 && src->id & EcsIsEntity) { src->id &= ~EcsTraverseFlags; term->trav = 0; } /* If source is wildcard, term won't return any data */ if ((src->id & EcsIsVariable) && ecs_id_is_wildcard(ECS_TERM_REF_ID(src))) { term->inout = EcsInOutNone; } /* If operator is Not, automatically convert wildcard queries to any */ if (term->oper == EcsNot) { if (ECS_TERM_REF_ID(first) == EcsWildcard) { first->id = EcsAny | ECS_TERM_REF_FLAGS(first); } if (ECS_TERM_REF_ID(second) == EcsWildcard) { second->id = EcsAny | ECS_TERM_REF_FLAGS(second); } term->id = flecs_wildcard_to_any(term->id); } return 0; } static ecs_entity_t flecs_term_ref_get_entity( const ecs_term_ref_t *ref) { if (ref->id & EcsIsEntity) { return ECS_TERM_REF_ID(ref); /* Id is known */ } else if (ref->id & EcsIsVariable) { /* Return wildcard for variables, as they aren't known yet */ if (ECS_TERM_REF_ID(ref) != EcsAny) { /* Any variable should not use wildcard, as this would return all * ids matching a wildcard, whereas Any returns the first match */ return EcsWildcard; } else { return EcsAny; } } else { return 0; /* Term id is uninitialized */ } } static int flecs_term_populate_id( ecs_term_t *term) { ecs_entity_t first = flecs_term_ref_get_entity(&term->first); ecs_entity_t second = flecs_term_ref_get_entity(&term->second); ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; if (first & ECS_ID_FLAGS_MASK) { return -1; } if (second & ECS_ID_FLAGS_MASK) { return -1; } if ((second || (term->second.id & EcsIsEntity))) { flags |= ECS_PAIR; } if (!second && !ECS_HAS_ID_FLAG(flags, PAIR)) { term->id = first | flags; } else { term->id = ecs_pair(first, second) | flags; } return 0; } static int flecs_term_populate_from_id( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { ecs_entity_t first = 0; ecs_entity_t second = 0; if (ECS_HAS_ID_FLAG(term->id, PAIR)) { first = ECS_PAIR_FIRST(term->id); second = ECS_PAIR_SECOND(term->id); if (!first) { flecs_query_validator_error(ctx, "missing first element in term.id"); return -1; } if (!second) { if (first != EcsChildOf) { flecs_query_validator_error(ctx, "missing second element in term.id"); return -1; } else { /* (ChildOf, 0) is allowed so query can be used to efficiently * query for root entities */ } } } else { first = term->id & ECS_COMPONENT_MASK; if (!first) { flecs_query_validator_error(ctx, "missing first element in term.id"); return -1; } } ecs_entity_t term_first = flecs_term_ref_get_entity(&term->first); if (term_first) { if ((uint32_t)term_first != (uint32_t)first) { flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); return -1; } } else { ecs_entity_t first_id = ecs_get_alive(world, first); if (!first_id) { term->first.id = first | ECS_TERM_REF_FLAGS(&term->first); } else { term->first.id = first_id | ECS_TERM_REF_FLAGS(&term->first); } } ecs_entity_t term_second = flecs_term_ref_get_entity(&term->second); if (term_second) { if ((uint32_t)term_second != second) { flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); return -1; } } else if (second) { ecs_entity_t second_id = ecs_get_alive(world, second); if (!second_id) { term->second.id = second | ECS_TERM_REF_FLAGS(&term->second); } else { term->second.id = second_id | ECS_TERM_REF_FLAGS(&term->second); } } return 0; } static int flecs_term_verify_eq_pred( const ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { const ecs_term_ref_t *second = &term->second; const ecs_term_ref_t *src = &term->src; ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); ecs_entity_t second_id = ECS_TERM_REF_ID(&term->second); ecs_entity_t src_id = ECS_TERM_REF_ID(&term->src); if (term->oper != EcsAnd && term->oper != EcsNot && term->oper != EcsOr) { flecs_query_validator_error(ctx, "invalid operator combination"); goto error; } if ((src->id & EcsIsName) && (second->id & EcsIsName)) { flecs_query_validator_error(ctx, "both sides of operator cannot be a name"); goto error; } if ((src->id & EcsIsEntity) && (second->id & EcsIsEntity)) { flecs_query_validator_error(ctx, "both sides of operator cannot be an entity"); goto error; } if (!(src->id & EcsIsVariable)) { flecs_query_validator_error(ctx, "left-hand of operator must be a variable"); goto error; } if (first_id == EcsPredMatch && !(second->id & EcsIsName)) { flecs_query_validator_error(ctx, "right-hand of match operator must be a string"); goto error; } if ((src->id & EcsIsVariable) && (second->id & EcsIsVariable)) { if (src_id && src_id == second_id) { flecs_query_validator_error(ctx, "both sides of operator are equal"); goto error; } if (src->name && second->name && !ecs_os_strcmp(src->name, second->name)) { flecs_query_validator_error(ctx, "both sides of operator are equal"); goto error; } } if (first_id == EcsPredEq) { if (second_id == EcsPredEq || second_id == EcsPredMatch) { flecs_query_validator_error(ctx, "invalid right-hand side for equality operator"); goto error; } } return 0; error: return -1; } static int flecs_term_verify( const ecs_world_t *world, const ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { const ecs_term_ref_t *first = &term->first; const ecs_term_ref_t *second = &term->second; const ecs_term_ref_t *src = &term->src; ecs_entity_t first_id = 0, second_id = 0; ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; ecs_id_t id = term->id; if ((src->id & EcsIsName) && (second->id & EcsIsName)) { flecs_query_validator_error(ctx, "mismatch between term.id_flags & term.id"); return -1; } ecs_entity_t src_id = ECS_TERM_REF_ID(src); if (first->id & EcsIsEntity) { first_id = ECS_TERM_REF_ID(first); } if (second->id & EcsIsEntity) { second_id = ECS_TERM_REF_ID(second); } if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { return flecs_term_verify_eq_pred(term, ctx); } if (ecs_term_ref_is_set(second) && !ECS_HAS_ID_FLAG(flags, PAIR)) { flecs_query_validator_error(ctx, "expected PAIR flag for term with pair"); return -1; } else if (!ecs_term_ref_is_set(second) && ECS_HAS_ID_FLAG(flags, PAIR)) { if (first_id != EcsChildOf) { flecs_query_validator_error(ctx, "unexpected PAIR flag for term without pair"); return -1; } else { /* Exception is made for ChildOf so we can use (ChildOf, 0) to match * all entities in the root */ } } if (!ecs_term_ref_is_set(src)) { flecs_query_validator_error(ctx, "term.src is not initialized"); return -1; } if (!ecs_term_ref_is_set(first)) { flecs_query_validator_error(ctx, "term.first is not initialized"); return -1; } if (ECS_HAS_ID_FLAG(flags, PAIR)) { if (!ECS_PAIR_FIRST(id)) { flecs_query_validator_error(ctx, "invalid 0 for first element in pair id"); return -1; } if ((ECS_PAIR_FIRST(id) != EcsChildOf) && !ECS_PAIR_SECOND(id)) { flecs_query_validator_error(ctx, "invalid 0 for second element in pair id"); return -1; } if ((first->id & EcsIsEntity) && (ecs_entity_t_lo(first_id) != ECS_PAIR_FIRST(id))) { flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); return -1; } if ((first->id & EcsIsVariable) && !ecs_id_is_wildcard(ECS_PAIR_FIRST(id))) { char *id_str = ecs_id_str(world, id); flecs_query_validator_error(ctx, "expected wildcard for variable term.first (got %s)", id_str); ecs_os_free(id_str); return -1; } if ((second->id & EcsIsEntity) && (ecs_entity_t_lo(second_id) != ECS_PAIR_SECOND(id))) { flecs_query_validator_error(ctx, "mismatch between term.id and term.second"); return -1; } if ((second->id & EcsIsVariable) && !ecs_id_is_wildcard(ECS_PAIR_SECOND(id))) { char *id_str = ecs_id_str(world, id); flecs_query_validator_error(ctx, "expected wildcard for variable term.second (got %s)", id_str); ecs_os_free(id_str); return -1; } } else { ecs_entity_t component = id & ECS_COMPONENT_MASK; if (!component) { flecs_query_validator_error(ctx, "missing component id"); return -1; } if ((first->id & EcsIsEntity) && (ecs_entity_t_lo(first_id) != ecs_entity_t_lo(component))) { flecs_query_validator_error(ctx, "mismatch between term.id and term.first"); return -1; } if ((first->id & EcsIsVariable) && !ecs_id_is_wildcard(component)) { char *id_str = ecs_id_str(world, id); flecs_query_validator_error(ctx, "expected wildcard for variable term.first (got %s)", id_str); ecs_os_free(id_str); return -1; } } if (first_id) { if (ecs_term_ref_is_set(second)) { ecs_flags64_t mask = EcsIsEntity | EcsIsVariable; if ((src->id & mask) == (second->id & mask)) { bool is_same = false; if (src->id & EcsIsEntity) { is_same = src_id == second_id; } else if (src->name && second->name) { is_same = !ecs_os_strcmp(src->name, second->name); } if (is_same && ecs_has_id(world, first_id, EcsAcyclic) && !(term->flags_ & EcsTermReflexive)) { char *pred_str = ecs_get_path(world, term->first.id); flecs_query_validator_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_path(world, second_id); char *oneof_str = ecs_get_path(world, oneof); char *id_str = ecs_id_str(world, term->id); flecs_query_validator_error(ctx, "invalid target '%s' for %s: must be child of '%s'", second_str, id_str, oneof_str); ecs_os_free(second_str); ecs_os_free(oneof_str); ecs_os_free(id_str); return -1; } } } } if (term->trav) { if (!ecs_has_id(world, term->trav, EcsTraversable)) { char *r_str = ecs_get_path(world, term->trav); flecs_query_validator_error(ctx, "cannot traverse non-traversable relationship '%s'", r_str); ecs_os_free(r_str); return -1; } } return 0; } static int flecs_term_finalize( const ecs_world_t *world, ecs_term_t *term, ecs_query_validator_ctx_t *ctx) { ctx->term = term; ecs_term_ref_t *src = &term->src; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *second = &term->second; ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); ecs_flags64_t second_flags = ECS_TERM_REF_FLAGS(second); if (first->name && (first->id & ~EcsTermRefFlags)) { flecs_query_validator_error(ctx, "first.name and first.id have competing values"); return -1; } if (src->name && (src->id & ~EcsTermRefFlags)) { flecs_query_validator_error(ctx, "src.name and src.id have competing values"); return -1; } if (second->name && (second->id & ~EcsTermRefFlags)) { flecs_query_validator_error(ctx, "second.name and second.id have competing values"); return -1; } if (term->id & ~ECS_ID_FLAGS_MASK) { if (flecs_term_populate_from_id(world, term, ctx)) { return -1; } } if (flecs_term_refs_finalize(world, term, ctx)) { return -1; } ecs_entity_t first_id = ECS_TERM_REF_ID(first); ecs_entity_t second_id = ECS_TERM_REF_ID(second); ecs_entity_t src_id = ECS_TERM_REF_ID(src); if ((first->id & EcsIsVariable) && (first_id == EcsAny)) { term->flags_ |= EcsTermMatchAny; } if ((second->id & EcsIsVariable) && (second_id == EcsAny)) { term->flags_ |= EcsTermMatchAny; } if ((src->id & EcsIsVariable) && (src_id == EcsAny)) { term->flags_ |= EcsTermMatchAnySrc; } ecs_flags64_t ent_var_mask = EcsIsEntity | EcsIsVariable; /* If EcsVariable is used by itself, assign to predicate (singleton) */ if ((ECS_TERM_REF_ID(src) == EcsVariable) && (src->id & EcsIsVariable)) { src->id = first->id | ECS_TERM_REF_FLAGS(src); src->id &= ~ent_var_mask; src->id |= first->id & ent_var_mask; src->name = first->name; } if ((ECS_TERM_REF_ID(second) == EcsVariable) && (second->id & EcsIsVariable)) { second->id = first->id | ECS_TERM_REF_FLAGS(second); second->id &= ~ent_var_mask; second->id |= first->id & ent_var_mask; second->name = first->name; } if (!(term->id & ~ECS_ID_FLAGS_MASK)) { if (flecs_term_populate_id(term)) { return -1; } } /* If term queries for !(ChildOf, _), translate it to the builtin * (ChildOf, 0) index which is a cheaper way to find root entities */ if (term->oper == EcsNot && term->id == ecs_pair(EcsChildOf, EcsAny)) { /* Only if the source is not EcsAny */ if (!(ECS_TERM_REF_ID(&term->src) == EcsAny && (term->src.id & EcsIsVariable))) { term->oper = EcsAnd; term->id = ecs_pair(EcsChildOf, 0); second->id = 0; second->id |= EcsSelf|EcsIsEntity; } } ecs_entity_t first_entity = 0; if ((first->id & EcsIsEntity)) { first_entity = first_id; } ecs_id_record_t *idr = flecs_id_record_get(world, term->id); ecs_flags32_t id_flags = 0; if (idr) { id_flags = idr->flags; } else if (ECS_IS_PAIR(term->id)) { ecs_id_record_t *wc_idr = flecs_id_record_get( world, ecs_pair(ECS_PAIR_FIRST(term->id), EcsWildcard)); if (wc_idr) { id_flags = wc_idr->flags; } } if (src_id || src->name) { if (!(term->src.id & EcsTraverseFlags)) { if (id_flags & EcsIdOnInstantiateInherit) { term->src.id |= EcsSelf|EcsUp; if (!term->trav) { term->trav = EcsIsA; } } else { term->src.id |= EcsSelf; if (term->trav) { char *idstr = ecs_id_str(world, term->id); flecs_query_validator_error(ctx, ".trav specified for " "'%s' which can't be inherited", idstr); ecs_os_free(idstr); return -1; } } } if (term->src.id & EcsCascade) { /* Cascade always implies up traversal */ term->src.id |= EcsUp; } if ((src->id & EcsUp) && !term->trav) { /* When traversal flags are specified but no traversal relationship, * default to ChildOf, which is the most common kind of traversal. */ term->trav = EcsChildOf; } } if (!(id_flags & EcsIdOnInstantiateInherit) && (term->trav == EcsIsA)) { if (src->id & EcsUp) { char *idstr = ecs_id_str(world, term->id); flecs_query_validator_error(ctx, "IsA traversal not allowed " "for '%s', add the (OnInstantiate, Inherit) trait", idstr); ecs_os_free(idstr); return -1; } } if (first_entity) { /* Only enable inheritance for ids which are inherited from at the time * of query creation. To force component inheritance to be evaluated, * an application can explicitly set traversal flags. */ if (flecs_id_record_get(world, ecs_pair(EcsIsA, first->id))) { if (!((first_flags & EcsTraverseFlags) == EcsSelf)) { term->flags_ |= EcsTermIdInherited; } } /* If component id is final, don't attempt component inheritance */ ecs_record_t *first_record = flecs_entities_get(world, first_entity); ecs_table_t *first_table = first_record ? first_record->table : NULL; if (first_table) { /* Add traversal flags for transitive relationships */ if (ecs_term_ref_is_set(second) && !((second_flags & EcsTraverseFlags) == EcsSelf)) { if (!((src->id & EcsIsVariable) && (src_id == EcsAny))) { if (!((second->id & EcsIsVariable) && (second_id == EcsAny))) { if (ecs_table_has_id(world, first_table, EcsTransitive)) { term->flags_ |= EcsTermTransitive; } } } } if (ecs_table_has_id(world, first_table, EcsReflexive)) { term->flags_ |= EcsTermReflexive; } /* Check if term is union */ if (ecs_table_has_id(world, first_table, EcsUnion)) { /* Any wildcards don't need special handling as they just return * (Rel, *). */ if (ECS_IS_PAIR(term->id) && ECS_PAIR_SECOND(term->id) != EcsAny) { term->flags_ |= EcsTermIsUnion; } } } /* Check if term has toggleable component */ if (id_flags & EcsIdCanToggle) { /* If the term isn't matched on a #0 source */ if (term->src.id != EcsIsEntity) { term->flags_ |= EcsTermIsToggle; } } /* Check if this is a member query */ #ifdef FLECS_META if (ecs_id(EcsMember) != 0) { if (first_entity) { if (ecs_has(world, first_entity, EcsMember)) { term->flags_ |= EcsTermIsMember; } } } #endif } if (ECS_TERM_REF_ID(first) == EcsVariable) { flecs_query_validator_error(ctx, "invalid $ for term.first"); return -1; } if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { if (term->inout != EcsInOutDefault && term->inout != EcsInOutNone) { flecs_query_validator_error(ctx, "invalid inout value for AndFrom/OrFrom/NotFrom term"); return -1; } } /* Is term trivial/cacheable */ bool cacheable_term = true; bool trivial_term = true; if (term->oper != EcsAnd || term->flags_ & EcsTermIsOr) { trivial_term = false; } if (ecs_id_is_wildcard(term->id)) { if (!(id_flags & EcsIdExclusive)) { trivial_term = false; } if (first->id & EcsIsVariable) { if (!ecs_id_is_wildcard(first_id) || first_id == EcsAny) { trivial_term = false; cacheable_term = false; } } if (second->id & EcsIsVariable) { if (!ecs_id_is_wildcard(second_id) || second_id == EcsAny) { trivial_term = false; cacheable_term = false; } } } if (!ecs_term_match_this(term)) { trivial_term = false; } if (term->flags_ & EcsTermTransitive) { trivial_term = false; cacheable_term = false; } if (term->flags_ & EcsTermIdInherited) { trivial_term = false; cacheable_term = false; } if (term->trav && term->trav != EcsIsA) { trivial_term = false; } if (!(src->id & EcsSelf)) { trivial_term = false; } if (((ECS_TERM_REF_ID(&term->first) == EcsPredEq) || (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) || (ECS_TERM_REF_ID(&term->first) == EcsPredLookup)) && (term->first.id & EcsIsEntity)) { trivial_term = false; cacheable_term = false; } if (ECS_TERM_REF_ID(src) != EcsThis) { cacheable_term = false; } if (term->id == ecs_childof(0)) { cacheable_term = false; } if (term->flags_ & EcsTermIsMember) { trivial_term = false; cacheable_term = false; } if (term->flags_ & EcsTermIsToggle) { trivial_term = false; } if (term->flags_ & EcsTermIsUnion) { trivial_term = false; cacheable_term = false; } ECS_BIT_COND16(term->flags_, EcsTermIsTrivial, trivial_term); ECS_BIT_COND16(term->flags_, EcsTermIsCacheable, cacheable_term); if (flecs_term_verify(world, term, ctx)) { return -1; } return 0; } bool flecs_identifier_is_0( const char *id) { return id[0] == '#' && id[1] == '0' && !id[2]; } bool ecs_term_ref_is_set( const ecs_term_ref_t *ref) { return ECS_TERM_REF_ID(ref) != 0 || ref->name != NULL || ref->id & EcsIsEntity; } bool ecs_term_is_initialized( const ecs_term_t *term) { return term->id != 0 || ecs_term_ref_is_set(&term->first); } bool ecs_term_match_this( const ecs_term_t *term) { return (term->src.id & EcsIsVariable) && (ECS_TERM_REF_ID(&term->src) == EcsThis); } bool ecs_term_match_0( const ecs_term_t *term) { return (!ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)); } int ecs_term_finalize( const ecs_world_t *world, ecs_term_t *term) { ecs_query_validator_ctx_t ctx = {0}; ctx.world = world; ctx.term = term; return flecs_term_finalize(world, term, &ctx); } static ecs_term_t* flecs_query_or_other_type( ecs_query_t *q, int32_t t) { ecs_term_t *term = &q->terms[t]; ecs_term_t *first = NULL; while (t--) { if (q->terms[t].oper != EcsOr) { break; } first = &q->terms[t]; } if (first) { ecs_world_t *world = q->world; const ecs_type_info_t *first_type = ecs_get_type_info(world, first->id); const ecs_type_info_t *term_type = ecs_get_type_info(world, term->id); if (first_type == term_type) { return NULL; } return first; } else { return NULL; } } static void flecs_normalize_term_name( ecs_term_ref_t *ref) { if (ref->name && ref->name[0] == '$' && ref->name[1]) { ecs_assert(ref->id & EcsIsVariable, ECS_INTERNAL_ERROR, NULL); const char *old = ref->name; ref->name = &old[1]; if (!ecs_os_strcmp(ref->name, "this")) { ref->name = NULL; ref->id |= EcsThis; } } } static int flecs_query_finalize_terms( const ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc) { int32_t i, term_count = q->term_count, field_count = 0; ecs_term_t *terms = q->terms; int32_t nodata_terms = 0, scope_nesting = 0, cacheable_terms = 0; bool cond_set = false; ecs_query_validator_ctx_t ctx = {0}; ctx.world = world; ctx.query = q; ctx.desc = desc; q->flags |= EcsQueryMatchOnlyThis; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper == EcsOr) { term->flags_ |= EcsTermIsOr; if (i != (term_count - 1)) { term[1].flags_ |= EcsTermIsOr; } } } bool cacheable = true; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; bool prev_is_or = i && term[-1].oper == EcsOr; bool nodata_term = false; ctx.term_index = i; if (flecs_term_finalize(world, term, &ctx)) { return -1; } if (scope_nesting) { /* Terms inside a scope are not cacheable */ ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } /* If one of the terms in an OR chain isn't cacheable, none are */ if (term->flags_ & EcsTermIsCacheable) { /* Current term is marked as cacheable. Check if it is part of an OR * chain, and if so, the previous term was also cacheable. */ if (prev_is_or) { if (term[-1].flags_ & EcsTermIsCacheable) { cacheable_terms ++; } else { ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } } else { cacheable_terms ++; } /* Toggle terms may be cacheable for fetching the initial component, * but require an additional toggle instruction for evaluation. */ if (term->flags_ & EcsTermIsToggle) { cacheable = false; } } else if (prev_is_or) { /* Current term is not cacheable. If it is part of an OR chain, mark * previous terms in the chain as also not cacheable. */ int32_t j; for (j = i - 1; j >= 0; j --) { if (terms[j].oper != EcsOr) { break; } if (terms[j].flags_ & EcsTermIsCacheable) { cacheable_terms --; ECS_BIT_CLEAR16(terms[j].flags_, EcsTermIsCacheable); } } } if (prev_is_or) { if (ECS_TERM_REF_ID(&term[-1].src) != ECS_TERM_REF_ID(&term->src)) { flecs_query_validator_error(&ctx, "mismatching src.id for OR terms"); return -1; } if (term->oper != EcsOr && term->oper != EcsAnd) { flecs_query_validator_error(&ctx, "term after OR operator must use AND operator"); return -1; } } else { field_count ++; } term->field_index = flecs_ito(int16_t, field_count - 1); if (ecs_id_is_wildcard(term->id)) { q->flags |= EcsQueryMatchWildcards; } else if (!(term->flags_ & EcsTermIsOr)) { ECS_TERMSET_SET(q->static_id_fields, 1u << term->field_index); } if (ecs_term_match_this(term)) { ECS_BIT_SET(q->flags, EcsQueryMatchThis); } else { ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlyThis); } if (ECS_TERM_REF_ID(term) == EcsPrefab) { ECS_BIT_SET(q->flags, EcsQueryMatchPrefab); } if (ECS_TERM_REF_ID(term) == EcsDisabled && (term->src.id & EcsSelf)) { ECS_BIT_SET(q->flags, EcsQueryMatchDisabled); } if (ECS_BIT_IS_SET(q->flags, EcsQueryNoData)) { term->inout = EcsInOutNone; } if (term->oper == EcsNot && term->inout == EcsInOutDefault) { term->inout = EcsInOutNone; } if ((term->id == EcsWildcard) || (term->id == ecs_pair(EcsWildcard, EcsWildcard))) { /* If term type is unknown beforehand, default the inout type to * none. This prevents accidentally requesting lots of components, * which can put stress on serializer code. */ if (term->inout == EcsInOutDefault) { term->inout = EcsInOutNone; } } if (term->src.id == EcsIsEntity) { nodata_term = true; } else if (term->inout == EcsInOutNone) { nodata_term = true; } else if (!ecs_get_type_info(world, term->id)) { nodata_term = true; } else { if (ecs_id_is_tag(world, term->id)) { nodata_term = true; } else if ((ECS_PAIR_SECOND(term->id) == EcsWildcard) || (ECS_PAIR_SECOND(term->id) == EcsAny)) { /* If the second element of a pair is a wildcard and the first * element is not a type, we can't know in advance what the * type of the term is, so it can't provide data. */ if (!ecs_get_type_info(world, ecs_pair_first(world, term->id))) { nodata_term = true; } } } if (term->inout != EcsIn && term->inout != EcsInOutNone) { /* Non-this terms default to EcsIn */ if (ecs_term_match_this(term) || term->inout != EcsInOutDefault) { q->flags |= EcsQueryHasOutTerms; } bool match_non_this = !ecs_term_match_this(term) || (term->src.id & EcsUp); if (match_non_this && term->inout != EcsInOutDefault) { q->flags |= EcsQueryHasNonThisOutTerms; } } if (!nodata_term) { /* If terms in an OR chain do not all return the same type, the * field will not provide any data */ if (term->flags_ & EcsTermIsOr) { ecs_term_t *first = flecs_query_or_other_type(q, i); if (first) { if (first == &term[-1]) { if (!(term[-1].flags_ & EcsTermNoData)) { nodata_terms ++; } } nodata_term = true; } q->data_fields &= (ecs_termset_t)~(1llu << term->field_index); } } if (term->flags_ & EcsTermIsMember) { nodata_term = false; } if (nodata_term) { nodata_terms ++; term->flags_ |= EcsTermNoData; } else if (term->oper != EcsNot) { ECS_TERMSET_SET(q->data_fields, 1u << term->field_index); if (term->inout != EcsIn) { ECS_TERMSET_SET(q->write_fields, 1u << term->field_index); } if (term->inout != EcsOut) { ECS_TERMSET_SET(q->read_fields, 1u << term->field_index); } if (term->inout == EcsInOutDefault) { ECS_TERMSET_SET(q->shared_readonly_fields, 1u << term->field_index); } } if (ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)) { ECS_TERMSET_SET(q->fixed_fields, 1u << term->field_index); } ecs_id_record_t *idr = flecs_id_record_get(world, term->id); if (idr) { if (ecs_os_has_threading()) { ecs_os_ainc(&idr->keep_alive); } else { idr->keep_alive ++; } term->flags_ |= EcsTermKeepAlive; if (idr->flags & EcsIdIsSparse) { if (!(term->flags_ & EcsTermNoData)) { term->flags_ |= EcsTermIsSparse; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); if (term->flags_ & EcsTermIsCacheable) { cacheable_terms --; ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } } } } if (term->oper == EcsOptional || term->oper == EcsNot) { cond_set = true; } ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); if (first_id == EcsPredEq || first_id == EcsPredMatch || first_id == EcsPredLookup) { q->flags |= EcsQueryHasPred; term->src.id = (term->src.id & ~EcsTraverseFlags) | EcsSelf; term->inout = EcsInOutNone; } else { if (!ecs_term_match_0(term) && term->oper != EcsNot && term->oper != EcsNotFrom) { ECS_TERMSET_SET(q->set_fields, 1u << term->field_index); } } if (first_id == EcsScopeOpen) { q->flags |= EcsQueryHasScopes; scope_nesting ++; } if (scope_nesting) { term->flags_ |= EcsTermIsScope; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); cacheable_terms --; } if (first_id == EcsScopeClose) { if (i && ECS_TERM_REF_ID(&terms[i - 1].first) == EcsScopeOpen) { flecs_query_validator_error(&ctx, "invalid empty scope"); return -1; } q->flags |= EcsQueryHasScopes; scope_nesting --; } if (scope_nesting < 0) { flecs_query_validator_error(&ctx, "'}' without matching '{'"); } } if (scope_nesting != 0) { flecs_query_validator_error(&ctx, "missing '}'"); return -1; } if (term_count && (terms[term_count - 1].oper == EcsOr)) { flecs_query_validator_error(&ctx, "last term of query can't have OR operator"); return -1; } q->field_count = flecs_ito(int8_t, field_count); if (field_count) { for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int32_t field = term->field_index; q->ids[field] = term->id; if (term->flags_ & EcsTermIsOr) { if (flecs_query_or_other_type(q, i)) { q->sizes[field] = 0; q->ids[field] = 0; continue; } } ecs_id_record_t *idr = flecs_id_record_get(world, term->id); if (idr) { if (!ECS_IS_PAIR(idr->id) || ECS_PAIR_FIRST(idr->id) != EcsWildcard) { if (idr->type_info) { q->sizes[field] = idr->type_info->size; q->ids[field] = idr->id; } } } else { const ecs_type_info_t *ti = ecs_get_type_info( world, term->id); if (ti) { q->sizes[field] = ti->size; q->ids[field] = term->id; } } if (term->flags_ & EcsTermIsMember) { q->sizes[field] = ECS_SIZEOF(ecs_entity_t); #ifdef FLECS_META q->ids[field] = ecs_id(ecs_entity_t); #endif } } } ecs_assert(nodata_terms <= term_count, ECS_INTERNAL_ERROR, NULL); if (nodata_terms == term_count) { ECS_BIT_SET(q->flags, EcsQueryNoData); } ECS_BIT_COND(q->flags, EcsQueryHasCondSet, cond_set); /* Check if this is a trivial query */ if ((q->flags & EcsQueryMatchOnlyThis)) { if (!(q->flags & (EcsQueryHasPred|EcsQueryMatchDisabled|EcsQueryMatchPrefab))) { ECS_BIT_SET(q->flags, EcsQueryMatchOnlySelf); for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *src = &term->src; if (src->id & EcsUp) { ECS_BIT_CLEAR(q->flags, EcsQueryMatchOnlySelf); } if (!(term->flags_ & EcsTermIsTrivial)) { break; } if (!(q->flags & EcsQueryNoData)) { if (term->inout == EcsInOutNone) { break; } } } if (term_count && (i == term_count)) { ECS_BIT_SET(q->flags, EcsQueryIsTrivial); } } } /* Set cacheable flags */ ECS_BIT_COND(q->flags, EcsQueryHasCacheable, cacheable_terms != 0); ECS_BIT_COND(q->flags, EcsQueryIsCacheable, cacheable && (cacheable_terms == term_count)); for (i = 0; i < q->term_count; i ++) { ecs_term_t *term = &q->terms[i]; /* Post process term names in case they were used to create variables */ flecs_normalize_term_name(&term->first); flecs_normalize_term_name(&term->second); flecs_normalize_term_name(&term->src); } return 0; } static int flecs_query_query_populate_terms( ecs_world_t *world, ecs_stage_t *stage, ecs_query_t *q, const ecs_query_desc_t *desc) { /* Count number of initialized terms in desc->terms */ int32_t i, term_count = 0; for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { if (!ecs_term_is_initialized(&desc->terms[i])) { break; } term_count ++; } /* Copy terms from array to query */ if (term_count) { ecs_os_memcpy_n(&q->terms, desc->terms, ecs_term_t, term_count); } /* Parse query expression if set */ const char *expr = desc->expr; if (expr && expr[0]) { #ifdef FLECS_SCRIPT ecs_script_impl_t script = { .pub.world = world, .pub.name = desc->entity ? ecs_get_name(world, desc->entity) : NULL, .pub.code = expr }; /* Allocate buffer that's large enough to tokenize the query string */ script.token_buffer_size = ecs_os_strlen(expr) * 2 + 1; script.token_buffer = flecs_alloc( &stage->allocator, script.token_buffer_size); if (flecs_terms_parse(&script.pub, &q->terms[term_count], &term_count)) { flecs_free(&stage->allocator, script.token_buffer_size, script.token_buffer); goto error; } /* Store on query object so we can free later */ flecs_query_impl(q)->tokens = script.token_buffer; flecs_query_impl(q)->tokens_len = flecs_ito(int16_t, script.token_buffer_size); #else (void)world; (void)stage; ecs_err("cannot parse query expression: script addon required"); goto error; #endif } q->term_count = flecs_ito(int8_t, term_count); return 0; error: return -1; } int flecs_query_finalize_query( ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_query_desc_t was not initialized to zero"); ecs_stage_t *stage = flecs_stage_from_world(&world); q->flags |= desc->flags | world->default_query_flags; /* Populate term array from desc terms & DSL expression */ if (flecs_query_query_populate_terms(world, stage, q, desc)) { goto error; } /* Ensure all fields are consistent and properly filled out */ if (flecs_query_finalize_terms(world, q, desc)) { goto error; } return 0; error: return -1; } static ecs_entity_index_page_t* flecs_entity_index_ensure_page( ecs_entity_index_t *index, uint32_t id) { int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); if (page_index >= ecs_vec_count(&index->pages)) { ecs_vec_set_min_count_zeromem_t(index->allocator, &index->pages, ecs_entity_index_page_t*, page_index + 1); } ecs_entity_index_page_t **page_ptr = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index); ecs_entity_index_page_t *page = *page_ptr; if (!page) { page = *page_ptr = flecs_bcalloc(&index->page_allocator); ecs_assert(page != NULL, ECS_OUT_OF_MEMORY, NULL); } return page; } void flecs_entity_index_init( ecs_allocator_t *allocator, ecs_entity_index_t *index) { index->allocator = allocator; index->alive_count = 1; ecs_vec_init_t(allocator, &index->dense, uint64_t, 1); ecs_vec_set_count_t(allocator, &index->dense, uint64_t, 1); ecs_vec_init_t(allocator, &index->pages, ecs_entity_index_page_t*, 0); flecs_ballocator_init(&index->page_allocator, ECS_SIZEOF(ecs_entity_index_page_t)); } void flecs_entity_index_fini( ecs_entity_index_t *index) { ecs_vec_fini_t(index->allocator, &index->dense, uint64_t); #if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) int32_t i, count = ecs_vec_count(&index->pages); ecs_entity_index_page_t **pages = ecs_vec_first(&index->pages); for (i = 0; i < count; i ++) { flecs_bfree(&index->page_allocator, pages[i]); } #endif ecs_vec_fini_t(index->allocator, &index->pages, ecs_entity_index_page_t*); flecs_ballocator_fini(&index->page_allocator); } ecs_record_t* flecs_entity_index_get_any( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index)[0]; ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; ecs_assert(r->dense != 0, ECS_INVALID_PARAMETER, "entity %u does not exist", (uint32_t)entity); return r; } ecs_record_t* flecs_entity_index_get( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_get_any(index, entity); ecs_assert(r->dense < index->alive_count, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] == entity, ECS_INVALID_PARAMETER, "mismatching liveliness generation for entity"); return r; } ecs_record_t* flecs_entity_index_try_get_any( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; int32_t page_index = (int32_t)(id >> FLECS_ENTITY_PAGE_BITS); if (page_index >= ecs_vec_count(&index->pages)) { return NULL; } ecs_entity_index_page_t *page = ecs_vec_get_t(&index->pages, ecs_entity_index_page_t*, page_index)[0]; if (!page) { return NULL; } ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; if (!r->dense) { return NULL; } return r; } ecs_record_t* flecs_entity_index_try_get( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { if (r->dense >= index->alive_count) { return NULL; } if (ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] != entity) { return NULL; } } return r; } ecs_record_t* flecs_entity_index_ensure( ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; int32_t dense = r->dense; if (dense) { /* Entity is already alive, nothing to be done */ if (dense < index->alive_count) { ecs_assert( ecs_vec_get_t(&index->dense, uint64_t, dense)[0] == entity, ECS_INTERNAL_ERROR, NULL); return r; } } else { /* Entity doesn't have a dense index yet */ ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = entity; r->dense = dense = ecs_vec_count(&index->dense) - 1; index->max_id = id > index->max_id ? id : index->max_id; } ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); /* Entity is not alive, swap with first not alive element */ uint64_t *ids = ecs_vec_first(&index->dense); uint64_t e_swap = ids[index->alive_count]; ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); ecs_assert(r_swap->dense == index->alive_count, ECS_INTERNAL_ERROR, NULL); r_swap->dense = dense; r->dense = index->alive_count; ids[dense] = e_swap; ids[index->alive_count ++] = entity; ecs_assert(flecs_entity_index_is_alive(index, entity), ECS_INTERNAL_ERROR, NULL); return r; } void flecs_entity_index_remove( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get(index, entity); if (!r) { /* Entity is not alive or doesn't exist, nothing to be done */ return; } int32_t dense = r->dense; int32_t i_swap = -- index->alive_count; uint64_t *e_swap_ptr = ecs_vec_get_t(&index->dense, uint64_t, i_swap); uint64_t e_swap = e_swap_ptr[0]; ecs_record_t *r_swap = flecs_entity_index_get_any(index, e_swap); ecs_assert(r_swap->dense == i_swap, ECS_INTERNAL_ERROR, NULL); r_swap->dense = dense; r->table = NULL; r->idr = NULL; r->row = 0; r->dense = i_swap; ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = e_swap; e_swap_ptr[0] = ECS_GENERATION_INC(entity); ecs_assert(!flecs_entity_index_is_alive(index, entity), ECS_INTERNAL_ERROR, NULL); } void flecs_entity_index_make_alive( ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0] = entity; } } uint64_t flecs_entity_index_get_alive( const ecs_entity_index_t *index, uint64_t entity) { ecs_record_t *r = flecs_entity_index_try_get_any(index, entity); if (r) { return ecs_vec_get_t(&index->dense, uint64_t, r->dense)[0]; } else { return 0; } } bool flecs_entity_index_is_alive( const ecs_entity_index_t *index, uint64_t entity) { return flecs_entity_index_try_get(index, entity) != NULL; } bool flecs_entity_index_is_valid( const ecs_entity_index_t *index, uint64_t entity) { uint32_t id = (uint32_t)entity; ecs_record_t *r = flecs_entity_index_try_get_any(index, id); if (!r || !r->dense) { /* Doesn't exist yet, so is valid */ return true; } /* If the id exists, it must be alive */ return r->dense < index->alive_count; } bool flecs_entity_index_exists( const ecs_entity_index_t *index, uint64_t entity) { return flecs_entity_index_try_get_any(index, entity) != NULL; } uint64_t flecs_entity_index_new_id( ecs_entity_index_t *index) { if (index->alive_count != ecs_vec_count(&index->dense)) { /* Recycle id */ return ecs_vec_get_t(&index->dense, uint64_t, index->alive_count ++)[0]; } /* Create new id */ uint32_t id = (uint32_t)++ index->max_id; ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = index->alive_count ++; ecs_assert(index->alive_count == ecs_vec_count(&index->dense), ECS_INTERNAL_ERROR, NULL); return id; } uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count) { int32_t alive_count = index->alive_count; int32_t new_count = alive_count + count; int32_t dense_count = ecs_vec_count(&index->dense); if (new_count < dense_count) { /* Recycle ids */ index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } /* Allocate new ids */ ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); int32_t i, to_add = new_count - dense_count; for (i = 0; i < to_add; i ++) { uint32_t id = (uint32_t)++ index->max_id; int32_t dense = dense_count + i; ecs_vec_get_t(&index->dense, uint64_t, dense)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = dense; } index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } void flecs_entity_index_set_size( ecs_entity_index_t *index, int32_t size) { ecs_vec_set_size_t(index->allocator, &index->dense, uint64_t, size); } int32_t flecs_entity_index_count( const ecs_entity_index_t *index) { return index->alive_count - 1; } int32_t flecs_entity_index_size( const ecs_entity_index_t *index) { return ecs_vec_count(&index->dense) - 1; } int32_t flecs_entity_index_not_alive_count( const ecs_entity_index_t *index) { return ecs_vec_count(&index->dense) - index->alive_count; } void flecs_entity_index_clear( ecs_entity_index_t *index) { int32_t i, count = ecs_vec_count(&index->pages); ecs_entity_index_page_t **pages = ecs_vec_first_t(&index->pages, ecs_entity_index_page_t*); for (i = 0; i < count; i ++) { ecs_entity_index_page_t *page = pages[i]; if (page) { ecs_os_zeromem(page); } } ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, 1); index->alive_count = 1; index->max_id = 0; } const uint64_t* flecs_entity_index_ids( const ecs_entity_index_t *index) { return ecs_vec_get_t(&index->dense, uint64_t, 1); } /** * @file storage/id_index.c * @brief Index for looking up tables by (component) id. * * An id record stores the administration for an in use (component) id, that is * an id that has been used in tables. * * An id record contains a table cache, which stores the list of tables that * have the id. Each entry in the cache (a table record) stores the first * occurrence of the id in the table and the number of occurrences of the id in * the table (in the case of wildcard ids). * * Id records are used in lots of scenarios, like uncached queries, or for * getting a component array/component for an entity. */ static ecs_id_record_elem_t* flecs_id_record_elem( ecs_id_record_t *head, ecs_id_record_elem_t *list, ecs_id_record_t *idr) { return ECS_OFFSET(idr, (uintptr_t)list - (uintptr_t)head); } static void flecs_id_record_elem_insert( ecs_id_record_t *head, ecs_id_record_t *idr, ecs_id_record_elem_t *elem) { ecs_id_record_elem_t *head_elem = flecs_id_record_elem(idr, elem, head); ecs_id_record_t *cur = head_elem->next; elem->next = cur; elem->prev = head; if (cur) { ecs_id_record_elem_t *cur_elem = flecs_id_record_elem(idr, elem, cur); cur_elem->prev = idr; } head_elem->next = idr; } static void flecs_id_record_elem_remove( ecs_id_record_t *idr, ecs_id_record_elem_t *elem) { ecs_id_record_t *prev = elem->prev; ecs_id_record_t *next = elem->next; ecs_assert(prev != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_elem_t *prev_elem = flecs_id_record_elem(idr, elem, prev); prev_elem->next = next; if (next) { ecs_id_record_elem_t *next_elem = flecs_id_record_elem(idr, elem, next); next_elem->prev = prev; } } static void flecs_insert_id_elem( ecs_world_t *world, ecs_id_record_t *idr, ecs_id_t wildcard, ecs_id_record_t *widr) { ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); if (!widr) { widr = flecs_id_record_ensure(world, wildcard); } ecs_assert(widr != NULL, ECS_INTERNAL_ERROR, NULL); if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_insert(widr, idr, &idr->first); } else { ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_insert(widr, idr, &idr->second); if (idr->flags & EcsIdTraversable) { flecs_id_record_elem_insert(widr, idr, &idr->trav); } } } static void flecs_remove_id_elem( ecs_id_record_t *idr, ecs_id_t wildcard) { ecs_assert(ecs_id_is_wildcard(wildcard), ECS_INTERNAL_ERROR, NULL); if (ECS_PAIR_SECOND(wildcard) == EcsWildcard) { ecs_assert(ECS_PAIR_FIRST(wildcard) != EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_remove(idr, &idr->first); } else { ecs_assert(ECS_PAIR_FIRST(wildcard) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); flecs_id_record_elem_remove(idr, &idr->second); if (idr->flags & EcsIdTraversable) { flecs_id_record_elem_remove(idr, &idr->trav); } } } static ecs_id_t flecs_id_record_hash( ecs_id_t id) { id = ecs_strip_generation(id); if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); if (r == EcsAny) { r = EcsWildcard; } if (o == EcsAny) { o = EcsWildcard; } id = ecs_pair(r, o); } return id; } void flecs_id_record_init_sparse( ecs_world_t *world, ecs_id_record_t *idr) { if (!idr->sparse) { if (idr->flags & EcsIdIsSparse) { ecs_assert(!(idr->flags & EcsIdIsUnion), ECS_CONSTRAINT_VIOLATED, "cannot mix union and sparse traits"); ecs_assert(idr->type_info != NULL, ECS_INVALID_OPERATION, "only components can be marked as sparse"); idr->sparse = flecs_walloc_t(world, ecs_sparse_t); flecs_sparse_init(idr->sparse, NULL, NULL, idr->type_info->size); } else if (idr->flags & EcsIdIsUnion) { idr->sparse = flecs_walloc_t(world, ecs_switch_t); flecs_switch_init(idr->sparse, &world->allocator); } } } static void flecs_id_record_fini_sparse( ecs_world_t *world, ecs_id_record_t *idr) { if (idr->sparse) { if (idr->flags & EcsIdIsSparse) { ecs_assert(flecs_sparse_count(idr->sparse) == 0, ECS_INTERNAL_ERROR, NULL); flecs_sparse_fini(idr->sparse); flecs_wfree_t(world, ecs_sparse_t, idr->sparse); } else if (idr->flags & EcsIdIsUnion) { flecs_switch_fini(idr->sparse); flecs_wfree_t(world, ecs_switch_t, idr->sparse); } else { ecs_abort(ECS_INTERNAL_ERROR, "unknown sparse storage"); } } } static ecs_id_record_t* flecs_id_record_new( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr, *idr_t = NULL; ecs_id_t hash = flecs_id_record_hash(id); if (hash >= FLECS_HI_ID_RECORD_ID) { idr = flecs_bcalloc(&world->allocators.id_record); ecs_map_insert_ptr(&world->id_index_hi, hash, idr); } else { idr = &world->id_index_lo[hash]; ecs_os_zeromem(idr); } ecs_table_cache_init(world, &idr->cache); idr->id = id; idr->refcount = 1; idr->reachable.current = -1; bool is_wildcard = ecs_id_is_wildcard(id); bool is_pair = ECS_IS_PAIR(id); ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; if (is_pair) { // rel = ecs_pair_first(world, id); rel = ECS_PAIR_FIRST(id); rel = flecs_entities_get_alive(world, rel); ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); /* Relationship object can be 0, as tables without a ChildOf * relationship are added to the (ChildOf, 0) id record */ tgt = ECS_PAIR_SECOND(id); #ifdef FLECS_DEBUG /* Check constraints */ if (tgt) { tgt = flecs_entities_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); /* Can't use relationship as target */ if (ecs_has_id(world, tgt, EcsRelationship)) { if (!ecs_id_is_wildcard(rel) && !ecs_has_id(world, rel, EcsTrait)) { char *idstr = ecs_id_str(world, id); char *tgtstr = ecs_id_str(world, tgt); ecs_err("constraint violated: relationship '%s' cannot be used" " as target in pair '%s'", tgtstr, idstr); ecs_os_free(tgtstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } } if (ecs_has_id(world, rel, EcsTarget)) { char *idstr = ecs_id_str(world, id); char *relstr = ecs_id_str(world, rel); ecs_err("constraint violated: " "%s: target '%s' cannot be used as relationship", idstr, relstr); ecs_os_free(relstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } if (tgt && !ecs_id_is_wildcard(tgt) && tgt != EcsUnion) { /* Check if target of relationship satisfies OneOf property */ ecs_entity_t oneof = flecs_get_oneof(world, rel); if (oneof) { if (!ecs_has_pair(world, tgt, EcsChildOf, oneof)) { char *idstr = ecs_id_str(world, id); char *tgtstr = ecs_get_path(world, tgt); char *oneofstr = ecs_get_path(world, oneof); ecs_err("OneOf constraint violated: " "%s: '%s' is not a child of '%s'", idstr, tgtstr, oneofstr); ecs_os_free(oneofstr); ecs_os_free(tgtstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } /* Check if we're not trying to inherit from a final target */ if (rel == EcsIsA) { if (ecs_has_id(world, tgt, EcsFinal)) { char *idstr = ecs_id_str(world, id); char *tgtstr = ecs_get_path(world, tgt); ecs_err("Final constraint violated: " "%s: cannot inherit from final entity '%s'", idstr, tgtstr); ecs_os_free(tgtstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } } } #endif if (!is_wildcard && (rel != EcsFlag)) { /* Inherit flags from (relationship, *) record */ ecs_id_record_t *idr_r = flecs_id_record_ensure( world, ecs_pair(rel, EcsWildcard)); idr->parent = idr_r; idr->flags = idr_r->flags; /* If pair is not a wildcard, append it to wildcard lists. These * allow for quickly enumerating all relationships for an object, * or all objects for a relationship. */ flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); } } else { rel = id & ECS_COMPONENT_MASK; ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); /* Can't use relationship outside of a pair */ #ifdef FLECS_DEBUG rel = flecs_entities_get_alive(world, rel); bool is_tgt = false; if (ecs_has_id(world, rel, EcsRelationship) || (is_tgt = ecs_has_id(world, rel, EcsTarget))) { char *idstr = ecs_id_str(world, id); char *relstr = ecs_id_str(world, rel); ecs_err("constraint violated: " "%s: relationship%s '%s' cannot be used as component", idstr, is_tgt ? " target" : "", relstr); ecs_os_free(relstr); ecs_os_free(idstr); #ifndef FLECS_SOFT_ASSERT ecs_abort(ECS_CONSTRAINT_VIOLATED, NULL); #endif } #endif } /* Initialize type info if id is not a tag */ if (!is_wildcard && (!role || is_pair)) { if (!(idr->flags & EcsIdTag)) { const ecs_type_info_t *ti = flecs_type_info_get(world, rel); if (!ti && tgt) { ti = flecs_type_info_get(world, tgt); } idr->type_info = ti; } } /* Mark entities that are used as component/pair ids. When a tracked * entity is deleted, cleanup policies are applied so that the store * won't contain any tables with deleted ids. */ /* Flag for OnDelete policies */ flecs_add_flag(world, rel, EcsEntityIsId); if (tgt) { /* Flag for OnDeleteTarget policies */ ecs_record_t *tgt_r = flecs_entities_get_any(world, tgt); ecs_assert(tgt_r != NULL, ECS_INTERNAL_ERROR, NULL); flecs_record_add_flag(tgt_r, EcsEntityIsTarget); if (idr->flags & EcsIdTraversable) { /* Flag used to determine if object should be traversed when * propagating events or with super/subset queries */ flecs_record_add_flag(tgt_r, EcsEntityIsTraversable); /* Add reference to (*, tgt) id record to entity record */ tgt_r->idr = idr_t; } } ecs_observable_t *o = &world->observable; idr->flags |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; idr->flags |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; idr->flags |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; idr->flags |= flecs_observers_exist(o, id, EcsOnTableFill) * EcsIdHasOnTableFill; idr->flags |= flecs_observers_exist(o, id, EcsOnTableEmpty) * EcsIdHasOnTableEmpty; idr->flags |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; idr->flags |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; if (idr->flags & EcsIdIsSparse) { flecs_id_record_init_sparse(world, idr); } else if (idr->flags & EcsIdIsUnion) { if (ECS_IS_PAIR(id) && ECS_PAIR_SECOND(id) == EcsUnion) { flecs_id_record_init_sparse(world, idr); } } if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); ecs_dbg_1("#[green]id#[normal] %s #[green]created", id_str); ecs_os_free(id_str); } /* Update counters */ world->info.id_create_total ++; world->info.component_id_count += idr->type_info != NULL; world->info.tag_id_count += idr->type_info == NULL; world->info.pair_id_count += is_pair; return idr; } static void flecs_id_record_assert_empty( ecs_id_record_t *idr) { (void)idr; ecs_assert(flecs_table_cache_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_table_cache_empty_count(&idr->cache) == 0, ECS_INTERNAL_ERROR, NULL); } static void flecs_id_record_free( ecs_world_t *world, ecs_id_record_t *idr) { flecs_poly_assert(world, ecs_world_t); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = idr->id; flecs_id_record_assert_empty(idr); /* Id is still in use by a query */ ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), ECS_ID_IN_USE, "cannot delete id that is queried for"); if (ECS_IS_PAIR(id)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t tgt = ECS_PAIR_SECOND(id); if (!ecs_id_is_wildcard(id)) { if (ECS_PAIR_FIRST(id) != EcsFlag) { /* If id is not a wildcard, remove it from the wildcard lists */ flecs_remove_id_elem(idr, ecs_pair(rel, EcsWildcard)); flecs_remove_id_elem(idr, ecs_pair(EcsWildcard, tgt)); } } else { ecs_log_push_2(); /* If id is a wildcard, it means that all id records that match the * wildcard are also empty, so release them */ if (ECS_PAIR_FIRST(id) == EcsWildcard) { /* Iterate (*, Target) list */ ecs_id_record_t *cur, *next = idr->second.next; while ((cur = next)) { flecs_id_record_assert_empty(cur); next = cur->second.next; flecs_id_record_release(world, cur); } } else { /* Iterate (Relationship, *) list */ ecs_assert(ECS_PAIR_SECOND(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *cur, *next = idr->first.next; while ((cur = next)) { flecs_id_record_assert_empty(cur); next = cur->first.next; flecs_id_record_release(world, cur); } } ecs_log_pop_2(); } } /* Cleanup sparse storage */ flecs_id_record_fini_sparse(world, idr); /* Update counters */ world->info.id_delete_total ++; world->info.pair_id_count -= ECS_IS_PAIR(id); world->info.component_id_count -= idr->type_info != NULL; world->info.tag_id_count -= idr->type_info == NULL; /* Unregister the id record from the world & free resources */ ecs_table_cache_fini(&idr->cache); flecs_name_index_free(idr->name_index); ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); ecs_id_t hash = flecs_id_record_hash(id); if (hash >= FLECS_HI_ID_RECORD_ID) { ecs_map_remove(&world->id_index_hi, hash); flecs_bfree(&world->allocators.id_record, idr); } else { idr->id = 0; /* Tombstone */ } if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); ecs_dbg_1("#[green]id#[normal] %s #[red]deleted", id_str); ecs_os_free(id_str); } } ecs_id_record_t* flecs_id_record_ensure( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { idr = flecs_id_record_new(world, id); } return idr; } ecs_id_record_t* flecs_id_record_get( const ecs_world_t *world, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); if (id == ecs_pair(EcsIsA, EcsWildcard)) { return world->idr_isa_wildcard; } else if (id == ecs_pair(EcsChildOf, EcsWildcard)) { return world->idr_childof_wildcard; } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { return world->idr_identifier_name; } ecs_id_t hash = flecs_id_record_hash(id); ecs_id_record_t *idr = NULL; if (hash >= FLECS_HI_ID_RECORD_ID) { idr = ecs_map_get_deref(&world->id_index_hi, ecs_id_record_t, hash); } else { idr = &world->id_index_lo[hash]; if (!idr->id) { idr = NULL; } } return idr; } 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) { ecs_table_cache_iter_t it; if (flecs_table_cache_all_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { /* Release current table */ flecs_table_fini(world, tr->hdr.table); } } } bool flecs_id_record_set_type_info( ecs_world_t *world, ecs_id_record_t *idr, const ecs_type_info_t *ti) { bool is_wildcard = ecs_id_is_wildcard(idr->id); if (!is_wildcard) { if (ti) { if (!idr->type_info) { world->info.tag_id_count --; world->info.component_id_count ++; } } else { if (idr->type_info) { world->info.tag_id_count ++; world->info.component_id_count --; } } } bool changed = idr->type_info != ti; idr->type_info = ti; return changed; } ecs_hashmap_t* flecs_id_record_name_index_ensure( ecs_world_t *world, ecs_id_record_t *idr) { ecs_hashmap_t *map = idr->name_index; if (!map) { map = idr->name_index = flecs_name_index_new(world, &world->allocator); } return map; } ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_id_record_name_index_ensure(world, idr); } ecs_hashmap_t* flecs_id_name_index_get( const ecs_world_t *world, ecs_id_t id) { flecs_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) { flecs_poly_assert(world, ecs_world_t); ecs_id_record_t* idr = flecs_id_record_get(world, id); if (!idr) { return NULL; } return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table) { ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } void flecs_init_id_records( ecs_world_t *world) { /* Cache often used id records on world */ world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); world->idr_wildcard_wildcard = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, EcsWildcard)); world->idr_any = flecs_id_record_ensure(world, EcsAny); world->idr_isa_wildcard = flecs_id_record_ensure(world, ecs_pair(EcsIsA, EcsWildcard)); } void flecs_fini_id_records( ecs_world_t *world) { /* Loop & delete first element until there are no elements left. Id records * can recursively delete each other, this ensures we always have a * valid iterator. */ while (ecs_map_count(&world->id_index_hi) > 0) { ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); ecs_map_next(&it); flecs_id_record_release(world, ecs_map_ptr(&it)); } int32_t i; for (i = 0; i < FLECS_HI_ID_RECORD_ID; i ++) { ecs_id_record_t *idr = &world->id_index_lo[i]; if (idr->id) { flecs_id_record_release(world, idr); } } ecs_assert(ecs_map_count(&world->id_index_hi) == 0, ECS_INTERNAL_ERROR, NULL); ecs_map_fini(&world->id_index_hi); ecs_os_free(world->id_index_lo); } /** * @file storage/table.c * @brief Table storage implementation. * * Tables are the data structure that store the component data. Tables have * columns for each component in the table, and rows for each entity stored in * the table. Once created, the component list for a table doesn't change, but * entities can move from one table to another. * * Each table has a type, which is a vector with the (component) ids in the * table. The vector is sorted by id, which ensures that there can be only one * table for each unique combination of components. * * Not all ids in a table have to be components. Tags are ids that have no * data type associated with them, and as a result don't need to be explicitly * stored beyond an element in the table type. To save space and speed up table * creation, each table has a reference to a "storage table", which is a table * that only includes component ids (so excluding tags). * * Note that the actual data is not stored on the storage table. The storage * table is only used for sharing administration. A column_map member maps * between column indices of the table and its storage table. Tables are * refcounted, which ensures that storage tables won't be deleted if other * tables have references to it. */ /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severely slow down many ECS operations. */ #ifdef FLECS_SANITIZE static void flecs_table_check_sanity(ecs_table_t *table) { int32_t size = ecs_vec_size(&table->data.entities); int32_t count = ecs_vec_count(&table->data.entities); int32_t i; int32_t bs_offset = table->_ ? table->_->bs_offset : 0; int32_t bs_count = table->_ ? table->_->bs_count : 0; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); if (table->column_count) { int32_t column_count = table->column_count; ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); int32_t *column_map = table->column_map; ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &table->data.columns[i].data; ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); int32_t column_map_id = column_map[i + type_count]; ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); } } else { ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); } if (bs_count) { ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &table->_->bs_columns[i]; ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), ECS_INTERNAL_ERROR, NULL); } } ecs_assert((table->_->traversable_count == 0) || (table->flags & EcsTableHasTraversable), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif /* Set flags for type hooks so table operations can quickly check whether a * fast or complex operation that invokes hooks is required. */ static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) { ecs_flags32_t flags = 0; if (ti->hooks.ctor) { flags |= EcsTableHasCtors; } if (ti->hooks.on_add) { flags |= EcsTableHasCtors; } if (ti->hooks.dtor) { flags |= EcsTableHasDtors; } if (ti->hooks.on_remove) { flags |= EcsTableHasDtors; } if (ti->hooks.copy) { flags |= EcsTableHasCopy; } if (ti->hooks.move) { flags |= EcsTableHasMove; } return flags; } static void flecs_table_init_columns( ecs_world_t *world, ecs_table_t *table, int32_t column_count) { if (!column_count) { return; } int32_t i, cur = 0, ids_count = table->type.count; ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); table->data.columns = columns; ecs_id_t *ids = table->type.array; ecs_table_record_t *records = table->_->records; int32_t *t2s = table->column_map; int32_t *s2t = &table->column_map[ids_count]; for (i = 0; i < ids_count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; const ecs_type_info_t *ti = idr->type_info; if (!ti || (idr->flags & EcsIdIsSparse)) { t2s[i] = -1; continue; } t2s[i] = cur; s2t[cur] = i; tr->column = flecs_ito(int16_t, cur); columns[cur].ti = ECS_CONST_CAST(ecs_type_info_t*, ti); columns[cur].id = ids[i]; columns[cur].size = ti->size; if (ECS_IS_PAIR(ids[i])) { ecs_table_record_t *wc_tr = flecs_id_record_get_table( idr->parent, table); if (wc_tr->index == tr->index) { wc_tr->column = tr->column; } } #ifdef FLECS_DEBUG ecs_vec_init(NULL, &columns[cur].data, ti->size, 0); #endif table->flags |= flecs_type_info_flags(ti); cur ++; } } /* Initialize table storage */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { ecs_data_t *storage = &table->data; ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); flecs_table_init_columns(world, table, table->column_count); ecs_table__t *meta = table->_; int32_t i, bs_count = meta->bs_count; if (bs_count) { meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); for (i = 0; i < bs_count; i ++) { flecs_bitset_init(&meta->bs_columns[i]); } } } /* Initialize table flags. Table flags are used in lots of scenarios to quickly * check the features of a table without having to inspect the table type. Table * flags are typically used to early-out of potentially expensive operations. */ static void flecs_table_init_flags( ecs_world_t *world, ecs_table_t *table) { ecs_id_t *ids = table->type.array; int32_t count = table->type.count; int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (id <= EcsLastInternalComponentId) { table->flags |= EcsTableHasBuiltins; } if (id == EcsModule) { table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } else if (id == EcsPrefab) { table->flags |= EcsTableIsPrefab; } else if (id == EcsDisabled) { table->flags |= EcsTableIsDisabled; } else if (id == EcsNotQueryable) { table->flags |= EcsTableNotQueryable; } else { if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); table->flags |= EcsTableHasPairs; if (r == EcsIsA) { table->flags |= EcsTableHasIsA; } else if (r == EcsChildOf) { table->flags |= EcsTableHasChildOf; ecs_entity_t obj = ecs_pair_second(world, id); ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); if (obj == EcsFlecs || obj == EcsFlecsCore || ecs_has_id(world, obj, EcsModule)) { /* If table contains entities that are inside one of the * builtin modules, it contains builtin entities */ table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } } else if (id == ecs_pair_t(EcsIdentifier, EcsName)) { table->flags |= EcsTableHasName; } else if (r == ecs_id(EcsPoly)) { table->flags |= EcsTableHasBuiltins; } } else { if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_table__t *meta = table->_; table->flags |= EcsTableHasToggle; if (!meta->bs_count) { meta->bs_offset = flecs_ito(int16_t, i); } meta->bs_count ++; } if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { table->flags |= EcsTableHasOverrides; } } } } } /* Utility function that appends an element to the table record array */ static void flecs_table_append_to_records( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, ecs_id_t id, int32_t column) { /* To avoid a quadratic search, use the O(1) lookup that the index * already provides. */ ecs_id_record_t *idr = flecs_id_record_ensure(world, id); ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( idr, table); if (!tr) { tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); tr->index = flecs_ito(int16_t, column); tr->count = 1; ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } else { tr->count ++; } ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } void flecs_table_emit( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { flecs_emit(world, world, 0, &(ecs_event_desc_t) { .ids = &table->type, .event = event, .table = table, .flags = EcsEventTableOnly, .observable = world }); } /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { /* Make sure table->flags is initialized */ flecs_table_init_flags(world, table); /* The following code walks the table type to discover which id records the * table needs to register table records with. * * In addition to registering itself with id records for each id in the * table type, a table also registers itself with wildcard id records. For * example, if a table contains (Eats, Apples), it will register itself with * wildcard id records (Eats, *), (*, Apples) and (*, *). This makes it * easier for wildcard queries to find the relevant tables. */ int32_t dst_i = 0, dst_count = table->type.count; int32_t src_i = 0, src_count = 0; ecs_id_t *dst_ids = table->type.array; ecs_id_t *src_ids = NULL; ecs_table_record_t *tr = NULL, *src_tr = NULL; if (from) { src_count = from->type.count; src_ids = from->type.array; src_tr = from->_->records; } /* We don't know in advance how large the records array will be, so use * cached vector. This eliminates unnecessary allocations, and/or expensive * iterations to determine how many records we need. */ ecs_allocator_t *a = &world->allocator; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(a, records, ecs_table_record_t); ecs_id_record_t *idr, *childof_idr = NULL; int32_t last_id = -1; /* Track last regular (non-pair) id */ int32_t first_pair = -1; /* Track the first pair in the table */ int32_t first_role = -1; /* Track first id with role */ /* Scan to find boundaries of regular ids, pairs and roles */ for (dst_i = 0; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { first_pair = dst_i; } if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { last_id = dst_i; } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { first_role = dst_i; } } /* The easy part: initialize a record for every id in the type */ for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t src_id = src_ids[src_i]; idr = NULL; if (dst_id == src_id) { ecs_assert(src_tr != NULL, ECS_INTERNAL_ERROR, NULL); idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; } else if (dst_id < src_id) { idr = flecs_id_record_ensure(world, dst_id); } if (idr) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } dst_i += dst_id <= src_id; src_i += dst_id >= src_id; } /* Add remaining ids that the "from" table didn't have */ for (; (dst_i < dst_count); dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; tr = ecs_vec_append_t(a, records, ecs_table_record_t); idr = flecs_id_record_ensure(world, dst_id); tr->hdr.cache = (ecs_table_cache_t*)idr; ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); tr->index = flecs_ito(int16_t, dst_i); tr->count = 1; } /* We're going to insert records from the vector into the index that * will get patched up later. To ensure the record pointers don't get * invalidated we need to grow the vector so that it won't realloc as * we're adding the next set of records */ if (first_role != -1 || first_pair != -1) { int32_t start = first_role; if (first_pair != -1 && (start != -1 || first_pair < start)) { start = first_pair; } /* Total number of records can never be higher than * - number of regular (non-pair) ids + * - three records for pairs: (R,T), (R,*), (*,T) * - one wildcard (*), one any (_) and one pair wildcard (*,*) record * - one record for (ChildOf, 0) */ int32_t flag_id_count = dst_count - start; int32_t record_count = start + 3 * flag_id_count + 3 + 1; ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); } /* Add records for ids with roles (used by cleanup logic) */ if (first_role != -1) { for (dst_i = first_role; dst_i < dst_count; dst_i ++) { ecs_id_t id = dst_ids[dst_i]; if (!ECS_IS_PAIR(id)) { ecs_entity_t first = 0; ecs_entity_t second = 0; if (ECS_HAS_ID_FLAG(id, PAIR)) { first = ECS_PAIR_FIRST(id); second = ECS_PAIR_SECOND(id); } else { first = id & ECS_COMPONENT_MASK; } if (first) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, first), dst_i); } if (second) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, second), dst_i); } } } } int32_t last_pair = -1; bool has_childof = table->flags & EcsTableHasChildOf; if (first_pair != -1) { /* Add a (Relationship, *) record for each relationship. */ ecs_entity_t r = 0; for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (!ECS_IS_PAIR(dst_id)) { break; /* no more pairs */ } if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); ecs_id_record_t *p_idr = (ecs_id_record_t*)tr->hdr.cache; r = ECS_PAIR_FIRST(dst_id); if (r == EcsChildOf) { childof_idr = p_idr; } idr = p_idr->parent; /* (R, *) */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->index = flecs_ito(int16_t, dst_i); tr->count = 0; } ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); tr->count ++; } last_pair = dst_i; /* Add a (*, Target) record for each relationship target. Type * ids are sorted relationship-first, so we can't simply do a single * linear scan to find all occurrences for a target. */ for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); flecs_table_append_to_records( world, table, records, tgt_id, dst_i); } } /* Lastly, add records for all-wildcard ids */ if (last_id >= 0) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; tr->index = 0; tr->count = flecs_ito(int16_t, last_id + 1); } if (last_pair - first_pair) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; tr->index = flecs_ito(int16_t, first_pair); tr->count = flecs_ito(int16_t, last_pair - first_pair); } if (dst_count) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; tr->index = 0; tr->count = 1; } if (dst_count && !has_childof) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); childof_idr = world->idr_childof_0; tr->hdr.cache = (ecs_table_cache_t*)childof_idr; tr->index = 0; tr->count = 1; } /* Now that all records have been added, copy them to array */ int32_t i, dst_record_count = ecs_vec_count(records); ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); table->_->record_count = flecs_ito(int16_t, dst_record_count); table->_->records = dst_tr; int32_t column_count = 0; /* Register & patch up records */ for (i = 0; i < dst_record_count; i ++) { tr = &dst_tr[i]; idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_cache_get(&idr->cache, table)) { /* If this is a target wildcard record it has already been * registered, but the record is now at a different location in * memory. Patch up the linked list with the new address */ ecs_table_cache_replace(&idr->cache, table, &tr->hdr); } else { /* Other records are not registered yet */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } /* Claim id record so it stays alive as long as the table exists */ flecs_id_record_claim(world, idr); /* Initialize event flags */ table->flags |= idr->flags & EcsIdEventMask; /* Initialize column index (will be overwritten by init_columns) */ tr->column = -1; if (ECS_ID_ON_INSTANTIATE(idr->flags) == EcsOverride) { table->flags |= EcsTableHasOverrides; } if ((i < table->type.count) && (idr->type_info != NULL)) { if (!(idr->flags & EcsIdIsSparse)) { column_count ++; } } } if (column_count) { table->column_map = flecs_walloc_n(world, int32_t, dst_count + column_count); } table->column_count = flecs_ito(int16_t, column_count); flecs_table_init_data(world, table); if (table->flags & EcsTableHasName) { ecs_assert(childof_idr != NULL, ECS_INTERNAL_ERROR, NULL); table->_->name_index = flecs_id_record_name_index_ensure(world, childof_idr); ecs_assert(table->_->name_index != NULL, ECS_INTERNAL_ERROR, NULL); } if (table->flags & EcsTableHasOnTableCreate) { flecs_table_emit(world, table, EcsOnTableCreate); } } /* Unregister table from id records */ static void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) { uint64_t table_id = table->id; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; ecs_id_t id = ((ecs_id_record_t*)cache)->id; ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, ECS_INTERNAL_ERROR, NULL); (void)id; ecs_table_cache_remove(cache, table_id, &tr->hdr); flecs_id_record_release(world, (ecs_id_record_t*)cache); } flecs_wfree_n(world, ecs_table_record_t, count, table->_->records); } /* Keep track for what kind of builtin events observers are registered that can * potentially match the table. This allows code to early out of calling the * emit function that notifies observers. */ static void flecs_table_add_trigger_flags( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { (void)world; if (event == EcsOnAdd) { table->flags |= EcsTableHasOnAdd; } else if (event == EcsOnRemove) { table->flags |= EcsTableHasOnRemove; } else if (event == EcsOnSet) { table->flags |= EcsTableHasOnSet; } else if (event == EcsOnTableFill) { table->flags |= EcsTableHasOnTableFill; } else if (event == EcsOnTableEmpty) { table->flags |= EcsTableHasOnTableEmpty; } else if (event == EcsWildcard) { table->flags |= EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| EcsTableHasOnTableFill|EcsTableHasOnTableEmpty| EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; } } /* Invoke OnRemove observers for all entities in table. Useful during table * deletion or when clearing entities from a table. */ static void flecs_table_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { int32_t count = data->entities.count; if (count) { flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); } } /* Invoke type hook for entities in table */ static void flecs_table_invoke_hook( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, ecs_entity_t event, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count) { void *ptr = ecs_vec_get(&column->data, column->size, row); flecs_invoke_hook(world, table, count, row, entities, ptr, column->id, column->ti, event, callback); } /* Construct components */ static void flecs_table_invoke_ctor( ecs_column_t *column, int32_t row, int32_t count) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { void *ptr = ecs_vec_get(&column->data, column->size, row); ctor(ptr, count, ti); } } /* Destruct components */ static void flecs_table_invoke_dtor( ecs_column_t *column, int32_t row, int32_t count) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { void *ptr = ecs_vec_get(&column->data, column->size, row); dtor(ptr, count, ti); } } /* Run hooks that get invoked when component is added to entity */ static void flecs_table_invoke_add_hooks( ecs_world_t *world, ecs_table_t *table, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count, bool construct) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { flecs_table_invoke_ctor(column, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { flecs_table_invoke_hook(world, table, on_add, EcsOnAdd, column, entities, row, count); } } /* Run hooks that get invoked when component is removed from entity */ static void flecs_table_invoke_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_column_t *column, ecs_entity_t *entities, int32_t row, int32_t count, bool dtor) { ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, entities, row, count); } if (dtor) { flecs_table_invoke_dtor(column, row, count); } } /* Destruct all components and/or delete all entities in table in range */ static void flecs_table_dtor_all( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t row, int32_t count, bool is_delete) { int32_t ids_count = table->column_count; ecs_entity_t *entities = data->entities.array; int32_t i, c, end = row + count; if (is_delete && table->_->traversable_count) { /* If table contains monitored entities with traversable relationships, * make sure to invalidate observer cache */ flecs_emit_propagate_invalidate(world, table, row, count); } /* If table has components with destructors, iterate component columns */ if (table->flags & EcsTableHasDtors) { /* Throw up a lock just to be sure */ table->_->lock = true; /* Run on_remove callbacks first before destructing components */ for (c = 0; c < ids_count; c++) { ecs_column_t *column = &data->columns[c]; ecs_iter_action_t on_remove = column->ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entities[row], row, count); } } /* Destruct components */ for (c = 0; c < ids_count; c++) { flecs_table_invoke_dtor(&data->columns[c], row, count); } /* Iterate entities first, then components. This ensures that only one * entity is invalidated at a time, which ensures that destructors can * safely access other entities. */ for (i = row; i < end; i ++) { /* Update entity index after invoking destructors so that entity can * be safely used in destructor callbacks. */ ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); if (is_delete) { flecs_entities_remove(world, e); ecs_assert(ecs_is_valid(world, e) == false, ECS_INTERNAL_ERROR, NULL); } else { // If this is not a delete, clear the entity index record ecs_record_t *record = flecs_entities_get(world, e); record->table = NULL; record->row = 0; } } table->_->lock = false; /* If table does not have destructors, just update entity index */ } else { if (is_delete) { for (i = row; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); flecs_entities_remove(world, e); ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); } } else { for (i = row; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); ecs_record_t *record = flecs_entities_get(world, e); record->table = NULL; record->row = record->row & ECS_ROW_FLAGS_MASK; (void)e; } } } } #define FLECS_LOCKED_STORAGE_MSG \ "to fix, defer operations with defer_begin/defer_end" /* Cleanup table storage */ static void flecs_table_fini_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, bool do_on_remove, bool is_delete, bool deactivate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); if (!data) { return; } if (do_on_remove) { flecs_table_notify_on_remove(world, table, data); } int32_t count = flecs_table_data_count(data); if (count) { flecs_table_dtor_all(world, table, data, 0, count, is_delete); } ecs_column_t *columns = data->columns; if (columns) { int32_t c, column_count = table->column_count; for (c = 0; c < column_count; c ++) { /* Sanity check */ ecs_assert(columns[c].data.count == data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_vec_fini(&world->allocator, &columns[c].data, columns[c].size); } flecs_wfree_n(world, ecs_column_t, column_count, columns); data->columns = NULL; } ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; if (bs_columns) { int32_t c, column_count = meta->bs_count; for (c = 0; c < column_count; c ++) { flecs_bitset_fini(&bs_columns[c]); } flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); meta->bs_columns = NULL; } ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); if (deactivate && count) { flecs_table_set_empty(world, table); } table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; } const ecs_vec_t* flecs_table_entities( const ecs_table_t *table) { return &table->data.entities; } ecs_entity_t* flecs_table_entities_array( const ecs_table_t *table) { return ecs_vec_first(flecs_table_entities(table)); } /* 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, 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, 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); } /* Unset all components in table. This function is called before a table is * deleted, and invokes all OnRemove handlers, if any */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table) { (void)world; flecs_table_notify_on_remove(world, table, &table->data); } /* Free table resources. */ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table) { bool is_root = table == &world->store.root; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), ECS_INTERNAL_ERROR, NULL); (void)world; if (!is_root && !(world->flags & EcsWorldQuit)) { if (table->flags & EcsTableHasOnTableDelete) { flecs_table_emit(world, table, EcsOnTableDelete); } } if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &table->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", expr, table->id); ecs_os_free(expr); ecs_log_push_2(); } 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, false); flecs_table_clear_edges(world, table); if (!is_root) { ecs_type_t ids = { .array = table->type.array, .count = table->type.count }; flecs_hashmap_remove_w_hash( &world->store.table_map, &ids, ecs_table_t*, table->_->hash); } flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); flecs_wfree_n(world, int32_t, table->column_count + table->type.count, table->column_map); flecs_table_records_unregister(world, table); /* Update counters */ world->info.table_count --; world->info.table_delete_total ++; flecs_free_t(&world->allocator, ecs_table__t, table->_); if (!(world->flags & EcsWorldFini)) { ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); flecs_table_free_type(world, table); flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); } ecs_log_pop_2(); } /* Free table type. Do this separately from freeing the table as types can be * in use by application destructors. */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table) { flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } /* Reset a table to its initial state. */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_clear_edges(world, table); } /* Keep track of number of traversable entities in table. A traversable entity * is an entity used as target in a pair with a traversable relationship. The * traversable count and flag are used by code to early out of mechanisms like * event propagation and recursive cleanup. */ void flecs_table_traversable_add( ecs_table_t *table, int32_t value) { int32_t result = table->_->traversable_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { table->flags &= ~EcsTableHasTraversable; } else if (result == value) { table->flags |= EcsTableHasTraversable; } } /* Mark table column dirty. This usually happens as the result of a set * operation, or iteration of a query with [out] fields. */ static void flecs_table_mark_table_dirty( ecs_world_t *world, ecs_table_t *table, int32_t index) { (void)world; if (table->dirty_state) { table->dirty_state[index] ++; } } /* Mark table component dirty */ void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table->dirty_state) { ecs_id_record_t *idr = flecs_id_record_get(world, component); if (!idr) { return; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr || tr->column == -1) { return; } table->dirty_state[tr->column + 1] ++; } } /* Get (or create) dirty state of table. Used by queries for change tracking */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table) { flecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->dirty_state) { int32_t column_count = table->column_count; table->dirty_state = flecs_alloc_n(&world->allocator, int32_t, column_count + 1); ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < column_count + 1; i ++) { table->dirty_state[i] = 1; } } return table->dirty_state; } /* Table move logic for bitset (toggle component) column */ static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { ecs_table__t *dst_meta = dst_table->_; ecs_table__t *src_meta = src_table->_; if (!dst_meta && !src_meta) { return; } int32_t i_old = 0, src_column_count = src_meta ? src_meta->bs_count : 0; int32_t i_new = 0, dst_column_count = dst_meta ? dst_meta->bs_count : 0; if (!src_column_count && !dst_column_count) { return; } ecs_bitset_t *src_columns = src_meta ? src_meta->bs_columns : NULL; ecs_bitset_t *dst_columns = dst_meta ? dst_meta->bs_columns : NULL; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_meta ? dst_meta->bs_offset : 0; int32_t offset_old = src_meta ? src_meta->bs_offset : 0; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new + offset_new]; ecs_id_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_bitset_t *dst_bs = &dst_columns[i_new]; flecs_bitset_ensure(dst_bs, dst_index + count); int i; for (i = 0; i < count; i ++) { uint64_t value = flecs_bitset_get(src_bs, src_index + i); flecs_bitset_set(dst_bs, dst_index + i, value); } if (clear) { ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } else if (dst_id > src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; flecs_bitset_fini(src_bs); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } } /* Grow table column. When a column needs to be reallocated this function takes * care of correctly invoking ctor/move/dtor hooks. */ static void flecs_table_grow_column( ecs_world_t *world, ecs_column_t *column, int32_t to_add, int32_t dst_size, bool construct) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; int32_t size = column->size; int32_t count = column->data.count; int32_t src_size = column->data.size; int32_t dst_count = count + to_add; bool can_realloc = dst_size != src_size; void *result = NULL; ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); /* If the array could possibly realloc and the component has a move action * defined, move old elements manually */ ecs_move_t move_ctor; if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { ecs_xtor_t ctor = ti->hooks.ctor; ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); /* Create vector */ ecs_vec_t dst; ecs_vec_init(&world->allocator, &dst, size, dst_size); dst.count = dst_count; void *src_buffer = column->data.array; void *dst_buffer = dst.array; /* Move (and construct) existing elements to new vector */ move_ctor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ result = ECS_ELEM(dst_buffer, size, count); ctor(result, to_add, ti); } /* Free old vector */ ecs_vec_fini(&world->allocator, &column->data, size); column->data = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { ecs_vec_set_size(&world->allocator, &column->data, size, dst_size); } result = ecs_vec_grow(&world->allocator, &column->data, size, to_add); ecs_xtor_t ctor; if (construct && (ctor = ti->hooks.ctor)) { /* If new elements need to be constructed and component has a * constructor, construct */ ctor(result, to_add, ti); } } ecs_assert(column->data.size == dst_size, ECS_INTERNAL_ERROR, NULL); } /* Grow all data structures in a table */ static int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t to_add, int32_t size, const ecs_entity_t *ids) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur_count = flecs_table_data_count(data); int32_t column_count = table->column_count; /* Add entity to column with entity ids */ ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size); ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1; data->entities.count += to_add; if (data->entities.size > size) { size = data->entities.size; } /* Initialize entity ids and record ptrs */ int32_t i; if (ids) { ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); } else { ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); } /* Add elements to each column array */ ecs_column_t *columns = data->columns; for (i = 0; i < column_count; i ++) { flecs_table_grow_column(world, &columns[i], to_add, size, true); ecs_assert(columns[i].data.size == size, ECS_INTERNAL_ERROR, NULL); flecs_table_invoke_add_hooks(world, table, &columns[i], e, cur_count, to_add, false); } ecs_table__t *meta = table->_; int32_t bs_count = meta->bs_count; ecs_bitset_t *bs_columns = meta->bs_columns; /* Add elements to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, to_add); } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); if (!(world->flags & EcsWorldReadonly) && !cur_count) { flecs_table_set_empty(world, table); } /* Return index of first added entity */ return cur_count; } /* Append operation for tables that don't have any complex logic */ static void flecs_table_fast_append( ecs_world_t *world, ecs_column_t *columns, int32_t count) { /* Add elements to each column array */ int32_t i; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; ecs_vec_append(&world->allocator, &column->data, column->size); } } /* Append entity to table */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, bool construct, bool on_add) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(table); /* Get count & size before growing entities array. This tells us whether the * arrays will realloc */ ecs_data_t *data = &table->data; int32_t count = data->entities.count; int32_t column_count = table->column_count; ecs_column_t *columns = table->data.columns; /* Grow buffer with entity ids, set new element to new entity */ ecs_entity_t *e = ecs_vec_append_t(&world->allocator, &data->entities, ecs_entity_t); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); *e = entity; /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); /* Fast path: no switch columns, no lifecycle actions */ if (!(table->flags & EcsTableIsComplex)) { flecs_table_fast_append(world, columns, column_count); if (!count) { flecs_table_set_empty(world, table); /* See below */ } return count; } ecs_entity_t *entities = data->entities.array; /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ int32_t size = data->entities.size; /* Grow component arrays with 1 element */ int32_t i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; flecs_table_grow_column(world, column, 1, size, construct); ecs_iter_action_t on_add_hook; if (on_add && (on_add_hook = column->ti->hooks.on_add)) { flecs_table_invoke_hook(world, table, on_add_hook, EcsOnAdd, column, &entities[count], count, 1); } ecs_assert(columns[i].data.size == data->entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns[i].data.count == data->entities.count, ECS_INTERNAL_ERROR, NULL); } ecs_table__t *meta = table->_; int32_t bs_count = meta->bs_count; ecs_bitset_t *bs_columns = meta->bs_columns; /* Add element to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, 1); } /* If this is the first entity in this table, signal queries so that the * table moves from an inactive table to an active table. */ if (!count) { flecs_table_set_empty(world, table); } flecs_table_check_sanity(table); return count; } /* Delete last operation for tables that don't have any complex logic */ static void flecs_table_fast_delete_last( ecs_column_t *columns, int32_t column_count) { int i; for (i = 0; i < column_count; i ++) { ecs_vec_remove_last(&columns[i].data); } } /* Delete operation for tables that don't have any complex logic */ static void flecs_table_fast_delete( ecs_column_t *columns, int32_t column_count, int32_t index) { int i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_vec_remove(&column->data, column->size, index); } } /* Delete entity from table */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(table); ecs_data_t *data = &table->data; int32_t count = data->entities.count; ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); count --; ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); /* Move last entity id to index */ ecs_entity_t *entities = data->entities.array; ecs_entity_t entity_to_move = entities[count]; ecs_entity_t entity_to_delete = entities[index]; entities[index] = entity_to_move; ecs_vec_remove_last(&data->entities); /* Update record of moved entity in entity index */ if (index != count) { ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); if (record_to_move) { uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); /* If table is empty, deactivate it */ if (!count) { flecs_table_set_empty(world, table); } /* Destruct component data */ ecs_column_t *columns = data->columns; int32_t column_count = table->column_count; int32_t i; /* If this is a table without lifecycle callbacks or special columns, take * fast path that just remove an element from the array(s) */ if (!(table->flags & EcsTableIsComplex)) { if (index == count) { flecs_table_fast_delete_last(columns, column_count); } else { flecs_table_fast_delete(columns, column_count, index); } flecs_table_check_sanity(table); return; } /* Last element, destruct & remove */ if (index == count) { /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { flecs_table_invoke_remove_hooks(world, table, &columns[i], &entity_to_delete, index, 1, true); } } flecs_table_fast_delete_last(columns, column_count); /* Not last element, move last element to deleted element & destruct */ } else { /* If table has component destructors, invoke */ if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_type_info_t *ti = column->ti; ecs_size_t size = column->size; void *dst = ecs_vec_get(&column->data, size, index); void *src = ecs_vec_last(&column->data, size); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (destruct && on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, index, 1); } ecs_move_t move_dtor = ti->hooks.move_dtor; // If neither move nor move_ctor are set, this indicates that non-destructive move // semantics are not supported for this type. In such cases, we set the move_dtor // as ctor_move_dtor, which indicates a destructive move operation. // This adjustment ensures compatibility with different language bindings. if (!ti->hooks.move_ctor && ti->hooks.ctor_move_dtor) { move_dtor = ti->hooks.ctor_move_dtor; } if (move_dtor) { move_dtor(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } ecs_vec_remove_last(&column->data); } } else { flecs_table_fast_delete(columns, column_count, index); } } /* Remove elements from bitset columns */ ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; int32_t bs_count = meta->bs_count; for (i = 0; i < bs_count; i ++) { flecs_bitset_remove(&bs_columns[i], index); } flecs_table_check_sanity(table); } /* Move operation for tables that don't have any complex logic */ static void flecs_table_fast_move( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = dst_column->id; ecs_id_t src_id = src_column->id; if (dst_id == src_id) { int32_t size = dst_column->size; void *dst = ecs_vec_get(&dst_column->data, size, dst_index); void *src = ecs_vec_get(&src_column->data, size, src_index); ecs_os_memcpy(dst, src, size); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } } /* Move entity from src to dst table */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, bool construct) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); 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_bitset_columns( dst_table, dst_index, src_table, src_index, 1, false); /* If the source and destination entities are the same, move component * between tables. If the entities are not the same (like when cloning) use * a copy. */ bool same_entity = dst_entity == src_entity; /* Call move_dtor for moved away from storage only if the entity is at the * last index in the source table. If it isn't the last entity, the last * entity in the table will be moved to the src storage, which will take * care of cleaning up resources. */ bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = dst_column->id; ecs_id_t src_id = src_column->id; if (dst_id == src_id) { int32_t size = dst_column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *dst = ecs_vec_get(&dst_column->data, size, dst_index); void *src = ecs_vec_get(&src_column->data, size, src_index); ecs_type_info_t *ti = dst_column->ti; if (same_entity) { ecs_move_t move = ti->hooks.move_ctor; if (use_move_dtor || !move) { /* Also use move_dtor if component doesn't have a move_ctor * registered, to ensure that the dtor gets called to * cleanup resources. */ move = ti->hooks.ctor_move_dtor; } if (move) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } } else { if (dst_id < src_id) { flecs_table_invoke_add_hooks(world, dst_table, dst_column, &dst_entity, dst_index, 1, construct); } else if (same_entity) { flecs_table_invoke_remove_hooks(world, src_table, src_column, &src_entity, src_index, 1, use_move_dtor); } } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } for (; (i_new < dst_column_count); i_new ++) { flecs_table_invoke_add_hooks(world, dst_table, &dst_columns[i_new], &dst_entity, dst_index, 1, construct); } if (same_entity) { for (; (i_old < src_column_count); i_old ++) { flecs_table_invoke_remove_hooks(world, src_table, &src_columns[i_old], &src_entity, src_index, 1, use_move_dtor); } } flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); } /* Append n entities to table */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t to_add, const ecs_entity_t *ids) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); int32_t result = flecs_table_grow_data( world, table, data, to_add, cur_count + to_add, ids); flecs_table_check_sanity(table); return result; } /* Set allocated table size */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t size) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); if (cur_count < size) { flecs_table_grow_data(world, table, data, 0, size, NULL); flecs_table_check_sanity(table); } } /* Shrink table storage to fit number of entities */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); (void)world; flecs_table_check_sanity(table); ecs_data_t *data = &table->data; bool has_payload = data->entities.array != NULL; ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t); int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &data->columns[i]; ecs_vec_reclaim(&world->allocator, &column->data, column->size); } return has_payload; } /* Return number of entities in table */ int32_t flecs_table_data_count( const ecs_data_t *data) { return data ? data->entities.count : 0; } /* Swap operation for bitset (toggle component) columns */ static void flecs_table_swap_bitset_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->_->bs_count; if (!column_count) { return; } ecs_bitset_t *columns = table->_->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } /* Swap two rows in a table. Used for table sorting. */ void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2) { (void)world; ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(table); if (row_1 == row_2) { return; } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_entity_t *entities = table->data.entities.array; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of whether entity is watched */ uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); /* Swap entities & records */ entities[row_1] = e2; entities[row_2] = e1; record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); flecs_table_swap_bitset_columns(table, row_1, row_2); ecs_column_t *columns = table->data.columns; if (!columns) { flecs_table_check_sanity(table); return; } /* Find the maximum size of column elements * and allocate a temporary buffer for swapping */ int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; for (i = 0; i < column_count; i++) { temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].size); } void* tmp = ecs_os_alloca(temp_buffer_size); /* Swap columns */ for (i = 0; i < column_count; i ++) { int32_t size = columns[i].size; ecs_column_t *column = &columns[i]; void *ptr = column->data.array; const ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); void *el_1 = ECS_ELEM(ptr, size, row_1); void *el_2 = ECS_ELEM(ptr, size, row_2); ecs_move_t move = ti->hooks.move; if (!move) { ecs_os_memcpy(tmp, el_1, size); ecs_os_memcpy(el_1, el_2, size); ecs_os_memcpy(el_2, tmp, size); } else { ecs_move_t move_ctor = ti->hooks.move_ctor; ecs_move_t move_dtor = ti->hooks.move_dtor; ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_dtor != NULL, ECS_INTERNAL_ERROR, NULL); move_ctor(tmp, el_1, 1, ti); move(el_1, el_2, 1, ti); move_dtor(el_2, tmp, 1, ti); } } flecs_table_check_sanity(table); } static void flecs_table_merge_vec( ecs_world_t *world, ecs_vec_t *dst, ecs_vec_t *src, int32_t size, int32_t elem_size) { int32_t dst_count = dst->count; if (!dst_count) { ecs_vec_fini(&world->allocator, dst, size); *dst = *src; src->array = NULL; src->count = 0; src->size = 0; } else { int32_t src_count = src->count; if (elem_size) { ecs_vec_set_size(&world->allocator, dst, size, elem_size); } ecs_vec_set_count(&world->allocator, dst, size, dst_count + src_count); void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); void *src_ptr = src->array; ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); ecs_vec_fini(&world->allocator, src, size); } } /* Merge data from one table column into other table column */ static void flecs_table_merge_column( ecs_world_t *world, ecs_column_t *dst, ecs_column_t *src, int32_t column_size) { ecs_size_t size = dst->size; int32_t dst_count = dst->data.count; if (!dst_count) { ecs_vec_fini(&world->allocator, &dst->data, size); *dst = *src; src->data.array = NULL; src->data.count = 0; src->data.size = 0; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { int32_t src_count = src->data.count; flecs_table_grow_column(world, dst, src_count, column_size, false); void *dst_ptr = ECS_ELEM(dst->data.array, size, dst_count); void *src_ptr = src->data.array; /* Move values into column */ ecs_type_info_t *ti = dst->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move = ti->hooks.ctor_move_dtor; if (move) { move(dst_ptr, src_ptr, src_count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); } ecs_vec_fini(&world->allocator, &src->data, size); } } /* Merge storage of two tables. */ static void flecs_table_merge_data( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, int32_t src_count, int32_t dst_count, ecs_data_t *src_data, ecs_data_t *dst_data) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_data->columns; ecs_column_t *dst_columns = dst_data->columns; ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ flecs_table_merge_vec(world, &dst_data->entities, &src_data->entities, ECS_SIZEOF(ecs_entity_t), 0); ecs_assert(dst_data->entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); int32_t column_size = dst_data->entities.size; ecs_allocator_t *a = &world->allocator; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = dst_column->id; ecs_id_t src_id = src_column->id; if (dst_id == src_id) { flecs_table_merge_column(world, dst_column, src_column, column_size); flecs_table_mark_table_dirty(world, dst_table, i_new + 1); i_new ++; i_old ++; } else if (dst_id < src_id) { /* New column, make sure vector is large enough. */ ecs_size_t size = dst_column->size; ecs_vec_set_size(a, &dst_column->data, size, column_size); ecs_vec_set_count(a, &dst_column->data, size, src_count + dst_count); flecs_table_invoke_ctor(dst_column, dst_count, src_count); i_new ++; } else if (dst_id > src_id) { /* Old column does not occur in new table, destruct */ flecs_table_invoke_dtor(src_column, 0, src_count); ecs_vec_fini(a, &src_column->data, src_column->size); i_old ++; } } flecs_table_move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); /* Initialize remaining columns */ for (; i_new < dst_column_count; i_new ++) { ecs_column_t *column = &dst_columns[i_new]; int32_t size = column->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_set_size(a, &column->data, size, column_size); ecs_vec_set_count(a, &column->data, size, src_count + dst_count); flecs_table_invoke_ctor(column, dst_count, src_count); } /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { ecs_column_t *column = &src_columns[i_old]; flecs_table_invoke_dtor(column, 0, src_count); ecs_vec_fini(a, &column->data, column->size); } /* Mark entity column as dirty */ flecs_table_mark_table_dirty(world, dst_table, 0); } /* Merge source table into destination table. This typically happens as result * of a bulk operation, like when a component is removed from all entities in * the source table (like for the Remove OnDelete policy). */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table) { ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!src_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(!dst_table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); ecs_data_t *dst_data = &dst_table->data; ecs_data_t *src_data = &src_table->data; ecs_entity_t *src_entities = src_data->entities.array; int32_t src_count = src_data->entities.count; int32_t dst_count = dst_data->entities.count; /* First, update entity index so old entities point to new type */ int32_t i; for(i = 0; i < src_count; i ++) { ecs_record_t *record = flecs_entities_ensure(world, src_entities[i]); uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); record->table = dst_table; } /* Merge table columns */ flecs_table_merge_data(world, dst_table, src_table, src_count, dst_count, src_data, dst_data); if (src_count) { if (!dst_count) { flecs_table_set_empty(world, dst_table); } flecs_table_set_empty(world, src_table); flecs_table_traversable_add(dst_table, src_table->_->traversable_count); flecs_table_traversable_add(src_table, -src_table->_->traversable_count); ecs_assert(src_table->_->traversable_count == 0, ECS_INTERNAL_ERROR, NULL); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } /* Internal mechanism for propagating information to tables */ void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_table_event_t *event) { if (world->flags & EcsWorldFini) { return; } switch(event->kind) { case EcsTableTriggersForId: flecs_table_add_trigger_flags(world, table, event->event); break; case EcsTableNoTriggersForId: break; } } int32_t flecs_table_get_toggle_column( ecs_table_t *table, ecs_id_t id) { ecs_id_t *ids = table->type.array; int32_t i = table->_->bs_offset, end = i + table->_->bs_count; for (; i < end; i ++) { if (ids[i] == (ECS_TOGGLE | id)) { return i; } } return -1; } ecs_bitset_t* flecs_table_get_toggle( ecs_table_t *table, ecs_id_t id) { int32_t toggle_column = flecs_table_get_toggle_column(table, id); if (toggle_column == -1) { return NULL; } toggle_column -= table->_->bs_offset; ecs_assert(toggle_column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(toggle_column < table->_->bs_count, ECS_INTERNAL_ERROR, NULL); return &table->_->bs_columns[toggle_column]; } /* -- Public API -- */ void ecs_table_lock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock ++; } } } void ecs_table_unlock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock --; ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, "table_unlock called more often than table_lock"); } } } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } int32_t ecs_table_get_type_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return -1; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return -1; } return tr->index; error: return -1; } int32_t ecs_table_get_column_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return -1; } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return -1; } return tr->column; error: return -1; } int32_t ecs_table_column_count( const ecs_table_t *table) { return table->column_count; } int32_t ecs_table_type_to_column_index( const ecs_table_t *table, int32_t index) { ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int32_t *column_map = table->column_map; if (column_map) { return column_map[index]; } error: return -1; } int32_t ecs_table_column_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->column_map[offset + index]; error: return -1; } void* ecs_table_get_column( const ecs_table_t *table, int32_t index, int32_t offset) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_column_t *column = &table->data.columns[index]; void *result = column->data.array; if (offset) { result = ECS_ELEM(result, column->size, offset); } return result; error: return NULL; } void* ecs_table_get_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, int32_t offset) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t index = ecs_table_get_column_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } size_t ecs_table_get_column_size( const ecs_table_t *table, int32_t column) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_ito(size_t, table->data.columns[column].size); error: return 0; } int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_table_data_count(&table->data); } bool ecs_table_has_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { return ecs_table_get_type_index(world, table, id) != -1; } int32_t ecs_table_get_depth( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t rel) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, "cannot safely determine depth for relationship that is not acyclic " "(add Acyclic property to relationship)"); world = ecs_get_world(world); return flecs_relation_depth(world, rel, table); error: return -1; } bool ecs_table_has_flags( ecs_table_t *table, ecs_flags32_t flags) { return (table->flags & flags) == flags; } void ecs_table_swap_rows( ecs_world_t* world, ecs_table_t* table, int32_t row_1, int32_t row_2) { flecs_table_swap(world, table, row_1, row_2); } int32_t flecs_table_observed_count( const ecs_table_t *table) { return table->_->traversable_count; } void* ecs_record_get_by_column( const ecs_record_t *r, int32_t index, size_t c_size) { (void)c_size; ecs_table_t *table = r->table; ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_column_t *column = &table->data.columns[index]; ecs_size_t size = column->size; ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, ECS_INVALID_PARAMETER, NULL); return ecs_vec_get(&column->data, size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } ecs_record_t* ecs_record_find( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); if (r) { return r; } error: return NULL; } /** * @file storage/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_w_empty( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result, bool empty) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_table_cache_get(cache, table) == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); result->cache = cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); result->empty = empty; flecs_table_cache_list_insert(cache, result); if (table) { ecs_map_insert_ptr(&cache->index, table->id, result); } ecs_assert(empty || cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!empty || cache->empty_tables.first != NULL, ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result) { bool empty; if (!table) { empty = false; } else { empty = ecs_table_count(table) == 0; } ecs_table_cache_insert_w_empty(cache, table, result, empty); } void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t **r = ecs_map_get_ref( &cache->index, ecs_table_cache_hdr_t, table->id); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *old = *r; ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; if (prev) { ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); prev->next = elem; } if (next) { ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); next->prev = elem; } if (cache->empty_tables.first == old) { cache->empty_tables.first = elem; } if (cache->empty_tables.last == old) { cache->empty_tables.last = elem; } if (cache->tables.first == old) { cache->tables.first = elem; } if (cache->tables.last == old) { cache->tables.last = elem; } *r = elem; elem->prev = prev; elem->next = next; } void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); if (table) { if (ecs_map_is_init(&cache->index)) { return ecs_map_get_deref(&cache->index, void**, table->id); } return NULL; } else { ecs_table_cache_hdr_t *elem = cache->tables.first; ecs_assert(!elem || elem->table == NULL, ECS_INTERNAL_ERROR, NULL); return elem; } } void* ecs_table_cache_remove( ecs_table_cache_t *cache, uint64_t table_id, ecs_table_cache_hdr_t *elem) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table_id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->cache == cache, ECS_INTERNAL_ERROR, NULL); flecs_table_cache_list_remove(cache, elem); ecs_map_remove(&cache->index, table_id); return elem; } bool ecs_table_cache_set_empty( ecs_table_cache_t *cache, const ecs_table_t *table, bool empty) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *elem = ecs_map_get_deref(&cache->index, ecs_table_cache_hdr_t, table->id); if (!elem) { return false; } if (elem->empty == empty) { return false; } flecs_table_cache_list_remove(cache, elem); elem->empty = empty; flecs_table_cache_list_insert(cache, elem); return true; } bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->tables.first; out->next_list = NULL; out->cur = NULL; return out->next != NULL; } bool flecs_table_cache_empty_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->empty_tables.first; out->next_list = NULL; out->cur = NULL; return out->next != NULL; } bool flecs_table_cache_all_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out != NULL, ECS_INTERNAL_ERROR, NULL); out->next = cache->empty_tables.first; out->next_list = cache->tables.first; out->cur = NULL; return out->next != NULL || out->next_list != NULL; } ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it) { ecs_table_cache_hdr_t *next = it->next; if (!next) { next = it->next_list; it->next_list = NULL; if (!next) { return NULL; } } it->cur = next; it->next = next->next; return next; } /** * @file storage/table_graph.c * @brief Data structure to speed up table transitions. * * The table graph is used to speed up finding tables in add/remove operations. * For example, if component C is added to an entity in table [A, B], the entity * must be moved to table [A, B, C]. The graph speeds this process up with an * edge for component C that connects [A, B] to [A, B, C]. */ /* Id sequence (type) utilities */ static uint64_t flecs_type_hash(const void *ptr) { const ecs_type_t *type = ptr; ecs_id_t *ids = type->array; int32_t count = type->count; return flecs_hash(ids, count * ECS_SIZEOF(ecs_id_t)); } static int flecs_type_compare(const void *ptr_1, const void *ptr_2) { const ecs_type_t *type_1 = ptr_1; const ecs_type_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } const ecs_id_t *ids_1 = type_1->array; const ecs_id_t *ids_2 = type_2->array; int result = 0; int32_t i; for (i = 0; !result && (i < count_1); i ++) { ecs_id_t id_1 = ids_1[i]; ecs_id_t id_2 = ids_2[i]; result = (id_1 > id_2) - (id_1 < id_2); } return result; } void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm) { flecs_hashmap_init(hm, ecs_type_t, ecs_table_t*, flecs_type_hash, flecs_type_compare, &world->allocator); } /* Find location where to insert id into type */ static int flecs_type_find_insert( const ecs_type_t *type, int32_t offset, ecs_id_t to_add) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = offset; i < count; i ++) { ecs_id_t id = array[i]; if (id == to_add) { return -1; } if (id > to_add) { return i; } } return i; } /* Find location of id in type */ static int flecs_type_find( const ecs_type_t *type, ecs_id_t id) { ecs_id_t *array = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t cur = array[i]; if (ecs_id_match(cur, id)) { return i; } if (cur > id) { return -1; } } return -1; } /* Count number of matching ids */ static int flecs_type_count_matches( const ecs_type_t *type, ecs_id_t wildcard, int32_t offset) { ecs_id_t *array = type->array; int32_t i = offset, count = type->count; for (; i < count; i ++) { ecs_id_t cur = array[i]; if (!ecs_id_match(cur, wildcard)) { break; } } return i - offset; } /* Create type from source type with id */ static int flecs_type_new_with( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t with) { ecs_id_t *src_array = src->array; int32_t at = flecs_type_find_insert(src, 0, with); if (at == -1) { return -1; } int32_t dst_count = src->count + 1; ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->count = dst_count; dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = src->count - at; if (remain) { ecs_os_memcpy_n(&dst_array[at + 1], &src_array[at], ecs_id_t, remain); } dst_array[at] = with; return 0; } /* Create type from source type without ids matching wildcard */ static int flecs_type_new_filtered( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t wildcard, int32_t at) { *dst = flecs_type_copy(world, src); ecs_id_t *dst_array = dst->array; ecs_id_t *src_array = src->array; if (at) { ecs_assert(dst_array != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t i = at + 1, w = at, count = src->count; for (; i < count; i ++) { ecs_id_t id = src_array[i]; if (!ecs_id_match(id, wildcard)) { dst_array[w] = id; w ++; } } dst->count = w; if (w != count) { dst->array = flecs_wrealloc_n(world, ecs_id_t, w, count, dst->array); } return 0; } /* Create type from source type without id */ static int flecs_type_new_without( ecs_world_t *world, ecs_type_t *dst, const ecs_type_t *src, ecs_id_t without) { ecs_id_t *src_array = src->array; int32_t count = 1, at = flecs_type_find(src, without); if (at == -1) { return -1; } int32_t src_count = src->count; if (src_count == 1) { dst->array = NULL; dst->count = 0; return 0; } if (ecs_id_is_wildcard(without)) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = ECS_PAIR_FIRST(without); ecs_entity_t o = ECS_PAIR_SECOND(without); if (r == EcsWildcard && o != EcsWildcard) { return flecs_type_new_filtered(world, dst, src, without, at); } } count += flecs_type_count_matches(src, without, at + 1); } int32_t dst_count = src_count - count; dst->count = dst_count; if (!dst_count) { dst->array = NULL; return 0; } ecs_id_t *dst_array = flecs_walloc_n(world, ecs_id_t, dst_count); dst->array = dst_array; if (at) { ecs_os_memcpy_n(dst_array, src_array, ecs_id_t, at); } int32_t remain = dst_count - at; if (remain) { ecs_os_memcpy_n( &dst_array[at], &src_array[at + count], ecs_id_t, remain); } return 0; } /* Copy type */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src) { int32_t src_count = src->count; if (!src_count) { return (ecs_type_t){ 0 }; } ecs_id_t *ids = flecs_walloc_n(world, ecs_id_t, src_count); ecs_os_memcpy_n(ids, src->array, ecs_id_t, src_count); return (ecs_type_t) { .array = ids, .count = src_count }; } /* Free type */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type) { int32_t count = type->count; if (count) { flecs_wfree_n(world, ecs_id_t, type->count, type->array); } } /* Add to type */ static void flecs_type_add( ecs_world_t *world, ecs_type_t *type, ecs_id_t add) { ecs_type_t new_type; int res = flecs_type_new_with(world, &new_type, type, add); if (res != -1) { flecs_type_free(world, type); type->array = new_type.array; type->count = new_type.count; } } /* Graph edge utilities */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &builder->added, ecs_id_t, 256); ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); } void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &builder->added, ecs_id_t); ecs_vec_fini_t(a, &builder->removed, ecs_id_t); } void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder) { ecs_vec_clear(&builder->added); ecs_vec_clear(&builder->removed); } static void flecs_table_diff_build_type( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *type, int32_t offset) { int32_t count = vec->count - offset; ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); if (count) { type->array = flecs_wdup_n(world, ecs_id_t, count, ECS_ELEM_T(vec->array, ecs_id_t, offset)); type->count = count; ecs_vec_set_count_t(&world->allocator, vec, ecs_id_t, offset); } } void flecs_table_diff_build( ecs_world_t *world, ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, int32_t removed_offset) { flecs_table_diff_build_type(world, &builder->added, &diff->added, added_offset); flecs_table_diff_build_type(world, &builder->removed, &diff->removed, removed_offset); } void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff) { diff->added = (ecs_type_t){ .array = builder->added.array, .count = builder->added.count }; diff->removed = (ecs_type_t){ .array = builder->removed.array, .count = builder->removed.count }; } static void flecs_table_diff_build_add_type_to_vec( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *add) { if (!add || !add->count) { return; } int32_t offset = vec->count; ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), add->array, ecs_id_t, add->count); } void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src) { flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); } static void flecs_table_diff_free( ecs_world_t *world, ecs_table_diff_t *diff) { flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); flecs_bfree(&world->allocators.table_diff, diff); } static ecs_graph_edge_t* flecs_table_ensure_hi_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { if (!edges->hi) { edges->hi = flecs_alloc_t(&world->allocator, ecs_map_t); ecs_map_init_w_params(edges->hi, &world->allocators.ptr); } ecs_graph_edge_t **r = ecs_map_ensure_ref(edges->hi, ecs_graph_edge_t, id); ecs_graph_edge_t *edge = r[0]; if (edge) { return edge; } if (id < FLECS_HI_COMPONENT_ID) { edge = &edges->lo[id]; } else { edge = flecs_bcalloc(&world->allocators.graph_edge); } r[0] = edge; return edge; } static ecs_graph_edge_t* flecs_table_ensure_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id) { ecs_graph_edge_t *edge; if (id < FLECS_HI_COMPONENT_ID) { if (!edges->lo) { edges->lo = flecs_bcalloc(&world->allocators.graph_edge_lo); } edge = &edges->lo[id]; } else { edge = flecs_table_ensure_hi_edge(world, edges, id); } return edge; } static void flecs_table_disconnect_edge( ecs_world_t *world, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edge != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->id == id, ECS_INTERNAL_ERROR, NULL); (void)id; /* Remove backref from destination table */ ecs_graph_edge_hdr_t *next = edge->hdr.next; ecs_graph_edge_hdr_t *prev = edge->hdr.prev; if (next) { next->prev = prev; } if (prev) { prev->next = next; } /* Remove data associated with edge */ ecs_table_diff_t *diff = edge->diff; if (diff) { flecs_table_diff_free(world, diff); } /* If edge id is low, clear it from fast lookup array */ if (id < FLECS_HI_COMPONENT_ID) { ecs_os_memset_t(edge, 0, ecs_graph_edge_t); } else { flecs_bfree(&world->allocators.graph_edge, edge); } } static void flecs_table_remove_edge( ecs_world_t *world, ecs_graph_edges_t *edges, ecs_id_t id, ecs_graph_edge_t *edge) { ecs_assert(edges != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(edges->hi != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_disconnect_edge(world, id, edge); ecs_map_remove(edges->hi, id); } static void flecs_table_init_edges( ecs_graph_edges_t *edges) { edges->lo = NULL; edges->hi = NULL; } static void flecs_table_init_node( ecs_graph_node_t *node) { flecs_table_init_edges(&node->add); flecs_table_init_edges(&node->remove); } bool flecs_table_records_update_empty( ecs_table_t *table) { bool result = false; bool is_empty = ecs_table_count(table) == 0; int32_t i, count = table->_->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->_->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; result |= ecs_table_cache_set_empty(cache, table, is_empty); } return result; } static void flecs_init_table( ecs_world_t *world, ecs_table_t *table, ecs_table_t *prev) { table->flags = 0; table->dirty_state = NULL; table->_->lock = 0; table->_->generation = 0; flecs_table_init_node(&table->node); flecs_table_init(world, table, prev); } static ecs_table_t *flecs_table_new( ecs_world_t *world, ecs_type_t *type, flecs_hashmap_result_t table_elem, ecs_table_t *prev) { ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE int32_t i, j, count = type->count; for (i = 0; i < count - 1; i ++) { if (type->array[i] >= type->array[i + 1]) { for (j = 0; j < count; j ++) { char *str = ecs_id_str(world, type->array[j]); if (i == j) { ecs_err(" > %d: %s", j, str); } else { ecs_err(" %d: %s", j, str); } ecs_os_free(str); } ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); } } #endif result->id = flecs_sparse_last_id(&world->store.tables); result->type = *type; if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &result->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", expr, result->id); ecs_os_free(expr); } ecs_log_push_2(); /* Store table in table hashmap */ *(ecs_table_t**)table_elem.value = result; /* Set keyvalue to one that has the same lifecycle as the table */ *(ecs_type_t*)table_elem.key = result->type; result->_->hash = table_elem.hash; flecs_init_table(world, result, prev); /* Update counters */ world->info.table_count ++; world->info.empty_table_count ++; world->info.table_create_total ++; ecs_log_pop_2(); return result; } static ecs_table_t* flecs_table_ensure( ecs_world_t *world, ecs_type_t *type, bool own_type, ecs_table_t *prev) { flecs_poly_assert(world, ecs_world_t); int32_t id_count = type->count; if (!id_count) { return &world->store.root; } ecs_table_t *table; flecs_hashmap_result_t elem = flecs_hashmap_ensure( &world->store.table_map, type, ecs_table_t*); if ((table = *(ecs_table_t**)elem.value)) { if (own_type) { flecs_type_free(world, type); } return table; } /* If we get here, table needs to be created which is only allowed when the * application is not currently in progress */ ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); /* If we get here, the table has not been found, so create it. */ if (own_type) { return flecs_table_new(world, type, elem, prev); } ecs_type_t copy = flecs_type_copy(world, type); return flecs_table_new(world, ©, elem, prev); } static void flecs_diff_insert_added( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; } static void flecs_diff_insert_removed( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_allocator_t *a = &world->allocator; ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; } static 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) { ecs_type_t node_type = node->type; ecs_type_t next_type = next->type; 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 & EcsIdIsUnion) { if (node != next) { id = ecs_pair(ECS_PAIR_FIRST(id), EcsUnion); } 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_id_t *ids_node = node_type.array; ecs_id_t *ids_next = next_type.array; int32_t i_node = 0, node_count = node_type.count; int32_t i_next = 0, next_count = next_type.count; int32_t added_count = 0; int32_t removed_count = 0; bool trivial_edge = !ECS_HAS_RELATION(id, EcsIsA); /* First do a scan to see how big the diff is, so we don't have to realloc * or alloc more memory than required. */ for (; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; bool added = id_next < id_node; bool removed = id_node < id_next; trivial_edge &= !added || id_next == id; trivial_edge &= !removed || id_node == id; added_count += added; removed_count += removed; i_node += id_node <= id_next; i_next += id_next <= id_node; } added_count += next_count - i_next; removed_count += node_count - i_node; trivial_edge &= (added_count + removed_count) <= 1 && !ecs_id_is_wildcard(id); if (trivial_edge) { /* If edge is trivial there's no need to create a diff element for it */ return; } ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; int32_t added_offset = builder->added.count; int32_t removed_offset = builder->removed.count; for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; if (id_next < id_node) { flecs_diff_insert_added(world, builder, id_next); } else if (id_node < id_next) { flecs_diff_insert_removed(world, builder, id_node); } i_node += id_node <= id_next; i_next += id_next <= id_node; } for (; i_next < next_count; i_next ++) { flecs_diff_insert_added(world, builder, ids_next[i_next]); } for (; i_node < node_count; i_node ++) { flecs_diff_insert_removed(world, builder, ids_node[i_node]); } ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff = diff; flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); } static void flecs_add_overrides_for_base( ecs_world_t *world, ecs_type_t *dst_type, ecs_id_t pair) { ecs_entity_t base = ecs_pair_second(world, pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_table_t *base_table = ecs_get_table(world, base); if (!base_table) { return; } ecs_id_t *ids = base_table->type.array; ecs_flags32_t flags = base_table->flags; if (flags & EcsTableHasOverrides) { int32_t i, count = base_table->type.count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; ecs_id_t to_add = 0; if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { to_add = id & ~ECS_AUTO_OVERRIDE; } else { ecs_table_record_t *tr = &base_table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (ECS_ID_ON_INSTANTIATE(idr->flags) == EcsOverride) { to_add = id; } } if (to_add) { ecs_id_t wc = ecs_pair(ECS_PAIR_FIRST(to_add), EcsWildcard); bool exclusive = false; if (ECS_IS_PAIR(to_add)) { ecs_id_record_t *idr = flecs_id_record_get(world, wc); if (idr) { exclusive = (idr->flags & EcsIdExclusive) != 0; } } if (!exclusive) { flecs_type_add(world, dst_type, to_add); } else { int32_t column = flecs_type_find(dst_type, wc); if (column == -1) { flecs_type_add(world, dst_type, to_add); } else { dst_type->array[column] = to_add; } } } } } if (flags & EcsTableHasIsA) { ecs_table_record_t *tr = flecs_id_record_get_table( world->idr_isa_wildcard, base_table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = tr->index, end = i + tr->count; for (; i != end; i ++) { flecs_add_overrides_for_base(world, dst_type, ids[i]); } } } static void flecs_add_with_property( ecs_world_t *world, ecs_id_record_t *idr_with_wildcard, ecs_type_t *dst_type, ecs_entity_t r, ecs_entity_t o) { r = ecs_get_alive(world, r); /* Check if component/relationship has With pairs, which contain ids * that need to be added to the table. */ ecs_table_t *table = ecs_get_table(world, r); if (!table) { return; } ecs_table_record_t *tr = flecs_id_record_get_table( idr_with_wildcard, table); if (tr) { int32_t i = tr->index, end = i + tr->count; ecs_id_t *ids = table->type.array; for (; i < end; i ++) { ecs_id_t id = ids[i]; ecs_assert(ECS_PAIR_FIRST(id) == EcsWith, ECS_INTERNAL_ERROR, NULL); ecs_id_t ra = ECS_PAIR_SECOND(id); ecs_id_t a = ra; if (o) { a = ecs_pair(ra, o); } flecs_type_add(world, dst_type, a); flecs_add_with_property(world, idr_with_wildcard, dst_type, ra, o); } } } static ecs_table_t* flecs_find_table_with( ecs_world_t *world, ecs_table_t *node, ecs_id_t with) { ecs_make_alive_id(world, with); ecs_id_record_t *idr = NULL; ecs_entity_t r = 0, o = 0; if (ECS_IS_PAIR(with)) { r = ECS_PAIR_FIRST(with); o = ECS_PAIR_SECOND(with); idr = flecs_id_record_ensure(world, ecs_pair(r, EcsWildcard)); if (idr->flags & EcsIdIsUnion) { with = ecs_pair(r, EcsUnion); } else if (idr->flags & EcsIdExclusive) { /* Relationship is exclusive, check if table already has it */ ecs_table_record_t *tr = flecs_id_record_get_table(idr, node); if (tr) { /* Table already has an instance of the relationship, create * a new id sequence with the existing id replaced */ ecs_type_t dst_type = flecs_type_copy(world, &node->type); ecs_assert(dst_type.array != NULL, ECS_INTERNAL_ERROR, NULL); dst_type.array[tr->index] = with; return flecs_table_ensure(world, &dst_type, true, node); } } } else { idr = flecs_id_record_ensure(world, with); r = with; } /* Create sequence with new id */ ecs_type_t dst_type; int res = flecs_type_new_with(world, &dst_type, &node->type, with); if (res == -1) { return node; /* Current table already has id */ } if (r == EcsIsA) { /* If adding a prefab, check if prefab has overrides */ flecs_add_overrides_for_base(world, &dst_type, with); } else if (r == EcsChildOf) { o = ecs_get_alive(world, o); if (ecs_has_id(world, o, EcsPrefab)) { flecs_type_add(world, &dst_type, EcsPrefab); } } if (idr->flags & EcsIdWith) { ecs_id_record_t *idr_with_wildcard = flecs_id_record_get(world, ecs_pair(EcsWith, EcsWildcard)); /* If id has With property, add targets to type */ flecs_add_with_property(world, idr_with_wildcard, &dst_type, r, o); } return flecs_table_ensure(world, &dst_type, true, node); } static ecs_table_t* flecs_find_table_without( ecs_world_t *world, ecs_table_t *node, ecs_id_t without) { if (ECS_IS_PAIR(without)) { ecs_entity_t r = 0; ecs_id_record_t *idr = NULL; r = ECS_PAIR_FIRST(without); idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); if (idr && idr->flags & EcsIdIsUnion) { without = ecs_pair(r, EcsUnion); } } /* 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) { flecs_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) { flecs_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) { flecs_poly_assert(world, ecs_world_t); return flecs_table_ensure(world, type, false, NULL); } void flecs_init_root_table( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); world->store.root.type = (ecs_type_t){0}; world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); flecs_init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ uint64_t new_id = flecs_sparse_new_id(&world->store.tables); ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); (void)new_id; } void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table) { (void)world; flecs_poly_assert(world, ecs_world_t); ecs_log_push_1(); ecs_map_iter_t it; ecs_graph_node_t *table_node = &table->node; ecs_graph_edges_t *node_add = &table_node->add; ecs_graph_edges_t *node_remove = &table_node->remove; ecs_map_t *add_hi = node_add->hi; ecs_map_t *remove_hi = node_remove->hi; ecs_graph_edge_hdr_t *node_refs = &table_node->refs; /* Cleanup outgoing edges */ it = ecs_map_iter(add_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } it = ecs_map_iter(remove_hi); while (ecs_map_next(&it)) { flecs_table_disconnect_edge(world, ecs_map_key(&it), ecs_map_ptr(&it)); } /* Cleanup incoming add edges */ ecs_graph_edge_hdr_t *next, *cur = node_refs->next; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->next; flecs_table_remove_edge(world, &edge->from->node.add, edge->id, edge); } while ((cur = next)); } /* Cleanup incoming remove edges */ cur = node_refs->prev; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; ecs_assert(edge->to == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(edge->from != NULL, ECS_INTERNAL_ERROR, NULL); next = cur->prev; flecs_table_remove_edge(world, &edge->from->node.remove, edge->id, edge); } while ((cur = next)); } if (node_add->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_add->lo); } if (node_remove->lo) { flecs_bfree(&world->allocators.graph_edge_lo, node_remove->lo); } ecs_map_fini(add_hi); ecs_map_fini(remove_hi); flecs_free_t(&world->allocator, ecs_map_t, add_hi); flecs_free_t(&world->allocator, ecs_map_t, remove_hi); table_node->add.lo = NULL; table_node->remove.lo = NULL; table_node->add.hi = NULL; table_node->remove.hi = NULL; ecs_log_pop_1(); } /* Public convenience functions for traversing table graph */ ecs_table_t* ecs_table_add_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; return flecs_table_traverse_add(world, table, &id, &diff); } ecs_table_t* ecs_table_remove_id( ecs_world_t *world, ecs_table_t *table, ecs_id_t id) { ecs_table_diff_t diff; return flecs_table_traverse_remove(world, table, &id, &diff); } ecs_table_t* ecs_table_find( ecs_world_t *world, const ecs_id_t *ids, int32_t id_count) { ecs_type_t type = { .array = ECS_CONST_CAST(ecs_id_t*, ids), .count = id_count }; return flecs_table_ensure(world, &type, false, NULL); } /** * @file addons/json/deserialize.c * @brief Deserialize JSON strings into (component) values. */ /** * @file addons/json/json.h * @brief Internal functions for JSON addon. */ #ifndef FLECS_JSON_PRIVATE_H #define FLECS_JSON_PRIVATE_H #ifdef FLECS_JSON /* Deserialize from JSON */ typedef enum ecs_json_token_t { JsonObjectOpen, JsonObjectClose, JsonArrayOpen, JsonArrayClose, JsonColon, JsonComma, JsonNumber, JsonString, JsonBoolean, JsonTrue, JsonFalse, JsonNull, JsonLargeInt, JsonLargeString, JsonInvalid } ecs_json_token_t; typedef struct ecs_json_value_ser_ctx_t { ecs_entity_t type; const EcsTypeSerializer *ser; char *id_label; bool initialized; } ecs_json_value_ser_ctx_t; /* Cached data for serializer */ typedef struct ecs_json_ser_ctx_t { ecs_id_record_t *idr_doc_name; ecs_id_record_t *idr_doc_color; ecs_json_value_ser_ctx_t value_ctx[64]; } ecs_json_ser_ctx_t; typedef struct ecs_json_this_data_t { ecs_entity_t *ids; const EcsIdentifier *names; const EcsDocDescription *label; const EcsDocDescription *brief; const EcsDocDescription *detail; const EcsDocDescription *color; const EcsDocDescription *link; bool has_alerts; } ecs_json_this_data_t; const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token); const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf); const char* flecs_json_parse_next_member( const char *json, char *token, ecs_json_token_t *token_kind, const ecs_from_json_desc_t *desc); const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_string( const char *json, char *token, char **out, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_next_member( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_array( const char *json, char *token, const ecs_from_json_desc_t *desc); /* Serialize to JSON */ void flecs_json_next( ecs_strbuf_t *buf); void flecs_json_number( ecs_strbuf_t *buf, double value); void flecs_json_u32( ecs_strbuf_t *buf, uint32_t value); void flecs_json_true( ecs_strbuf_t *buf); void flecs_json_false( ecs_strbuf_t *buf); void flecs_json_bool( ecs_strbuf_t *buf, bool value); void flecs_json_null( ecs_strbuf_t *buf); void flecs_json_array_push( ecs_strbuf_t *buf); void flecs_json_array_pop( ecs_strbuf_t *buf); void flecs_json_object_push( ecs_strbuf_t *buf); void flecs_json_object_pop( ecs_strbuf_t *buf); void flecs_json_string( ecs_strbuf_t *buf, const char *value); void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value); void flecs_json_member( ecs_strbuf_t *buf, const char *name); void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len); #define flecs_json_memberl(buf, name)\ flecs_json_membern(buf, name, sizeof(name) - 1) void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_path_or_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e, bool path); void flecs_json_color( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id); void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id, bool fullpath); ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind); int flecs_json_serialize_iter_result( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx); void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, const ecs_query_t *q, int field, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx); void flecs_json_serialize_query( const ecs_world_t *world, const ecs_query_t *q, ecs_strbuf_t *buf); int flecs_json_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str); int flecs_json_serialize_iter_result_fields( const ecs_world_t *world, const ecs_iter_t *it, int32_t i, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx); bool flecs_json_serialize_get_value_ctx( const ecs_world_t *world, ecs_id_t id, ecs_json_value_ser_ctx_t *ctx, const ecs_iter_to_json_desc_t *desc); int flecs_json_serialize_iter_result_table( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data); int flecs_json_serialize_iter_result_query( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data); void flecs_json_serialize_iter_this( const ecs_iter_t *it, const char *parent_path, const ecs_json_this_data_t *this_data, int32_t row, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc); bool flecs_json_serialize_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc); int flecs_json_serialize_matches( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity); int flecs_json_serialize_refs( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, ecs_entity_t relationship); int flecs_json_serialize_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity); bool flecs_json_is_builtin( ecs_id_t id); #endif #endif /* FLECS_JSON_PRIVATE_H */ #include #ifdef FLECS_JSON typedef struct { ecs_allocator_t *a; ecs_vec_t table_type; ecs_vec_t remove_ids; ecs_map_t anonymous_ids; ecs_map_t missing_reflection; const char *expr; } ecs_from_json_ctx_t; static void flecs_from_json_ctx_init( ecs_allocator_t *a, ecs_from_json_ctx_t *ctx) { ctx->a = a; ecs_vec_init_t(a, &ctx->table_type, ecs_id_t, 0); ecs_vec_init_t(a, &ctx->remove_ids, ecs_id_t, 0); ecs_map_init(&ctx->anonymous_ids, a); ecs_map_init(&ctx->missing_reflection, a); } static void flecs_from_json_ctx_fini( ecs_from_json_ctx_t *ctx) { ecs_vec_fini_t(ctx->a, &ctx->table_type, ecs_record_t*); ecs_vec_fini_t(ctx->a, &ctx->remove_ids, ecs_record_t*); ecs_map_fini(&ctx->anonymous_ids); ecs_map_fini(&ctx->missing_reflection); } static ecs_entity_t flecs_json_new_id( ecs_world_t *world, ecs_entity_t ser_id) { /* Try to honor low id requirements */ if (ser_id < FLECS_HI_COMPONENT_ID) { return ecs_new_low_id(world); } else { return ecs_new(world); } } static void flecs_json_missing_reflection( ecs_world_t *world, ecs_id_t id, const char *json, ecs_from_json_ctx_t *ctx, const ecs_from_json_desc_t *desc) { if (!desc->strict || ecs_map_get(&ctx->missing_reflection, id)) { return; } /* Don't spam log when multiple values of a type can't be deserialized */ ecs_map_ensure(&ctx->missing_reflection, id); char *id_str = ecs_id_str(world, id); ecs_parser_error(desc->name, desc->expr, json - desc->expr, "missing reflection for '%s'", id_str); ecs_os_free(id_str); } static ecs_entity_t flecs_json_lookup( ecs_world_t *world, ecs_entity_t parent, const char *name, const ecs_from_json_desc_t *desc) { ecs_entity_t scope = 0; if (parent) { scope = ecs_set_scope(world, parent); } ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); if (parent) { ecs_set_scope(world, scope); } return result; } static void flecs_json_mark_reserved( ecs_map_t *anonymous_ids, ecs_entity_t e) { ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); reserved[0] = 0; } static ecs_entity_t flecs_json_ensure_entity( ecs_world_t *world, const char *name, ecs_map_t *anonymous_ids) { ecs_entity_t e = 0; if (flecs_name_is_id(name)) { /* Anonymous entity, find or create mapping to new id */ ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(&name[1])); ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); if (deser_id) { if (!deser_id[0]) { /* Id is already issued by deserializer, create new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } else { /* Id mapping exists */ } } else { /* Id has not yet been issued by deserializer, which means it's safe * to use. This allows the deserializer to bind to existing * anonymous ids, as they will never be reissued. */ deser_id = ecs_map_ensure(anonymous_ids, ser_id); if (!ecs_exists(world, ser_id) || (ecs_is_alive(world, ser_id) && !ecs_get_name(world, ser_id))) { /* Only use existing id if it's alive or doesn't exist yet. The * id could have been recycled for another entity * Also don't use existing id if the existing entity is not * anonymous. */ deser_id[0] = ser_id; ecs_make_alive(world, ser_id); } else { /* If id exists and is not alive, create a new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } } e = deser_id[0]; } else { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); if (!e) { e = ecs_entity(world, { .name = name }); flecs_json_mark_reserved(anonymous_ids, e); } } return e; } static bool flecs_json_add_id_to_type( ecs_id_t id) { if (id == ecs_pair_t(EcsIdentifier, EcsName)) { return false; } if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsChildOf) { return false; } return true; } static const char* flecs_json_deser_tags( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; const char *expr = ctx->expr, *lah; json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { json = lah; goto end; } do { char *str = NULL; json = flecs_json_expect_string(json, token, &str, desc); if (!json) { goto error; } ecs_entity_t tag = flecs_json_lookup(world, 0, str, desc); if (flecs_json_add_id_to_type(tag)) { ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = tag; } ecs_add_id(world, e, tag); if (str != token) { ecs_os_free(str); } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonComma) { break; } } while (true); if (token_kind != JsonArrayClose) { ecs_parser_error(NULL, expr, json - expr, "expected }"); goto error; } end: return json; error: return NULL; } static const char* flecs_json_deser_pairs( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; const char *expr = ctx->expr, *lah; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonObjectClose) { json = lah; goto end; } do { json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } ecs_entity_t rel = flecs_json_lookup(world, 0, token, desc); bool multiple_targets = false; do { json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonString) { ecs_entity_t tgt = flecs_json_lookup(world, 0, token, desc); ecs_id_t id = ecs_pair(rel, tgt); ecs_add_id(world, e, id); if (flecs_json_add_id_to_type(id)) { ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; } } else if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } char *str = ecs_strbuf_get(&large_token); ecs_entity_t tgt = flecs_json_lookup(world, 0, str, desc); ecs_os_free(str); ecs_id_t id = ecs_pair(rel, tgt); ecs_add_id(world, e, id); if (flecs_json_add_id_to_type(id)) { ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; } } else if (token_kind == JsonArrayOpen) { if (multiple_targets) { ecs_parser_error(NULL, expr, json - expr, "expected string"); goto error; } multiple_targets = true; } else if (token_kind == JsonArrayClose) { if (!multiple_targets) { ecs_parser_error(NULL, expr, json - expr, "unexpected ]"); goto error; } multiple_targets = false; } else if (token_kind == JsonComma) { if (!multiple_targets) { ecs_parser_error(NULL, expr, json - expr, "unexpected ,"); goto error; } } else { ecs_parser_error(NULL, expr, json - expr, "expected array or string"); goto error; } } while (multiple_targets); json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonComma) { break; } } while (true); if (token_kind != JsonObjectClose) { ecs_parser_error(NULL, expr, json - expr, "expected }"); goto error; } end: return json; error: return NULL; } static const char* flecs_json_deser_components( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; const char *expr = ctx->expr, *lah; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonObjectClose) { json = lah; goto end; } do { json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } ecs_id_t id = 0; if (token[0] != '(') { id = flecs_json_lookup(world, 0, token, desc); } else { char token_buffer[256]; ecs_term_t term = {0}; if (!flecs_term_parse(world, NULL, token, &term, token_buffer)) { goto error; } ecs_assert(term.first.name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(term.second.name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t rel = flecs_json_lookup( world, 0, term.first.name, desc); ecs_entity_t tgt = flecs_json_lookup( world, 0, term.second.name, desc); id = ecs_pair(rel, tgt); } lah = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonNull) { ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { flecs_json_missing_reflection(world, id, json, ctx, desc); if (desc->strict) { goto error; } json = flecs_json_skip_object(json + 1, token, desc); if (!json) { goto error; } } else { void *ptr = ecs_ensure_id(world, e, id); lah = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonNull) { const char *next = ecs_ptr_from_json( world, type, ptr, json, desc); if (!next) { flecs_json_missing_reflection( world, id, json, ctx, desc); if (desc->strict) { goto error; } json = flecs_json_skip_object(json + 1, token, desc); if (!json) { goto error; } } else { json = next; ecs_modified_id(world, e, id); } } else { json = lah; } } } else { ecs_add_id(world, e, id); json = lah; } /* Don't add ids that have their own fields in serialized data. */ if (flecs_json_add_id_to_type(id)) { ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = id; } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonComma) { break; } } while (true); if (token_kind != JsonObjectClose) { ecs_parser_error(NULL, expr, json - expr, "expected }"); goto error; } end: return json; error: return NULL; } static const char* flecs_entity_from_json( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc, ecs_from_json_ctx_t *ctx) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; const char *expr = ctx->expr, *lah; ecs_vec_clear(&ctx->table_type); ecs_entity_t parent = 0; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonObjectClose) { json = lah; goto end; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } if (!ecs_os_strcmp(token, "parent")) { char *str = NULL; json = flecs_json_expect_string(json, token, &str, desc); if (!json) { goto error; } parent = flecs_json_lookup(world, 0, str, desc); if (e) { ecs_add_pair(world, e, EcsChildOf, parent); } ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = ecs_pair(EcsChildOf, parent); if (str != token) ecs_os_free(str); json = flecs_json_parse_next_member(json, token, &token_kind, desc); if (!json) { goto error; } if (token_kind == JsonObjectClose) { goto end; } } if (!ecs_os_strcmp(token, "name")) { char *str = NULL; json = flecs_json_expect_string(json, token, &str, desc); if (!json) { goto error; } if (!e) { e = flecs_json_lookup(world, parent, str, desc); } else { ecs_set_name(world, e, str); } if (str[0] != '#') { ecs_vec_append_t(ctx->a, &ctx->table_type, ecs_id_t)[0] = ecs_pair_t(EcsIdentifier, EcsName); } if (str != token) ecs_os_free(str); json = flecs_json_parse_next_member(json, token, &token_kind, desc); if (!json) { goto error; } if (token_kind == JsonObjectClose) { goto end; } } if (!ecs_os_strcmp(token, "id")) { json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } uint64_t id; if (token_kind == JsonNumber || token_kind == JsonLargeInt) { id = flecs_ito(uint64_t, atoll(token)); } else { ecs_parser_error(NULL, expr, json - expr, "expected entity id"); goto error; } if (!e) { char name[32]; ecs_os_snprintf(name, 32, "#%u", (uint32_t)id); e = flecs_json_lookup(world, 0, name, desc); } else { /* If we already have an id, ignore explicit id */ } json = flecs_json_parse_next_member(json, token, &token_kind, desc); if (!json) { goto error; } if (token_kind == JsonObjectClose) { goto end; } } if (!e) { ecs_parser_error(NULL, expr, json - expr, "failed to create entity"); return NULL; } if (!ecs_os_strcmp(token, "alerts")) { json = flecs_json_expect(json, JsonBoolean, token, desc); if (!json) { goto error; } json = flecs_json_parse_next_member(json, token, &token_kind, desc); if (!json) { goto error; } if (token_kind == JsonObjectClose) { goto end; } } if (!ecs_os_strcmp(token, "tags")) { json = flecs_json_deser_tags(world, e, json, desc, ctx); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonObjectClose) { goto end; } else if (token_kind != JsonComma) { ecs_parser_error(NULL, expr, json - expr, "expected ','"); goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } } if (!ecs_os_strcmp(token, "pairs")) { json = flecs_json_deser_pairs(world, e, json, desc, ctx); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonObjectClose) { goto end; } else if (token_kind != JsonComma) { ecs_parser_error(NULL, expr, json - expr, "expected ','"); goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } } if (!ecs_os_strcmp(token, "components")) { json = flecs_json_deser_components(world, e, json, desc, ctx); if (!json) { goto error; } } json = flecs_json_expect(json, JsonObjectClose, token, desc); if (!json) { goto error; } ecs_record_t *r = flecs_entities_get(world, e); ecs_table_t *table = r ? r->table : NULL; if (table) { ecs_id_t *ids = ecs_vec_first(&ctx->table_type); int32_t ids_count = ecs_vec_count(&ctx->table_type); qsort(ids, flecs_itosize(ids_count), sizeof(ecs_id_t), flecs_id_qsort_cmp); ecs_table_t *dst_table = ecs_table_find(world, ecs_vec_first(&ctx->table_type), ecs_vec_count(&ctx->table_type)); if (dst_table->type.count == 0) { dst_table = NULL; } /* Entity had existing components that weren't in the serialized data */ if (table != dst_table) { ecs_assert(ecs_get_target(world, e, EcsChildOf, 0) != EcsFlecsCore, ECS_INVALID_OPERATION, "%s\n[%s] => \n[%s]", ecs_get_path(world, e), ecs_table_str(world, table), ecs_table_str(world, dst_table)); if (!dst_table) { ecs_clear(world, e); } else { ecs_vec_clear(&ctx->remove_ids); ecs_type_t *type = &table->type, *dst_type = &dst_table->type; int32_t i = 0, i_dst = 0; for (; (i_dst < dst_type->count) && (i < type->count); ) { ecs_id_t id = type->array[i], dst_id = dst_type->array[i_dst]; if (dst_id > id) { ecs_vec_append_t( ctx->a, &ctx->remove_ids, ecs_id_t)[0] = id; } i_dst += dst_id <= id; i += dst_id >= id; } ecs_type_t removed = { .array = ecs_vec_first(&ctx->remove_ids), .count = ecs_vec_count(&ctx->remove_ids) }; ecs_commit(world, e, r, dst_table, NULL, &removed); } ecs_assert(ecs_get_table(world, e) == dst_table, ECS_INTERNAL_ERROR, NULL); } } end: return json; error: return NULL; } const char* ecs_entity_from_json( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_from_json_desc_t desc = {0}; if (desc_arg) { desc = *desc_arg; } desc.expr = json; ecs_allocator_t *a = &world->allocator; ecs_from_json_ctx_t ctx; flecs_from_json_ctx_init(a, &ctx); ctx.expr = json; if (!desc.lookup_action) { desc.lookup_action = (ecs_entity_t(*)( const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; desc.lookup_ctx = &ctx.anonymous_ids; } json = flecs_entity_from_json(world, e, json, &desc, &ctx); flecs_from_json_ctx_fini(&ctx); return json; } const char* ecs_world_from_json( ecs_world_t *world, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; if (desc_arg) { desc = *desc_arg; } desc.expr = json; ecs_allocator_t *a = &world->allocator; ecs_from_json_ctx_t ctx; flecs_from_json_ctx_init(a, &ctx); const char *expr = json, *lah; ctx.expr = expr; if (!desc.lookup_action) { desc.lookup_action = (ecs_entity_t(*)( const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; desc.lookup_ctx = &ctx.anonymous_ids; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "results", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { json = lah; goto end; } do { json = flecs_entity_from_json(world, 0, json, &desc, &ctx); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonComma) { if (token_kind != JsonArrayClose) { ecs_parser_error(NULL, expr, json - expr, "expected ']'"); goto error; } break; } } while (true); end: json = flecs_json_expect(json, JsonObjectClose, token, &desc); flecs_from_json_ctx_fini(&ctx); return json; error: flecs_from_json_ctx_fini(&ctx); return NULL; } const char* ecs_world_from_json_file( ecs_world_t *world, const char *filename, const ecs_from_json_desc_t *desc) { char *json = flecs_load_from_file(filename); if (!json) { ecs_err("file not found: %s", filename); return NULL; } const char *result = ecs_world_from_json(world, json, desc); ecs_os_free(json); return result; } #endif /** * @file addons/json/deserialize_legacy.c * @brief Deserialize JSON strings into (component) values. */ #include #ifdef FLECS_JSON typedef struct { ecs_allocator_t *a; ecs_vec_t records; ecs_vec_t result_ids; ecs_vec_t columns_set; ecs_map_t anonymous_ids; ecs_map_t missing_reflection; } ecs_from_json_ctx_legacy_t; static void flecs_from_json_ctx_init_legacy( ecs_allocator_t *a, ecs_from_json_ctx_legacy_t *ctx) { ctx->a = a; ecs_vec_init_t(a, &ctx->records, ecs_record_t*, 0); ecs_vec_init_t(a, &ctx->result_ids, ecs_id_t, 0); ecs_vec_init_t(a, &ctx->columns_set, ecs_id_t, 0); ecs_map_init(&ctx->anonymous_ids, a); ecs_map_init(&ctx->missing_reflection, a); } static void flecs_from_json_ctx_fini_legacy( ecs_from_json_ctx_legacy_t *ctx) { ecs_vec_fini_t(ctx->a, &ctx->records, ecs_record_t*); ecs_vec_fini_t(ctx->a, &ctx->result_ids, ecs_record_t*); ecs_vec_fini_t(ctx->a, &ctx->columns_set, ecs_id_t); ecs_map_fini(&ctx->anonymous_ids); ecs_map_fini(&ctx->missing_reflection); } static const char* flecs_json_parse_path_legacy( const ecs_world_t *world, const char *json, char *token, ecs_entity_t *out, const ecs_from_json_desc_t *desc) { char *path = NULL; json = flecs_json_expect_string(json, token, &path, desc); if (!json) { goto error; } ecs_entity_t result = ecs_lookup(world, path); if (!result) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "unresolved identifier '%s'", path); goto error; } *out = result; if (path != token) { ecs_os_free(path); } return json; error: if (path != token) { ecs_os_free(path); } return NULL; } const char* ecs_entity_from_json_legacy( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc_param) { ecs_json_token_t token_kind = 0; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; if (desc_param) { desc = *desc_param; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonObjectClose) { return lah; } json = flecs_json_expect_member(json, token, &desc); if (!json) { return NULL; } if (!ecs_os_strcmp(token, "path")) { char *path = NULL; json = flecs_json_expect_string(json, token, &path, &desc); if (!json) { goto error; } ecs_add_fullpath(world, e, path); if (path != token) { ecs_os_free(path); } json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } if (token_kind == JsonObjectClose) { return json; } else if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "unexpected character"); goto error; } json = flecs_json_expect_member_name(json, token, "ids", &desc); if (!json) { goto error; } } else if (ecs_os_strcmp(token, "ids")) { ecs_parser_error(name, expr, json - expr, "expected member 'ids'"); goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } ids = json; json = flecs_json_skip_array(json, token, &desc); if (!json) { return NULL; } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonObjectClose) { if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "expected ','"); goto error; } json = flecs_json_expect_member_name(json, token, "values", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } values = json; } do { ecs_entity_t first = 0, second = 0, type_id = 0; ecs_id_t id; ids = flecs_json_parse(ids, &token_kind, token); if (!ids) { goto error; } if (token_kind == JsonArrayClose) { if (values) { if (values[0] != ']') { ecs_parser_error(name, expr, values - expr, "expected ']'"); goto error; } json = flecs_parse_ws_eol(values + 1); } else { json = ids; } break; } else if (token_kind == JsonArrayOpen) { ids = flecs_json_parse_path_legacy(world, ids, token, &first, &desc); if (!ids) { goto error; } ids = flecs_json_parse(ids, &token_kind, token); if (!ids) { goto error; } if (token_kind == JsonComma) { /* Id is a pair*/ ids = flecs_json_parse_path_legacy(world, ids, token, &second, &desc); if (!ids) { goto error; } ids = flecs_json_expect(ids, JsonArrayClose, token, &desc); if (!ids) { goto error; } } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); goto error; } lah = flecs_json_parse(ids, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonComma) { ids = lah; } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, lah - expr, "expected ',' or ']'"); goto error; } } else { ecs_parser_error(name, expr, lah - expr, "expected '[' or ']'"); goto error; } if (second) { id = ecs_pair(first, second); type_id = ecs_get_typeid(world, id); if (!type_id) { ecs_parser_error(name, expr, ids - expr, "id is not a type"); goto error; } } else { id = first; type_id = first; } /* Get mutable pointer */ void *comp_ptr = ecs_ensure_id(world, e, id); if (!comp_ptr) { char *idstr = ecs_id_str(world, id); ecs_parser_error(name, expr, json - expr, "id '%s' is not a valid component", idstr); ecs_os_free(idstr); goto error; } if (values) { ecs_from_json_desc_t parse_desc = { .name = name, .expr = expr, }; values = ecs_ptr_from_json( world, type_id, comp_ptr, values, &parse_desc); if (!values) { goto error; } lah = flecs_json_parse(values, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonComma) { values = lah; } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, json - expr, "expected ',' or ']'"); goto error; } else { values = flecs_parse_ws_eol(values); } ecs_modified_id(world, e, id); } } while(ids[0]); return flecs_json_expect(json, JsonObjectClose, token, &desc); error: return NULL; } static ecs_entity_t flecs_json_new_id_legacy( ecs_world_t *world, ecs_entity_t ser_id) { /* Try to honor low id requirements */ if (ser_id < FLECS_HI_COMPONENT_ID) { return ecs_new_low_id(world); } else { return ecs_new(world); } } static ecs_entity_t flecs_json_lookup_legacy( ecs_world_t *world, ecs_entity_t parent, const char *name, const ecs_from_json_desc_t *desc) { ecs_entity_t scope = 0; if (parent) { scope = ecs_set_scope(world, parent); } ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); if (parent) { ecs_set_scope(world, scope); } return result; } static void flecs_json_mark_reserved_legacy( ecs_map_t *anonymous_ids, ecs_entity_t e) { ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); reserved[0] = 0; } static bool flecs_json_name_is_anonymous_legacy( const char *name) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (isdigit(name[0])) { const char *ptr; for (ptr = name + 1; *ptr; ptr ++) { if (!isdigit(*ptr)) { break; } } if (!(*ptr)) { return true; } } return false; } static ecs_entity_t flecs_json_ensure_entity_legacy( ecs_world_t *world, const char *name, ecs_map_t *anonymous_ids) { ecs_entity_t e = 0; if (flecs_json_name_is_anonymous_legacy(name)) { /* Anonymous entity, find or create mapping to new id */ ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(name)); ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); if (deser_id) { if (!deser_id[0]) { /* Id is already issued by deserializer, create new id */ deser_id[0] = flecs_json_new_id_legacy(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved_legacy(anonymous_ids, deser_id[0]); } else { /* Id mapping exists */ } } else { /* Id has not yet been issued by deserializer, which means it's safe * to use. This allows the deserializer to bind to existing * anonymous ids, as they will never be reissued. */ deser_id = ecs_map_ensure(anonymous_ids, ser_id); if (!ecs_exists(world, ser_id) || (ecs_is_alive(world, ser_id) && !ecs_get_name(world, ser_id))) { /* Only use existing id if it's alive or doesn't exist yet. The * id could have been recycled for another entity * Also don't use existing id if the existing entity is not * anonymous. */ deser_id[0] = ser_id; ecs_make_alive(world, ser_id); } else { /* If id exists and is not alive, create a new id */ deser_id[0] = flecs_json_new_id_legacy(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved_legacy(anonymous_ids, deser_id[0]); } } e = deser_id[0]; } else { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); if (!e) { e = ecs_entity(world, { .name = name }); flecs_json_mark_reserved_legacy(anonymous_ids, e); } } return e; } static ecs_table_t* flecs_json_parse_table_legacy( ecs_world_t *world, const char *json, char *token, ecs_from_json_ctx_legacy_t *ctx, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; ecs_vec_clear(&ctx->result_ids); do { ecs_id_t id = 0; json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } char *first_name = NULL; json = flecs_json_expect_string(json, token, &first_name, desc); if (!json) { goto error; } ecs_entity_t first = flecs_json_lookup_legacy(world, 0, first_name, desc); if (first_name != token) { ecs_os_free(first_name); } if (!first) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { char *second_name = NULL; json = flecs_json_expect_string(json, token, &second_name, desc); if (!json) { goto error; } ecs_entity_t second = flecs_json_lookup_legacy(world, 0, second_name, desc); if (second_name != token) { ecs_os_free(second_name); } if (!second) { goto error; } id = ecs_pair(first, second); json = flecs_json_expect(json, JsonArrayClose, token, desc); if (!json) { goto error; } } else if (token_kind == JsonArrayClose) { id = first; } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']"); goto error; } ecs_vec_append_t(ctx->a, &ctx->result_ids, ecs_id_t)[0] = id; const char *lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = lah; } else if (token_kind == JsonArrayClose) { break; } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while (json[0]); /* Make a copy of the ids array because we need the original order for * deserializing the component values, and the sorted order for finding or * creating the table. */ ecs_vec_t id_copy = ecs_vec_copy_t(ctx->a, &ctx->result_ids, ecs_id_t); ecs_type_t type = { .array = ecs_vec_first(&id_copy), .count = ecs_vec_count(&id_copy) }; qsort(type.array, flecs_itosize(type.count), sizeof(ecs_id_t), flecs_id_qsort_cmp); ecs_table_t *table = flecs_table_find_or_create(world, &type); if (!table) { goto error; } ecs_vec_fini_t(ctx->a, &id_copy, ecs_id_t); return table; error: return NULL; } static void flecs_json_zeromem_table_legacy( ecs_table_t *table) { int32_t count = ecs_vec_count(&table->data.entities); int32_t i, column_count = table->column_count; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &table->data.columns[i]; ecs_type_info_t *ti = column->ti; if (!ti->hooks.ctor) { void *ptr = ecs_vec_first(&column->data); ecs_os_memset(ptr, 0, ti->size * count); } } } static int flecs_json_parse_entities_legacy( ecs_world_t *world, ecs_table_t *table, ecs_entity_t parent, const char *json, char *token, ecs_from_json_ctx_legacy_t *ctx, const ecs_from_json_desc_t *desc) { char name_token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t token_kind = 0; ecs_vec_clear(&ctx->records); do { char *name = NULL; json = flecs_json_parse(json, &token_kind, name_token); if (!json) { goto error; } if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } name = ecs_strbuf_get(&large_token); token_kind = JsonString; } else { name = name_token; } if ((token_kind != JsonNumber) && (token_kind != JsonString)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected number or string"); goto error; } ecs_entity_t e = flecs_json_lookup_legacy(world, parent, name, desc); ecs_record_t *r = flecs_entities_try(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (r->table != table) { bool cleared = false; if (r->table) { ecs_commit(world, e, r, r->table, NULL, &r->table->type); cleared = true; } ecs_commit(world, e, r, table, &table->type, NULL); if (cleared) { char *entity_name = strrchr(name, '.'); if (entity_name) { entity_name ++; } else { entity_name = name; } if (!flecs_json_name_is_anonymous_legacy(entity_name)) { ecs_set_name(world, e, entity_name); } } } if (name != name_token) { ecs_os_free(name); } if (table != r->table) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid entity identifier"); goto error; } else { ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); ecs_record_t** elem = ecs_vec_append_t( ctx->a, &ctx->records, ecs_record_t*); *elem = r; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while(json[0]); return 0; error: return -1; } static const char* flecs_json_missing_reflection_legacy( ecs_world_t *world, ecs_id_t id, const char *json, char *token, ecs_from_json_ctx_legacy_t *ctx, const ecs_from_json_desc_t *desc) { if (!desc->strict && ecs_map_get(&ctx->missing_reflection, id)) { json = flecs_json_skip_array(json, token, desc); goto done; } /* Don't spam log when multiple values of a type can't be deserialized */ ecs_map_ensure(&ctx->missing_reflection, id); char *id_str = ecs_id_str(world, id); ecs_parser_error(desc->name, desc->expr, json - desc->expr, "missing reflection for '%s'", id_str); ecs_os_free(id_str); done: if (desc->strict) { return NULL; } else { return flecs_json_skip_array(json, token, desc); } } static const char* flecs_json_parse_column_legacy( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, const char *json, char *token, ecs_from_json_ctx_legacy_t *ctx, const ecs_from_json_desc_t *desc) { /* If deserializing id caused trouble before, don't bother trying again */ if (!desc->strict && ecs_map_get(&ctx->missing_reflection, id)) { return flecs_json_skip_array(json, token, desc); } ecs_table_record_t *tr = flecs_table_record_get(world, table, id); /* Table was created with this id, it should exist */ ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* If id is not a component, reflection data is missing */ int32_t column_index = tr->column; if (column_index == -1) { return flecs_json_missing_reflection_legacy(world, id, json, token, ctx, desc); } ecs_json_token_t token_kind = 0; ecs_column_t *column = &table->data.columns[column_index]; ecs_type_info_t *ti = column->ti; ecs_size_t size = ti->size; ecs_entity_t type = ti->component; ecs_record_t **record_array = ecs_vec_first_t(&ctx->records, ecs_record_t*); int32_t entity = 0; bool values_set = false; const char *values_start = json; do { ecs_record_t *r = record_array[entity]; int32_t row = ECS_RECORD_TO_ROW(r->row); void *ptr = ecs_vec_get(&column->data, size, row); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); json = ecs_ptr_from_json(world, type, ptr, json, desc); if (!json) { if (desc->strict) { break; } else { return flecs_json_missing_reflection_legacy( world, id, values_start, token, ctx, desc); } } else { values_set = true; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); } entity ++; } while (json[0]); if (values_set) { ecs_id_t *id_set = ecs_vec_append_t( ctx->a, &ctx->columns_set, ecs_id_t); *id_set = id; } return json; } static const char* flecs_json_parse_values_legacy( ecs_world_t *world, ecs_table_t *table, const char *json, char *token, ecs_from_json_ctx_legacy_t *ctx, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; int32_t value = 0; ecs_vec_clear(&ctx->columns_set); do { if (value >= table->type.count) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "more elements in values array than expected for table"); goto error; } json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } if (token_kind == JsonArrayClose) { break; } else if (token_kind == JsonArrayOpen) { ecs_id_t *idptr = ecs_vec_get_t(&ctx->result_ids, ecs_id_t, value); ecs_assert(idptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t id = idptr[0]; json = flecs_json_parse_column_legacy(world, table, id, json, token, ctx, desc); if (!json) { goto error; } } else if (token_kind == JsonNumber) { if (!ecs_os_strcmp(token, "0")) { /* no data */ } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "unexpected number"); goto error; } } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } value ++; } while (json[0]); /* Send OnSet notifications */ ecs_defer_begin(world); ecs_type_t type = { .array = ctx->columns_set.array, .count = ctx->columns_set.count }; int32_t table_count = ecs_table_count(table); int32_t i, record_count = ecs_vec_count(&ctx->records); /* If the entire table was inserted, send bulk notification */ if (table_count == ecs_vec_count(&ctx->records)) { flecs_notify_on_set( world, table, 0, ecs_table_count(table), &type, true); } else { ecs_record_t **rvec = ecs_vec_first_t(&ctx->records, ecs_record_t*); for (i = 0; i < record_count; i ++) { ecs_record_t *r = rvec[i]; int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_notify_on_set(world, table, row, 1, &type, true); } } ecs_defer_end(world); return json; error: return NULL; } static const char* flecs_json_parse_result_legacy( ecs_world_t *world, const char *json, char *token, ecs_from_json_ctx_legacy_t *ctx, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; const char *ids = NULL, *values = NULL, *entities = NULL; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "ids", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } ids = json; /* store start of ids array */ json = flecs_json_skip_array(json, token, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } ecs_entity_t parent = 0; if (!ecs_os_strcmp(token, "parent")) { char *parent_name = NULL; json = flecs_json_expect_string(json, token, &parent_name, desc); if (!json) { goto error; } parent = flecs_json_lookup_legacy(world, 0, parent_name, desc); if (parent_name != token) { ecs_os_free(parent_name); } json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } } if (ecs_os_strcmp(token, "entities")) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected 'entities'"); goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } entities = json; /* store start of entity id array */ json = flecs_json_skip_array(json, token, desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = flecs_json_expect_member_name(json, token, "values", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } values = json; /* store start of entities array */ } else if (token_kind != JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or '}'"); goto error; } /* Find table from ids */ ecs_table_t *table = flecs_json_parse_table_legacy(world, ids, token, ctx, desc); if (!table) { goto error; } /* Add entities to table */ if (flecs_json_parse_entities_legacy(world, table, parent, entities, token, ctx, desc)) { goto error; } /* If not parsing in strict mode, initialize component arrays to 0. This * ensures that even if components can't be deserialized (because of * incomplete reflection data) component values aren't left uninitialized */ if (!desc->strict) { flecs_json_zeromem_table_legacy(table); } /* Parse values */ if (values) { json = flecs_json_parse_values_legacy(world, table, values, token, ctx, desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = flecs_json_expect_member_name(json, token, "alerts", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonBoolean, token, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonObjectClose, token, desc); if (!json) { goto error; } } else if (token_kind != JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected '}'"); goto error; } } return json; error: return NULL; } const char* ecs_world_from_json_legacy( ecs_world_t *world, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; ecs_allocator_t *a = &world->allocator; ecs_from_json_ctx_legacy_t ctx; flecs_from_json_ctx_init_legacy(a, &ctx); const char *name = NULL, *expr = json, *lah; if (desc_arg) { desc = *desc_arg; } if (!desc.lookup_action) { desc.lookup_action = (ecs_entity_t(*)( const ecs_world_t*, const char*, void*))flecs_json_ensure_entity_legacy; desc.lookup_ctx = &ctx.anonymous_ids; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "results", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { json = lah; goto end; } do { json = flecs_json_parse_result_legacy(world, json, token, &ctx, &desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "expected ',' or ']'"); goto error; } } while(json && json[0]); end: flecs_from_json_ctx_fini_legacy(&ctx); json = flecs_json_expect(json, JsonObjectClose, token, &desc); if (!json) { goto error; } return json; error: flecs_from_json_ctx_fini_legacy(&ctx); return NULL; } const char* ecs_world_from_json_file_legacy( ecs_world_t *world, const char *filename, const ecs_from_json_desc_t *desc) { char *json = flecs_load_from_file(filename); if (!json) { ecs_err("file not found: %s", filename); return NULL; } const char *result = ecs_world_from_json_legacy(world, json, desc); ecs_os_free(json); return result; } #endif /** * @file addons/json/deserialize_value.c * @brief Deserialize JSON strings into (component) values. */ #include #ifdef FLECS_JSON const char* ecs_ptr_from_json( const ecs_world_t *world, ecs_entity_t type, void *ptr, const char *json, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; char *token = token_buffer; int depth = 0; const char *name = NULL; const char *expr = NULL; ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); if (cur.valid == false) { return NULL; } if (desc) { name = desc->name; expr = desc->expr; cur.lookup_action = desc->lookup_action; cur.lookup_ctx = desc->lookup_ctx; } while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } token = ecs_strbuf_get(&large_token); token_kind = JsonString; } if (token_kind == JsonObjectOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '['"); return NULL; } } else if (token_kind == JsonObjectClose) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonArrayOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '{'"); return NULL; } } else if (token_kind == JsonArrayClose) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonComma) { if (ecs_meta_next(&cur) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonString) { const char *lah = flecs_json_parse( json, &token_kind, t_lah); if (token_kind == JsonColon) { /* Member assignment */ json = lah; if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } } else if (token_kind == JsonNumber) { double number = atof(token); if (ecs_meta_set_float(&cur, number) != 0) { goto error; } } else if (token_kind == JsonLargeInt) { int64_t number = flecs_ito(int64_t, atoll(token)); if (ecs_meta_set_int(&cur, number) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonTrue) { if (ecs_meta_set_bool(&cur, true) != 0) { goto error; } } else if (token_kind == JsonFalse) { if (ecs_meta_set_bool(&cur, false) != 0) { goto error; } } else { goto error; } if (token != token_buffer) { ecs_os_free(token); token = token_buffer; } if (!depth) { break; } } return json; error: return NULL; } #endif /** * @file addons/json/json.c * @brief JSON serializer utilities. */ #include #ifdef FLECS_JSON static const char* flecs_json_token_str( ecs_json_token_t token_kind) { switch(token_kind) { case JsonObjectOpen: return "{"; case JsonObjectClose: return "}"; case JsonArrayOpen: return "["; case JsonArrayClose: return "]"; case JsonColon: return ":"; case JsonComma: return ","; case JsonNumber: return "number"; case JsonLargeInt: return "large integer"; case JsonLargeString: case JsonString: return "string"; case JsonBoolean: return "bool"; case JsonTrue: return "true"; case JsonFalse: return "false"; case JsonNull: return "null"; case JsonInvalid: return "invalid"; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return "<>"; } const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); json = flecs_parse_ws_eol(json); char ch = json[0]; if (ch == '{') { token_kind[0] = JsonObjectOpen; return json + 1; } else if (ch == '}') { token_kind[0] = JsonObjectClose; return json + 1; } else if (ch == '[') { token_kind[0] = JsonArrayOpen; return json + 1; } else if (ch == ']') { token_kind[0] = JsonArrayClose; return json + 1; } else if (ch == ':') { token_kind[0] = JsonColon; return json + 1; } else if (ch == ',') { token_kind[0] = JsonComma; return json + 1; } else if (ch == '"') { const char *start = json; char *token_ptr = token; json ++; for (; (ch = json[0]); ) { if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { /* Token doesn't fit in buffer, signal to app to try again with * dynamic buffer. */ token_kind[0] = JsonLargeString; return start; } if (ch == '"') { json ++; token_ptr[0] = '\0'; break; } json = flecs_chrparse(json, token_ptr ++); } if (!ch) { token_kind[0] = JsonInvalid; return NULL; } else { token_kind[0] = JsonString; return json; } } else if (isdigit(ch) || (ch == '-')) { token_kind[0] = JsonNumber; const char *result = flecs_parse_digit(json, token); /* Cheap initial check if parsed token could represent large int */ if (result - json > 15) { /* Less cheap secondary check to see if number is integer */ if (!strchr(token, '.')) { token_kind[0] = JsonLargeInt; } } return result; } else if (isalpha(ch)) { if (!ecs_os_strncmp(json, "null", 4)) { token_kind[0] = JsonNull; json += 4; } else if (!ecs_os_strncmp(json, "true", 4)) { token_kind[0] = JsonTrue; json += 4; } else if (!ecs_os_strncmp(json, "false", 5)) { token_kind[0] = JsonFalse; json += 5; } if (isalpha(json[0]) || isdigit(json[0])) { token_kind[0] = JsonInvalid; return NULL; } return json; } else { token_kind[0] = JsonInvalid; return NULL; } } const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf) { if (json[0] != '"') { return NULL; /* can only parse strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = flecs_chrparse(json, &ch_out); ecs_strbuf_appendch(buf, ch_out); } if (!ch) { return NULL; } else { return json; } } const char* flecs_json_parse_next_member( const char *json, char *token, ecs_json_token_t *token_kind, const ecs_from_json_desc_t *desc) { json = flecs_json_parse(json, token_kind, token); if (*token_kind == JsonObjectClose) { return json; } if (*token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expecteded } or ,"); return NULL; } json = flecs_json_parse(json, token_kind, token); if (*token_kind != JsonString) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expecteded member name"); return NULL; } char temp_token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t temp_token_kind; json = flecs_json_parse(json, &temp_token_kind, temp_token); if (temp_token_kind != JsonColon) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expecteded :"); return NULL; } return json; } const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc) { /* Strings must be handled by flecs_json_expect_string for LargeString */ ecs_assert(token_kind != JsonString, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t kind = 0; const char *lah = flecs_json_parse(json, &kind, token); if (kind == JsonInvalid) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (kind != token_kind) { if (token_kind == JsonBoolean && (kind == JsonTrue || kind == JsonFalse)) { /* ok */ } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s, got %s", flecs_json_token_str(token_kind), flecs_json_token_str(kind)); return NULL; } } return lah; } const char* flecs_json_expect_string( const char *json, char *token, char **out, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonInvalid) { ecs_parser_error( desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (token_kind != JsonString && token_kind != JsonLargeString) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected string"); return NULL; } if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { return NULL; } if (out) { *out = ecs_strbuf_get(&large_token); } else { ecs_strbuf_reset(&large_token); } } else if (out) { *out = token; } return json; } const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc) { char *out = NULL; json = flecs_json_expect_string(json, token, &out, desc); if (!json) { return NULL; } if (out != token) { ecs_os_free(out); } json = flecs_json_expect(json, JsonColon, token, desc); if (!json) { return NULL; } return json; } const char* flecs_json_expect_next_member( const char *json, char *token, const ecs_from_json_desc_t *desc) { json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { return NULL; } return flecs_json_expect_member(json, token, desc); } const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc) { json = flecs_json_expect_member(json, token, desc); if (!json) { return NULL; } if (ecs_os_strcmp(token, member_name)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected member '%s'", member_name); return NULL; } return json; } static const char* flecs_json_skip_string( const char *json) { if (json[0] != '"') { return NULL; /* can only skip strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = flecs_chrparse(json, &ch_out); } if (!ch) { return NULL; } else { return json; } } const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_object(json, token, desc); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_array(json, token, desc); } else if (token_kind == JsonLargeString) { json = flecs_json_skip_string(json); } else if (token_kind == JsonObjectClose) { return json; } else if (token_kind == JsonArrayClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }, got ]"); return NULL; } ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_parser_error(desc->name, json, 0, "expected }, got end of string"); return NULL; } const char* flecs_json_skip_array( const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_object(json, token, desc); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_array(json, token, desc); } else if (token_kind == JsonLargeString) { json = flecs_json_skip_string(json); } else if (token_kind == JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); return NULL; } else if (token_kind == JsonArrayClose) { return json; } ecs_assert(json != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); return NULL; } void flecs_json_next( ecs_strbuf_t *buf) { ecs_strbuf_list_next(buf); } void flecs_json_number( ecs_strbuf_t *buf, double value) { ecs_strbuf_appendflt(buf, value, '"'); } void flecs_json_u32( ecs_strbuf_t *buf, uint32_t value) { ecs_strbuf_appendint(buf, flecs_uto(int64_t, value)); } void flecs_json_true( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "true"); } void flecs_json_false( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "false"); } void flecs_json_bool( ecs_strbuf_t *buf, bool value) { if (value) { flecs_json_true(buf); } else { flecs_json_false(buf); } } void flecs_json_null( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "null"); } void flecs_json_array_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "[", ", "); } void flecs_json_array_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "]"); } void flecs_json_object_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "{", ", "); } void flecs_json_object_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "}"); } void flecs_json_string( ecs_strbuf_t *buf, const char *value) { 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 = flecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, value, length); ecs_strbuf_appendch(buf, '"'); } else { char *out = ecs_os_malloc(length + 3); flecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr(buf, out); ecs_os_free(out); } } void flecs_json_member( ecs_strbuf_t *buf, const char *name) { flecs_json_membern(buf, name, ecs_os_strlen(name)); } void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len) { ecs_strbuf_list_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, name, name_len); ecs_strbuf_appendlit(buf, "\":"); } void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } static const char* flecs_json_entity_label( const ecs_world_t *world, ecs_entity_t e) { const char *lbl = NULL; if (!e) { return "#0"; } #ifdef FLECS_DOC lbl = ecs_doc_get_name(world, e); #else lbl = ecs_get_name(world, e); #endif return lbl; } void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { const char *lbl = flecs_json_entity_label(world, e); if (lbl) { flecs_json_string_escape(buf, lbl); } else { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, (uint32_t)e); ecs_strbuf_appendch(buf, '"'); } } void flecs_json_path_or_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e, bool path) { if (!path) { flecs_json_label(buf, world, e); } else { flecs_json_path(buf, world, e); } } void flecs_json_color( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { (void)world; (void)e; const char *color = NULL; #ifdef FLECS_DOC color = ecs_doc_get_color(world, e); #endif if (color) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, color); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '0'); } } void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_appendch(buf, '['); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } ecs_strbuf_appendch(buf, ']'); } static void flecs_json_id_member_fullpath( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); } } void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id, bool fullpath) { if (fullpath) { flecs_json_id_member_fullpath(buf, world, id); return; } if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); { const char *lbl = flecs_json_entity_label(world, first); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ','); { const char *lbl = flecs_json_entity_label(world, second); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ')'); } else { const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } } ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind) { return kind - EcsOpPrimitive; } #endif /** * @file addons/json/serialize_entity.c * @brief Serialize single entity. */ /** * @file addons/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( EcsTypeSerializer *ptr); ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind( ecs_primitive_kind_t kind); bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data); void flecs_meta_import_definitions( ecs_world_t *world); int flecs_expr_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str, bool is_expr); #endif #endif #ifdef FLECS_JSON int ecs_entity_to_json_buf( const ecs_world_t *stage, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_entity_to_json_desc_t *desc) { const ecs_world_t *world = ecs_get_world(stage); if (!entity || !ecs_is_valid(world, entity)) { return -1; } /* Cache id record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC ser_ctx.idr_doc_name = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsName)); ser_ctx.idr_doc_color = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif ecs_record_t *r = ecs_record_find(world, entity); if (!r || !r->table) { flecs_json_object_push(buf); flecs_json_member(buf, "name"); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, (uint32_t)entity); ecs_strbuf_appendch(buf, '"'); flecs_json_object_pop(buf); return 0; } /* Create iterator that's populated just with entity */ int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_iter_t it = { .world = ECS_CONST_CAST(ecs_world_t*, world), .real_world = ECS_CONST_CAST(ecs_world_t*, world), .table = r->table, .offset = row, .count = 1, .entities = &flecs_table_entities_array(r->table)[row], .field_count = 0 }; /* Initialize iterator parameters */ ecs_iter_to_json_desc_t iter_desc = { .serialize_table = true, .serialize_entity_ids = desc ? desc->serialize_entity_id : false, .serialize_values = desc ? desc->serialize_values : true, .serialize_builtin = desc ? desc->serialize_builtin : false, .serialize_doc = desc ? desc->serialize_doc : false, .serialize_matches = desc ? desc->serialize_matches : false, .serialize_refs = desc ? desc->serialize_refs : 0, .serialize_alerts = desc ? desc->serialize_alerts : false, .serialize_full_paths = desc ? desc->serialize_full_paths : true, .serialize_inherited = desc ? desc->serialize_inherited : false, .serialize_type_info = desc ? desc->serialize_type_info : false }; if (flecs_json_serialize_iter_result( world, &it, buf, &iter_desc, &ser_ctx)) { return -1; } return 0; } 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); } #endif /** * @file addons/json/serialize_field_info.c * @brief Serialize query field information to JSON. */ #ifdef FLECS_JSON static bool flecs_json_serialize_get_field_ctx( const ecs_world_t *world, const ecs_iter_t *it, int32_t f, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc) { ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; if (it->query) { return flecs_json_serialize_get_value_ctx( world, it->query->ids[f], value_ctx, desc); } else if (it->ids[f]) { return flecs_json_serialize_get_value_ctx( world, it->ids[f], value_ctx, desc); } else { return false; } } void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, const ecs_query_t *q, int field, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx) { flecs_json_object_push(buf); flecs_json_memberl(buf, "id"); flecs_json_serialize_get_field_ctx(world, it, field, ctx, NULL); ecs_json_value_ser_ctx_t *value_ctx = &ctx->value_ctx[field]; if (value_ctx->id_label) { flecs_json_string(buf, value_ctx->id_label); const ecs_term_t *term = &q->terms[0]; int t; for (t = 0; t < q->term_count; t ++) { if (q->terms[t].field_index == field) { term = &q->terms[t]; break; } } if (term->oper != EcsNot) { if (term->oper == EcsOptional) { flecs_json_memberl(buf, "optional"); flecs_json_bool(buf, true); } if (ECS_IS_PAIR(term->id)) { if ((term->first.id & EcsIsEntity) && ECS_TERM_REF_ID(&term->first)) { if (ecs_has_id(world, ECS_TERM_REF_ID(&term->first), EcsExclusive)) { flecs_json_memberl(buf, "exclusive"); flecs_json_bool(buf, true); } } } if (value_ctx->type) { flecs_json_memberl(buf, "type"); flecs_json_label(buf, world, value_ctx->type); const char *symbol = ecs_get_symbol(world, value_ctx->type); if (symbol) { flecs_json_memberl(buf, "symbol"); flecs_json_string(buf, symbol); } } if (value_ctx->ser) { flecs_json_memberl(buf, "schema"); ecs_type_info_to_json_buf(world, value_ctx->type, buf); } } else { flecs_json_memberl(buf, "not"); flecs_json_bool(buf, true); } } else { ecs_strbuf_appendlit(buf, "0"); } flecs_json_object_pop(buf); } #endif /** * @file addons/json/serialize_iter.c * @brief Serialize iterator to JSON. */ #ifdef FLECS_JSON static void flecs_json_serialize_id_str( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_strbuf_appendch(buf, '"'); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_first(world, id); ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf( world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); } ecs_strbuf_appendch(buf, '"'); } static void flecs_json_serialize_type_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "type_info"); flecs_json_object_push(buf); int32_t field_count = it->field_count; if (!field_count) { goto done; } if (it->flags & EcsIterNoData) { goto done; } for (int i = 0; i < field_count; i ++) { flecs_json_next(buf); ecs_entity_t typeid = 0; if (it->query->terms[i].inout != EcsInOutNone) { typeid = ecs_get_typeid(world, it->query->terms[i].id); } if (typeid) { flecs_json_serialize_id_str(world, typeid, buf); ecs_strbuf_appendch(buf, ':'); ecs_type_info_to_json_buf(world, typeid, buf); } else { flecs_json_serialize_id_str(world, it->query->terms[i].id, buf); ecs_strbuf_appendlit(buf, ":0"); } } done: flecs_json_object_pop(buf); } static void flecs_json_serialize_field_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ctx) { int32_t field_count = it->field_count; if (!field_count || !it->query) { return; } const ecs_query_t *q = it->query; flecs_json_memberl(buf, "field_info"); flecs_json_array_push(buf); int f; for (f = 0; f < field_count; f ++) { flecs_json_next(buf); flecs_json_serialize_field(world, it, q, f, buf, ctx); } flecs_json_array_pop(buf); } static void flecs_json_serialize_query_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->query) { return; } const ecs_query_t *q = it->query; flecs_json_memberl(buf, "query_info"); flecs_json_serialize_query(world, q, buf); } static void flecs_json_serialize_query_plan( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { (void)world; (void)buf; (void)desc; if (!desc->query) { return; } const ecs_query_t *q = desc->query; flecs_poly_assert(q, ecs_query_t); flecs_json_memberl(buf, "query_plan"); bool prev_color = ecs_log_enable_colors(true); char *plan = ecs_query_plan(q); flecs_json_string_escape(buf, plan); ecs_os_free(plan); ecs_log_enable_colors(prev_color); } static void flecs_json_serialize_query_profile( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { if (!desc->query) { return; } ecs_time_t t = {0}; int32_t result_count = 0, entity_count = 0, i, sample_count = 100; ecs_size_t component_bytes = 0, shared_component_bytes = 0; double eval_time = 0, eval_min = 0, eval_max = 0; ecs_time_measure(&t); for (i = 0; i < sample_count; i ++) { result_count = 0; entity_count = 0; component_bytes = 0; shared_component_bytes = 0; ecs_iter_t qit = ecs_query_iter(world, desc->query); qit.flags |= EcsIterIsInstanced; while (ecs_iter_next(&qit)) { result_count ++; entity_count += qit.count; int32_t f, field_count = qit.field_count; for (f = 0; f < field_count; f ++) { size_t size = ecs_field_size(&qit, f); if (ecs_field_is_set(&qit, f) && size) { if (ecs_field_is_self(&qit, f)) { component_bytes += flecs_uto(ecs_size_t, size) * qit.count; } else { shared_component_bytes += flecs_uto(ecs_size_t, size); } } } } double time_measure = ecs_time_measure(&t); if (!i) { eval_min = time_measure; } else if (time_measure < eval_min) { eval_min = time_measure; } if (time_measure > eval_max) { eval_max = time_measure; } eval_time += time_measure; /* Don't profile for too long */ if (eval_time > 0.001) { i ++; break; } } eval_time /= i; flecs_json_memberl(buf, "query_profile"); flecs_json_object_push(buf); if (it->query) { /* Correct for profiler */ ECS_CONST_CAST(ecs_query_t*, it->query)->eval_count -= i; flecs_json_memberl(buf, "eval_count"); flecs_json_number(buf, it->query->eval_count); } flecs_json_memberl(buf, "result_count"); flecs_json_number(buf, result_count); flecs_json_memberl(buf, "entity_count"); flecs_json_number(buf, entity_count); flecs_json_memberl(buf, "eval_time_avg_us"); flecs_json_number(buf, eval_time * 1000.0 * 1000.0); flecs_json_memberl(buf, "eval_time_min_us"); flecs_json_number(buf, eval_min * 1000.0 * 1000.0); flecs_json_memberl(buf, "eval_time_max_us"); flecs_json_number(buf, eval_max * 1000.0 * 1000.0); flecs_json_memberl(buf, "component_bytes"); flecs_json_number(buf, component_bytes); flecs_json_memberl(buf, "shared_component_bytes"); flecs_json_number(buf, shared_component_bytes); flecs_json_object_pop(buf); } static void flecs_iter_free_ser_ctx( ecs_iter_t *it, ecs_json_ser_ctx_t *ser_ctx) { int32_t f, field_count = it->field_count; for (f = 0; f < field_count; f ++) { ecs_os_free(ser_ctx->value_ctx[f].id_label); } } int ecs_iter_to_json_buf( ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_world_t *world = it->real_world; /* Cache id record for flecs.doc ids */ ecs_json_ser_ctx_t ser_ctx; ecs_os_zeromem(&ser_ctx); #ifdef FLECS_DOC ser_ctx.idr_doc_name = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsName)); ser_ctx.idr_doc_color = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif flecs_json_object_push(buf); /* Serialize type info if enabled */ if (desc && desc->serialize_type_info) { flecs_json_serialize_type_info(world, it, buf); } /* Serialize field info if enabled */ if (desc && desc->serialize_field_info) { flecs_json_serialize_field_info(world, it, buf, &ser_ctx); } /* Serialize query info if enabled */ if (desc && desc->serialize_query_info) { flecs_json_serialize_query_info(world, it, buf); } /* Serialize query plan if enabled */ if (desc && desc->serialize_query_plan) { flecs_json_serialize_query_plan(world, buf, desc); } /* Profile query */ if (desc && desc->serialize_query_profile) { flecs_json_serialize_query_profile(world, buf, it, desc); } /* Serialize results */ if (!desc || !desc->dont_serialize_results) { flecs_json_memberl(buf, "results"); flecs_json_array_push(buf); /* Use instancing for improved performance */ ECS_BIT_SET(it->flags, EcsIterIsInstanced); /* If serializing entire table, don't bother letting the iterator populate * data fields as we'll be iterating all columns. */ if (desc && desc->serialize_table) { ECS_BIT_SET(it->flags, EcsIterNoData); } ecs_iter_next_action_t next = it->next; while (next(it)) { if (flecs_json_serialize_iter_result(world, it, buf, desc, &ser_ctx)) { ecs_strbuf_reset(buf); flecs_iter_free_ser_ctx(it, &ser_ctx); ecs_iter_fini(it); return -1; } } flecs_json_array_pop(buf); } else { ecs_iter_fini(it); } flecs_iter_free_ser_ctx(it, &ser_ctx); flecs_json_object_pop(buf); return 0; } char* ecs_iter_to_json( ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_iter_to_json_buf(it, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif /** * @file addons/json/serialize_iter_rows.c * @brief Serialize (component) values to JSON strings. */ #ifdef FLECS_JSON static bool flecs_json_skip_variable( const char *name) { if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { return true; } else { return false; } } bool flecs_json_serialize_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { char **variable_names = it->variable_names; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 1; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; ecs_entity_t var = it->variables[i].entity; if (!var) { /* Can't happen, but not the place of the serializer to complain */ continue; } if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_object_push(buf); actual_count ++; } flecs_json_member(buf, var_name); flecs_json_path_or_label(buf, world, var, desc ? desc->serialize_full_paths : true); } if (actual_count) { flecs_json_object_pop(buf); } return actual_count != 0; } int flecs_json_serialize_matches( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { flecs_json_memberl(buf, "matches"); flecs_json_array_push(buf); ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair_t(EcsPoly, EcsQuery)); if (idr) { ecs_table_cache_iter_t it; if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (i = 0; i < count; i ++) { ecs_query_t *q = queries[i].poly; if (!q) { continue; } ecs_assert(flecs_poly_is(q, ecs_query_t), ECS_INTERNAL_ERROR, NULL); if (!(q->flags & EcsQueryMatchThis)) { continue; } ecs_iter_t qit = ecs_query_iter(world, q); if (!qit.variables) { ecs_iter_fini(&qit); continue; } ecs_iter_set_var(&qit, 0, entity); if (ecs_iter_is_true(&qit)) { flecs_json_next(buf); flecs_json_path(buf, world, entities[i]); } } } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_serialize_refs_idr( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_id_record_t *idr) { char *id_str = ecs_id_str(world, ecs_pair_first(world, idr->id)); flecs_json_member(buf, id_str); ecs_os_free(id_str); flecs_json_array_push(buf); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; flecs_json_next(buf); flecs_json_path(buf, world, e); } } } flecs_json_array_pop(buf); return 0; } int flecs_json_serialize_refs( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, ecs_entity_t relationship) { flecs_json_memberl(buf, "refs"); flecs_json_object_push(buf); ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(relationship, entity)); if (idr) { if (relationship == EcsWildcard) { ecs_id_record_t *cur = idr; while ((cur = cur->second.next)) { flecs_json_serialize_refs_idr(world, buf, cur); } } else { flecs_json_serialize_refs_idr(world, buf, idr); } } flecs_json_object_pop(buf); return 0; } #ifdef FLECS_ALERTS static int flecs_json_serialize_entity_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, const EcsAlertsActive *alerts, bool self) { ecs_assert(alerts != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_iter_t it = ecs_map_iter(&alerts->alerts); while (ecs_map_next(&it)) { flecs_json_next(buf); flecs_json_object_push(buf); ecs_entity_t ai = ecs_map_value(&it); char *alert_name = ecs_get_path(world, ai); flecs_json_memberl(buf, "alert"); flecs_json_string(buf, alert_name); ecs_os_free(alert_name); ecs_entity_t severity_id = ecs_get_target( world, ai, ecs_id(EcsAlert), 0); const char *severity = ecs_get_name(world, severity_id); const EcsAlertInstance *alert = ecs_get( world, ai, EcsAlertInstance); if (alert) { if (alert->message) { flecs_json_memberl(buf, "message"); flecs_json_string(buf, alert->message); } flecs_json_memberl(buf, "severity"); flecs_json_string(buf, severity); if (!self) { char *path = ecs_get_path(world, entity); flecs_json_memberl(buf, "path"); flecs_json_string(buf, path); ecs_os_free(path); } } flecs_json_object_pop(buf); } return 0; } static int flecs_json_serialize_children_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { ecs_query_t *q = ecs_query(ECS_CONST_CAST(ecs_world_t*, world), { .terms = {{ .id = ecs_pair(EcsChildOf, entity) }} }); ecs_iter_t it = ecs_query_iter(world, q); while (ecs_query_next(&it)) { EcsAlertsActive *alerts = ecs_table_get_id( world, it.table, ecs_id(EcsAlertsActive), it.offset); int32_t i; for (i = 0; i < it.count; i ++) { ecs_entity_t child = it.entities[i]; if (alerts) { if (flecs_json_serialize_entity_alerts( world, buf, child, &alerts[i], false)) { goto error; } } ecs_record_t *r = flecs_entities_get(world, it.entities[i]); if (r->row & EcsEntityIsTraversable) { if (flecs_json_serialize_children_alerts( world, buf, child)) { goto error; } } } } ecs_query_fini(q); return 0; error: return -1; } #endif int flecs_json_serialize_alerts( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity) { (void)world; (void)buf; (void)entity; #ifdef FLECS_ALERTS if (!ecs_id(EcsAlertsActive)) { return 0; /* Alert module not imported */ } flecs_json_memberl(buf, "alerts"); flecs_json_array_push(buf); const EcsAlertsActive *alerts = ecs_get(world, entity, EcsAlertsActive); if (alerts) { flecs_json_serialize_entity_alerts(world, buf, entity, alerts, true); } flecs_json_serialize_children_alerts(world, buf, entity); flecs_json_array_pop(buf); #endif return 0; } bool flecs_json_serialize_get_value_ctx( const ecs_world_t *world, ecs_id_t id, ecs_json_value_ser_ctx_t *ctx, const ecs_iter_to_json_desc_t *desc) { if (!id) { return false; } if (!ctx->initialized) { ctx->initialized = true; ecs_strbuf_t idlbl = ECS_STRBUF_INIT; flecs_json_id_member(&idlbl, world, id, desc ? desc->serialize_full_paths : true); ctx->id_label = ecs_strbuf_get(&idlbl); ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { return false; } ctx->type = type; ctx->ser = ecs_get(world, type, EcsTypeSerializer); if (!ctx->ser) { return false; } return true; } else { return ctx->ser != NULL; } } void flecs_json_serialize_iter_this( const ecs_iter_t *it, const char *parent_path, const ecs_json_this_data_t *this_data, int32_t row, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_assert(row < it->count, ECS_INTERNAL_ERROR, NULL); if (parent_path) { flecs_json_memberl(buf, "parent"); flecs_json_string(buf, parent_path); } flecs_json_memberl(buf, "name"); if (this_data->names) { flecs_json_string(buf, this_data->names[row].value); } else { ecs_strbuf_appendlit(buf, "\"#"); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)it->entities[row])); ecs_strbuf_appendlit(buf, "\""); } if (desc && desc->serialize_entity_ids) { flecs_json_memberl(buf, "id"); flecs_json_u32(buf, (uint32_t)this_data->ids[row]); } #ifdef FLECS_DOC if (desc && desc->serialize_doc) { flecs_json_memberl(buf, "doc"); flecs_json_object_push(buf); if (this_data->label) { flecs_json_memberl(buf, "label"); flecs_json_string_escape(buf, this_data->label[row].value); } else { flecs_json_memberl(buf, "label"); if (this_data->names) { flecs_json_string(buf, this_data->names[row].value); } else { ecs_strbuf_appendlit(buf, "\"#"); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)it->entities[row])); ecs_strbuf_appendlit(buf, "\""); } } if (this_data->brief) { flecs_json_memberl(buf, "brief"); flecs_json_string_escape(buf, this_data->brief[row].value); } if (this_data->detail) { flecs_json_memberl(buf, "detail"); flecs_json_string_escape(buf, this_data->detail[row].value); } if (this_data->color) { flecs_json_memberl(buf, "color"); flecs_json_string_escape(buf, this_data->color[row].value); } if (this_data->link) { flecs_json_memberl(buf, "link"); flecs_json_string_escape(buf, this_data->link[row].value); } flecs_json_object_pop(buf); } #endif if (this_data->has_alerts) { flecs_json_memberl(buf, "alerts"); flecs_json_true(buf); } } int flecs_json_serialize_iter_result( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { char *parent_path = NULL; ecs_json_this_data_t this_data = {0}; int32_t count = it->count; bool has_this = true; if (!count) { count = 1; /* Query without this variable */ has_this = false; } else { ecs_table_t *table = it->table; if (table) { this_data.ids = &flecs_table_entities_array(table)[it->offset]; /* Get path to parent once for entire table */ if (table->flags & EcsTableHasChildOf) { const ecs_table_record_t *tr = flecs_table_record_get( world, table, ecs_pair(EcsChildOf, EcsWildcard)); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t parent = ecs_pair_second( world, table->type.array[tr->index]); parent_path = ecs_get_path_w_sep(world, 0, parent, ".", ""); } /* Fetch name column once vs. calling ecs_get_name for each row */ if (table->flags & EcsTableHasName) { this_data.names = ecs_table_get_id(it->world, it->table, ecs_pair_t(EcsIdentifier, EcsName), it->offset); } /* Get entity labels */ #ifdef FLECS_DOC if (desc && desc->serialize_doc) { this_data.label = ecs_table_get_id(it->world, it->table, ecs_pair_t(EcsDocDescription, EcsName), it->offset); this_data.brief = ecs_table_get_id(it->world, it->table, ecs_pair_t(EcsDocDescription, EcsDocBrief), it->offset); this_data.detail = ecs_table_get_id(it->world, it->table, ecs_pair_t(EcsDocDescription, EcsDocDetail), it->offset); this_data.color = ecs_table_get_id(it->world, it->table, ecs_pair_t(EcsDocDescription, EcsDocColor), it->offset); this_data.link = ecs_table_get_id(it->world, it->table, ecs_pair_t(EcsDocDescription, EcsDocLink), it->offset); } #endif #ifdef FLECS_ALERTS if (it->table && (ecs_id(EcsAlertsActive) != 0)) { /* Only add field if alerts addon is imported */ if (ecs_table_has_id(world, table, ecs_id(EcsAlertsActive))) { this_data.has_alerts = true; } } #endif } else { /* Very rare case, but could happen if someone's using an iterator * to return empty entities. */ } } if (desc && desc->serialize_table) { if (flecs_json_serialize_iter_result_table(world, it, buf, desc, count, has_this, parent_path, &this_data)) { goto error; } } else { if (flecs_json_serialize_iter_result_query(world, it, buf, ser_ctx, desc, count, has_this, parent_path, &this_data)) { goto error; } } ecs_os_free(parent_path); return 0; error: ecs_os_free(parent_path); return -1; } #endif /** * @file addons/json/serialize_iter_result_query.c * @brief Serialize matched query data of result. */ #ifdef FLECS_JSON static bool flecs_json_serialize_iter_result_is_set( const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!(it->flags & EcsIterHasCondSet)) { return false; } flecs_json_memberl(buf, "is_set"); flecs_json_array_push(buf); int32_t i, count = it->field_count; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(buf); if (ecs_field_is_set(it, i)) { flecs_json_true(buf); } else { flecs_json_false(buf); } } flecs_json_array_pop(buf); return true; } static bool flecs_json_serialize_iter_result_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { const ecs_query_t *q = it->query; if (!q) { return false; } ecs_world_t *world = it->world; int16_t f, field_count = flecs_ito(int16_t, it->field_count); uint16_t field_mask = flecs_ito(uint16_t, (1 << field_count) - 1); if (q->static_id_fields == field_mask) { /* All matched ids are static, nothing to serialize */ return false; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { ecs_flags16_t field_bit = flecs_ito(uint16_t, 1 << f); if (!(it->set_fields & field_bit)) { /* Don't serialize ids for fields that aren't set */ ecs_strbuf_list_appendlit(buf, "0"); continue; } if (q->static_id_fields & field_bit) { /* Only add non-static ids to save bandwidth/performance */ ecs_strbuf_list_appendlit(buf, "0"); continue; } flecs_json_next(buf); flecs_json_id(buf, world, it->ids[f]); } flecs_json_array_pop(buf); return true; } static bool flecs_json_serialize_iter_result_sources( const ecs_iter_t *it, ecs_strbuf_t *buf) { const ecs_query_t *q = it->query; if (!q) { return false; } ecs_world_t *world = it->world; int32_t f, field_count = it->field_count; for (f = 0; f < field_count; f ++) { if (it->sources[f]) { break; } } if (f == field_count) { /* All fields are matched on $this */ return false; } flecs_json_memberl(buf, "sources"); flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { ecs_flags16_t field_bit = flecs_ito(uint16_t, 1 << f); if (!(it->set_fields & field_bit)) { /* Don't serialize source for fields that aren't set */ ecs_strbuf_list_appendlit(buf, "0"); continue; } if (!it->sources[f]) { /* Don't serialize source for fields that have $this source */ ecs_strbuf_list_appendlit(buf, "0"); continue; } flecs_json_next(buf); flecs_json_path(buf, world, it->sources[f]); } flecs_json_array_pop(buf); return true; } static bool flecs_json_serialize_common_for_table( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_strbuf_list_push(buf, "", ","); flecs_json_serialize_vars(world, it, buf, desc); bool result = false; if (!desc || desc->serialize_fields) { ecs_strbuf_list_appendlit(buf, "\"fields\":"); flecs_json_object_push(buf); result |= flecs_json_serialize_iter_result_is_set(it, buf); result |= flecs_json_serialize_iter_result_ids(it, buf); result |= flecs_json_serialize_iter_result_sources(it, buf); } ecs_strbuf_list_pop(buf, ""); return result; } static int flecs_json_serialize_iter_result_field_values( const ecs_world_t *world, const ecs_iter_t *it, int32_t i, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, ecs_json_ser_ctx_t *ser_ctx) { int32_t f, field_count = it->field_count; if (!field_count) { return 0; } ecs_strbuf_appendlit(buf, "\"values\":"); flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { ecs_flags16_t field_bit = flecs_ito(uint16_t, 1 << f); if (!(it->set_fields & field_bit)) { ecs_strbuf_list_appendlit(buf, "0"); continue; } ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; if (!flecs_json_serialize_get_value_ctx( world, it->ids[f], value_ctx, desc)) { ecs_strbuf_list_appendlit(buf, "0"); continue; } if (!it->ptrs[f]) { ecs_strbuf_list_appendlit(buf, "0"); continue; } void *ptr = it->ptrs[f]; if (!it->sources[f]) { ptr = ECS_ELEM(ptr, it->sizes[f], i); } flecs_json_next(buf); if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { return -1; } } flecs_json_array_pop(buf); return 0; } int flecs_json_serialize_iter_result_query( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, ecs_json_ser_ctx_t *ser_ctx, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data) { /* Serialize tags, pairs, vars once, since they're the same for each row */ ecs_strbuf_t common_data_buf = ECS_STRBUF_INIT; bool common_field_data = flecs_json_serialize_common_for_table( world, it, &common_data_buf, desc); int32_t common_data_len = ecs_strbuf_written(&common_data_buf); char *common_data = NULL; if (!common_data_len) { ecs_strbuf_reset(&common_data_buf); } else { common_data = ecs_strbuf_get(&common_data_buf); } int32_t i; for (i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_object_push(buf); if (has_this) { flecs_json_serialize_iter_this( it, parent_path, this_data, i, buf, desc); } if (common_data) { ecs_strbuf_list_appendstrn(buf, common_data, common_data_len); } if (!desc || desc->serialize_fields) { bool has_values = !desc || desc->serialize_values; if (it->flags & EcsIterNoData || !it->field_count) { has_values = false; } const ecs_query_t *q = it->query; if (q && !q->data_fields) { has_values = false; } if (has_values) { if (common_field_data) { flecs_json_next(buf); } if (flecs_json_serialize_iter_result_field_values( world, it, i, buf, desc, ser_ctx)) { ecs_os_free(common_data); return -1; } } ecs_strbuf_appendstr(buf, "}"); // "fields": { } flecs_json_object_pop(buf); } ecs_os_free(common_data); return 0; } #endif /** * @file addons/json/serialize_iter_result_table.c * @brief Serialize all components of matched entity. */ #ifdef FLECS_JSON bool flecs_json_is_builtin( ecs_id_t id) { if (ECS_IS_PAIR(id)) { if (ECS_PAIR_FIRST(id) == EcsChildOf) { return true; } if (id == ecs_pair_t(EcsIdentifier, EcsName)) { return true; } } return false; } static bool flecs_json_serialize_table_type_info( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_column_t *columns = table->data.columns; int32_t i, column_count = table->column_count; flecs_json_memberl(buf, "type_info"); flecs_json_object_push(buf); if (!column_count) { flecs_json_object_pop(buf); return false; } for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(column->id)) { continue; } } ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_next(buf); ecs_strbuf_appendlit(buf, "\""); flecs_json_id_member(buf, world, column->id, desc->serialize_full_paths); ecs_strbuf_appendlit(buf, "\":"); ecs_type_info_to_json_buf(world, ti->component, buf); } flecs_json_object_pop(buf); return true; } static bool flecs_json_serialize_table_tags( const ecs_world_t *world, const ecs_table_t *table, const ecs_table_t *src_table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { int32_t f, type_count = table->type.count; ecs_id_t *ids = table->type.array; int32_t *column_map = table->column_map; int32_t tag_count = 0; ecs_table_record_t *trs = table->_->records; for (f = 0; f < type_count; f ++) { ecs_id_t id = ids[f]; if (ECS_IS_PAIR(id)) { continue; } if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(id)) { continue; } } if (column_map && column_map[f] != -1) { continue; /* Ignore components */ } const ecs_table_record_t *tr = &trs[f]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (src_table) { if (!(idr->flags & EcsIdOnInstantiateInherit)) { continue; } } if (!tag_count) { flecs_json_memberl(buf, "tags"); flecs_json_array_push(buf); } flecs_json_next(buf); flecs_json_path_or_label(buf, world, id, desc ? desc->serialize_full_paths : true); tag_count ++; } if (tag_count) { flecs_json_array_pop(buf); } return tag_count != 0; } static bool flecs_json_serialize_table_pairs( const ecs_world_t *world, const ecs_table_t *table, const ecs_table_t *src_table, int32_t row, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { int32_t f, type_count = table->type.count; ecs_id_t *ids = table->type.array; int32_t *column_map = table->column_map; int32_t pair_count = 0; bool same_first = false; ecs_table_record_t *trs = table->_->records; for (f = 0; f < type_count; f ++) { ecs_id_t id = ids[f]; if (!ECS_IS_PAIR(id)) { continue; } if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(id)) { continue; } } if (column_map && column_map[f] != -1) { continue; /* Ignore components */ } const ecs_table_record_t *tr = &trs[f]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (src_table) { if (!(idr->flags & EcsIdOnInstantiateInherit)) { continue; } } ecs_entity_t first = flecs_entities_get_alive( world, ECS_PAIR_FIRST(id)); if (!pair_count) { flecs_json_memberl(buf, "pairs"); flecs_json_object_push(buf); } ecs_entity_t second = flecs_entities_get_alive( world, ECS_PAIR_SECOND(id)); bool is_last = f == (type_count - 1); bool is_same = !is_last && (ECS_PAIR_FIRST(ids[f + 1]) == ECS_PAIR_FIRST(id)); if (same_first && f && ECS_PAIR_FIRST(ids[f - 1]) != ECS_PAIR_FIRST(id)) { /* New pair has different first elem, so close array */ flecs_json_array_pop(buf); same_first = false; } if (!same_first) { /* Only append pair label if we're not appending to array */ flecs_json_next(buf); flecs_json_path_or_label(buf, world, first, desc ? desc->serialize_full_paths : true); ecs_strbuf_appendlit(buf, ":"); /* Open array scope if this is a pair with multiple targets */ if (is_same) { flecs_json_array_push(buf); same_first = true; } } if (same_first) { flecs_json_next(buf); } if (second == EcsUnion) { ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = flecs_table_entities_array(table)[row]; second = ecs_get_target(world, e, first, 0); } flecs_json_path_or_label(buf, world, second, desc ? desc->serialize_full_paths : true); pair_count ++; } if (same_first) { flecs_json_array_pop(buf); } if (pair_count) { flecs_json_object_pop(buf); } return pair_count != 0; } static bool flecs_json_serialize_table_inherited_type_components( const ecs_world_t *world, ecs_record_t *r, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_table_t *table = r->table; int32_t row = ECS_RECORD_TO_ROW(r->row); bool result = false; int32_t i, count = table->column_count, component_count = 0; const ecs_table_record_t *trs = table->_->records; for (i = 0; i < count; i ++) { ecs_column_t *column = &table->data.columns[i]; int32_t type_index = table->column_map[table->type.count + i]; ecs_assert(type_index != -1, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = &trs[type_index]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (!(idr->flags & EcsIdOnInstantiateInherit)) { continue; } const ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); const EcsTypeSerializer *ts = NULL; if (!desc || desc->serialize_values) { ts = ecs_get(world, ti->component, EcsTypeSerializer); } if (!component_count) { flecs_json_memberl(buf, "components"); flecs_json_object_push(buf); } ecs_strbuf_list_next(buf); ecs_strbuf_appendlit(buf, "\""); flecs_json_id_member(buf, world, column->id, desc ? desc->serialize_full_paths : true); ecs_strbuf_appendlit(buf, "\":"); if (ts) { void *ptr = ecs_vec_get(&column->data, column->size, row); if (flecs_json_ser_type(world, &ts->ops, ptr, buf) != 0) { return -1; } } else { ecs_strbuf_appendlit(buf, "null"); } component_count ++; } if (component_count) { flecs_json_object_pop(buf); } return result; } static bool flecs_json_serialize_table_inherited_type( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { if (!(table->flags & EcsTableHasIsA)) { return false; } const ecs_table_record_t *tr = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); /* Table has IsA flag */ int32_t i, start = tr->index, end = start + tr->count; for (i = start; i < end; i ++) { ecs_entity_t base = ecs_pair_second(world, table->type.array[i]); ecs_record_t *base_record = ecs_record_find(world, base); if (!base_record || !base_record->table) { continue; } ecs_table_t *base_table = base_record->table; flecs_json_serialize_table_inherited_type(world, base_table, buf, desc); char *base_name = ecs_get_path(world, base); flecs_json_member(buf, base_name); flecs_json_object_push(buf); ecs_os_free(base_name); flecs_json_serialize_table_tags( world, base_table, table, buf, desc); flecs_json_serialize_table_pairs( world, base_table, table, ECS_RECORD_TO_ROW(base_record->row), buf, desc); flecs_json_serialize_table_inherited_type_components( world, base_record, buf, desc); if (desc->serialize_type_info) { flecs_json_serialize_table_type_info( world, base_table, buf, desc); } flecs_json_object_pop(buf); } return true; } static bool flecs_json_serialize_table_inherited( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { if (!(table->flags & EcsTableHasIsA)) { return false; } flecs_json_memberl(buf, "inherited"); flecs_json_object_push(buf); flecs_json_serialize_table_inherited_type(world, table, buf, desc); flecs_json_object_pop(buf); return true; } static bool flecs_json_serialize_table_tags_pairs_vars( const ecs_world_t *world, const ecs_iter_t *it, ecs_table_t *table, int32_t row, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { bool result = false; ecs_strbuf_list_push(buf, "", ","); result |= flecs_json_serialize_table_tags(world, table, NULL, buf, desc); result |= flecs_json_serialize_table_pairs(world, table, NULL, row, buf, desc); result |= flecs_json_serialize_vars(world, it, buf, desc); if (desc->serialize_inherited) { result |= flecs_json_serialize_table_inherited(world, table, buf, desc); } if (desc->serialize_type_info) { /* If we're serializing tables and are requesting type info, it must be * added to each result. */ result |= flecs_json_serialize_table_type_info(world, table, buf, desc); } ecs_strbuf_list_pop(buf, ""); if (!result) { ecs_strbuf_reset(buf); } return result; } static int flecs_json_serialize_table_components( const ecs_world_t *world, ecs_table_t *table, ecs_strbuf_t *buf, ecs_json_value_ser_ctx_t *values_ctx, const ecs_iter_to_json_desc_t *desc, int32_t row, int32_t column_count) { int32_t f, component_count = 0; for (f = 0; f < column_count; f ++) { ecs_column_t *column = &table->data.columns[f]; if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(column->id)) { continue; } } ecs_json_value_ser_ctx_t *value_ctx = &values_ctx[f]; bool has_reflection = flecs_json_serialize_get_value_ctx( world, column->id, value_ctx, desc); if (!component_count) { flecs_json_memberl(buf, "components"); flecs_json_object_push(buf); } flecs_json_member(buf, value_ctx->id_label); if (has_reflection && (!desc || desc->serialize_values)) { void *ptr = ecs_vec_get(&column->data, column->size, row); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(value_ctx->id_label != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_json_ser_type(world, &value_ctx->ser->ops, ptr, buf) != 0) { return -1; } } else { ecs_strbuf_appendlit(buf, "null"); } component_count ++; } if (component_count) { flecs_json_object_pop(buf); } return 0; } int flecs_json_serialize_iter_result_table( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, int32_t count, bool has_this, const char *parent_path, const ecs_json_this_data_t *this_data) { ecs_table_t *table = it->table; if (!table || !count) { return 0; } /* Serialize tags, pairs, vars once, since they're the same for each row, * except when table has union pairs, which can be different for each * entity. */ ecs_strbuf_t tags_pairs_vars_buf = ECS_STRBUF_INIT; int32_t tags_pairs_vars_len = 0; char *tags_pairs_vars = NULL; bool has_union = table->flags & EcsTableHasUnion; if (!has_union) { if (flecs_json_serialize_table_tags_pairs_vars( world, it, table, 0, &tags_pairs_vars_buf, desc)) { tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); } } /* If one entity has more than 256 components (oof), bad luck */ ecs_json_value_ser_ctx_t values_ctx[256] = {{0}}; int32_t column_count = table->column_count; if (column_count > 256) { column_count = 256; } int32_t i, end = it->offset + count; int result = 0; for (i = it->offset; i < end; i ++) { flecs_json_next(buf); flecs_json_object_push(buf); if (has_this) { ecs_json_this_data_t this_data_cpy = *this_data; flecs_json_serialize_iter_this( it, parent_path, &this_data_cpy, i - it->offset, buf, desc); } if (has_union) { if (flecs_json_serialize_table_tags_pairs_vars( world, it, table, i, &tags_pairs_vars_buf, desc)) { tags_pairs_vars_len = ecs_strbuf_written(&tags_pairs_vars_buf); tags_pairs_vars = ecs_strbuf_get(&tags_pairs_vars_buf); } } if (tags_pairs_vars) { ecs_strbuf_list_appendstrn(buf, tags_pairs_vars, tags_pairs_vars_len); } if (has_union) { ecs_os_free(tags_pairs_vars); tags_pairs_vars = NULL; } if (flecs_json_serialize_table_components( world, table, buf, values_ctx, desc, i, column_count)) { result = -1; break; } if (desc->serialize_matches) { flecs_json_serialize_matches( world, buf, it->entities[i - it->offset]); } if (desc->serialize_refs) { flecs_json_serialize_refs(world, buf, it->entities[i - it->offset], desc->serialize_refs); } if (desc->serialize_alerts) { flecs_json_serialize_alerts(world, buf, it->entities[i - it->offset]); } flecs_json_object_pop(buf); } for (i = 0; i < column_count; i ++) { ecs_os_free(values_ctx[i].id_label); } ecs_os_free(tags_pairs_vars); return result; } #endif /** * @file addons/json/serialize_query_info.c * @brief Serialize (component) values to JSON strings. */ #ifdef FLECS_JSON static const char* flecs_json_inout_str( int16_t kind) { switch(kind) { case EcsIn: return "in"; case EcsOut: return "out"; case EcsInOut: return "inout"; case EcsInOutNone: return "none"; case EcsInOutFilter: return "filter"; case EcsInOutDefault: return "default"; default: return "unknown"; } } static const char* flecs_json_oper_str( int16_t kind) { switch(kind) { case EcsAnd: return "and"; case EcsNot: return "not"; case EcsOr: return "or"; case EcsOptional: return "optional"; case EcsAndFrom: return "andfrom"; case EcsNotFrom: return "notfrom"; case EcsOrFrom: return "orfrom"; default: return "unknown"; } } static void flecs_json_serialize_term_entity( const ecs_world_t *world, ecs_entity_t e, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "entity"); flecs_json_path(buf, world, e); if (e) { const char *symbol = ecs_get_symbol(world, e); if (symbol) { flecs_json_memberl(buf, "symbol"); flecs_json_string(buf, symbol); } if (ecs_has(world, e, EcsComponent)) { flecs_json_memberl(buf, "type"); flecs_json_true(buf); } } } static void flecs_json_serialize_term_ref( const ecs_world_t *world, const ecs_term_ref_t *ref, ecs_strbuf_t *buf) { flecs_json_object_push(buf); if (ref->id & EcsIsEntity) { flecs_json_serialize_term_entity(world, ECS_TERM_REF_ID(ref), buf); } else if (ref->id & EcsIsVariable) { flecs_json_memberl(buf, "var"); if (ref->name) { flecs_json_string(buf, ref->name); } else if (ref->id) { if (ECS_TERM_REF_ID(ref) == EcsThis) { flecs_json_string(buf, "this"); } else { flecs_json_path(buf, world, ECS_TERM_REF_ID(ref)); } } } else if (ref->id & EcsIsName) { flecs_json_memberl(buf, "name"); flecs_json_string(buf, ref->name); } flecs_json_object_pop(buf); } static void flecs_json_serialize_term_trav( const ecs_world_t *world, const ecs_term_t *term, ecs_strbuf_t *buf) { if (term->trav) { flecs_json_memberl(buf, "trav"); flecs_json_object_push(buf); flecs_json_serialize_term_entity(world, term->trav, buf); flecs_json_object_pop(buf); } flecs_json_memberl(buf, "flags"); flecs_json_array_push(buf); if (term->src.id & EcsSelf) { flecs_json_next(buf); flecs_json_string(buf, "self"); } if (term->src.id & EcsCascade) { flecs_json_next(buf); flecs_json_string(buf, "cascade"); } else if (term->src.id & EcsUp) { flecs_json_next(buf); flecs_json_string(buf, "up"); } flecs_json_array_pop(buf); } static void flecs_json_serialize_term( const ecs_world_t *world, const ecs_query_t *q, int t, ecs_strbuf_t *buf) { const ecs_term_t *term = &q->terms[t]; flecs_json_object_push(buf); flecs_json_memberl(buf, "inout"); flecs_json_string(buf, flecs_json_inout_str(term->inout)); flecs_json_memberl(buf, "has_value"); flecs_json_bool(buf, 0 == (term->flags_ & EcsTermNoData)); ecs_entity_t first_id = ECS_TERM_REF_ID(&term->first); if (term->first.id & EcsIsEntity && first_id) { if (ecs_has_pair(world, first_id, EcsOnInstantiate, EcsInherit)) { flecs_json_memberl(buf, "can_inherit"); flecs_json_true(buf); } } flecs_json_memberl(buf, "oper"); flecs_json_string(buf, flecs_json_oper_str(term->oper)); flecs_json_memberl(buf, "src"); flecs_json_serialize_term_ref(world, &term->src, buf); flecs_json_memberl(buf, "first"); flecs_json_serialize_term_ref(world, &term->first, buf); if (ECS_TERM_REF_ID(&term->second) || term->second.name || term->second.id & EcsIsEntity) { flecs_json_memberl(buf, "second"); flecs_json_serialize_term_ref(world, &term->second, buf); } flecs_json_serialize_term_trav(world, term, buf); flecs_json_object_pop(buf); } void flecs_json_serialize_query( const ecs_world_t *world, const ecs_query_t *q, ecs_strbuf_t *buf) { flecs_json_object_push(buf); if (q->var_count) { flecs_json_memberl(buf, "vars"); flecs_json_array_push(buf); int32_t v, first = 0; if (!(q->flags & EcsQueryMatchThis)) { first = 1; } for (v = first; v < q->var_count; v ++) { flecs_json_next(buf); if (q->vars[v]) { flecs_json_string_escape(buf, q->vars[v]); } else { flecs_json_string(buf, "this"); } } flecs_json_array_pop(buf); } flecs_json_memberl(buf, "terms"); flecs_json_array_push(buf); int t; for (t = 0; t < q->term_count; t ++) { flecs_json_next(buf); flecs_json_serialize_term(world, q, t, buf); } flecs_json_array_pop(buf); flecs_json_object_pop(buf); } #endif /** * @file addons/json/serialize_type_info.c * @brief Serialize type (reflection) information to JSON. */ #ifdef FLECS_JSON static int json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf); static int json_typeinfo_ser_primitive( ecs_primitive_kind_t kind, ecs_strbuf_t *str) { switch(kind) { case EcsBool: flecs_json_string(str, "bool"); break; case EcsChar: case EcsString: flecs_json_string(str, "text"); break; case EcsByte: flecs_json_string(str, "byte"); break; case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsIPtr: case EcsUPtr: flecs_json_string(str, "int"); break; case EcsF32: case EcsF64: flecs_json_string(str, "float"); break; case EcsEntity: flecs_json_string(str, "entity"); break; case EcsId: flecs_json_string(str, "id"); break; default: return -1; } return 0; } static void json_typeinfo_ser_constants( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsChildOf, type)); while (ecs_each_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { flecs_json_next(str); flecs_json_string(str, ecs_get_name(world, it.entities[i])); } } } static void json_typeinfo_ser_enum( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"enum\""); json_typeinfo_ser_constants(world, type, str); } static void json_typeinfo_ser_bitmask( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"bitmask\""); json_typeinfo_ser_constants(world, type, str); } static int json_typeinfo_ser_array( const ecs_world_t *world, ecs_entity_t elem_type, int32_t count, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"array\""); flecs_json_next(str); if (json_typeinfo_ser_type(world, elem_type, str)) { goto error; } ecs_strbuf_list_append(str, "%u", count); return 0; error: return -1; } static int json_typeinfo_ser_array_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { const EcsArray *arr = ecs_get(world, type, EcsArray); ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { goto error; } return 0; error: return -1; } static int json_typeinfo_ser_vector( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { const EcsVector *arr = ecs_get(world, type, EcsVector); ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_strbuf_list_appendstr(str, "\"vector\""); flecs_json_next(str); if (json_typeinfo_ser_type(world, arr->type, str)) { goto error; } return 0; error: return -1; } /* Serialize unit information */ static int json_typeinfo_ser_unit( const ecs_world_t *world, ecs_strbuf_t *str, ecs_entity_t unit) { flecs_json_memberl(str, "unit"); flecs_json_path(str, world, unit); const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); if (uptr) { if (uptr->symbol) { flecs_json_memberl(str, "symbol"); flecs_json_string(str, uptr->symbol); } ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); if (quantity) { flecs_json_memberl(str, "quantity"); flecs_json_path(str, world, quantity); } } return 0; } static void json_typeinfo_ser_range( ecs_strbuf_t *str, const char *kind, ecs_member_value_range_t *range) { flecs_json_member(str, kind); flecs_json_array_push(str); flecs_json_next(str); flecs_json_number(str, range->min); flecs_json_next(str); flecs_json_number(str, range->max); flecs_json_array_pop(str); } /* Forward serialization to the different type kinds */ static int json_typeinfo_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, ecs_strbuf_t *str, const EcsStruct *st) { if (op->kind == EcsOpOpaque) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); return json_typeinfo_ser_type(world, ct->as_type, str); } flecs_json_array_push(str); switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, "unexpected push/pop serializer instruction"); break; case EcsOpEnum: json_typeinfo_ser_enum(world, op->type, str); break; case EcsOpBitmask: json_typeinfo_ser_bitmask(world, op->type, str); break; case EcsOpArray: json_typeinfo_ser_array_type(world, op->type, str); break; case EcsOpVector: json_typeinfo_ser_vector(world, op->type, str); break; case EcsOpOpaque: /* Can't happen, already handled above */ ecs_throw(ECS_INTERNAL_ERROR, NULL); break; case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: if (json_typeinfo_ser_primitive( flecs_json_op_to_primitive_kind(op->kind), str)) { ecs_throw(ECS_INTERNAL_ERROR, NULL); } break; case EcsOpScope: case EcsOpPrimitive: default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } if (st) { ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); bool value_range = ECS_NEQ(m->range.min, m->range.max); bool error_range = ECS_NEQ(m->error_range.min, m->error_range.max); bool warning_range = ECS_NEQ(m->warning_range.min, m->warning_range.max); ecs_entity_t unit = m->unit; if (unit || error_range || warning_range || value_range) { flecs_json_next(str); flecs_json_next(str); flecs_json_object_push(str); if (unit) { json_typeinfo_ser_unit(world, str, unit); } if (value_range) { json_typeinfo_ser_range(str, "range", &m->range); } if (error_range) { json_typeinfo_ser_range(str, "error_range", &m->error_range); } if (warning_range) { json_typeinfo_ser_range(str, "warning_range", &m->warning_range); } flecs_json_object_pop(str); } } flecs_json_array_pop(str); return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int json_typeinfo_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, ecs_strbuf_t *str, const EcsStruct *st) { const EcsStruct *stack[64] = {st}; int32_t sp = 1; for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op != ops) { if (op->name) { flecs_json_member(str, op->name); } } int32_t elem_count = op->count; if (elem_count > 1) { flecs_json_array_push(str); json_typeinfo_ser_array(world, op->type, op->count, str); flecs_json_array_pop(str); i += op->op_count - 1; continue; } switch(op->kind) { case EcsOpPush: flecs_json_object_push(str); ecs_assert(sp < 63, ECS_INVALID_OPERATION, "type nesting too deep"); stack[sp ++] = ecs_get(world, op->type, EcsStruct); break; case EcsOpPop: { ecs_entity_t unit = ecs_get_target_for(world, op->type, EcsIsA, EcsUnit); if (unit) { flecs_json_member(str, "@self"); flecs_json_array_push(str); flecs_json_object_push(str); json_typeinfo_ser_unit(world, str, unit); flecs_json_object_pop(str); flecs_json_array_pop(str); } flecs_json_object_pop(str); sp --; break; } case EcsOpArray: case EcsOpVector: case EcsOpEnum: case EcsOpBitmask: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (json_typeinfo_ser_type_op(world, op, str, stack[sp - 1])) { goto error; } break; case EcsOpPrimitive: case EcsOpScope: default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } } return 0; error: return -1; } static int json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { ecs_strbuf_appendch(buf, '0'); return 0; } const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (!ser) { ecs_strbuf_appendch(buf, '0'); return 0; } const EcsStruct *st = ecs_get(world, type, EcsStruct); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(&ser->ops); if (json_typeinfo_ser_type_ops(world, ops, count, buf, st)) { return -1; } return 0; } 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 addons/json/serialize_value.c * @brief Serialize value to JSON. */ #ifdef FLECS_JSON static int flecs_json_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array); static int flecs_json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str); /* Serialize enumeration */ static int flecs_json_ser_enum( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); int32_t value = *(const int32_t*)base; /* Enumeration constants are stored in a map that is keyed on the * enumeration value. */ ecs_enum_constant_t *constant = ecs_map_get_deref(&enum_type->constants, ecs_enum_constant_t, (ecs_map_key_t)value); if (!constant) { /* If the value is not found, it is not a valid enumeration constant */ char *name = ecs_get_path(world, op->type); ecs_err("enumeration value '%d' of type '%s' is not a valid constant", value, name); ecs_os_free(name); goto error; } ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); ecs_strbuf_appendch(str, '"'); return 0; error: return -1; } /* Serialize bitmask */ static int flecs_json_ser_bitmask( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); uint32_t value = *(const uint32_t*)ptr; if (!value) { ecs_strbuf_appendch(str, '0'); return 0; } ecs_strbuf_list_push(str, "\"", "|"); /* Multiple flags can be set at a given time. Iterate through all the flags * and append the ones that are set. */ ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, constant->constant)); value -= (uint32_t)key; } } if (value != 0) { /* All bits must have been matched by a constant */ char *name = ecs_get_path(world, op->type); ecs_err("bitmask value '%u' of type '%s' contains invalid/unknown bits", value, name); ecs_os_free(name); goto error; } ecs_strbuf_list_pop(str, "\""); return 0; error: return -1; } /* Serialize elements of a contiguous array */ static int flecs_json_ser_elements( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, int32_t elem_count, int32_t elem_size, ecs_strbuf_t *str, bool is_array) { flecs_json_array_push(str); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (flecs_json_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } flecs_json_array_pop(str); return 0; } static int flecs_json_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str, bool is_array) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vec_count(&ser->ops); return flecs_json_ser_elements( world, ops, op_count, base, elem_count, comp->size, str, is_array); } /* Serialize array */ static int json_ser_array( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsArray *a = ecs_get(world, op->type, EcsArray); ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_json_ser_type_elements( world, a->type, ptr, a->count, str, true); } /* Serialize vector */ static int json_ser_vector( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const ecs_vec_t *value = base; const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(value); void *array = ecs_vec_first(value); /* Serialize contiguous buffer of vector */ return flecs_json_ser_type_elements(world, v->type, array, count, str, false); } typedef struct json_serializer_ctx_t { ecs_strbuf_t *str; bool is_collection; bool is_struct; } json_serializer_ctx_t; static int json_ser_custom_value( const ecs_serializer_t *ser, ecs_entity_t type, const void *value) { json_serializer_ctx_t *json_ser = ser->ctx; if (json_ser->is_collection) { ecs_strbuf_list_next(json_ser->str); } return ecs_ptr_to_json_buf(ser->world, type, value, json_ser->str); } static int json_ser_custom_member( const ecs_serializer_t *ser, const char *name) { json_serializer_ctx_t *json_ser = ser->ctx; if (!json_ser->is_struct) { ecs_err("serializer::member can only be called for structs"); return -1; } flecs_json_member(json_ser->str, name); return 0; } static int json_ser_custom_type( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); ecs_assert(ct != NULL, ECS_INVALID_OPERATION, "entity %s in opaque type serializer instruction is not an opaque type", ecs_get_name(world, op->type)); ecs_assert(ct->as_type != 0, ECS_INVALID_OPERATION, "opaque type %s has not populated as_type field", ecs_get_name(world, op->type)); ecs_assert(ct->serialize != NULL, ECS_INVALID_OPERATION, "opaque type %s does not have serialize interface", ecs_get_name(world, op->type)); const EcsType *pt = ecs_get(world, ct->as_type, EcsType); ecs_assert(pt != NULL, ECS_INVALID_OPERATION, "opaque type %s is missing flecs.meta.Type component", ecs_get_name(world, op->type)); ecs_type_kind_t kind = pt->kind; bool is_collection = false; bool is_struct = false; if (kind == EcsStructType) { flecs_json_object_push(str); is_struct = true; } else if (kind == EcsArrayType || kind == EcsVectorType) { flecs_json_array_push(str); is_collection = true; } json_serializer_ctx_t json_ser = { .str = str, .is_struct = is_struct, .is_collection = is_collection }; ecs_serializer_t ser = { .world = world, .value = json_ser_custom_value, .member = json_ser_custom_member, .ctx = &json_ser }; if (ct->serialize(&ser, base)) { return -1; } if (kind == EcsStructType) { flecs_json_object_pop(str); } else if (kind == EcsArrayType || kind == EcsVectorType) { flecs_json_array_pop(str); } return 0; } /* Forward serialization to the different type kinds */ static int flecs_json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { void *vptr = ECS_OFFSET(ptr, op->offset); bool large_int = false; if (op->kind == EcsOpI64) { if (*(int64_t*)vptr >= 2147483648) { large_int = true; } } else if (op->kind == EcsOpU64) { if (*(uint64_t*)vptr >= 2147483648) { large_int = true; } } if (large_int) { ecs_strbuf_appendch(str, '"'); } switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpF32: ecs_strbuf_appendflt(str, (ecs_f64_t)*(const ecs_f32_t*)vptr, '"'); break; case EcsOpF64: ecs_strbuf_appendflt(str, *(ecs_f64_t*)vptr, '"'); break; case EcsOpEnum: if (flecs_json_ser_enum(world, op, vptr, str)) { goto error; } break; case EcsOpBitmask: if (flecs_json_ser_bitmask(world, op, vptr, str)) { goto error; } break; case EcsOpArray: if (json_ser_array(world, op, vptr, str)) { goto error; } break; case EcsOpVector: if (json_ser_vector(world, op, vptr, str)) { goto error; } break; case EcsOpOpaque: if (json_ser_custom_type(world, op, vptr, str)) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = *(const ecs_entity_t*)vptr; if (!e) { ecs_strbuf_appendlit(str, "\"#0\""); } else { flecs_json_path(str, world, e); } break; } case EcsOpId: { ecs_id_t id = *(const ecs_id_t*)vptr; if (!id) { ecs_strbuf_appendlit(str, "\"#0\""); } else { flecs_json_id(str, world, id); } break; } case EcsOpU64: case EcsOpI64: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: if (flecs_expr_ser_primitive(world, flecs_json_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str, true)) { ecs_throw(ECS_INTERNAL_ERROR, NULL); } break; case EcsOpPrimitive: case EcsOpScope: default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } if (large_int) { ecs_strbuf_appendch(str, '"'); } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int flecs_json_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (in_array <= 0) { if (op->name) { flecs_json_member(str, op->name); } int32_t elem_count = op->count; if (elem_count > 1) { /* Serialize inline array */ if (flecs_json_ser_elements(world, op, op->op_count, base, elem_count, op->size, str, true)) { return -1; } i += op->op_count - 1; continue; } } switch(op->kind) { case EcsOpPush: flecs_json_object_push(str); in_array --; break; case EcsOpPop: flecs_json_object_pop(str); in_array ++; break; case EcsOpArray: case EcsOpVector: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (flecs_json_ser_type_op(world, op, base, str)) { goto error; } break; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } } return 0; error: return -1; } /* Iterate over the type ops of a type */ int flecs_json_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); return flecs_json_ser_type_ops(world, ops, count, base, str, 0); } static int flecs_array_to_json_buf_w_type_data( const ecs_world_t *world, const void *ptr, int32_t count, ecs_strbuf_t *buf, const EcsComponent *comp, const EcsTypeSerializer *ser) { if (count) { ecs_size_t size = comp->size; flecs_json_array_push(buf); do { ecs_strbuf_list_next(buf); if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } ptr = ECS_OFFSET(ptr, size); } while (-- count); flecs_json_array_pop(buf); } else { if (flecs_json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } } return 0; } int ecs_array_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, int32_t count, ecs_strbuf_t *buf) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { char *path = ecs_get_path(world, type); ecs_err("cannot serialize to JSON, '%s' is not a component", path); ecs_os_free(path); return -1; } const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (!ser) { char *path = ecs_get_path(world, type); ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); ecs_os_free(path); return -1; } return flecs_array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } char* ecs_array_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr, int32_t count) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf) { return ecs_array_to_json_buf(world, type, ptr, 0, buf); } char* ecs_ptr_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { return ecs_array_to_json(world, type, ptr, 0); } #endif /** * @file addons/json/serialize_world.c * @brief Serialize world to JSON. */ #ifdef FLECS_JSON int ecs_world_to_json_buf( ecs_world_t *world, ecs_strbuf_t *buf_out, const ecs_world_to_json_desc_t *desc) { ecs_query_desc_t query_desc = {0}; if (desc && desc->serialize_builtin && desc->serialize_modules) { query_desc.terms[0].id = EcsAny; } else { bool serialize_builtin = desc && desc->serialize_builtin; bool serialize_modules = desc && desc->serialize_modules; int32_t term_id = 0; if (!serialize_builtin) { query_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); query_desc.terms[term_id].oper = EcsNot; query_desc.terms[term_id].src.id = EcsSelf | EcsUp; term_id ++; } if (!serialize_modules) { query_desc.terms[term_id].id = EcsModule; query_desc.terms[term_id].oper = EcsNot; query_desc.terms[term_id].src.id = EcsSelf | EcsUp; } } query_desc.flags = EcsQueryMatchDisabled|EcsQueryMatchPrefab; ecs_query_t *q = ecs_query_init(world, &query_desc); if (!q) { return -1; } ecs_iter_t it = ecs_query_iter(world, q); ecs_iter_to_json_desc_t json_desc = { .serialize_table = true, .serialize_full_paths = true, .serialize_entity_ids = true, .serialize_values = true }; int ret = ecs_iter_to_json_buf(&it, buf_out, &json_desc); ecs_query_fini(q); return ret; } char* ecs_world_to_json( ecs_world_t *world, const ecs_world_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_world_to_json_buf(world, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif /** * @file addons/meta/api.c * @brief API for creating entities with reflection data. */ #ifdef FLECS_META static bool flecs_type_is_number( ecs_world_t *world, ecs_entity_t type) { const EcsPrimitive *p = ecs_get(world, type, EcsPrimitive); if (!p) { return false; } switch(p->kind) { case EcsChar: case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsF32: case EcsF64: return true; case EcsBool: case EcsByte: case EcsUPtr: case EcsIPtr: case EcsString: case EcsEntity: case EcsId: return false; default: ecs_abort(ECS_INVALID_PARAMETER, NULL); } } /* Serialize a primitive value */ int flecs_expr_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str, bool is_expr) { switch(kind) { case EcsBool: if (*(const bool*)base) { ecs_strbuf_appendlit(str, "true"); } else { ecs_strbuf_appendlit(str, "false"); } break; case EcsChar: { char chbuf[3]; char ch = *(const char*)base; if (ch) { flecs_chresc(chbuf, *(const char*)base, '"'); if (is_expr) ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, chbuf); if (is_expr) ecs_strbuf_appendch(str, '"'); } else { ecs_strbuf_appendch(str, '0'); } break; } case EcsByte: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); break; case EcsU8: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint8_t*)base)); break; case EcsU16: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint16_t*)base)); break; case EcsU32: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(const uint32_t*)base)); break; case EcsU64: ecs_strbuf_append(str, "%llu", *(const uint64_t*)base); break; case EcsI8: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int8_t*)base)); break; case EcsI16: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int16_t*)base)); break; case EcsI32: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const int32_t*)base)); break; case EcsI64: ecs_strbuf_appendint(str, *(const int64_t*)base); break; case EcsF32: ecs_strbuf_appendflt(str, (double)*(const float*)base, 0); break; case EcsF64: ecs_strbuf_appendflt(str, *(const double*)base, 0); break; case EcsIPtr: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(const intptr_t*)base)); break; case EcsUPtr: ecs_strbuf_append(str, "%u", *(const uintptr_t*)base); break; case EcsString: { const char *value = *ECS_CONST_CAST(const char**, base); if (value) { if (!is_expr) { ecs_strbuf_appendstr(str, value); } else { ecs_size_t length = flecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstrn(str, value, length); ecs_strbuf_appendch(str, '"'); } else { char *out = ecs_os_malloc(length + 3); flecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr(str, out); ecs_os_free(out); } } } else { ecs_strbuf_appendlit(str, "null"); } break; } case EcsEntity: { ecs_entity_t e = *(const ecs_entity_t*)base; if (!e) { ecs_strbuf_appendlit(str, "#0"); } else { ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); } break; } case EcsId: { ecs_id_t id = *(const ecs_id_t*)base; if (!id) { ecs_strbuf_appendlit(str, "#0"); } else { ecs_id_str_buf(world, id, str); } break; } default: ecs_err("invalid primitive kind"); return -1; } return 0; } ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsPrimitive, { desc->kind }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_enum_init( ecs_world_t *world, const ecs_enum_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsEnum); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_enum_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_second(world, c, EcsConstant, ecs_i32_t, {m_desc->value}); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_bitmask_init( ecs_world_t *world, const ecs_bitmask_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsBitmask); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_second(world, c, EcsConstant, ecs_u32_t, {m_desc->value}); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_array_init( ecs_world_t *world, const ecs_array_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsArray, { .type = desc->type, .count = desc->count }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_vector_init( ecs_world_t *world, const ecs_vector_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsVector, { .type = desc->type }); flecs_resume_readonly(world, &rs); return t; } static bool flecs_member_range_overlaps( const ecs_member_value_range_t *range, const ecs_member_value_range_t *with) { if (ECS_EQ(with->min, with->max)) { return false; } if (ECS_EQ(range->min, range->max)) { return false; } if (range->min < with->min || range->max > with->max) { return true; } return false; } ecs_entity_t ecs_struct_init( ecs_world_t *world, const ecs_struct_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_member_t *m_desc = &desc->members[i]; if (!m_desc->type) { break; } if (!m_desc->name) { ecs_err("member %d of struct '%s' does not have a name", i, ecs_get_name(world, t)); goto error; } ecs_entity_t m = ecs_entity(world, { .name = m_desc->name }); ecs_set(world, m, EcsMember, { .type = m_desc->type, .count = m_desc->count, .offset = m_desc->offset, .unit = m_desc->unit }); EcsMemberRanges *ranges = NULL; const ecs_member_value_range_t *range = &m_desc->range; const ecs_member_value_range_t *error = &m_desc->error_range; const ecs_member_value_range_t *warning = &m_desc->warning_range; if (ECS_NEQ(range->min, range->max)) { ranges = ecs_ensure(world, m, EcsMemberRanges); if (range->min > range->max) { char *member_name = ecs_get_path(world, m); ecs_err("member '%s' has an invalid value range [%f..%f]", member_name, range->min, range->max); ecs_os_free(member_name); goto error; } ranges->value.min = range->min; ranges->value.max = range->max; } if (ECS_NEQ(error->min, error->max)) { if (error->min > error->max) { char *member_name = ecs_get_path(world, m); ecs_err("member '%s' has an invalid error range [%f..%f]", member_name, error->min, error->max); ecs_os_free(member_name); goto error; } if (flecs_member_range_overlaps(error, range)) { char *member_name = ecs_get_path(world, m); ecs_err("error range of member '%s' overlaps with value range", member_name); ecs_os_free(member_name); goto error; } if (!ranges) { ranges = ecs_ensure(world, m, EcsMemberRanges); } ranges->error.min = error->min; ranges->error.max = error->max; } if (ECS_NEQ(warning->min, warning->max)) { if (warning->min > warning->max) { char *member_name = ecs_get_path(world, m); ecs_err("member '%s' has an invalid warning range [%f..%f]", member_name, warning->min, warning->max); ecs_os_free(member_name); goto error; } if (flecs_member_range_overlaps(warning, range)) { char *member_name = ecs_get_path(world, m); ecs_err("warning range of member '%s' overlaps with value " "range", member_name); ecs_os_free(member_name); goto error; } if (flecs_member_range_overlaps(warning, error)) { char *member_name = ecs_get_path(world, m); ecs_err("warning range of member '%s' overlaps with error " "range", member_name); ecs_os_free(member_name); goto error; } if (!ranges) { ranges = ecs_ensure(world, m, EcsMemberRanges); } ranges->warning.min = warning->min; ranges->warning.max = warning->max; } if (ranges && !flecs_type_is_number(world, m_desc->type)) { char *member_name = ecs_get_path(world, m); ecs_err("member '%s' has an value/error/warning range, but is not a " "number", member_name); ecs_os_free(member_name); goto error; } if (ranges) { ecs_modified(world, m, EcsMemberRanges); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("struct '%s' has no members", ecs_get_name(world, t)); goto error; } if (!ecs_has(world, t, EcsStruct)) { goto error; } return t; error: flecs_resume_readonly(world, &rs); if (t) { ecs_delete(world, t); } return 0; } ecs_entity_t ecs_opaque_init( ecs_world_t *world, const ecs_opaque_desc_t *desc) { ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set_ptr(world, t, EcsOpaque, &desc->type); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_unit_init( ecs_world_t *world, const ecs_unit_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t quantity = desc->quantity; if (quantity) { if (!ecs_has_id(world, quantity, EcsQuantity)) { ecs_err("entity '%s' for unit '%s' is not a quantity", ecs_get_name(world, quantity), ecs_get_name(world, t)); goto error; } ecs_add_pair(world, t, EcsQuantity, desc->quantity); } else { ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); } EcsUnit *value = ecs_ensure(world, t, EcsUnit); value->base = desc->base; value->over = desc->over; value->translation = desc->translation; value->prefix = desc->prefix; ecs_os_strset(&value->symbol, desc->symbol); if (!flecs_unit_validate(world, t, value)) { goto error; } ecs_modified(world, t, EcsUnit); flecs_resume_readonly(world, &rs); return t; error: if (t) { ecs_delete(world, t); } flecs_resume_readonly(world, &rs); return 0; } ecs_entity_t ecs_unit_prefix_init( ecs_world_t *world, const ecs_unit_prefix_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsUnitPrefix, { .symbol = ECS_CONST_CAST(char*, desc->symbol), .translation = desc->translation }); flecs_resume_readonly(world, &rs); return t; } ecs_entity_t ecs_quantity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = ecs_entity_init(world, desc); if (!t) { return 0; } ecs_add_id(world, t, EcsQuantity); flecs_resume_readonly(world, &rs); return t; } #endif /** * @file addons/meta/c_utils.c * @brief C utilities for meta addon. */ #ifdef FLECS_META #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 = flecs_parse_ws_eol(ptr); ptr = flecs_parse_digit(ptr, token); if (!ptr) { goto error; } *value_out = strtol(token, NULL, 0); return flecs_parse_ws_eol(ptr); error: return NULL; } static const char* parse_c_identifier( const char *ptr, char *buff, char *params, meta_parse_ctx_t *ctx) { ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buff != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); char *bptr = buff, ch; if (params) { params[0] = '\0'; } /* Ignore whitespaces */ ptr = flecs_parse_ws_eol(ptr); ch = *ptr; if (!isalpha(ch) && (ch != '_')) { ecs_meta_error(ctx, ptr, "invalid identifier (starts with '%c')", ch); goto error; } while ((ch = *ptr) && !isspace(ch) && ch != ';' && ch != ',' && ch != ')' && ch != '>' && ch != '}' && ch != '*') { /* Type definitions can contain macros or templates */ if (ch == '(' || ch == '<') { if (!params) { ecs_meta_error(ctx, ptr, "unexpected %c", *ptr); goto error; } const char *end = skip_scope(ptr, ctx); ecs_os_strncpy(params, ptr, (ecs_size_t)(end - ptr)); params[end - ptr] = '\0'; ptr = end; } else { *bptr = ch; bptr ++; ptr ++; } } *bptr = '\0'; if (!ch) { ecs_meta_error(ctx, ptr, "unexpected end of token"); goto error; } return ptr; error: return NULL; } static const char * meta_open_scope( const char *ptr, meta_parse_ctx_t *ctx) { /* Skip initial whitespaces */ ptr = flecs_parse_ws_eol(ptr); /* Is this the start of the type definition? */ if (ctx->desc == ptr) { if (*ptr != '{') { ecs_meta_error(ctx, ptr, "missing '{' in struct definition"); goto error; } ptr ++; ptr = flecs_parse_ws_eol(ptr); } /* Is this the end of the type definition? */ if (!*ptr) { ecs_meta_error(ctx, ptr, "missing '}' at end of struct definition"); goto error; } /* Is this the end of the type definition? */ if (*ptr == '}') { ptr = flecs_parse_ws_eol(ptr + 1); if (*ptr) { ecs_meta_error(ctx, ptr, "stray characters after struct definition"); goto error; } return NULL; } return ptr; error: return NULL; } static const char* meta_parse_constant( const char *ptr, meta_constant_t *token, meta_parse_ctx_t *ctx) { ptr = meta_open_scope(ptr, ctx); if (!ptr) { return NULL; } token->is_value_set = false; /* Parse token, constant identifier */ ptr = parse_c_identifier(ptr, token->name, NULL, ctx); if (!ptr) { return NULL; } ptr = flecs_parse_ws_eol(ptr); if (!ptr) { return NULL; } /* Explicit value assignment */ if (*ptr == '=') { int64_t value = 0; ptr = parse_c_digit(ptr + 1, &value); token->value = value; token->is_value_set = true; } /* Expect a ',' or '}' */ if (*ptr != ',' && *ptr != '}') { ecs_meta_error(ctx, ptr, "missing , after enum constant"); goto error; } if (*ptr == ',') { return ptr + 1; } else { return ptr; } error: return NULL; } static const char* meta_parse_type( const char *ptr, meta_type_t *token, meta_parse_ctx_t *ctx) { token->is_ptr = false; token->is_const = false; ptr = flecs_parse_ws_eol(ptr); /* Parse token, expect type identifier or ECS_PROPERTY */ ptr = parse_c_identifier(ptr, token->type, token->params, ctx); if (!ptr) { goto error; } if (!strcmp(token->type, "ECS_PRIVATE")) { /* Members from this point are not stored in metadata */ ptr += ecs_os_strlen(ptr); goto done; } /* If token is const, set const flag and continue parsing type */ if (!strcmp(token->type, "const")) { token->is_const = true; /* Parse type after const */ ptr = parse_c_identifier(ptr + 1, token->type, token->params, ctx); } /* Check if type is a pointer */ ptr = flecs_parse_ws_eol(ptr); if (*ptr == '*') { token->is_ptr = true; ptr ++; } done: return ptr; error: return NULL; } static const char* meta_parse_member( const char *ptr, meta_member_t *token, meta_parse_ctx_t *ctx) { ptr = meta_open_scope(ptr, ctx); if (!ptr) { return NULL; } token->count = 1; token->is_partial = false; /* Parse member type */ ptr = meta_parse_type(ptr, &token->type, ctx); if (!ptr) { token->is_partial = true; goto error; } if (!ptr[0]) { return ptr; } /* Next token is the identifier */ ptr = parse_c_identifier(ptr, token->name, NULL, ctx); if (!ptr) { goto error; } /* Skip whitespace between member and [ or ; */ ptr = flecs_parse_ws_eol(ptr); /* Check if this is an array */ char *array_start = strchr(token->name, '['); if (!array_start) { /* If the [ was separated by a space, it will not be parsed as part of * the name */ if (*ptr == '[') { /* safe, will not be modified */ array_start = ECS_CONST_CAST(char*, ptr); } } if (array_start) { /* Check if the [ matches with a ] */ char *array_end = strchr(array_start, ']'); if (!array_end) { ecs_meta_error(ctx, ptr, "missing ']'"); goto error; } else if (array_end - array_start == 0) { ecs_meta_error(ctx, ptr, "dynamic size arrays are not supported"); goto error; } token->count = atoi(array_start + 1); if (array_start == ptr) { /* If [ was found after name, continue parsing after ] */ ptr = array_end + 1; } else { /* If [ was found 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 = flecs_parse_ws_eol(ptr); if (*ptr != '(' && *ptr != '<') { ecs_meta_error(ctx, ptr, "expected '(' at start of collection definition"); goto error; } ptr ++; /* Parse type identifier */ ptr = meta_parse_type(ptr, &token->type, ctx); if (!ptr) { goto error; } ptr = flecs_parse_ws_eol(ptr); /* If next token is a ',' the first type was a key type */ if (*ptr == ',') { ptr = flecs_parse_ws_eol(ptr + 1); if (isdigit(*ptr)) { int64_t value; ptr = parse_c_digit(ptr, &value); if (!ptr) { goto error; } token->count = value; token->is_fixed_size = true; } else { token->key_type = token->type; /* Parse element type */ ptr = meta_parse_type(ptr, &token->type, ctx); ptr = flecs_parse_ws_eol(ptr); token->is_key_value = true; } } if (*ptr != ')' && *ptr != '>') { ecs_meta_error(ctx, ptr, "expected ')' at end of collection definition"); goto error; } return 0; error: return -1; } static ecs_entity_t meta_lookup( ecs_world_t *world, meta_type_t *token, const char *ptr, int64_t count, meta_parse_ctx_t *ctx); static ecs_entity_t meta_lookup_array( ecs_world_t *world, ecs_entity_t e, const char *params_decl, meta_parse_ctx_t *ctx) { meta_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; meta_params_t params; if (meta_parse_desc(params_decl, ¶ms, ¶m_ctx)) { goto error; } if (!params.is_fixed_size) { ecs_meta_error(ctx, params_decl, "missing size for array"); goto error; } if (!params.count) { ecs_meta_error(ctx, params_decl, "invalid array size"); goto error; } ecs_entity_t element_type = ecs_lookup_symbol( world, params.type.type, true, true); if (!element_type) { ecs_meta_error(ctx, params_decl, "unknown element type '%s'", params.type.type); } if (!e) { e = ecs_new(world); } ecs_check(params.count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); ecs_set(world, e, EcsArray, { element_type, (int32_t)params.count }); return e; 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(world); } ecs_set(world, e, EcsVector, { element_type }); return e; 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 EcsType *type_ptr = ecs_get(world, bitmask_type, EcsType); ecs_check(type_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type_ptr->kind == EcsBitmaskType, ECS_INVALID_PARAMETER, NULL); #endif return bitmask_type; error: return 0; } static ecs_entity_t meta_lookup( ecs_world_t *world, meta_type_t *token, const char *ptr, int64_t count, meta_parse_ctx_t *ctx) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(token != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ctx != NULL, ECS_INTERNAL_ERROR, NULL); const char *typename = token->type; ecs_entity_t type = 0; /* Parse vector type */ if (!token->is_ptr) { if (!ecs_os_strcmp(typename, "ecs_array")) { type = meta_lookup_array(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "ecs_vector") || !ecs_os_strcmp(typename, "flecs::vector")) { type = meta_lookup_vector(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { type = meta_lookup_bitmask(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::byte")) { type = ecs_id(ecs_byte_t); } else if (!ecs_os_strcmp(typename, "char")) { type = ecs_id(ecs_char_t); } else if (!ecs_os_strcmp(typename, "bool") || !ecs_os_strcmp(typename, "_Bool")) { type = ecs_id(ecs_bool_t); } else if (!ecs_os_strcmp(typename, "int8_t")) { type = ecs_id(ecs_i8_t); } else if (!ecs_os_strcmp(typename, "int16_t")) { type = ecs_id(ecs_i16_t); } else if (!ecs_os_strcmp(typename, "int32_t")) { type = ecs_id(ecs_i32_t); } else if (!ecs_os_strcmp(typename, "int64_t")) { type = ecs_id(ecs_i64_t); } else if (!ecs_os_strcmp(typename, "uint8_t")) { type = ecs_id(ecs_u8_t); } else if (!ecs_os_strcmp(typename, "uint16_t")) { type = ecs_id(ecs_u16_t); } else if (!ecs_os_strcmp(typename, "uint32_t")) { type = ecs_id(ecs_u32_t); } else if (!ecs_os_strcmp(typename, "uint64_t")) { type = ecs_id(ecs_u64_t); } else if (!ecs_os_strcmp(typename, "float")) { type = ecs_id(ecs_f32_t); } else if (!ecs_os_strcmp(typename, "double")) { type = ecs_id(ecs_f64_t); } else if (!ecs_os_strcmp(typename, "ecs_entity_t")) { type = ecs_id(ecs_entity_t); } else if (!ecs_os_strcmp(typename, "ecs_id_t")) { type = ecs_id(ecs_id_t); } else if (!ecs_os_strcmp(typename, "char*")) { type = ecs_id(ecs_string_t); } else { type = ecs_lookup_symbol(world, typename, true, true); } } else { if (!ecs_os_strcmp(typename, "char")) { typename = "flecs.meta.string"; } else if (token->is_ptr) { typename = "flecs.meta.uptr"; } else if (!ecs_os_strcmp(typename, "char*") || !ecs_os_strcmp(typename, "flecs::string")) { typename = "flecs.meta.string"; } type = ecs_lookup_symbol(world, typename, true, true); } if (count != 1) { ecs_check(count <= INT32_MAX, ECS_INVALID_PARAMETER, NULL); type = ecs_insert(world, ecs_value(EcsArray, {type, (int32_t)count})); } if (!type) { ecs_meta_error(ctx, ptr, "unknown type '%s'", typename); goto error; } return type; error: return 0; } static int meta_parse_struct( ecs_world_t *world, ecs_entity_t t, const char *desc) { const char *ptr = desc; const char *name = ecs_get_name(world, t); meta_member_t token; meta_parse_ctx_t ctx = { .name = name, .desc = ptr }; ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = meta_parse_member(ptr, &token, &ctx)) && ptr[0]) { ecs_entity_t m = ecs_entity(world, { .name = token.name }); ecs_entity_t type = meta_lookup( world, &token.type, ptr, 1, &ctx); if (!type) { goto error; } ecs_set(world, m, EcsMember, { .type = type, .count = (ecs_size_t)token.count }); } ecs_set_scope(world, old_scope); return 0; error: return -1; } static int meta_parse_constants( ecs_world_t *world, ecs_entity_t t, const char *desc, bool is_bitmask) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(t != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); const char *ptr = desc; const char *name = ecs_get_name(world, t); int32_t name_len = ecs_os_strlen(name); const ecs_world_info_t *info = ecs_get_world_info(world); const char *name_prefix = info->name_prefix; int32_t name_prefix_len = name_prefix ? ecs_os_strlen(name_prefix) : 0; meta_parse_ctx_t ctx = { .name = name, .desc = ptr }; meta_constant_t token; int64_t last_value = 0; ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = meta_parse_constant(ptr, &token, &ctx))) { if (token.is_value_set) { last_value = token.value; } else if (is_bitmask) { ecs_meta_error(&ctx, ptr, "bitmask requires explicit value assignment"); goto error; } if (name_prefix) { if (!ecs_os_strncmp(token.name, name_prefix, name_prefix_len)) { ecs_os_memmove(token.name, token.name + name_prefix_len, ecs_os_strlen(token.name) - name_prefix_len + 1); } } if (!ecs_os_strncmp(token.name, name, name_len)) { ecs_os_memmove(token.name, token.name + name_len, ecs_os_strlen(token.name) - name_len + 1); } ecs_entity_t c = ecs_entity(world, { .name = token.name }); if (!is_bitmask) { ecs_set_pair_second(world, c, EcsConstant, ecs_i32_t, {(ecs_i32_t)last_value}); } else { ecs_set_pair_second(world, c, EcsConstant, ecs_u32_t, {(ecs_u32_t)last_value}); } last_value ++; } ecs_set_scope(world, old_scope); return 0; error: return -1; } static int meta_parse_enum( ecs_world_t *world, ecs_entity_t t, const char *desc) { ecs_add(world, t, EcsEnum); return meta_parse_constants(world, t, desc, false); } static int meta_parse_bitmask( ecs_world_t *world, ecs_entity_t t, const char *desc) { ecs_add(world, t, EcsBitmask); return meta_parse_constants(world, t, desc, true); } int ecs_meta_from_desc( ecs_world_t *world, ecs_entity_t component, ecs_type_kind_t kind, const char *desc) { switch(kind) { case EcsStructType: if (meta_parse_struct(world, component, desc)) { goto error; } break; case EcsEnumType: if (meta_parse_enum(world, component, desc)) { goto error; } break; case EcsBitmaskType: if (meta_parse_bitmask(world, component, desc)) { goto error; } break; case EcsPrimitiveType: case EcsArrayType: case EcsVectorType: case EcsOpaqueType: break; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); } return 0; error: return -1; } #endif /** * @file addons/meta/cursor.c * @brief API for assigning values of runtime types with reflection. */ #include #ifdef FLECS_META #ifdef FLECS_SCRIPT #endif static const char* flecs_meta_op_kind_str( ecs_meta_type_op_kind_t kind) { switch(kind) { case EcsOpEnum: return "Enum"; case EcsOpBitmask: return "Bitmask"; case EcsOpArray: return "Array"; case EcsOpVector: return "Vector"; case EcsOpOpaque: return "Opaque"; case EcsOpPush: return "Push"; case EcsOpPop: return "Pop"; case EcsOpPrimitive: return "Primitive"; case EcsOpBool: return "Bool"; case EcsOpChar: return "Char"; case EcsOpByte: return "Byte"; case EcsOpU8: return "U8"; case EcsOpU16: return "U16"; case EcsOpU32: return "U32"; case EcsOpU64: return "U64"; case EcsOpI8: return "I8"; case EcsOpI16: return "I16"; case EcsOpI32: return "I32"; case EcsOpI64: return "I64"; case EcsOpF32: return "F32"; case EcsOpF64: return "F64"; case EcsOpUPtr: return "UPtr"; case EcsOpIPtr: return "IPtr"; case EcsOpString: return "String"; case EcsOpEntity: return "Entity"; case EcsOpId: return "Id"; case EcsOpScope: return "Scope"; default: return "<< invalid kind >>"; } } /* Get current scope */ static ecs_meta_scope_t* flecs_meta_cursor_get_scope( const ecs_meta_cursor_t *cursor) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); return ECS_CONST_CAST(ecs_meta_scope_t*, &cursor->scope[cursor->depth]); error: return NULL; } /* Restore scope, if dotmember was used */ static ecs_meta_scope_t* flecs_meta_cursor_restore_scope( ecs_meta_cursor_t *cursor, const ecs_meta_scope_t* scope) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); if (scope->prev_depth) { cursor->depth = scope->prev_depth; } error: return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; } /* Get current operation for scope */ static ecs_meta_type_op_t* flecs_meta_cursor_get_op( ecs_meta_scope_t *scope) { ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, "type serializer is missing instructions"); return &scope->ops[scope->op_cur]; } /* Get component for type in current scope */ static const EcsComponent* get_ecs_component( const ecs_world_t *world, ecs_meta_scope_t *scope) { const EcsComponent *comp = scope->comp; if (!comp) { comp = scope->comp = ecs_get(world, scope->type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); } return comp; } /* Get size for type in current scope */ static ecs_size_t get_size( const ecs_world_t *world, ecs_meta_scope_t *scope) { return get_ecs_component(world, scope)->size; } static int32_t get_elem_count( ecs_meta_scope_t *scope) { const EcsOpaque *opaque = scope->opaque; if (scope->vector) { return ecs_vec_count(scope->vector); } else if (opaque && opaque->count) { return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->count; } /* Get pointer to current field/element */ static ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( const ecs_world_t *world, ecs_meta_scope_t *scope) { ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_size_t size = get_size(world, scope); const EcsOpaque *opaque = scope->opaque; if (scope->vector) { ecs_vec_set_min_count(NULL, scope->vector, size, scope->elem_cur + 1); scope->ptr = ecs_vec_first(scope->vector); } else if (opaque) { if (scope->is_collection) { if (!opaque->ensure_element) { char *str = ecs_get_path(world, scope->type); ecs_err("missing ensure_element for opaque type %s", str); ecs_os_free(str); return NULL; } scope->is_empty_scope = false; void *opaque_ptr = opaque->ensure_element( scope->ptr, flecs_ito(size_t, scope->elem_cur)); ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, "ensure_element returned NULL"); return opaque_ptr; } else if (op->name) { if (!opaque->ensure_member) { char *str = ecs_get_path(world, scope->type); ecs_err("missing ensure_member for opaque type %s", str); ecs_os_free(str); return NULL; } ecs_assert(scope->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return opaque->ensure_member(scope->ptr, op->name); } else { ecs_err("invalid operation for opaque type"); return NULL; } } return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); } static int flecs_meta_cursor_push_type( const ecs_world_t *world, ecs_meta_scope_t *scope, ecs_entity_t type, void *ptr) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for '%s' (missing reflection data)", str); ecs_os_free(str); return -1; } scope[0] = (ecs_meta_scope_t) { .type = type, .ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t), .op_count = ecs_vec_count(&ser->ops), .ptr = ptr }; return 0; } ecs_meta_cursor_t ecs_meta_cursor( const ecs_world_t *world, ecs_entity_t type, void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_meta_cursor_t result = { .world = world, .valid = true }; if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { result.valid = false; } return result; error: return (ecs_meta_cursor_t){ 0 }; } void* ecs_meta_get_ptr( ecs_meta_cursor_t *cursor) { return flecs_meta_cursor_get_ptr(cursor->world, flecs_meta_cursor_get_scope(cursor)); } int ecs_meta_next( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); if (scope->is_collection) { scope->elem_cur ++; scope->op_cur = 0; if (scope->opaque) { return 0; } if (scope->elem_cur >= get_elem_count(scope)) { ecs_err("out of collection bounds (%d)", scope->elem_cur); return -1; } return 0; } scope->op_cur += op->op_count; if (scope->op_cur >= scope->op_count) { ecs_err("out of bounds"); return -1; } return 0; } int ecs_meta_elem( ecs_meta_cursor_t *cursor, int32_t elem) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); if (!scope->is_collection) { ecs_err("ecs_meta_elem can be used for collections only"); return -1; } scope->elem_cur = elem; scope->op_cur = 0; if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { ecs_err("out of collection bounds (%d)", scope->elem_cur); return -1; } return 0; } int ecs_meta_member( ecs_meta_cursor_t *cursor, const char *name) { if (cursor->depth == 0) { ecs_err("cannot move to member in root scope"); return -1; } ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); ecs_hashmap_t *members = scope->members; const ecs_world_t *world = cursor->world; if (!members) { ecs_err("cannot move to member '%s' for non-struct type", name); return -1; } const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); if (!cur_ptr) { char *path = ecs_get_path(world, scope->type); ecs_err("unknown member '%s' for type '%s'", name, path); ecs_os_free(path); return -1; } scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); const EcsOpaque *opaque = scope->opaque; if (opaque) { if (!opaque->ensure_member) { char *str = ecs_get_path(world, scope->type); ecs_err("missing ensure_member for opaque type %s", str); ecs_os_free(str); } } return 0; } static const char* flecs_meta_parse_member( const char *start, char *token_out) { const char *ptr; char ch; for (ptr = start; (ch = *ptr); ptr ++) { if (ch == '.') { break; } } int32_t len = flecs_ito(int32_t, ptr - start); ecs_os_memcpy(token_out, start, len); token_out[len] = '\0'; if (ch == '.') { ptr ++; } return ptr; } int ecs_meta_dotmember( ecs_meta_cursor_t *cursor, const char *name) { ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); flecs_meta_cursor_restore_scope(cursor, cur_scope); int32_t prev_depth = cursor->depth; int dotcount = 0; char token[ECS_MAX_TOKEN_SIZE]; const char *ptr = name; while ((ptr = flecs_meta_parse_member(ptr, token))) { if (dotcount) { ecs_meta_push(cursor); } if (ecs_meta_member(cursor, token)) { goto error; } if (!ptr[0]) { break; } dotcount ++; } cur_scope = flecs_meta_cursor_get_scope(cursor); if (dotcount) { cur_scope->prev_depth = prev_depth; } return 0; error: return -1; } int ecs_meta_push( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); const ecs_world_t *world = cursor->world; if (cursor->depth == 0) { if (!cursor->is_primitive_scope) { if ((op->kind > EcsOpScope) && (op->count <= 1)) { cursor->is_primitive_scope = true; return 0; } } } void *ptr = flecs_meta_cursor_get_ptr(world, scope); cursor->depth ++; ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, ECS_INVALID_PARAMETER, NULL); ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); /* If we're not already in an inline array and this operation is an inline * array, push a frame for the array. * Doing this first ensures that inline arrays take precedence over other * kinds of push operations, such as for a struct element type. */ if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { /* Push a frame just for the element type, with inline_array = true */ next_scope[0] = (ecs_meta_scope_t){ .ops = op, .op_count = op->op_count, .ptr = scope->ptr, .type = op->type, .is_collection = true, .is_inline_array = true }; /* With 'is_inline_array' set to true we ensure that we can never push * the same inline array twice */ return 0; } /* Operation-specific switch behavior */ switch(op->kind) { /* Struct push: this happens when pushing a struct member. */ case EcsOpPush: { const EcsOpaque *opaque = scope->opaque; if (opaque) { /* If this is a nested push for an opaque type, push the type of the * element instead of the next operation. This ensures that we won't * use flattened offsets for nested members. */ if (flecs_meta_cursor_push_type( world, next_scope, op->type, ptr) != 0) { goto error; } /* Strip the Push operation since we already pushed */ next_scope->members = next_scope->ops[0].members; next_scope->ops = &next_scope->ops[1]; next_scope->op_count --; break; } /* The ops array contains a flattened list for all members and nested * members of a struct, so we can use (ops + 1) to initialize the ops * array of the next scope. */ next_scope[0] = (ecs_meta_scope_t) { .ops = &op[1], /* op after push */ .op_count = op->op_count - 1, /* don't include pop */ .ptr = scope->ptr, .type = op->type, .members = op->members }; break; } /* Array push for an array type. Arrays can be encoded in 2 ways: either by * setting the EcsMember::count member to a value >1, or by specifying an * array type as member type. This is the latter case. */ case EcsOpArray: { if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { goto error; } const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); next_scope->type = type_ptr->type; next_scope->is_collection = true; break; } /* Vector push */ case EcsOpVector: { next_scope->vector = ptr; if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { goto error; } const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); next_scope->type = type_ptr->type; next_scope->is_collection = true; break; } /* Opaque type push. Depending on the type the opaque type represents the * scope will be pushed as a struct or collection type. The type information * of the as_type is retained, as this is important for type checking and * for nested opaque type support. */ case EcsOpOpaque: { const EcsOpaque *type_ptr = ecs_get(world, op->type, EcsOpaque); ecs_entity_t as_type = type_ptr->as_type; const EcsType *mtype_ptr = ecs_get(world, as_type, EcsType); /* Check what kind of type the opaque type represents */ switch(mtype_ptr->kind) { /* Opaque vector support */ case EcsVectorType: { const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); next_scope->type = vt->type; /* Push the element type of the vector type */ if (flecs_meta_cursor_push_type( world, next_scope, vt->type, NULL) != 0) { goto error; } /* This tracks whether any data was assigned inside the scope. When * the scope is popped, and is_empty_scope is still true, the vector * will be resized to 0. */ next_scope->is_empty_scope = true; next_scope->is_collection = true; break; } /* Opaque array support */ case EcsArrayType: { const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); next_scope->type = at->type; /* Push the element type of the array type */ if (flecs_meta_cursor_push_type( world, next_scope, at->type, NULL) != 0) { goto error; } /* Arrays are always a fixed size */ next_scope->is_empty_scope = false; next_scope->is_collection = true; break; } /* Opaque struct support */ case EcsStructType: /* Push struct type that represents the opaque type. This ensures * that the deserializer retains information about members and * member types, which is necessary for nested opaque types, and * allows for error checking. */ if (flecs_meta_cursor_push_type( world, next_scope, as_type, NULL) != 0) { goto error; } /* Strip push op, since we already pushed */ next_scope->members = next_scope->ops[0].members; next_scope->ops = &next_scope->ops[1]; next_scope->op_count --; break; case EcsPrimitiveType: case EcsEnumType: case EcsBitmaskType: case EcsOpaqueType: default: break; } next_scope->ptr = ptr; next_scope->opaque = type_ptr; break; } case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: { char *path = ecs_get_path(world, scope->type); ecs_err("invalid push for type '%s'", path); ecs_os_free(path); goto error; } default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } if (scope->is_collection && !scope->opaque) { next_scope->ptr = ECS_OFFSET(next_scope->ptr, scope->elem_cur * get_size(world, scope)); } return 0; error: return -1; } int ecs_meta_pop( ecs_meta_cursor_t *cursor) { if (cursor->is_primitive_scope) { cursor->is_primitive_scope = false; return 0; } ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); cursor->depth --; if (cursor->depth < 0) { ecs_err("unexpected end of scope"); return -1; } ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(next_scope); if (!scope->is_inline_array) { if (op->kind == EcsOpPush) { next_scope->op_cur += op->op_count - 1; /* push + op_count should point to the operation after pop */ op = flecs_meta_cursor_get_op(next_scope); ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { /* Collection type, nothing else to do */ } else if (op->kind == EcsOpOpaque) { const EcsOpaque *opaque = scope->opaque; if (scope->is_collection) { const EcsType *mtype = ecs_get(cursor->world, opaque->as_type, EcsType); ecs_assert(mtype != NULL, ECS_INTERNAL_ERROR, NULL); /* When popping an opaque collection type, call resize to make * sure the vector isn't larger than the number of elements we * deserialized. * If the opaque type represents an array, don't call resize. */ if (mtype->kind != EcsArrayType) { ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); if (!opaque->resize) { char *str = ecs_get_path(cursor->world, scope->type); ecs_err("missing resize for opaque type %s", str); ecs_os_free(str); return -1; } if (scope->is_empty_scope) { /* If no values were serialized for scope, resize * collection to 0 elements. */ ecs_assert(!scope->elem_cur, ECS_INTERNAL_ERROR, NULL); opaque->resize(scope->ptr, 0); } else { /* Otherwise resize collection to the index of the last * deserialized element + 1 */ opaque->resize(scope->ptr, flecs_ito(size_t, scope->elem_cur + 1)); } } } else { /* Opaque struct type, nothing to be done */ } } else { /* should not have been able to push if the previous scope was not * a complex or collection type */ ecs_assert(false, ECS_INTERNAL_ERROR, NULL); } } return 0; } bool ecs_meta_is_collection( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); return scope->is_collection; } ecs_entity_t ecs_meta_get_type( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->type; } ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_entity_t type = scope->type; const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); if (!st) { return 0; } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); return m->unit; } const char* ecs_meta_get_member( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->name; } ecs_entity_t ecs_meta_get_member_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_entity_t type = scope->type; const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); if (!st) { return 0; } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_member_t *m = ecs_vec_get_t( &st->members, ecs_member_t, op->member_index); return m->member; } /* Utilities for type conversions and bounds checking */ static struct { int64_t min, max; } ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, INT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) }, [EcsOpEntity] = {0, INT64_MAX}, [EcsOpId] = {0, INT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, INT32_MAX} }; static struct { uint64_t min, max; } ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {0, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, UINT64_MAX}, [EcsOpI8] = {0, INT8_MAX}, [EcsOpI16] = {0, INT16_MAX}, [EcsOpI32] = {0, INT32_MAX}, [EcsOpI64] = {0, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, [EcsOpEntity] = {0, UINT64_MAX}, [EcsOpId] = {0, UINT64_MAX}, [EcsOpEnum] = {0, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; static struct { double min, max; } ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, (double)UINT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) }, [EcsOpEntity] = {0, (double)UINT64_MAX}, [EcsOpId] = {0, (double)UINT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; #define set_T(T, ptr, value)\ ((T*)ptr)[0] = ((T)value) #define case_T(kind, T, dst, src)\ case kind:\ set_T(T, dst, src);\ break #define case_T_checked(kind, T, dst, src, bounds)\ case kind:\ if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ ecs_err("value %.0f is out of bounds for type %s", (double)src,\ flecs_meta_op_kind_str(kind));\ return -1;\ }\ set_T(T, dst, src);\ break #define cases_T_float(dst, src)\ case_T(EcsOpF32, ecs_f32_t, dst, src);\ case_T(EcsOpF64, ecs_f64_t, dst, src) #define cases_T_signed(dst, src, bounds)\ case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) #define cases_T_unsigned(dst, src, bounds)\ case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpId, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) #define cases_T_bool(dst, src)\ case EcsOpBool:\ set_T(ecs_bool_t, dst, value != 0);\ break static void flecs_meta_conversion_error( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, const char *from) { if (op->kind == EcsOpPop) { ecs_err("cursor: out of bounds"); } else { char *path = ecs_get_path(cursor->world, op->type); ecs_err("unsupported conversion from %s to '%s'", from, path); ecs_os_free(path); } } int ecs_meta_set_bool( ecs_meta_cursor_t *cursor, bool value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_bool) { ot->assign_bool(ptr, value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpF32: case EcsOpF64: case EcsOpString: flecs_meta_conversion_error(cursor, op, "bool"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_char( ecs_meta_cursor_t *cursor, char value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, "entity %s is not an opaque type but serializer thinks so", ecs_get_name(cursor->world, op->type)); if (opaque->assign_char) { /* preferred operation */ opaque->assign_char(ptr, value); break; } else if (opaque->assign_uint) { opaque->assign_uint(ptr, (uint64_t)value); break; } else if (opaque->assign_int) { opaque->assign_int(ptr, value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: flecs_meta_conversion_error(cursor, op, "char"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_int( ecs_meta_cursor_t *cursor, int64_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, "entity %s is not an opaque type but serializer thinks so", ecs_get_name(cursor->world, op->type)); if (opaque->assign_int) { /* preferred operation */ opaque->assign_int(ptr, value); break; } else if (opaque->assign_float) { /* most expressive */ opaque->assign_float(ptr, (double)value); break; } else if (opaque->assign_uint && (value > 0)) { opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); break; } else if (opaque->assign_char && (value > 0) && (value < 256)) { opaque->assign_char(ptr, flecs_ito(char, value)); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpString: { if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "int"); return -1; } default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_uint( ecs_meta_cursor_t *cursor, uint64_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, "entity %s is not an opaque type but serializer thinks so", ecs_get_name(cursor->world, op->type)); if (opaque->assign_uint) { /* preferred operation */ opaque->assign_uint(ptr, value); break; } else if (opaque->assign_float) { /* most expressive */ opaque->assign_float(ptr, (double)value); break; } else if (opaque->assign_int && (value < INT64_MAX)) { opaque->assign_int(ptr, flecs_uto(int64_t, value)); break; } else if (opaque->assign_char && (value < 256)) { opaque->assign_char(ptr, flecs_uto(char, value)); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpString: if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "uint"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_float( ecs_meta_cursor_t *cursor, double value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: if (ECS_EQZERO(value)) { set_T(bool, ptr, false); } else { set_T(bool, ptr, true); } break; cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, "entity %s is not an opaque type but serializer thinks so", ecs_get_name(cursor->world, op->type)); if (opaque->assign_float) { /* preferred operation */ opaque->assign_float(ptr, value); break; } else if (opaque->assign_int && /* most expressive */ (value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) { opaque->assign_int(ptr, (int64_t)value); break; } else if (opaque->assign_uint && (value >= 0)) { opaque->assign_uint(ptr, (uint64_t)value); break; } else if (opaque->assign_entity && (value >= 0)) { opaque->assign_entity( ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), (ecs_entity_t)value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: case EcsOpString: flecs_meta_conversion_error(cursor, op, "float"); return -1; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } int ecs_meta_set_value( ecs_meta_cursor_t *cursor, const ecs_value_t *value) { ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t type = value->type; ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); const EcsType *mt = ecs_get(cursor->world, type, EcsType); if (!mt) { ecs_err("type of value does not have reflection data"); return -1; } if (mt->kind == EcsPrimitiveType) { const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); switch(prim->kind) { case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); case EcsString: return ecs_meta_set_string(cursor, *(char**)value->ptr); case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); case EcsId: return ecs_meta_set_id(cursor, *(ecs_id_t*)value->ptr); default: ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); goto error; } } else if (mt->kind == EcsEnumType) { return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); } else if (mt->kind == EcsBitmaskType) { return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); } else { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); if (op->type != value->type) { char *type_str = ecs_get_path(cursor->world, value->type); flecs_meta_conversion_error(cursor, op, type_str); ecs_os_free(type_str); goto error; } return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); } error: return -1; } static int flecs_meta_add_bitmask_constant( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, void *out, const char *value) { ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); if (!ecs_os_strcmp(value, "0")) { return 0; } ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); if (!c) { char *path = ecs_get_path(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_second( cursor->world, c, EcsConstant, ecs_u32_t); if (v == NULL) { char *path = ecs_get_path(cursor->world, op->type); ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); ecs_os_free(path); return -1; } *(ecs_u32_t*)out |= v[0]; return 0; } static int flecs_meta_parse_bitmask( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, void *out, const char *value) { char token[ECS_MAX_TOKEN_SIZE]; const char *prev = value, *ptr = value; *(ecs_u32_t*)out = 0; while ((ptr = strchr(ptr, '|'))) { ecs_os_memcpy(token, prev, ptr - prev); token[ptr - prev] = '\0'; if (flecs_meta_add_bitmask_constant(cursor, op, out, token) != 0) { return -1; } ptr ++; prev = ptr; } if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { return -1; } return 0; } static int flecs_meta_cursor_lookup( ecs_meta_cursor_t *cursor, const char *value, ecs_entity_t *out) { if (ecs_os_strcmp(value, "#0")) { if (cursor->lookup_action) { *out = cursor->lookup_action( cursor->world, value, cursor->lookup_ctx); } else { *out = ecs_lookup_from(cursor->world, 0, value); } if (!*out) { ecs_err("unresolved entity identifier '%s'", value); return -1; } } return 0; } static bool flecs_meta_valid_digit( const char *str) { return str[0] == '-' || isdigit(str[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 EcsOpI8: case EcsOpU8: case EcsOpByte: case EcsOpI16: case EcsOpU16: case EcsOpI32: case EcsOpU32: case EcsOpI64: case EcsOpU64: case EcsOpIPtr: case EcsOpUPtr: case EcsOpF32: case EcsOpF64: if (!flecs_meta_valid_digit(value)) { ecs_err("expected number, got '%s'", value); goto error; } case EcsOpEnum: case EcsOpBitmask: case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpString: case EcsOpEntity: case EcsOpId: case EcsOpScope: break; } switch(op->kind) { case EcsOpBool: if (!ecs_os_strcmp(value, "true")) { set_T(ecs_bool_t, ptr, true); } else if (!ecs_os_strcmp(value, "false")) { set_T(ecs_bool_t, ptr, false); } else if (isdigit(value[0])) { if (!ecs_os_strcmp(value, "0")) { set_T(ecs_bool_t, ptr, false); } else { set_T(ecs_bool_t, ptr, true); } } else { ecs_err("invalid value for boolean '%s'", value); goto error; } break; case EcsOpI8: case EcsOpU8: case EcsOpByte: set_T(ecs_i8_t, ptr, atol(value)); break; case EcsOpChar: set_T(char, ptr, value[0]); break; case EcsOpI16: case EcsOpU16: set_T(ecs_i16_t, ptr, atol(value)); break; case EcsOpI32: case EcsOpU32: set_T(ecs_i32_t, ptr, atol(value)); break; case EcsOpI64: case EcsOpU64: set_T(ecs_i64_t, ptr, atoll(value)); break; case EcsOpIPtr: case EcsOpUPtr: set_T(ecs_iptr_t, ptr, atoll(value)); break; case EcsOpF32: set_T(ecs_f32_t, ptr, atof(value)); break; case EcsOpF64: set_T(ecs_f64_t, ptr, atof(value)); break; case EcsOpString: { ecs_assert(*(ecs_string_t*)ptr != value, ECS_INVALID_PARAMETER, NULL); ecs_os_free(*(ecs_string_t*)ptr); char *result = ecs_os_strdup(value); set_T(ecs_string_t, ptr, result); break; } case EcsOpEnum: { ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); if (!c) { char *path = ecs_get_path(cursor->world, op->type); ecs_err("unresolved enum constant '%s' for type '%s'", value, path); ecs_os_free(path); goto error; } const ecs_i32_t *v = ecs_get_pair_second( cursor->world, c, EcsConstant, ecs_i32_t); if (v == NULL) { char *path = ecs_get_path(cursor->world, op->type); ecs_err("'%s' is not an enum constant for type '%s'", value, path); ecs_os_free(path); goto error; } set_T(ecs_i32_t, ptr, v[0]); break; } case EcsOpBitmask: if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { goto error; } set_T(ecs_entity_t, ptr, e); break; } case EcsOpId: { #ifdef FLECS_SCRIPT ecs_id_t id = 0; if (flecs_id_parse(cursor->world, NULL, value, &id) == NULL) { goto error; } set_T(ecs_id_t, ptr, id); #else ecs_err("cannot parse component expression: script addon required"); #endif break; } case EcsOpPop: ecs_err("excess element '%s' in scope", value); goto error; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, "entity %s is not an opaque type but serializer thinks so", ecs_get_name(cursor->world, op->type)); if (opaque->assign_string) { /* preferred */ opaque->assign_string(ptr, value); break; } else if (opaque->assign_char && value[0] && !value[1]) { opaque->assign_char(ptr, value[0]); break; } else if (opaque->assign_entity) { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { goto error; } opaque->assign_entity(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), e); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpScope: case EcsOpPrimitive: ecs_err("unsupported conversion from string '%s' to '%s'", value, flecs_meta_op_kind_str(op->kind)); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_string_literal( ecs_meta_cursor_t *cursor, const char *value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); ecs_size_t len = ecs_os_strlen(value); if (value[0] != '\"' || value[len - 1] != '\"') { ecs_err("invalid string literal '%s'", value); goto error; } switch(op->kind) { case EcsOpChar: set_T(ecs_char_t, ptr, value[1]); break; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: len -= 2; char *result = ecs_os_malloc(len + 1); ecs_os_memcpy(result, value + 1, len); result[len] = '\0'; if (ecs_meta_set_string(cursor, result)) { ecs_os_free(result); return -1; } ecs_os_free(result); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_entity( ecs_meta_cursor_t *cursor, ecs_entity_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: set_T(ecs_entity_t, ptr, value); break; case EcsOpId: set_T(ecs_id_t, ptr, value); /* entities are valid ids */ break; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_entity) { opaque->assign_entity(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: flecs_meta_conversion_error(cursor, op, "entity"); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_id( ecs_meta_cursor_t *cursor, ecs_entity_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpId: set_T(ecs_id_t, ptr, value); break; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_id) { opaque->assign_id(ptr, ECS_CONST_CAST(ecs_world_t*, cursor->world), value); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: flecs_meta_conversion_error(cursor, op, "id"); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } int ecs_meta_set_null( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch (op->kind) { case EcsOpString: ecs_os_free(*(char**)ptr); set_T(ecs_string_t, ptr, NULL); break; case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_null) { ot->assign_null(ptr); break; } } /* fall through */ case EcsOpArray: case EcsOpVector: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: flecs_meta_conversion_error(cursor, op, "null"); goto error; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } return 0; error: return -1; } bool ecs_meta_get_bool( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr != 0; case EcsOpU8: return *(ecs_u8_t*)ptr != 0; case EcsOpChar: return *(ecs_char_t*)ptr != 0; case EcsOpByte: return *(ecs_u8_t*)ptr != 0; case EcsOpI16: return *(ecs_i16_t*)ptr != 0; case EcsOpU16: return *(ecs_u16_t*)ptr != 0; case EcsOpI32: return *(ecs_i32_t*)ptr != 0; case EcsOpU32: return *(ecs_u32_t*)ptr != 0; case EcsOpI64: return *(ecs_i64_t*)ptr != 0; case EcsOpU64: return *(ecs_u64_t*)ptr != 0; case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; case EcsOpF32: return ECS_NEQZERO(*(ecs_f32_t*)ptr); case EcsOpF64: return ECS_NEQZERO(*(ecs_f64_t*)ptr); case EcsOpString: return *(const char**)ptr != NULL; case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; case EcsOpId: return *(ecs_id_t*)ptr != 0; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } char ecs_meta_get_char( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpChar: return *(ecs_char_t*)ptr != 0; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpEntity: case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); break; } error: return 0; } int64_t ecs_meta_get_int( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(const ecs_bool_t*)ptr; case EcsOpI8: return *(const ecs_i8_t*)ptr; case EcsOpU8: return *(const ecs_u8_t*)ptr; case EcsOpChar: return *(const ecs_char_t*)ptr; case EcsOpByte: return *(const ecs_u8_t*)ptr; case EcsOpI16: return *(const ecs_i16_t*)ptr; case EcsOpU16: return *(const ecs_u16_t*)ptr; case EcsOpI32: return *(const ecs_i32_t*)ptr; case EcsOpU32: return *(const ecs_u32_t*)ptr; case EcsOpI64: return *(const ecs_i64_t*)ptr; case EcsOpU64: return flecs_uto(int64_t, *(const ecs_u64_t*)ptr); case EcsOpIPtr: return *(const ecs_iptr_t*)ptr; case EcsOpUPtr: return flecs_uto(int64_t, *(const ecs_uptr_t*)ptr); case EcsOpF32: return (int64_t)*(const ecs_f32_t*)ptr; case EcsOpF64: return (int64_t)*(const ecs_f64_t*)ptr; case EcsOpString: return atoi(*(const char**)ptr); case EcsOpEnum: return *(const ecs_i32_t*)ptr; case EcsOpBitmask: return *(const ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to int"); break; case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from id to int"); break; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } uint64_t ecs_meta_get_uint( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return flecs_ito(uint64_t, *(const ecs_i8_t*)ptr); case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return flecs_ito(uint64_t, *(const ecs_char_t*)ptr); case EcsOpByte: return flecs_ito(uint64_t, *(const ecs_u8_t*)ptr); case EcsOpI16: return flecs_ito(uint64_t, *(const ecs_i16_t*)ptr); case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); case EcsOpU64: return *(ecs_u64_t*)ptr; case EcsOpIPtr: return flecs_ito(uint64_t, *(const ecs_i64_t*)ptr); case EcsOpUPtr: return *(ecs_uptr_t*)ptr; case EcsOpF32: return flecs_ito(uint64_t, *(const ecs_f32_t*)ptr); case EcsOpF64: return flecs_ito(uint64_t, *(const ecs_f64_t*)ptr); case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); case EcsOpEnum: return flecs_ito(uint64_t, *(const ecs_i32_t*)ptr); case EcsOpBitmask: return *(const ecs_u32_t*)ptr; case EcsOpEntity: return *(const ecs_entity_t*)ptr; case EcsOpId: return *(const ecs_id_t*)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } static double flecs_meta_to_float( ecs_meta_type_op_kind_t kind, const void *ptr) { switch(kind) { case EcsOpBool: return *(const ecs_bool_t*)ptr; case EcsOpI8: return *(const ecs_i8_t*)ptr; case EcsOpU8: return *(const ecs_u8_t*)ptr; case EcsOpChar: return *(const ecs_char_t*)ptr; case EcsOpByte: return *(const ecs_u8_t*)ptr; case EcsOpI16: return *(const ecs_i16_t*)ptr; case EcsOpU16: return *(const ecs_u16_t*)ptr; case EcsOpI32: return *(const ecs_i32_t*)ptr; case EcsOpU32: return *(const ecs_u32_t*)ptr; case EcsOpI64: return (double)*(const ecs_i64_t*)ptr; case EcsOpU64: return (double)*(const ecs_u64_t*)ptr; case EcsOpIPtr: return (double)*(const ecs_iptr_t*)ptr; case EcsOpUPtr: return (double)*(const ecs_uptr_t*)ptr; case EcsOpF32: return (double)*(const ecs_f32_t*)ptr; case EcsOpF64: return *(const ecs_f64_t*)ptr; case EcsOpString: return atof(*ECS_CONST_CAST(const char**, ptr)); case EcsOpEnum: return *(const ecs_i32_t*)ptr; case EcsOpBitmask: return *(const ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to float"); break; case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from id to float"); break; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpPrimitive: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); break; } error: return 0; } double ecs_meta_get_float( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); return flecs_meta_to_float(op->kind, ptr); } const char* ecs_meta_get_string( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpString: return *(const char**)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpChar: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); break; } error: return 0; } ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: return *(ecs_entity_t*)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpChar: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: case EcsOpId: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); break; } error: return 0; } ecs_entity_t ecs_meta_get_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: return *(ecs_id_t*)ptr; /* Entities are valid ids */ case EcsOpId: return *(ecs_id_t*)ptr; case EcsOpArray: case EcsOpVector: case EcsOpOpaque: case EcsOpPush: case EcsOpPop: case EcsOpScope: case EcsOpEnum: case EcsOpBitmask: case EcsOpPrimitive: case EcsOpChar: case EcsOpBool: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpString: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); break; } error: return 0; } double ecs_meta_ptr_to_float( ecs_primitive_kind_t type_kind, const void *ptr) { ecs_meta_type_op_kind_t kind = flecs_meta_primitive_to_op_kind(type_kind); return flecs_meta_to_float(kind, ptr); } #endif /** * @file addons/meta/definitions.c * @brief Reflection definitions for builtin types. */ #ifdef FLECS_META /* Opaque type serializatior addon vector */ static int flecs_addon_vec_serialize(const ecs_serializer_t *ser, const void *ptr) { char ***data = ECS_CONST_CAST(char***, ptr); char **addons = data[0]; do { ser->value(ser, ecs_id(ecs_string_t), addons); } while((++ addons)[0]); return 0; } static size_t flecs_addon_vec_count(const void *ptr) { int32_t count = 0; char ***data = ECS_CONST_CAST(char***, ptr); char **addons = data[0]; do { ++ count; } while(addons[count]); return flecs_ito(size_t, count); } /* Initialize reflection data for core components */ static void flecs_meta_import_core_definitions( ecs_world_t *world) { ecs_struct(world, { .entity = ecs_id(EcsComponent), .members = { { .name = "size", .type = ecs_id(ecs_i32_t) }, { .name = "alignment", .type = ecs_id(ecs_i32_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsDefaultChildComponent), .members = { { .name = "component", .type = ecs_id(ecs_entity_t) } } }); ecs_entity_t string_vec = ecs_vector(world, { .entity = ecs_entity(world, { .name = "flecs.core.string_vec_t", .root_sep = "" }), .type = ecs_id(ecs_string_t) }); ecs_entity_t addon_vec = ecs_opaque(world, { .entity = ecs_component(world, { .entity = ecs_entity(world, { .name = "flecs.core.addon_vec_t", .root_sep = "" }), .type = { .size = ECS_SIZEOF(char**), .alignment = ECS_ALIGNOF(char**) } }), .type = { .as_type = string_vec, .serialize = flecs_addon_vec_serialize, .count = flecs_addon_vec_count, } }); ecs_struct(world, { .entity = ecs_entity(world, { .name = "flecs.core.build_info_t", .root_sep = "" }), .members = { { .name = "compiler", .type = ecs_id(ecs_string_t) }, { .name = "addons", .type = addon_vec }, { .name = "version", .type = ecs_id(ecs_string_t) }, { .name = "version_major", .type = ecs_id(ecs_i16_t) }, { .name = "version_minor", .type = ecs_id(ecs_i16_t) }, { .name = "version_patch", .type = ecs_id(ecs_i16_t) }, { .name = "debug", .type = ecs_id(ecs_bool_t) }, { .name = "sanitize", .type = ecs_id(ecs_bool_t) }, { .name = "perf_trace", .type = ecs_id(ecs_bool_t) } } }); } /* Initialize reflection data for doc components */ static void flecs_meta_import_doc_definitions( ecs_world_t *world) { (void)world; #ifdef FLECS_DOC ecs_struct(world, { .entity = ecs_id(EcsDocDescription), .members = { { .name = "value", .type = ecs_id(ecs_string_t) } } }); #endif } /* Initialize reflection data for meta components */ static void flecs_meta_import_meta_definitions( ecs_world_t *world) { ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "TypeKind" }), .constants = { { .name = "PrimitiveType" }, { .name = "BitmaskType" }, { .name = "EnumType" }, { .name = "StructType" }, { .name = "ArrayType" }, { .name = "VectorType" }, { .name = "OpaqueType" } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsType), .members = { { .name = "kind", .type = type_kind } } }); ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "PrimitiveKind" }), .constants = { { .name = "Bool", 1 }, { .name = "Char" }, { .name = "Byte" }, { .name = "U8" }, { .name = "U16" }, { .name = "U32" }, { .name = "U64 "}, { .name = "I8" }, { .name = "I16" }, { .name = "I32" }, { .name = "I64" }, { .name = "F32" }, { .name = "F64" }, { .name = "UPtr "}, { .name = "IPtr" }, { .name = "String" }, { .name = "Entity" }, { .name = "Id" } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsPrimitive), .members = { { .name = "kind", .type = primitive_kind } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMember), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, { .name = "unit", .type = ecs_id(ecs_entity_t) }, { .name = "offset", .type = ecs_id(ecs_i32_t) } } }); ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "value_range" }), .members = { { .name = "min", .type = ecs_id(ecs_f64_t) }, { .name = "max", .type = ecs_id(ecs_f64_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMemberRanges), .members = { { .name = "value", .type = vr }, { .name = "warning", .type = vr }, { .name = "error", .type = vr } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsVector), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsOpaque), .members = { { .name = "as_type", .type = ecs_id(ecs_entity_t) } } }); ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "unit_translation" }), .members = { { .name = "factor", .type = ecs_id(ecs_i32_t) }, { .name = "power", .type = ecs_id(ecs_i32_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnit), .members = { { .name = "symbol", .type = ecs_id(ecs_string_t) }, { .name = "prefix", .type = ecs_id(ecs_entity_t) }, { .name = "base", .type = ecs_id(ecs_entity_t) }, { .name = "over", .type = ecs_id(ecs_entity_t) }, { .name = "translation", .type = ut } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnitPrefix), .members = { { .name = "symbol", .type = ecs_id(ecs_string_t) }, { .name = "translation", .type = ut } } }); /* Meta doc definitions */ #ifdef FLECS_DOC ecs_entity_t meta = ecs_lookup(world, "flecs.meta"); ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); ecs_doc_set_brief(world, ecs_id(EcsType), "Component added to types"); ecs_doc_set_brief(world, ecs_id(EcsTypeSerializer), "Component that stores reflection data in an optimized format"); ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); #endif } void flecs_meta_import_definitions( ecs_world_t *world) { flecs_meta_import_core_definitions(world); flecs_meta_import_doc_definitions(world); flecs_meta_import_meta_definitions(world); } #endif /** * @file addons/meta/meta.c * @brief Meta addon. */ #ifdef FLECS_META /* ecs_string_t lifecycle */ static ECS_COPY(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = ecs_os_strdup(*(const ecs_string_t*)src); }) static ECS_MOVE(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = *(ecs_string_t*)src; *(ecs_string_t*)src = NULL; }) static ECS_DTOR(ecs_string_t, ptr, { ecs_os_free(*(ecs_string_t*)ptr); *(ecs_string_t*)ptr = NULL; }) /* EcsTypeSerializer lifecycle */ void ecs_meta_dtor_serialized( EcsTypeSerializer *ptr) { int32_t i, count = ecs_vec_count(&ptr->ops); ecs_meta_type_op_t *ops = ecs_vec_first(&ptr->ops); for (i = 0; i < count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op->members) { flecs_name_index_free(op->members); } } ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); } static ECS_COPY(EcsTypeSerializer, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); int32_t o, count = ecs_vec_count(&dst->ops); ecs_meta_type_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_type_op_t); for (o = 0; o < count; o ++) { ecs_meta_type_op_t *op = &ops[o]; if (op->members) { op->members = flecs_name_index_copy(op->members); } } }) static ECS_MOVE(EcsTypeSerializer, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = src->ops; src->ops = (ecs_vec_t){0}; }) static ECS_DTOR(EcsTypeSerializer, ptr, { ecs_meta_dtor_serialized(ptr); }) /* EcsStruct lifecycle */ static void flecs_struct_dtor( EcsStruct *ptr) { ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); int32_t i, count = ecs_vec_count(&ptr->members); for (i = 0; i < count; i ++) { ecs_os_free(ECS_CONST_CAST(char*, members[i].name)); } ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); } static ECS_COPY(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); int32_t m, count = ecs_vec_count(&dst->members); for (m = 0; m < count; m ++) { members[m].name = ecs_os_strdup(members[m].name); } }) static ECS_MOVE(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = src->members; src->members = (ecs_vec_t){0}; }) static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) /* EcsEnum lifecycle */ static void flecs_constants_dtor( ecs_map_t *constants) { ecs_map_iter_t it = ecs_map_iter(constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_os_free(c); } ecs_map_fini(constants); } static void flecs_constants_copy( ecs_map_t *dst, const ecs_map_t *src) { ecs_map_copy(dst, src); ecs_map_iter_t it = ecs_map_iter(dst); while (ecs_map_next(&it)) { ecs_enum_constant_t **r = ecs_map_ref(&it, ecs_enum_constant_t); ecs_enum_constant_t *src_c = r[0]; ecs_enum_constant_t *dst_c = ecs_os_calloc_t(ecs_enum_constant_t); *dst_c = *src_c; dst_c->name = ecs_os_strdup(dst_c->name); r[0] = dst_c; } } static ECS_COPY(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); flecs_constants_copy(&dst->constants, &src->constants); }) static ECS_MOVE(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; ecs_os_zeromem(&src->constants); }) static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) /* EcsBitmask lifecycle */ static ECS_COPY(EcsBitmask, dst, src, { /* bitmask constant & enum constant have the same layout */ flecs_constants_dtor(&dst->constants); flecs_constants_copy(&dst->constants, &src->constants); }) static ECS_MOVE(EcsBitmask, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; ecs_os_zeromem(&src->constants); }) static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) /* EcsUnit lifecycle */ static void dtor_unit( EcsUnit *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnit, dst, src, { dtor_unit(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; }) static ECS_MOVE(EcsUnit, dst, src, { dtor_unit(dst); dst->symbol = src->symbol; dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; src->symbol = NULL; src->base = 0; src->over = 0; src->prefix = 0; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) /* EcsUnitPrefix lifecycle */ static void dtor_unit_prefix( EcsUnitPrefix *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnitPrefix, dst, src, { dtor_unit_prefix(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->translation = src->translation; }) static ECS_MOVE(EcsUnitPrefix, dst, src, { dtor_unit_prefix(dst); dst->symbol = src->symbol; dst->translation = src->translation; src->symbol = NULL; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) /* Type initialization */ static const char* flecs_type_kind_str( ecs_type_kind_t kind) { switch(kind) { case EcsPrimitiveType: return "Primitive"; case EcsBitmaskType: return "Bitmask"; case EcsEnumType: return "Enum"; case EcsStructType: return "Struct"; case EcsArrayType: return "Array"; case EcsVectorType: return "Vector"; case EcsOpaqueType: return "Opaque"; default: return "unknown"; } } static int flecs_init_type( ecs_world_t *world, ecs_entity_t type, ecs_type_kind_t kind, ecs_size_t size, ecs_size_t alignment) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); EcsType *meta_type = ecs_ensure(world, type, EcsType); 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 = flecs_default_ctor; } } else { if (meta_type->kind != kind) { ecs_err("type '%s' reregistered as '%s' (was '%s')", ecs_get_name(world, type), flecs_type_kind_str(kind), flecs_type_kind_str(meta_type->kind)); return -1; } } if (!meta_type->existing) { EcsComponent *comp = ecs_ensure(world, type, EcsComponent); comp->size = size; comp->alignment = alignment; ecs_modified(world, type, EcsComponent); } else { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (comp->size < size) { ecs_err("computed size (%d) for '%s' is larger than actual type (%d)", size, ecs_get_name(world, type), comp->size); return -1; } if (comp->alignment < alignment) { ecs_err("computed alignment (%d) for '%s' is larger than actual type (%d)", alignment, ecs_get_name(world, type), comp->alignment); return -1; } if (comp->size == size && comp->alignment != alignment) { if (comp->alignment < alignment) { ecs_err("computed size for '%s' matches with actual type but " "alignment is different (%d vs. %d)", ecs_get_name(world, type), alignment, comp->alignment); } } meta_type->partial = comp->size != size; } meta_type->kind = kind; ecs_modified(world, type, EcsType); return 0; } #define init_type_t(world, type, kind, T) \ flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) static void flecs_set_struct_member( ecs_member_t *member, ecs_entity_t entity, const char *name, ecs_entity_t type, int32_t count, int32_t offset, ecs_entity_t unit, EcsMemberRanges *ranges) { member->member = entity; member->type = type; member->count = count; member->unit = unit; member->offset = offset; if (!count) { member->count = 1; } ecs_os_strset(ECS_CONST_CAST(char**, &member->name), name); if (ranges) { member->range = ranges->value; member->error_range = ranges->error; member->warning_range = ranges->warning; } else { ecs_os_zeromem(&member->range); ecs_os_zeromem(&member->error_range); ecs_os_zeromem(&member->warning_range); } } static int flecs_add_member_to_struct( ecs_world_t *world, ecs_entity_t type, ecs_entity_t member, EcsMember *m, EcsMemberRanges *ranges) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); const char *name = ecs_get_name(world, member); if (!name) { char *path = ecs_get_path(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_path(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_path(world, member); char *ent_path = ecs_get_path(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)) { ecs_entity_t unit_base = ecs_get_target_for( world, m->type, EcsIsA, EcsUnit); if (unit_base) { unit = m->unit = unit_base; } else { unit = m->unit = m->type; } } } EcsStruct *s = ecs_ensure(world, type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); /* First check if member is already added to struct */ ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t i, count = ecs_vec_count(&s->members); for (i = 0; i < count; i ++) { if (members[i].member == member) { flecs_set_struct_member(&members[i], member, name, m->type, m->count, m->offset, unit, ranges); break; } } /* If member wasn't added yet, add a new element to vector */ if (i == count) { ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member, name, m->type, m->count, m->offset, unit, ranges); /* Reobtain members array in case it was reallocated */ members = ecs_vec_first_t(&s->members, ecs_member_t); count ++; } bool explicit_offset = false; if (m->offset) { explicit_offset = true; } /* Compute member offsets and size & alignment of struct */ ecs_size_t size = 0; ecs_size_t alignment = 0; if (!explicit_offset) { for (i = 0; i < count; i ++) { ecs_member_t *elem = &members[i]; ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); if (!mbr_comp) { char *path = ecs_get_path(world, elem->type); ecs_err("member '%s' is not a type", path); ecs_os_free(path); return -1; } ecs_size_t member_size = mbr_comp->size; ecs_size_t member_alignment = mbr_comp->alignment; if (!member_size || !member_alignment) { char *path = ecs_get_path(world, elem->type); ecs_err("member '%s' has 0 size/alignment", path); ecs_os_free(path); return -1; } member_size *= elem->count; size = ECS_ALIGN(size, member_alignment); elem->size = member_size; elem->offset = size; /* Synchronize offset with Member component */ if (elem->member == member) { m->offset = elem->offset; } else { EcsMember *other = ecs_ensure(world, elem->member, EcsMember); other->offset = elem->offset; } size += member_size; if (member_alignment > alignment) { alignment = member_alignment; } } } else { /* If members have explicit offsets, we can't rely on computed * size/alignment values. Calculate size as if this is the last member * instead, since this will validate if the member fits in the struct. * It doesn't matter if the size is smaller than the actual struct size * because flecs_init_type function compares computed size with actual * (component) size to determine if the type is partial. */ ecs_member_t *elem = &members[i]; ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); if (!mbr_comp) { char *path = ecs_get_path(world, elem->type); ecs_err("member '%s' is not a type", path); ecs_os_free(path); return -1; } ecs_size_t member_size = mbr_comp->size; ecs_size_t member_alignment = mbr_comp->alignment; if (!member_size || !member_alignment) { char *path = ecs_get_path(world, elem->type); ecs_err("member '%s' has 0 size/alignment", path); ecs_os_free(path); return -1; } member_size *= elem->count; elem->size = member_size; size = elem->offset + member_size; const EcsComponent* comp = ecs_get(world, type, EcsComponent); if (comp) { alignment = comp->alignment; } else { alignment = member_alignment; } } if (size == 0) { ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); return -1; } if (alignment == 0) { ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); return -1; } /* Align struct size to struct alignment */ size = ECS_ALIGN(size, alignment); ecs_modified(world, type, EcsStruct); /* Do this last as it triggers the update of EcsTypeSerializer */ if (flecs_init_type(world, type, EcsStructType, size, alignment)) { return -1; } /* If current struct is also a member, assign to itself */ if (ecs_has(world, type, EcsMember)) { EcsMember *type_mbr = ecs_ensure(world, type, EcsMember); ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); type_mbr->type = type; type_mbr->count = 1; ecs_modified(world, type, EcsMember); } return 0; } static int flecs_add_constant_to_enum( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsEnum *ptr = ecs_ensure(world, type, EcsEnum); /* Remove constant from map if it was already added */ ecs_map_iter_t it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); if (c->constant == e) { ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); } } /* Check if constant sets explicit value */ int32_t value = 0; bool value_set = false; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { char *path = ecs_get_path(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_second( 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_path(world, e); ecs_err("conflicting constant value %d for '%s' (other is '%s')", value, path, c->name); ecs_os_free(path); return -1; } } else { if (c->value >= value) { value = c->value + 1; } } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_enum_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, ecs_enum_constant_t, (ecs_map_key_t)value); c->name = ecs_os_strdup(ecs_get_name(world, e)); c->value = value; c->constant = e; ecs_i32_t *cptr = ecs_ensure_pair_second( world, e, EcsConstant, ecs_i32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; cptr = ecs_ensure_id(world, e, type); cptr[0] = value; return 0; } static int flecs_add_constant_to_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsBitmask *ptr = ecs_ensure(world, type, EcsBitmask); /* Remove constant from map if it was already added */ ecs_map_iter_t it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); if (c->constant == e) { ecs_os_free(ECS_CONST_CAST(char*, c->name)); ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); } } /* Check if constant sets explicit value */ uint32_t value = 1; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { char *path = ecs_get_path(world, e); ecs_err("expected u32 type for bitmask constant '%s'", path); ecs_os_free(path); return -1; } const uint32_t *value_ptr = ecs_get_pair_second( 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_path(world, e); ecs_err("conflicting constant value for '%s' (other is '%s')", path, c->name); ecs_os_free(path); return -1; } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_bitmask_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, ecs_bitmask_constant_t, value); c->name = ecs_os_strdup(ecs_get_name(world, e)); c->value = value; c->constant = e; ecs_u32_t *cptr = ecs_ensure_pair_second( world, e, EcsConstant, ecs_u32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; cptr = ecs_ensure_id(world, e, type); cptr[0] = value; return 0; } static void flecs_set_primitive(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsPrimitive *type = ecs_field(it, EcsPrimitive, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; switch(type->kind) { case EcsBool: init_type_t(world, e, EcsPrimitiveType, bool); break; case EcsChar: init_type_t(world, e, EcsPrimitiveType, char); break; case EcsByte: init_type_t(world, e, EcsPrimitiveType, bool); break; case EcsU8: init_type_t(world, e, EcsPrimitiveType, uint8_t); break; case EcsU16: init_type_t(world, e, EcsPrimitiveType, uint16_t); break; case EcsU32: init_type_t(world, e, EcsPrimitiveType, uint32_t); break; case EcsU64: init_type_t(world, e, EcsPrimitiveType, uint64_t); break; case EcsI8: init_type_t(world, e, EcsPrimitiveType, int8_t); break; case EcsI16: init_type_t(world, e, EcsPrimitiveType, int16_t); break; case EcsI32: init_type_t(world, e, EcsPrimitiveType, int32_t); break; case EcsI64: init_type_t(world, e, EcsPrimitiveType, int64_t); break; case EcsF32: init_type_t(world, e, EcsPrimitiveType, float); break; case EcsF64: init_type_t(world, e, EcsPrimitiveType, double); break; case EcsUPtr: init_type_t(world, e, EcsPrimitiveType, uintptr_t); break; case EcsIPtr: init_type_t(world, e, EcsPrimitiveType, intptr_t); break; case EcsString: init_type_t(world, e, EcsPrimitiveType, char*); break; case EcsEntity: init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); break; case EcsId: init_type_t(world, e, EcsPrimitiveType, ecs_id_t); break; } } } static void flecs_set_member(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMember *member = ecs_field(it, EcsMember, 0); EcsMemberRanges *ranges = ecs_table_get_id(world, it->table, ecs_id(EcsMemberRanges), it->offset); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); continue; } flecs_add_member_to_struct(world, parent, e, &member[i], ranges ? &ranges[i] : NULL); } } static void flecs_set_member_ranges(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMemberRanges *ranges = ecs_field(it, EcsMemberRanges, 0); EcsMember *member = ecs_table_get_id(world, it->table, ecs_id(EcsMember), it->offset); if (!member) { return; } int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); continue; } flecs_add_member_to_struct(world, parent, e, &member[i], &ranges[i]); } } static void flecs_add_enum(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { continue; } ecs_add_id(world, e, EcsExclusive); ecs_add_id(world, e, EcsOneOf); ecs_add_id(world, e, EcsPairIsTag); } } 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, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; int32_t elem_count = array[i].count; if (!elem_type) { ecs_err("array '%s' has no element type", ecs_get_name(world, e)); continue; } if (!elem_count) { ecs_err("array '%s' has size 0", ecs_get_name(world, e)); continue; } 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, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; if (!elem_type) { ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); continue; } if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { continue; } } } static void flecs_set_custom_type(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsOpaque *serialize = ecs_field(it, EcsOpaque, 0); 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, 0); ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; flecs_unit_validate(world, e, &u[i]); } } static void flecs_unit_quantity_monitor(ecs_iter_t *it) { 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; EcsType *type = ecs_field(it, EcsType, 0); int i; for (i = 0; i < it->count; i ++) { /* If a component is defined from reflection data, configure it with the * default constructor. This ensures that a new component value does not * contain uninitialized memory, which could cause serializers to crash * when for example inspecting string fields. */ if (!type->existing) { ecs_entity_t e = it->entities[i]; const ecs_type_info_t *ti = ecs_get_type_info(world, e); if (!ti || !ti->hooks.ctor) { ecs_set_hooks_id(world, e, &(ecs_type_hooks_t){ .ctor = flecs_default_ctor }); } } } } static void flecs_member_on_set(ecs_iter_t *it) { EcsMember *mbr = it->ptrs[0]; if (!mbr->count) { mbr->count = 1; } } void FlecsMetaImport( ecs_world_t *world) { ECS_MODULE(world, FlecsMeta); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTypeSerializer); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsType), .name = "type", .symbol = "EcsType" }), .type.size = sizeof(EcsType), .type.alignment = ECS_ALIGNOF(EcsType) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsPrimitive), .name = "primitive", .symbol = "EcsPrimitive" }), .type.size = sizeof(EcsPrimitive), .type.alignment = ECS_ALIGNOF(EcsPrimitive) }); ecs_component(world, { .entity = ecs_entity(world, { .id = EcsConstant, .name = "constant", .symbol = "EcsConstant" }) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsEnum), .name = "enum", .symbol = "EcsEnum" }), .type.size = sizeof(EcsEnum), .type.alignment = ECS_ALIGNOF(EcsEnum) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsBitmask), .name = "bitmask", .symbol = "EcsBitmask" }), .type.size = sizeof(EcsBitmask), .type.alignment = ECS_ALIGNOF(EcsBitmask) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsMember), .name = "member", .symbol = "EcsMember" }), .type.size = sizeof(EcsMember), .type.alignment = ECS_ALIGNOF(EcsMember) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsMemberRanges), .name = "member_ranges", .symbol = "EcsMemberRanges" }), .type.size = sizeof(EcsMemberRanges), .type.alignment = ECS_ALIGNOF(EcsMemberRanges) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsStruct), .name = "struct", .symbol = "EcsStruct" }), .type.size = sizeof(EcsStruct), .type.alignment = ECS_ALIGNOF(EcsStruct) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsArray), .name = "array", .symbol = "EcsArray" }), .type.size = sizeof(EcsArray), .type.alignment = ECS_ALIGNOF(EcsArray) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsVector), .name = "vector", .symbol = "EcsVector" }), .type.size = sizeof(EcsVector), .type.alignment = ECS_ALIGNOF(EcsVector) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsOpaque), .name = "opaque", .symbol = "EcsOpaque" }), .type.size = sizeof(EcsOpaque), .type.alignment = ECS_ALIGNOF(EcsOpaque) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsUnit), .name = "unit", .symbol = "EcsUnit", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) }), .type.size = sizeof(EcsUnit), .type.alignment = ECS_ALIGNOF(EcsUnit) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsUnitPrefix), .name = "unit_prefix", .symbol = "EcsUnitPrefix", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) }), .type.size = sizeof(EcsUnitPrefix), .type.alignment = ECS_ALIGNOF(EcsUnitPrefix) }); ecs_component(world, { .entity = ecs_entity(world, { .id = EcsQuantity, .name = "quantity", .symbol = "EcsQuantity", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsInherit)) }) }); ecs_set_hooks(world, EcsType, { .ctor = flecs_default_ctor }); ecs_set_hooks(world, EcsTypeSerializer, { .ctor = flecs_default_ctor, .move = ecs_move(EcsTypeSerializer), .copy = ecs_copy(EcsTypeSerializer), .dtor = ecs_dtor(EcsTypeSerializer) }); ecs_set_hooks(world, EcsStruct, { .ctor = flecs_default_ctor, .move = ecs_move(EcsStruct), .copy = ecs_copy(EcsStruct), .dtor = ecs_dtor(EcsStruct) }); ecs_set_hooks(world, EcsMember, { .ctor = flecs_default_ctor, .on_set = flecs_member_on_set }); ecs_set_hooks(world, EcsMemberRanges, { .ctor = flecs_default_ctor }); ecs_set_hooks(world, EcsEnum, { .ctor = flecs_default_ctor, .move = ecs_move(EcsEnum), .copy = ecs_copy(EcsEnum), .dtor = ecs_dtor(EcsEnum) }); ecs_set_hooks(world, EcsBitmask, { .ctor = flecs_default_ctor, .move = ecs_move(EcsBitmask), .copy = ecs_copy(EcsBitmask), .dtor = ecs_dtor(EcsBitmask) }); ecs_set_hooks(world, EcsUnit, { .ctor = flecs_default_ctor, .move = ecs_move(EcsUnit), .copy = ecs_copy(EcsUnit), .dtor = ecs_dtor(EcsUnit) }); ecs_set_hooks(world, EcsUnitPrefix, { .ctor = flecs_default_ctor, .move = ecs_move(EcsUnitPrefix), .copy = ecs_copy(EcsUnitPrefix), .dtor = ecs_dtor(EcsUnitPrefix) }); /* Register triggers to finalize type information from component data */ ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ world, EcsFlecsInternals); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsPrimitive), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_primitive }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMember), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMemberRanges), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member_ranges }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsEnum), .src.id = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_enum }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsBitmask), .src.id = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask }); ecs_observer(world, { .query.terms[0] = { .id = EcsConstant, .src.id = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_constant }); ecs_observer(world, { .query.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_add_constant }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsArray), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_array }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsVector), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_vector }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsOpaque), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_custom_type }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsUnit), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_unit }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsType), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_serialized_init }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsType), .src.id = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_init_default_ctor }); ecs_observer(world, { .query.terms = { { .id = ecs_id(EcsUnit) }, { .id = EcsQuantity } }, .events = { EcsMonitor }, .callback = flecs_unit_quantity_monitor }); ecs_set_scope(world, old_scope); /* Initialize primitive types */ #define ECS_PRIMITIVE(world, type, primitive_kind)\ ecs_entity_init(world, &(ecs_entity_desc_t){\ .id = ecs_id(ecs_##type##_t),\ .name = #type,\ .symbol = #type });\ ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ .kind = primitive_kind\ }); ECS_PRIMITIVE(world, bool, EcsBool); ECS_PRIMITIVE(world, char, EcsChar); ECS_PRIMITIVE(world, byte, EcsByte); ECS_PRIMITIVE(world, u8, EcsU8); ECS_PRIMITIVE(world, u16, EcsU16); ECS_PRIMITIVE(world, u32, EcsU32); ECS_PRIMITIVE(world, u64, EcsU64); ECS_PRIMITIVE(world, uptr, EcsUPtr); ECS_PRIMITIVE(world, i8, EcsI8); ECS_PRIMITIVE(world, i16, EcsI16); ECS_PRIMITIVE(world, i32, EcsI32); ECS_PRIMITIVE(world, i64, EcsI64); ECS_PRIMITIVE(world, iptr, EcsIPtr); ECS_PRIMITIVE(world, f32, EcsF32); ECS_PRIMITIVE(world, f64, EcsF64); ECS_PRIMITIVE(world, string, EcsString); ECS_PRIMITIVE(world, entity, EcsEntity); ECS_PRIMITIVE(world, id, EcsId); #undef ECS_PRIMITIVE ecs_set_hooks(world, ecs_string_t, { .ctor = flecs_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_set(world, ecs_id(EcsStruct), EcsDefaultChildComponent, {ecs_id(EcsMember)}); ecs_set(world, ecs_id(EcsMember), EcsDefaultChildComponent, {ecs_id(EcsMember)}); ecs_set(world, ecs_id(EcsEnum), EcsDefaultChildComponent, {EcsConstant}); ecs_set(world, ecs_id(EcsBitmask), EcsDefaultChildComponent, {EcsConstant}); /* Relationship properties */ ecs_add_id(world, EcsQuantity, EcsExclusive); ecs_add_id(world, EcsQuantity, EcsPairIsTag); /* Import reflection definitions for builtin types */ flecs_meta_import_definitions(world); } #endif /** * @file addons/meta/serialized.c * @brief Serialize type into flat operations array to speed up deserialization. */ #ifdef FLECS_META static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops); ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { return EcsOpPrimitive + kind; } static ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); return comp->size; } static ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) { ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t); op->kind = kind; op->offset = 0; op->count = 1; op->op_count = 1; op->size = 0; op->name = NULL; op->members = NULL; op->type = 0; op->member_index = 0; return op; } static ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index); ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); return op; } static int flecs_meta_serialize_primitive( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); if (!ptr) { char *name = ecs_get_path(world, type); ecs_err("entity '%s' is not a primitive type", name); ecs_os_free(name); return -1; } ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind)); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_enum( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); op->offset = offset; op->type = type; op->size = ECS_SIZEOF(ecs_i32_t); return 0; } static int flecs_meta_serialize_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); op->offset = offset; op->type = type; op->size = ECS_SIZEOF(ecs_u32_t); return 0; } static int flecs_meta_serialize_array( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_array_component( ecs_world_t *world, ecs_entity_t type, ecs_vec_t *ops) { const EcsArray *ptr = ecs_get(world, type, EcsArray); if (!ptr) { return -1; /* Should never happen, will trigger internal error */ } if (flecs_meta_serialize_type(world, ptr->type, 0, ops) != 0) { return -1; } ecs_meta_type_op_t *first = ecs_vec_first(ops); first->count = ptr->count; return 0; } static int flecs_meta_serialize_vector( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_custom_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_struct( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsStruct *ptr = ecs_get(world, type, EcsStruct); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur, first = ecs_vec_count(ops); ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); ecs_member_t *members = ecs_vec_first(&ptr->members); int32_t i, count = ecs_vec_count(&ptr->members); ecs_hashmap_t *member_index = NULL; if (count) { op->members = member_index = flecs_name_index_new( world, &world->allocator); } for (i = 0; i < count; i ++) { ecs_member_t *member = &members[i]; cur = ecs_vec_count(ops); if (flecs_meta_serialize_type(world, member->type, offset + member->offset, ops) != 0) { continue; } op = flecs_meta_ops_get(ops, cur); if (!op->type) { op->type = member->type; } if (op->count <= 1) { op->count = member->count; } const char *member_name = member->name; op->name = member_name; op->op_count = ecs_vec_count(ops) - cur; op->member_index = i; flecs_name_index_ensure( member_index, flecs_ito(uint64_t, cur - first - 1), member_name, 0, 0); } ecs_meta_type_op_t *pop = flecs_meta_ops_add(ops, EcsOpPop); pop->type = type; flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; return 0; } static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsType *ptr = ecs_get(world, type, EcsType); if (!ptr) { char *path = ecs_get_path(world, type); ecs_err("missing EcsType for type %s'", path); ecs_os_free(path); return -1; } switch(ptr->kind) { case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); } return 0; } static int flecs_meta_serialize_component( ecs_world_t *world, ecs_entity_t type, ecs_vec_t *ops) { const EcsType *ptr = ecs_get(world, type, EcsType); if (!ptr) { char *path = ecs_get_path(world, type); ecs_err("missing EcsType for type %s'", path); ecs_os_free(path); return -1; } if (ptr->kind == EcsArrayType) { return flecs_meta_serialize_array_component(world, type, ops); } else { return flecs_meta_serialize_type(world, type, 0, ops); } } void ecs_meta_type_serialized_init( ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_vec_t ops; ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); flecs_meta_serialize_component(world, e, &ops); EcsTypeSerializer *ptr = ecs_ensure( world, e, EcsTypeSerializer); if (ptr->ops.array) { ecs_meta_dtor_serialized(ptr); } ptr->ops = ops; } } #endif /** * @file addons/os_api_impl/os_api_impl.c * @brief Builtin implementation for OS API. */ #ifdef FLECS_OS_API_IMPL #ifdef ECS_TARGET_WINDOWS /** * @file addons/os_api_impl/posix_impl.inl * @brief Builtin Windows implementation for OS API. */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include typedef struct ecs_win_thread_t { HANDLE thread; ecs_os_thread_callback_t callback; void *arg; } ecs_win_thread_t; static DWORD flecs_win_thread(void *ptr) { ecs_win_thread_t *thread = ptr; thread->callback(thread->arg); return 0; } static ecs_os_thread_t win_thread_new( ecs_os_thread_callback_t callback, void *arg) { ecs_win_thread_t *thread = ecs_os_malloc_t(ecs_win_thread_t); thread->arg= arg; thread->callback = callback; thread->thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)flecs_win_thread, thread, 0, NULL); return (ecs_os_thread_t)(uintptr_t)thread; } static void* win_thread_join( ecs_os_thread_t thr) { ecs_win_thread_t *thread = (ecs_win_thread_t*)(uintptr_t)thr; DWORD r = WaitForSingleObject(thread->thread, INFINITE); if (r == WAIT_FAILED) { ecs_err("win_thread_join: WaitForSingleObject failed"); } ecs_os_free(thread); return NULL; } static ecs_os_thread_id_t win_thread_self(void) { return (ecs_os_thread_id_t)GetCurrentThreadId(); } static int32_t win_ainc( int32_t *count) { return InterlockedIncrement((volatile long*)count); } static int32_t win_adec( int32_t *count) { return InterlockedDecrement((volatile long*)count); } static int64_t win_lainc( int64_t *count) { return InterlockedIncrement64(count); } static int64_t win_ladec( int64_t *count) { return InterlockedDecrement64(count); } static ecs_os_mutex_t win_mutex_new(void) { CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); InitializeCriticalSection(mutex); return (ecs_os_mutex_t)(uintptr_t)mutex; } static void win_mutex_free( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; DeleteCriticalSection(mutex); ecs_os_free(mutex); } static void win_mutex_lock( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; EnterCriticalSection(mutex); } static void win_mutex_unlock( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; LeaveCriticalSection(mutex); } static ecs_os_cond_t win_cond_new(void) { CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); InitializeConditionVariable(cond); return (ecs_os_cond_t)(uintptr_t)cond; } static void win_cond_free( ecs_os_cond_t c) { (void)c; } static void win_cond_signal( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeConditionVariable(cond); } static void win_cond_broadcast( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeAllConditionVariable(cond); } static void win_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; SleepConditionVariableCS(cond, mutex, INFINITE); } static bool win_time_initialized; static double win_time_freq; static LARGE_INTEGER win_time_start; static ULONG win_current_resolution; static void win_time_setup(void) { if ( win_time_initialized) { return; } win_time_initialized = true; LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&win_time_start); win_time_freq = (double)freq.QuadPart / 1000000000.0; } static void win_sleep( int32_t sec, int32_t nanosec) { HANDLE timer; LARGE_INTEGER ft; ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); } static void win_enable_high_timer_resolution(bool enable) { HMODULE hntdll = GetModuleHandle(TEXT("ntdll.dll")); if (!hntdll) { return; } union { LONG (__stdcall *f)( ULONG desired, BOOLEAN set, ULONG * current); FARPROC p; } func; func.p = GetProcAddress(hntdll, "NtSetTimerResolution"); if(!func.p) { return; } ULONG current, resolution = 10000; /* 1 ms */ if (!enable && win_current_resolution) { func.f(win_current_resolution, 0, ¤t); win_current_resolution = 0; return; } else if (!enable) { return; } if (resolution == win_current_resolution) { return; } if (win_current_resolution) { func.f(win_current_resolution, 0, ¤t); } if (func.f(resolution, 1, ¤t)) { /* Try setting a lower resolution */ resolution *= 2; if(func.f(resolution, 1, ¤t)) return; } win_current_resolution = resolution; } static uint64_t win_time_now(void) { uint64_t now; LARGE_INTEGER qpc_t; QueryPerformanceCounter(&qpc_t); now = (uint64_t)((double)qpc_t.QuadPart / win_time_freq); return now; } static void win_fini(void) { if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(false); } } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = win_thread_new; api.thread_join_ = win_thread_join; api.thread_self_ = win_thread_self; api.task_new_ = win_thread_new; api.task_join_ = win_thread_join; api.ainc_ = win_ainc; api.adec_ = win_adec; api.lainc_ = win_lainc; api.ladec_ = win_ladec; api.mutex_new_ = win_mutex_new; api.mutex_free_ = win_mutex_free; api.mutex_lock_ = win_mutex_lock; api.mutex_unlock_ = win_mutex_unlock; api.cond_new_ = win_cond_new; api.cond_free_ = win_cond_free; api.cond_signal_ = win_cond_signal; api.cond_broadcast_ = win_cond_broadcast; api.cond_wait_ = win_cond_wait; api.sleep_ = win_sleep; api.now_ = win_time_now; api.fini_ = win_fini; win_time_setup(); if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(true); } ecs_os_set_api(&api); } #else /** * @file addons/os_api_impl/posix_impl.inl * @brief Builtin POSIX implementation for OS API. */ #include "pthread.h" #if defined(__APPLE__) && defined(__MACH__) #include #elif defined(__EMSCRIPTEN__) #include #else #include #endif /* This mutex is used to emulate atomic operations when the gnu builtins are * not supported. This is probably not very fast but if the compiler doesn't * support the gnu built-ins, then speed is probably not a priority. */ #ifndef __GNUC__ static pthread_mutex_t atomic_mutex = PTHREAD_MUTEX_INITIALIZER; #endif static ecs_os_thread_t posix_thread_new( ecs_os_thread_callback_t callback, void *arg) { pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); if (pthread_create (thread, NULL, callback, arg) != 0) { ecs_os_abort(); } return (ecs_os_thread_t)(uintptr_t)thread; } static void* posix_thread_join( ecs_os_thread_t thread) { void *arg; pthread_t *thr = (pthread_t*)(uintptr_t)thread; pthread_join(*thr, &arg); ecs_os_free(thr); return arg; } static ecs_os_thread_id_t posix_thread_self(void) { return (ecs_os_thread_id_t)pthread_self(); } static int32_t posix_ainc( int32_t *count) { int value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) += 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int32_t posix_adec( int32_t *count) { int32_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) -= 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int64_t posix_lainc( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) += 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static int64_t posix_ladec( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else if (pthread_mutex_lock(&atomic_mutex)) { abort(); } value = (*count) -= 1; if (pthread_mutex_unlock(&atomic_mutex)) { abort(); } return value; #endif } static ecs_os_mutex_t posix_mutex_new(void) { pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); if (pthread_mutex_init(mutex, NULL)) { abort(); } return (ecs_os_mutex_t)(uintptr_t)mutex; } static void posix_mutex_free( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; pthread_mutex_destroy(mutex); ecs_os_free(mutex); } static void posix_mutex_lock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_lock(mutex)) { abort(); } } static void posix_mutex_unlock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_unlock(mutex)) { abort(); } } static ecs_os_cond_t posix_cond_new(void) { pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); if (pthread_cond_init(cond, NULL)) { abort(); } return (ecs_os_cond_t)(uintptr_t)cond; } static void posix_cond_free( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_destroy(cond)) { abort(); } ecs_os_free(cond); } static void posix_cond_signal( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_signal(cond)) { abort(); } } static void posix_cond_broadcast( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_broadcast(cond)) { abort(); } } static void posix_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_cond_wait(cond, mutex)) { abort(); } } static bool posix_time_initialized; #if defined(__APPLE__) && defined(__MACH__) static mach_timebase_info_data_t posix_osx_timebase; static uint64_t posix_time_start; #else static uint64_t posix_time_start; #endif static void posix_time_setup(void) { if (posix_time_initialized) { return; } posix_time_initialized = true; #if defined(__APPLE__) && defined(__MACH__) mach_timebase_info(&posix_osx_timebase); posix_time_start = mach_absolute_time(); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; #endif } static void posix_sleep( int32_t sec, int32_t nanosec) { struct timespec sleepTime; ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); sleepTime.tv_sec = sec; sleepTime.tv_nsec = nanosec; if (nanosleep(&sleepTime, NULL)) { ecs_err("nanosleep failed"); } } /* prevent 64-bit overflow when computing relative timestamp see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 */ #if defined(ECS_TARGET_DARWIN) static int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { int64_t q = value / denom; int64_t r = value % denom; return q * numer + r * numer / denom; } #endif static uint64_t posix_time_now(void) { ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); uint64_t now; #if defined(ECS_TARGET_DARWIN) now = (uint64_t) posix_int64_muldiv( (int64_t)mach_absolute_time(), (int64_t)posix_osx_timebase.numer, (int64_t)posix_osx_timebase.denom); #elif defined(__EMSCRIPTEN__) now = (long long)(emscripten_get_now() * 1000.0 * 1000); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); #endif return now; } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = posix_thread_new; api.thread_join_ = posix_thread_join; api.thread_self_ = posix_thread_self; api.task_new_ = posix_thread_new; api.task_join_ = posix_thread_join; api.ainc_ = posix_ainc; api.adec_ = posix_adec; api.lainc_ = posix_lainc; api.ladec_ = posix_ladec; api.mutex_new_ = posix_mutex_new; api.mutex_free_ = posix_mutex_free; api.mutex_lock_ = posix_mutex_lock; api.mutex_unlock_ = posix_mutex_unlock; api.cond_new_ = posix_cond_new; api.cond_free_ = posix_cond_free; api.cond_signal_ = posix_cond_signal; api.cond_broadcast_ = posix_cond_broadcast; api.cond_wait_ = posix_cond_wait; api.sleep_ = posix_sleep; api.now_ = posix_time_now; posix_time_setup(); ecs_os_set_api(&api); } #endif #endif /** * @file addons/pipeline/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->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_ref_t *src = &term->src; if (term->inout == EcsInOutNone || term->inout == EcsInOutFilter) { return false; } ecs_id_t id = term->id; int16_t oper = term->oper; int16_t inout = term->inout; bool from_any = ecs_term_match_0(term); bool from_this = ecs_term_match_this(term); bool is_shared = !from_any && (!from_this || !(src->id & EcsSelf)); ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); if (from_this && ws >= WriteStateToStage) { /* A staged write could have happened for an id that's matched on the * main storage. Even if the id isn't read, still insert a merge so that * a write to the main storage after the staged write doesn't get * overwritten. */ return true; } if (inout == EcsInOutDefault) { if (from_any) { /* If no inout kind is specified for terms without a source, this is * not interpreted as a read/write annotation but just a (component) * id that's passed to a system. */ return false; } else if (is_shared) { inout = EcsIn; } else { /* Default for owned terms is InOut */ inout = EcsInOut; } } if (oper == EcsNot && inout == EcsOut) { /* If a Not term is combined with Out, it signals that the system * intends to add a component that the entity doesn't yet have */ from_any = true; } if (from_any) { switch(inout) { case EcsOut: case EcsInOut: if (is_active) { /* Only flag component as written if system is active */ flecs_pipeline_set_write_state(write_state, id); } break; case EcsInOutDefault: case EcsInOutNone: case EcsInOutFilter: case EcsIn: break; } switch(inout) { case EcsIn: case EcsInOut: if (ws == WriteStateToStage) { /* If a system does a get/ensure, the component is fetched from * the main store so it must be merged first */ return true; } /* fall through */ case EcsInOutDefault: case EcsInOutNone: case EcsInOutFilter: case EcsOut: break; } } return false; } static bool flecs_pipeline_check_terms( ecs_world_t *world, ecs_query_t *query, bool is_active, ecs_write_state_t *ws) { bool needs_merge = false; ecs_term_t *terms = query->terms; int32_t t, term_count = query->term_count; /* Check This terms first. This way if a term indicating writing to a stage * was added before the term, it won't cause merging. */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } /* Now check staged terms */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (!ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } return needs_merge; } static EcsPoly* flecs_pipeline_term_system( ecs_iter_t *it) { int32_t index = ecs_table_get_column_index( it->real_world, it->table, flecs_poly_id(EcsSystem)); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); return poly; } static bool flecs_pipeline_build( ecs_world_t *world, ecs_pipeline_state_t *pq) { ecs_iter_t it = ecs_query_iter(world, pq->query); int32_t new_match_count = ecs_query_match_count(pq->query); if (pq->match_count == new_match_count) { /* No need to rebuild the pipeline */ ecs_iter_fini(&it); return false; } world->info.pipeline_build_count_total ++; pq->rebuild_count ++; ecs_allocator_t *a = &world->allocator; ecs_pipeline_op_t *op = NULL; ecs_write_state_t ws = {0}; ecs_map_init(&ws.ids, a); ecs_map_init(&ws.wildcard_ids, a); ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); ecs_vec_reset_t(a, &pq->systems, ecs_entity_t); bool multi_threaded = false; bool immediate = false; bool first = true; /* Iterate systems in pipeline, add ops for running / merging */ while (ecs_query_next(&it)) { EcsPoly *poly = flecs_pipeline_term_system(&it); bool is_active = ecs_table_get_type_index( world, it.table, EcsEmpty) == -1; int32_t i; for (i = 0; i < it.count; i ++) { flecs_poly_assert(poly[i].poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly[i].poly; ecs_query_t *q = sys->query; bool needs_merge = false; needs_merge = flecs_pipeline_check_terms( world, q, is_active, &ws); if (is_active) { if (first) { multi_threaded = sys->multi_threaded; immediate = sys->immediate; first = false; } if (sys->multi_threaded != multi_threaded) { needs_merge = true; multi_threaded = sys->multi_threaded; } if (sys->immediate != immediate) { needs_merge = true; immediate = sys->immediate; } } if (immediate) { needs_merge = true; } if (needs_merge) { /* After merge all components will be merged, so reset state */ flecs_pipeline_reset_write_state(&ws); /* An inactive system can insert a merge if one of its * components got written, which could make the system * active. If this is the only system in the pipeline operation, * it results in an empty operation when we get here. If that's * the case, reuse the empty operation for the next op. */ if (op && op->count) { op = NULL; } /* Re-evaluate columns to set write flags if system is active. * If system is inactive, it can't write anything and so it * should not insert unnecessary merges. */ needs_merge = false; if (is_active) { needs_merge = flecs_pipeline_check_terms( world, q, true, &ws); } /* The component states were just reset, so if we conclude that * another merge is needed something is wrong. */ ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); } if (!op) { op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); op->offset = ecs_vec_count(&pq->systems); op->count = 0; op->multi_threaded = false; op->immediate = false; op->time_spent = 0; op->commands_enqueued = 0; } /* Don't increase count for inactive systems, as they are ignored by * the query used to run the pipeline. */ if (is_active) { ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] = it.entities[i]; if (!op->count) { op->multi_threaded = multi_threaded; op->immediate = immediate; } op->count ++; } } } if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { ecs_vec_remove_last(&pq->ops); } ecs_map_fini(&ws.ids); ecs_map_fini(&ws.wildcard_ids); op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); if (!op) { ecs_dbg("#[green]pipeline#[reset] is empty"); return true; } else { /* Add schedule to debug tracing */ ecs_dbg("#[bold]pipeline rebuild"); ecs_log_push_1(); ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", op->multi_threaded, !op->immediate); ecs_log_push_1(); int32_t i, count = ecs_vec_count(&pq->systems); int32_t op_index = 0, ran_since_merge = 0; ecs_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); flecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t *sys = (ecs_system_t*)poly->poly; #ifdef FLECS_LOG_1 char *path = ecs_get_path(world, system); const char *doc_name = NULL; #ifdef FLECS_DOC const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, EcsDocDescription, EcsName); if (doc_name_id) { doc_name = doc_name_id->value; } #endif if (doc_name) { ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); } else { ecs_dbg("#[green]system#[reset] %s", path); } ecs_os_free(path); #endif ecs_assert(op[op_index].offset + ran_since_merge == i, ECS_INTERNAL_ERROR, NULL); ran_since_merge ++; if (ran_since_merge == op[op_index].count) { ecs_dbg("#[magenta]merge#[reset]"); ecs_log_pop_1(); ran_since_merge = 0; op_index ++; if (op_index < ecs_vec_count(&pq->ops)) { ecs_dbg( "#[green]schedule#[reset]: " "threading: %d, staging: %d:", op[op_index].multi_threaded, !op[op_index].immediate); } ecs_log_push_1(); } if (sys->last_frame == (world->info.frame_count_total + 1)) { if (op_index < ecs_vec_count(&pq->ops)) { pq->cur_op = &op[op_index]; pq->cur_i = i; } else { pq->cur_op = NULL; pq->cur_i = 0; } } } ecs_log_pop_1(); ecs_log_pop_1(); } pq->match_count = new_match_count; 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) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot update pipeline while world is in readonly mode"); /* If any entity mutations happened that could have affected query matching * notify appropriate queries so caches are up to date. This includes the * pipeline query. */ if (start_of_frame) { ecs_run_aperiodic(world, 0); } ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); bool rebuilt = flecs_pipeline_build(world, pq); if (start_of_frame) { /* Initialize iterators */ int32_t i, count = pq->iter_count; for (i = 0; i < count; i ++) { ecs_world_t *stage = ecs_get_stage(world, i); pq->iters[i] = ecs_query_iter(stage, pq->query); } pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); pq->cur_i = 0; } else { flecs_pipeline_next_system(pq); } return rebuilt; } void ecs_run_pipeline( ecs_world_t *world, ecs_entity_t pipeline, ecs_ftime_t delta_time) { if (!pipeline) { pipeline = world->pipeline; } /* create any worker task threads request */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } EcsPipeline *p = ECS_CONST_CAST(EcsPipeline*, ecs_get(world, pipeline, EcsPipeline)); flecs_workers_progress(world, p->state, delta_time); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } } int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time) { ecs_pipeline_state_t* pq = world->pq; ecs_pipeline_op_t* op = pq->cur_op; int32_t i = pq->cur_i; ecs_assert(!stage_index || op->multi_threaded, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(&pq->systems); ecs_entity_t* systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); int32_t ran_since_merge = i - op->offset; for (; i < count; i++) { ecs_entity_t system = systems[i]; const EcsPoly* poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); flecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t* sys = (ecs_system_t*)poly->poly; /* Keep track of the last frame for which the system has ran, so we * know from where to resume the schedule in case the schedule * changes during a merge. */ sys->last_frame = world->info.frame_count_total + 1; ecs_stage_t* s = NULL; if (!op->immediate) { /* If system is immediate it operates on the actual world, not * the stage. Only pass stage to system if it's readonly. */ s = stage; } flecs_run_intern(world, s, system, sys, stage_index, stage_count, delta_time, NULL); world->info.systems_ran_frame++; ran_since_merge++; if (ran_since_merge == op->count) { /* Merge */ break; } } return i; } void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); int32_t stage_index = ecs_stage_get_id(stage->thread_ctx); int32_t stage_count = ecs_get_stage_count(world); bool multi_threaded = world->worker_cond != 0; ecs_assert(!stage_index, ECS_INVALID_OPERATION, "cannot run pipeline on stage"); // Update the pipeline the workers will execute world->pq = pq; // Update the pipeline before waking the workers. flecs_pipeline_update(world, pq, true); // If there are no operations to execute in the pipeline bail early, // no need to wake the workers since they have nothing to do. while (pq->cur_op != NULL) { if (pq->cur_i == ecs_vec_count(&pq->systems)) { flecs_pipeline_update(world, pq, false); continue; } bool immediate = pq->cur_op->immediate; bool op_multi_threaded = multi_threaded && pq->cur_op->multi_threaded; pq->immediate = immediate; if (!immediate) { ecs_readonly_begin(world, multi_threaded); } ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, op_multi_threaded); ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); if (op_multi_threaded) { flecs_signal_workers(world); } ecs_time_t st = { 0 }; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&st); } const int32_t i = flecs_run_pipeline_ops( world, stage, stage_index, stage_count, delta_time); if (measure_time) { /* Don't include merge time in system time */ world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } if (op_multi_threaded) { flecs_wait_for_sync(world); } if (!immediate) { ecs_time_t mt = { 0 }; if (measure_time) { ecs_time_measure(&mt); } int32_t si; for (si = 0; si < stage_count; si ++) { ecs_stage_t *s = world->stages[si]; pq->cur_op->commands_enqueued += ecs_vec_count(&s->cmd->queue); } ecs_readonly_end(world); if (measure_time) { pq->cur_op->time_spent += ecs_time_measure(&mt); } } /* Store the current state of the schedule after we synchronized the * threads, to avoid race conditions. */ pq->cur_i = i; flecs_pipeline_update(world, pq, false); } } static void flecs_run_startup_systems( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_dependson(EcsOnStart)); if (!idr || !flecs_table_cache_count(&idr->cache)) { /* Don't bother creating startup pipeline if no systems exist */ return; } ecs_dbg_2("#[bold]startup#[reset]"); ecs_log_push_2(); int32_t stage_count = world->stage_count; world->stage_count = 1; /* Prevents running startup systems on workers */ /* Creating a pipeline is relatively expensive, but this only happens * for the first frame. The startup pipeline is deleted afterwards, which * eliminates the overhead of keeping its query cache in sync. */ ecs_dbg_2("#[bold]create startup pipeline#[reset]"); ecs_log_push_2(); ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ .query = { .terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.id = EcsCascade, .trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .trav = EcsDependsOn }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsChildOf, .oper = EcsNot } }, .order_by_callback = flecs_entity_compare } }); ecs_log_pop_2(); /* Run & delete pipeline */ ecs_dbg_2("#[bold]run startup systems#[reset]"); ecs_log_push_2(); ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, "pipeline entity is missing flecs.pipeline.Pipeline component"); flecs_workers_progress(world, p->state, 0); ecs_log_pop_2(); ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); ecs_log_push_2(); ecs_delete(world, start_pip); ecs_log_pop_2(); world->stage_count = stage_count; ecs_log_pop_2(); error: return; } bool ecs_progress( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); /* If this is the first frame, run startup systems */ if (world->info.frame_count_total == 0) { flecs_run_startup_systems(world); } /* create any worker task threads request */ if (ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); ecs_log_push_3(); const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, "pipeline entity is missing flecs.pipeline.Pipeline component"); flecs_workers_progress(world, p->state, delta_time); ecs_log_pop_3(); ecs_frame_end(world); if (ecs_using_task_threads(world)) { /* task threads were temporary and may now be joined */ flecs_join_worker_threads(world); } return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return false; } void ecs_set_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) { flecs_poly_assert(world, ecs_world_t); ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, ECS_INVALID_PARAMETER, "not a pipeline"); world->pipeline = pipeline; error: return; } ecs_entity_t ecs_get_pipeline( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->pipeline; error: return 0; } ecs_entity_t ecs_pipeline_init( ecs_world_t *world, const ecs_pipeline_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); } ecs_query_desc_t qd = desc->query; if (!qd.order_by_callback) { qd.order_by_callback = flecs_entity_compare; } qd.entity = result; ecs_query_t *query = ecs_query_init(world, &qd); if (!query) { ecs_delete(world, result); return 0; } ecs_check(query->terms != NULL, ECS_INVALID_PARAMETER, "pipeline query cannot be empty"); ecs_check(query->terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, "pipeline must start with System term"); ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); pq->query = query; pq->match_count = -1; pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); ecs_set(world, result, EcsPipeline, { pq }); return result; error: return 0; } /* -- Module implementation -- */ static void FlecsPipelineFini( ecs_world_t *world, void *ctx) { (void)ctx; if (ecs_get_stage_count(world)) { ecs_set_threads(world, 0); } ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } #define flecs_bootstrap_phase(world, phase, depends_on)\ flecs_bootstrap_tag(world, phase);\ flecs_bootstrap_phase_(world, phase, depends_on) static void flecs_bootstrap_phase_( ecs_world_t *world, ecs_entity_t phase, ecs_entity_t depends_on) { ecs_add_id(world, phase, EcsPhase); if (depends_on) { ecs_add_pair(world, phase, EcsDependsOn, depends_on); } } void FlecsPipelineImport( ecs_world_t *world) { ECS_MODULE(world, FlecsPipeline); ECS_IMPORT(world, FlecsSystem); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsPipeline), "Module that schedules and runs systems"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsPipeline); flecs_bootstrap_tag(world, EcsPhase); /* Create anonymous phases to which the builtin phases will have DependsOn * relationships. This ensures that, for example, EcsOnUpdate doesn't have a * direct DependsOn relationship on EcsPreUpdate, which ensures that when * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ ecs_entity_t phase_0 = ecs_entity(world, {0}); ecs_entity_t phase_1 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_0)) }); ecs_entity_t phase_2 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_1)) }); ecs_entity_t phase_3 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_2)) }); ecs_entity_t phase_4 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_3)) }); ecs_entity_t phase_5 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_4)) }); ecs_entity_t phase_6 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_5)) }); ecs_entity_t phase_7 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_6)) }); ecs_entity_t phase_8 = ecs_entity(world, { .add = ecs_ids(ecs_dependson(phase_7)) }); flecs_bootstrap_phase(world, EcsOnStart, 0); flecs_bootstrap_phase(world, EcsPreFrame, 0); flecs_bootstrap_phase(world, EcsOnLoad, phase_0); flecs_bootstrap_phase(world, EcsPostLoad, phase_1); flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); flecs_bootstrap_phase(world, EcsOnValidate, phase_4); flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); flecs_bootstrap_phase(world, EcsPreStore, phase_6); flecs_bootstrap_phase(world, EcsOnStore, phase_7); flecs_bootstrap_phase(world, EcsPostFrame, phase_8); ecs_set_hooks(world, EcsPipeline, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsPipeline), .move = ecs_move(EcsPipeline) }); world->pipeline = ecs_pipeline(world, { .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), .query = { .terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.id = EcsCascade, .trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.id = EcsUp, .trav = EcsChildOf, .oper = EcsNot } }, .order_by_callback = flecs_entity_compare } }); /* Cleanup thread administration when world is destroyed */ ecs_atfini(world, FlecsPipelineFini, NULL); } #endif /** * @file addons/pipeline/worker.c * @brief Functions for running pipelines on one or more threads. */ #ifdef FLECS_PIPELINE /* Synchronize workers */ static void flecs_sync_worker( ecs_world_t* world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } /* Signal that thread is waiting */ ecs_os_mutex_lock(world->sync_mutex); if (++world->workers_waiting == (stage_count - 1)) { /* Only signal main thread when all threads are waiting */ ecs_os_cond_signal(world->sync_cond); } /* Wait until main thread signals that thread can continue */ ecs_os_cond_wait(world->worker_cond, world->sync_mutex); ecs_os_mutex_unlock(world->sync_mutex); } /* Worker thread */ static void* flecs_worker(void *arg) { ecs_stage_t *stage = arg; ecs_world_t *world = stage->world; flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); ecs_dbg_2("worker %d: start", stage->id); /* Start worker, increase counter so main thread knows how many * workers are ready */ ecs_os_mutex_lock(world->sync_mutex); world->workers_running ++; if (!(world->flags & EcsWorldQuitWorkers)) { ecs_os_cond_wait(world->worker_cond, world->sync_mutex); } ecs_os_mutex_unlock(world->sync_mutex); while (!(world->flags & EcsWorldQuitWorkers)) { ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); ecs_dbg_3("worker %d: run", stage->id); flecs_run_pipeline_ops(world, stage, stage->id, world->stage_count, world->info.delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); flecs_sync_worker(world); } ecs_dbg_2("worker %d: finalizing", stage->id); ecs_os_mutex_lock(world->sync_mutex); world->workers_running --; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_2("worker %d: stop", stage->id); return NULL; } /* Start threads */ void flecs_create_worker_threads( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); int32_t stages = ecs_get_stage_count(world); for (int32_t i = 1; i < stages; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(stage, ecs_stage_t); ecs_assert(stage->thread == 0, ECS_INTERNAL_ERROR, NULL); if (ecs_using_task_threads(world)) { /* workers are using tasks in an external task manager provided to * the OS API */ stage->thread = ecs_os_task_new(flecs_worker, stage); } else { /* workers are using long-running os threads */ stage->thread = ecs_os_thread_new(flecs_worker, stage); } ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, "failed to create thread"); } } static void flecs_start_workers( ecs_world_t *world, int32_t threads) { ecs_set_stage_count(world, threads); ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); if (!ecs_using_task_threads(world)) { flecs_create_worker_threads(world); } } /* Wait until all workers are running */ static void flecs_wait_for_workers( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } bool wait = true; do { ecs_os_mutex_lock(world->sync_mutex); if (world->workers_running == (stage_count - 1)) { wait = false; } ecs_os_mutex_unlock(world->sync_mutex); } while (wait); } /* Wait until all threads are waiting on sync point */ void flecs_wait_for_sync( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); ecs_os_mutex_lock(world->sync_mutex); if (world->workers_waiting != (stage_count - 1)) { ecs_os_cond_wait(world->sync_cond, world->sync_mutex); } /* We shouldn't have been signalled unless all workers are waiting on sync */ ecs_assert(world->workers_waiting == (stage_count - 1), ECS_INTERNAL_ERROR, NULL); world->workers_waiting = 0; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_3("#[bold]pipeline: workers synced"); } /* Signal workers that they can start/resume work */ void flecs_signal_workers( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: signal workers"); ecs_os_mutex_lock(world->sync_mutex); ecs_os_cond_broadcast(world->worker_cond); ecs_os_mutex_unlock(world->sync_mutex); } void flecs_join_worker_threads( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); bool threads_active = false; /* Test if threads are created. Cannot use workers_running, since this is * a potential race if threads haven't spun up yet. */ int i, count = world->stage_count; for (i = 1; i < count; i ++) { ecs_stage_t *stage = world->stages[i]; if (stage->thread) { threads_active = true; break; } }; /* If no threads are active, just return */ if (!threads_active) { return; } /* Make sure all threads are running, to ensure they catch the signal */ flecs_wait_for_workers(world); /* Signal threads should quit */ world->flags |= EcsWorldQuitWorkers; flecs_signal_workers(world); /* Join all threads with main */ for (i = 1; i < count; i ++) { ecs_stage_t *stage = world->stages[i]; if (ecs_using_task_threads(world)) { ecs_os_task_join(stage->thread); } else { ecs_os_thread_join(stage->thread); } stage->thread = 0; } world->flags &= ~EcsWorldQuitWorkers; ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } /* -- Private functions -- */ void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, "cannot call progress while world is deferred"); /* Make sure workers are running and ready */ flecs_wait_for_workers(world); /* Run pipeline on main thread */ ecs_world_t *stage = ecs_get_stage(world, 0); ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); flecs_run_pipeline(stage, pq, delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } static void flecs_set_threads_internal( ecs_world_t *world, int32_t threads, bool use_task_api) { ecs_assert(threads <= 1 || (use_task_api ? ecs_os_has_task_support() : ecs_os_has_threading()), ECS_MISSING_OS_API, NULL); int32_t stage_count = ecs_get_stage_count(world); bool worker_method_changed = (use_task_api != world->workers_use_task_api); if ((stage_count != threads) || worker_method_changed) { /* Stop existing threads */ if (stage_count > 1) { flecs_join_worker_threads(world); ecs_set_stage_count(world, 1); if (world->worker_cond) { ecs_os_cond_free(world->worker_cond); } if (world->sync_cond) { ecs_os_cond_free(world->sync_cond); } if (world->sync_mutex) { ecs_os_mutex_free(world->sync_mutex); } } world->workers_use_task_api = use_task_api; /* Start threads if number of threads > 1 */ if (threads > 1) { world->worker_cond = ecs_os_cond_new(); world->sync_cond = ecs_os_cond_new(); world->sync_mutex = ecs_os_mutex_new(); flecs_start_workers(world, threads); } } } /* -- Public functions -- */ void ecs_set_threads( ecs_world_t *world, int32_t threads) { flecs_set_threads_internal(world, threads, false /* use thread API */); } void ecs_set_task_threads( ecs_world_t *world, int32_t task_threads) { flecs_set_threads_internal(world, task_threads, true /* use task API */); } bool ecs_using_task_threads( ecs_world_t *world) { return world->workers_use_task_api; } #endif /** * @file addons/script/ast.c * @brief Script AST implementation. */ #ifdef FLECS_SCRIPT #define flecs_ast_strdup(parser, str)\ (str ? flecs_strdup(&parser->script->allocator, str) : NULL) #define flecs_ast_new(parser, T, kind)\ (T*)flecs_ast_new_(parser, ECS_SIZEOF(T), kind) #define flecs_ast_vec(parser, vec, T) \ ecs_vec_init_t(&parser->script->allocator, &vec, T*, 0) #define flecs_ast_append(parser, vec, T, node) \ ecs_vec_append_t(&parser->script->allocator, &vec, T*)[0] = node static void* flecs_ast_new_( ecs_script_parser_t *parser, ecs_size_t size, ecs_script_node_kind_t kind) { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; ecs_script_node_t *result = flecs_calloc(a, size); result->kind = kind; result->pos = parser->pos; return result; } ecs_script_scope_t* flecs_script_scope_new( ecs_script_parser_t *parser) { ecs_script_scope_t *result = flecs_ast_new( parser, ecs_script_scope_t, EcsAstScope); flecs_ast_vec(parser, result->stmts, ecs_script_node_t); return result; } bool flecs_scope_is_empty( ecs_script_scope_t *scope) { return ecs_vec_count(&scope->stmts) == 0; } ecs_script_scope_t* flecs_script_insert_scope( ecs_script_parser_t *parser) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_scope_t *result = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_scope_t, result); return result; } ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, const char *name) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_entity_t *result = flecs_ast_new( parser, ecs_script_entity_t, EcsAstEntity); if (name && !ecs_os_strcmp(name, "_")) { name = NULL; } result->name = name; ecs_script_scope_t *entity_scope = flecs_script_scope_new(parser); ecs_assert(entity_scope != NULL, ECS_INTERNAL_ERROR, NULL); result->scope = entity_scope; flecs_ast_append(parser, scope->stmts, ecs_script_entity_t, result); return result; } static void flecs_script_set_id( ecs_script_id_t *id, const char *first, const char *second) { ecs_assert(first != NULL, ECS_INTERNAL_ERROR, NULL); id->first = first; id->second = second; } ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_script_parser_t *parser, const char *first, const char *second) { ecs_script_scope_t *scope = parser->scope; ecs_script_pair_scope_t *result = flecs_ast_new( parser, ecs_script_pair_scope_t, EcsAstPairScope); flecs_script_set_id(&result->id, first, second); result->scope = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_pair_scope_t, result); return result; } ecs_script_tag_t* flecs_script_insert_pair_tag( ecs_script_parser_t *parser, const char *first, const char *second) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_tag_t *result = flecs_ast_new( parser, ecs_script_tag_t, EcsAstTag); flecs_script_set_id(&result->id, first, second); flecs_ast_append(parser, scope->stmts, ecs_script_tag_t, result); return result; } ecs_script_tag_t* flecs_script_insert_tag( ecs_script_parser_t *parser, const char *name) { return flecs_script_insert_pair_tag(parser, name, NULL); } ecs_script_component_t* flecs_script_insert_pair_component( ecs_script_parser_t *parser, const char *first, const char *second) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_component_t *result = flecs_ast_new( parser, ecs_script_component_t, EcsAstComponent); flecs_script_set_id(&result->id, first, second); flecs_ast_append(parser, scope->stmts, ecs_script_component_t, result); return result; } ecs_script_component_t* flecs_script_insert_component( ecs_script_parser_t *parser, const char *name) { return flecs_script_insert_pair_component(parser, name, NULL); } ecs_script_default_component_t* flecs_script_insert_default_component( ecs_script_parser_t *parser) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_default_component_t *result = flecs_ast_new( parser, ecs_script_default_component_t, EcsAstDefaultComponent); flecs_ast_append(parser, scope->stmts, ecs_script_default_component_t, result); return result; } ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_parser_t *parser, const char *var_name) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var_name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_var_component_t *result = flecs_ast_new( parser, ecs_script_var_component_t, EcsAstVarComponent); result->name = var_name; flecs_ast_append(parser, scope->stmts, ecs_script_var_component_t, result); return result; } ecs_script_with_t* flecs_script_insert_with( ecs_script_parser_t *parser) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_with_t *result = flecs_ast_new( parser, ecs_script_with_t, EcsAstWith); result->expressions = flecs_script_scope_new(parser); result->scope = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_with_t, result); return result; } ecs_script_using_t* flecs_script_insert_using( ecs_script_parser_t *parser, const char *name) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_using_t *result = flecs_ast_new( parser, ecs_script_using_t, EcsAstUsing); result->name = name; flecs_ast_append(parser, scope->stmts, ecs_script_using_t, result); return result; } ecs_script_module_t* flecs_script_insert_module( ecs_script_parser_t *parser, const char *name) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_module_t *result = flecs_ast_new( parser, ecs_script_module_t, EcsAstModule); result->name = name; flecs_ast_append(parser, scope->stmts, ecs_script_module_t, result); return result; } ecs_script_annot_t* flecs_script_insert_annot( ecs_script_parser_t *parser, const char *name, const char *expr) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_annot_t *result = flecs_ast_new( parser, ecs_script_annot_t, EcsAstAnnotation); result->name = name; result->expr = expr; flecs_ast_append(parser, scope->stmts, ecs_script_annot_t, result); return result; } ecs_script_template_node_t* flecs_script_insert_template( ecs_script_parser_t *parser, const char *name) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_template_node_t *result = flecs_ast_new( parser, ecs_script_template_node_t, EcsAstTemplate); result->name = name; result->scope = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_template_node_t, result); return result; } ecs_script_var_node_t* flecs_script_insert_var( ecs_script_parser_t *parser, const char *name) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_var_node_t *result = flecs_ast_new( parser, ecs_script_var_node_t, EcsAstConst); result->name = name; flecs_ast_append(parser, scope->stmts, ecs_script_var_node_t, result); return result; } ecs_script_if_t* flecs_script_insert_if( ecs_script_parser_t *parser) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_if_t *result = flecs_ast_new( parser, ecs_script_if_t, EcsAstIf); result->if_true = flecs_script_scope_new(parser); result->if_false = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_if_t, result); return result; } #endif /** * @file addons/script/expr.c * @brief Evaluate script expressions. */ #ifdef FLECS_SCRIPT #include #include /* String deserializer for script 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 FLECS_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[FLECS_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_script_expr_run( ecs_world_t *world, ecs_value_stack_t *stack, const char *ptr, ecs_value_t *value, ecs_expr_oper_t op, const ecs_script_expr_run_desc_t *desc); #define TOK_VARIABLE '$' /* -- Private functions -- */ static 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 const char* flecs_parse_ws( const char *ptr) { while ((*ptr != '\n') && isspace(*ptr)) { ptr ++; } return ptr; } static const char* flecs_parse_expr_token( const char *name, const char *expr, const char *ptr, char *token_out, char delim) { int64_t column = ptr - expr; ptr = flecs_parse_ws(ptr); char *tptr = token_out, ch = ptr[0]; if (!flecs_valid_token_start_char(ch)) { if (ch == '\0' || ch == '\n') { ecs_parser_error(name, expr, column, "unexpected end of expression"); } else { ecs_parser_error(name, expr, column, "invalid start of token '%s'", ptr); } return NULL; } tptr[0] = ch; tptr ++; ptr ++; if (ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ',' || ch == '`') { tptr[0] = 0; return ptr; } int tmpl_nesting = 0; bool in_str = ch == '"'; for (; (ch = *ptr); ptr ++) { if (ch == '<') { tmpl_nesting ++; } else if (ch == '>') { if (!tmpl_nesting) { break; } tmpl_nesting --; } else if (ch == '"') { in_str = !in_str; } else if (ch == '\\') { ptr ++; tptr[0] = ptr[0]; tptr ++; continue; } else if (!flecs_valid_token_char(ch) && !in_str) { break; } if (delim && (ch == delim)) { break; } tptr[0] = ch; tptr ++; } tptr[0] = '\0'; if (tmpl_nesting != 0) { ecs_parser_error(name, expr, column, "identifier '%s' has mismatching < > pairs", ptr); return NULL; } const char *next_ptr = flecs_parse_ws(ptr); if (next_ptr[0] == ':' && next_ptr != ptr) { /* Whitespace between token and : is significant */ ptr = next_ptr - 1; } else { ptr = next_ptr; } return ptr; } 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; } static const char *flecs_script_expr_parse_token( const char *name, const char *expr, const char *ptr, char *token) { char *token_ptr = token; if (ptr[0] == '/') { char ch; if (ptr[1] == '/') { // Single line comment for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} token[0] = 0; return flecs_parse_ws_eol(ptr); } else if (ptr[1] == '*') { // Multi line comment for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { if (ch == '*' && ptr[1] == '/') { token[0] = 0; return flecs_parse_ws_eol(ptr + 2); } } ecs_parser_error(name, expr, ptr - expr, "missing */ for multiline comment"); return NULL; } } ecs_expr_oper_t op; if (ptr[0] == '(') { token[0] = '('; token[1] = 0; return ptr + 1; } else if (ptr[0] != '-') { const char *tptr = flecs_str_to_expr_oper(ptr, &op); if (tptr) { ecs_os_strncpy(token, ptr, tptr - ptr); return tptr; } } while ((ptr = flecs_parse_expr_token(name, expr, ptr, token_ptr, 0))) { if (ptr[0] == '|' && ptr[1] != '|') { token_ptr = &token_ptr[ecs_os_strlen(token_ptr)]; token_ptr[0] = '|'; token_ptr[1] = '\0'; token_ptr ++; ptr ++; } else { break; } } return ptr; } static const char* flecs_parse_multiline_string( ecs_meta_cursor_t *cur, const char *name, const char *expr, const char *ptr) { /* Multiline string */ ecs_strbuf_t str = ECS_STRBUF_INIT; char ch; while ((ch = ptr[0]) && (ch != '`')) { if (ch == '\\' && ptr[1] == '`') { ch = '`'; ptr ++; } ecs_strbuf_appendch(&str, ch); ptr ++; } if (ch != '`') { ecs_parser_error(name, expr, ptr - expr, "missing '`' to close multiline string"); goto error; } char *strval = ecs_strbuf_get(&str); if (ecs_meta_set_string(cur, strval) != 0) { goto error; } ecs_os_free(strval); return ptr + 1; error: return NULL; } static bool flecs_parse_is_float( const char *ptr) { ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); char ch; while ((ch = (++ptr)[0])) { if (ch == '.' || ch == 'e') { return true; } if (!isdigit(ch)) { return false; } } return false; } /* Attempt to resolve variable dotexpression to value (foo.bar) */ static ecs_value_t flecs_dotresolve_var( ecs_world_t *world, ecs_script_vars_t *vars, char *token) { char *dot = strchr(token, '.'); if (!dot) { return (ecs_value_t){ .type = ecs_id(ecs_entity_t) }; } dot[0] = '\0'; const ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); if (!var) { return (ecs_value_t){0}; } ecs_meta_cursor_t cur = ecs_meta_cursor( world, var->value.type, var->value.ptr); ecs_meta_push(&cur); if (ecs_meta_dotmember(&cur, dot + 1) != 0) { return (ecs_value_t){0}; } return (ecs_value_t){ .ptr = ecs_meta_get_ptr(&cur), .type = ecs_meta_get_type(&cur) }; } static int flecs_meta_call( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_meta_cursor_t *cur, const char *function) { ecs_entity_t type = ecs_meta_get_type(cur); void *value_ptr = ecs_meta_get_ptr(cur); if (!ecs_os_strcmp(function, "parent")) { if (type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, ptr - expr, "parent() can only be called on entity"); return -1; } *(ecs_entity_t*)value_ptr = ecs_get_parent( world, *(ecs_entity_t*)value_ptr); } else if (!ecs_os_strcmp(function, "name")) { if (type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, ptr - expr, "name() can only be called on entity"); return -1; } char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); *result = ecs_os_strdup(ecs_get_name(world, *(ecs_entity_t*)value_ptr)); *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); } else if (!ecs_os_strcmp(function, "doc_name")) { #ifdef FLECS_DOC if (type != ecs_id(ecs_entity_t)) { ecs_parser_error(name, expr, ptr - expr, "name() can only be called on entity"); return -1; } char **result = flecs_expr_value_new(stack, ecs_id(ecs_string_t)); *result = ecs_os_strdup(ecs_doc_get_name(world, *(ecs_entity_t*)value_ptr)); *cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), result); #else ecs_parser_error(name, expr, ptr - expr, "doc_name() is not available without FLECS_DOC addon"); return -1; #endif } else { ecs_parser_error(name, expr, ptr - expr, "unknown function '%s'", function); return -1; } return 0; } /* Determine the type of an expression from the first character(s). This allows * us to initialize a storage for a type if none was provided. */ static ecs_entity_t flecs_parse_discover_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_entity_t input_type, const ecs_script_expr_run_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 (flecs_script_expr_parse_token(name, expr, &ptr[1], token) == NULL) { return 0; } const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); if (!var) { ecs_size_t len = ecs_os_strlen(token); if (ptr[len + 1] == '(') { while ((len > 0) && (token[len] != '.')) { len --; } if (token[len] == '.') { token[len] = '\0'; } return ecs_id(ecs_entity_t); } ecs_value_t v = flecs_dotresolve_var(world, desc->vars, token); if (v.type) { return v.type; } ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return 0; } return var->value.type; } /* Boolean */ if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { if (!isalpha(ptr[4]) && ptr[4] != '_') { return ecs_id(ecs_bool_t); } } if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { if (!isalpha(ptr[5]) && ptr[5] != '_') { return ecs_id(ecs_bool_t); } } /* Entity identifier */ if (isalpha(ptr[0])) { if (!input_type) { /* Identifier could also be enum/bitmask constant */ char token[ECS_MAX_TOKEN_SIZE]; const char *tptr = flecs_script_expr_parse_token( name, expr, ptr, token); if (!tptr) { return 0; } if (tptr[0] != '[') { return ecs_id(ecs_entity_t); } tptr = flecs_script_expr_parse_token( name, expr, tptr + 1, token); if (!tptr) { return 0; } ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t type = desc->lookup_action( world, token, desc->lookup_ctx); if (!type) { ecs_parser_error(name, expr, ptr - expr, "unresolved type '%s'", token); return 0; } if (tptr[0] != ']') { ecs_parser_error(name, expr, ptr - expr, "missing ']' after '%s'", token); return 0; } if (tptr[1] != '.') { return type; } ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, NULL); ecs_meta_push(&cur); tptr = flecs_script_expr_parse_token( name, expr, tptr + 2, token); if (!tptr) { return 0; } if (ecs_meta_dotmember(&cur, token) != 0) { ecs_parser_error(name, expr, ptr - expr, "failed to assign member '%s'", token); return 0; } return ecs_meta_get_type(&cur); } } /* If no default type was provided we can't automatically deduce the type of * composite/collection expressions. */ if (!input_type) { if (ptr[0] == '{') { ecs_parser_error(name, expr, ptr - expr, "unknown type for composite literal"); return 0; } if (ptr[0] == '[') { ecs_parser_error(name, expr, ptr - expr, "unknown type for collection literal"); return 0; } ecs_parser_error(name, expr, ptr - expr, "invalid expression"); } return input_type; } /* Normalize types to their largest representation. * Rather than taking the original type of a value, use the largest * representation of the type so we don't have to worry about overflowing the * original type in the operation. */ static ecs_entity_t flecs_largest_type( const EcsPrimitive *type) { switch(type->kind) { case EcsBool: return ecs_id(ecs_bool_t); case EcsChar: return ecs_id(ecs_char_t); case EcsByte: return ecs_id(ecs_u8_t); case EcsU8: return ecs_id(ecs_u64_t); case EcsU16: return ecs_id(ecs_u64_t); case EcsU32: return ecs_id(ecs_u64_t); case EcsU64: return ecs_id(ecs_u64_t); case EcsI8: return ecs_id(ecs_i64_t); case EcsI16: return ecs_id(ecs_i64_t); case EcsI32: return ecs_id(ecs_i64_t); case EcsI64: return ecs_id(ecs_i64_t); case EcsF32: return ecs_id(ecs_f64_t); case EcsF64: return ecs_id(ecs_f64_t); case EcsUPtr: return ecs_id(ecs_u64_t); case EcsIPtr: return ecs_id(ecs_i64_t); case EcsString: return ecs_id(ecs_string_t); case EcsEntity: return ecs_id(ecs_entity_t); case EcsId: return ecs_id(ecs_id_t); default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } /** Test if a normalized type can promote to another type in an expression */ static bool flecs_is_type_number( ecs_entity_t type) { if (type == ecs_id(ecs_bool_t)) return false; else if (type == ecs_id(ecs_char_t)) return false; else if (type == ecs_id(ecs_u8_t)) return false; else if (type == ecs_id(ecs_u64_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; } static bool flecs_oper_valid_for_type( ecs_entity_t type, ecs_expr_oper_t op) { switch(op) { case EcsAdd: case EcsSub: case EcsMul: case EcsDiv: return flecs_is_type_number(type); case EcsCondEq: case EcsCondNeq: case EcsCondAnd: case EcsCondOr: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: return flecs_is_type_number(type) || (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); case EcsShiftLeft: case EcsShiftRight: return (type == ecs_id(ecs_u64_t)); case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: return false; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } /** Promote type to most expressive (f64 > i64 > u64) */ static ecs_entity_t flecs_promote_type( ecs_entity_t type, ecs_entity_t promote_to) { if (type == ecs_id(ecs_u64_t)) { return promote_to; } if (promote_to == ecs_id(ecs_u64_t)) { return type; } if (type == ecs_id(ecs_f64_t)) { return type; } if (promote_to == ecs_id(ecs_f64_t)) { return promote_to; } return ecs_id(ecs_i64_t); } static int flecs_oper_precedence( ecs_expr_oper_t left, ecs_expr_oper_t right) { return (left > right) - (left < right); } static void flecs_value_cast( ecs_world_t *world, ecs_value_stack_t *stack, ecs_value_t *value, ecs_entity_t type) { if (value->type == type) { return; } ecs_value_t result; result.type = type; result.ptr = flecs_expr_value_new(stack, type); if (value->ptr) { ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); ecs_meta_set_value(&cur, value); } *value = result; } static bool flecs_expr_op_is_equality( ecs_expr_oper_t op) { switch(op) { case EcsCondEq: case EcsCondNeq: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: return true; case EcsCondAnd: case EcsCondOr: case EcsShiftLeft: case EcsShiftRight: case EcsAdd: case EcsSub: case EcsMul: case EcsDiv: case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: return false; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } error: return false; } static ecs_entity_t flecs_binary_expr_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *rvalue, ecs_expr_oper_t op, ecs_entity_t *operand_type_out) { ecs_entity_t result_type = 0, operand_type = 0; switch(op) { case EcsDiv: /* Result type of a division is always a float */ *operand_type_out = ecs_id(ecs_f64_t); return ecs_id(ecs_f64_t); case EcsCondAnd: case EcsCondOr: /* Result type of a condition operator is always a bool */ *operand_type_out = ecs_id(ecs_bool_t); return ecs_id(ecs_bool_t); case EcsCondEq: case EcsCondNeq: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: /* Result type of equality operator is always bool, but operand types * should not be casted to bool */ result_type = ecs_id(ecs_bool_t); break; case EcsShiftLeft: case EcsShiftRight: case EcsAdd: case EcsSub: case EcsMul: case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: break; default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } /* Result type for arithmetic operators is determined by operands */ const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { char *lname = ecs_get_path(world, lvalue->type); char *rname = ecs_get_path(world, rvalue->type); ecs_parser_error(name, expr, ptr - expr, "invalid non-primitive type in binary expression (%s, %s)", lname, rname); ecs_os_free(lname); ecs_os_free(rname); return 0; } ecs_entity_t ltype = flecs_largest_type(ltype_ptr); ecs_entity_t rtype = flecs_largest_type(rtype_ptr); if (ltype == rtype) { operand_type = ltype; goto done; } if (flecs_expr_op_is_equality(op)) { char *lstr = ecs_id_str(world, ltype); char *rstr = ecs_id_str(world, rtype); ecs_parser_error(name, expr, ptr - expr, "mismatching types in equality expression (%s vs %s)", lstr, rstr); ecs_os_free(rstr); ecs_os_free(lstr); return 0; } if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { ecs_parser_error(name, expr, ptr - expr, "incompatible types in binary expression"); return 0; } operand_type = flecs_promote_type(ltype, rtype); done: if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ operand_type = ecs_id(ecs_i64_t); } if (!result_type) { result_type = operand_type; } *operand_type_out = operand_type; return result_type; error: return 0; } /* Macro's to let the compiler do the operations & conversion work for us */ #define ECS_VALUE_GET(value, T) (*(T*)value->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) #define ECS_BINARY_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ecs_parser_error(name, expr, ptr - expr, "unsupported operator for floating point");\ return -1;\ } else if (left->type == ecs_id(ecs_u8_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if (left->type == ecs_id(ecs_char_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ } else if (left->type == ecs_id(ecs_u8_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if (left->type == ecs_id(ecs_char_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_BOOL_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_UINT_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } static int flecs_binary_expr_do( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *rvalue, ecs_value_t *result, ecs_expr_oper_t op) { /* Find expression type */ ecs_entity_t operand_type, type = flecs_binary_expr_type( world, name, expr, ptr, lvalue, rvalue, op, &operand_type); if (!type) { goto error; } if (!flecs_oper_valid_for_type(type, op)) { ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); goto error; } flecs_value_cast(world, stack, lvalue, operand_type); flecs_value_cast(world, stack, rvalue, operand_type); ecs_value_t *storage = result; ecs_value_t tmp_storage = {0}; if (result->type != type) { storage = &tmp_storage; storage->type = type; storage->ptr = flecs_expr_value_new(stack, type); } switch(op) { case EcsAdd: ECS_BINARY_OP(lvalue, rvalue, storage, +); break; case EcsSub: ECS_BINARY_OP(lvalue, rvalue, storage, -); break; case EcsMul: ECS_BINARY_OP(lvalue, rvalue, storage, *); break; case EcsDiv: ECS_BINARY_OP(lvalue, rvalue, storage, /); break; case EcsCondEq: ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, ==); break; case EcsCondNeq: ECS_BINARY_COND_EQ_OP(lvalue, rvalue, storage, !=); break; case EcsCondGt: ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); break; case EcsCondGtEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); break; case EcsCondLt: ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); break; case EcsCondLtEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); break; case EcsCondAnd: ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); break; case EcsCondOr: ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); break; case EcsShiftLeft: ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); break; case EcsShiftRight: ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); break; case EcsExprOperUnknown: case EcsLeftParen: case EcsMin: ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); goto error; default: ecs_throw(ECS_INTERNAL_ERROR, NULL); } if (storage->ptr != result->ptr) { if (!result->ptr) { *result = *storage; } else { ecs_meta_cursor_t cur = ecs_meta_cursor(world, result->type, result->ptr); ecs_meta_set_value(&cur, storage); } } return 0; error: return -1; } static const char* flecs_binary_expr_parse( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *result, ecs_expr_oper_t left_op, const ecs_script_expr_run_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 = flecs_parse_ws_eol(ptr); ecs_value_t rvalue = {0}; const char *rptr = flecs_script_expr_run(world, stack, ptr, &rvalue, op, desc); if (!rptr) { return NULL; } if (flecs_binary_expr_do(world, stack, name, expr, ptr, lvalue, &rvalue, result, op)) { return NULL; } ptr = rptr; ecs_expr_oper_t right_op; flecs_str_to_expr_oper(rptr, &right_op); if (right_op > left_op) { if (result_type) { /* If result was initialized, preserve its value */ lvalue->type = result->type; lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); continue; } else { /* Otherwise move result to lvalue */ *lvalue = *result; ecs_os_zeromem(result); continue; } } break; } while (true); return ptr; } static const char* flecs_funccall_parse( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, char *token, ecs_meta_cursor_t *cur, ecs_value_t *value, bool isvar, const ecs_script_expr_run_desc_t *desc) { char *sep = strrchr(token, '.'); if (!sep) { ecs_parser_error(name, expr, ptr - expr, "missing object for function call '%s'", token); return NULL; } sep[0] = '\0'; const char *function = sep + 1; if (!isvar) { if (ecs_meta_set_string(cur, token) != 0) { goto error; } } else { const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, token); ecs_meta_set_value(cur, &var->value); } do { if (!function[0]) { ecs_parser_error(name, expr, ptr - expr, "missing function name for function call '%s'", token); return NULL; } if (flecs_meta_call(world, stack, name, expr, ptr, cur, function) != 0) { goto error; } ecs_entity_t type = ecs_meta_get_type(cur); value->ptr = ecs_meta_get_ptr(cur); value->type = type; ptr += 2; if (ptr[0] != '.') { break; } ptr ++; char *paren = strchr(ptr, '('); if (!paren) { break; } ecs_size_t len = flecs_ito(int32_t, paren - ptr); if (len >= ECS_MAX_TOKEN_SIZE) { ecs_parser_error(name, expr, ptr - expr, "token exceeds maximum token size"); goto error; } ecs_os_strncpy(token, ptr, len); token[len] = '\0'; function = token; ptr = paren; } while (true); return ptr; error: return NULL; } static ecs_entity_t flecs_script_default_lookup( const ecs_world_t *world, const char *name, void *ctx) { (void)ctx; return ecs_lookup(world, name); } static const char* flecs_script_expr_run( ecs_world_t *world, ecs_value_stack_t *stack, const char *ptr, ecs_value_t *value, ecs_expr_oper_t left_op, const ecs_script_expr_run_desc_t *desc) { ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); char token[ECS_MAX_TOKEN_SIZE]; int depth = 0; ecs_value_t result = {0}; ecs_meta_cursor_t cur = {0}; const char *name = desc ? desc->name : NULL; const char *expr = desc ? desc->expr : NULL; token[0] = '\0'; expr = expr ? expr : ptr; ptr = flecs_parse_ws_eol(ptr); /* Check for postfix operators */ ecs_expr_oper_t unary_op = EcsExprOperUnknown; if (ptr[0] == '-' && !isdigit(ptr[1])) { unary_op = EcsMin; ptr = flecs_parse_ws_eol(ptr + 1); } /* Initialize storage and cursor. If expression starts with a '(' storage * will be initialized by a nested expression */ if (ptr[0] != '(') { ecs_entity_t type = flecs_parse_discover_type( world, name, expr, ptr, value->type, desc); if (!type) { return NULL; } result.type = type; if (type != value->type) { result.ptr = flecs_expr_value_new(stack, type); } else { result.ptr = value->ptr; } cur = ecs_meta_cursor(world, result.type, result.ptr); if (!cur.valid) { return NULL; } cur.lookup_action = desc ? desc->lookup_action : NULL; cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; } /* Loop that parses all values in a value scope */ while ((ptr = flecs_script_expr_parse_token(name, expr, ptr, token))) { /* Used to track of the result of the parsed token can be used as the * lvalue for a binary expression */ bool is_lvalue = false; bool newline = false; if (!token[0]) { /* Comment */ continue; } if (!ecs_os_strcmp(token, "(")) { ecs_value_t temp_result, *out; if (!depth) { out = &result; } else { temp_result.type = ecs_meta_get_type(&cur); temp_result.ptr = ecs_meta_get_ptr(&cur); out = &temp_result; } /* Parenthesis, parse nested expression */ ptr = flecs_script_expr_run( world, stack, ptr, out, EcsLeftParen, desc); if (ptr[0] != ')') { ecs_parser_error(name, expr, ptr - expr, "missing closing parenthesis"); return NULL; } ptr = flecs_parse_ws(ptr + 1); is_lvalue = true; } else if (!ecs_os_strcmp(token, "{")) { /* Parse nested value scope */ ecs_entity_t scope_type = ecs_meta_get_type(&cur); depth ++; /* Keep track of depth so we know when parsing is done */ if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { char *path = ecs_get_path(world, scope_type); ecs_parser_error(name, expr, ptr - expr, "expected '[' for collection type '%s'", path); ecs_os_free(path); return NULL; } } else if (!ecs_os_strcmp(token, "}")) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "[")) { /* Open collection value scope */ depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "unexpected '['"); return NULL; } } else if (!ecs_os_strcmp(token, "]")) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "-")) { if (unary_op != EcsExprOperUnknown) { ecs_parser_error(name, expr, ptr - expr, "unexpected unary operator"); return NULL; } unary_op = EcsMin; } else if (!ecs_os_strcmp(token, ",")) { /* Move to next field */ if (ecs_meta_next(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "null")) { if (ecs_meta_set_null(&cur) != 0) { goto error; } is_lvalue = true; } else if (token[0] == '\"') { /* Regular string */ if (ecs_meta_set_string_literal(&cur, token) != 0) { goto error; } is_lvalue = true; } else if (!ecs_os_strcmp(token, "`")) { /* Multiline string */ if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { goto error; } is_lvalue = true; } else if (token[0] == '$') { /* Variable */ if (!desc || !desc->vars) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s' (no variable scope)", token); return NULL; } if (!token[1]) { /* Empty name means default to assigned member */ const char *member = ecs_meta_get_member(&cur); if (!member) { ecs_parser_error(name, expr, ptr - expr, "invalid default variable outside member assignment"); return NULL; } ecs_os_strcpy(&token[1], member); } const ecs_script_var_t *var = ecs_script_vars_lookup(desc->vars, &token[1]); if (!var) { if (ptr[0] == '(') { /* Function */ ptr = flecs_funccall_parse(world, stack, name, expr, ptr, &token[1], &cur, &result, true, desc); if (!ptr) { goto error; } } else { ecs_value_t v = flecs_dotresolve_var(world, desc->vars, &token[1]); if (!v.ptr) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return NULL; } else { ecs_meta_set_value(&cur, &v); } } } else { ecs_meta_set_value(&cur, &var->value); } is_lvalue = true; } else { const char *tptr = flecs_parse_ws(ptr); for (; ptr != tptr; ptr ++) { if (ptr[0] == '\n') { newline = true; } } if (ptr[0] == ':') { /* Member assignment */ ptr ++; if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else if (ptr[0] == '(') { /* Function */ ptr = flecs_funccall_parse(world, stack, name, expr, ptr, token, &cur, &result, false, desc); if (!ptr) { goto error; } } else { if (ptr[0] != '[') { /* Entity id expression */ if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } else { /* Component expression */ ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = desc->lookup_action( world, token, desc->lookup_ctx); if (!e) { ecs_parser_error(name, expr, ptr - expr, "entity '%s' not found", token); goto error; } ptr = flecs_script_expr_parse_token( name, expr, ptr + 1, token); if (!ptr) { goto error; } ecs_entity_t component = desc->lookup_action( world, token, desc->lookup_ctx); if (!component) { ecs_parser_error(name, expr, ptr - expr, "unresolved component '%s'", token); goto error; } ecs_entity_t type = ecs_get_typeid(world, component); if (!type) { ecs_parser_error(name, expr, ptr - expr, "entity '%s' is not a component", token); goto error; } result.type = type; result.ptr = ECS_CONST_CAST(void*, ecs_get_id(world, e, component)); if (!result.ptr) { char *entitystr = ecs_id_str(world, e); char *idstr = ecs_id_str(world, component); ecs_parser_error(name, expr, ptr - expr, "entity '%s' does not have component '%s'", entitystr, idstr); ecs_os_free(idstr); ecs_os_free(entitystr); goto error; } if (ptr[0] != ']') { ecs_parser_error(name, expr, ptr - expr, "missing ] for component operator"); goto error; } ptr ++; if (ptr[0] == '.') { ecs_meta_cursor_t member_cur = ecs_meta_cursor( world, result.type, result.ptr); ptr = flecs_script_expr_parse_token( name, expr, ptr + 1, token); if (!ptr) { goto error; } ecs_meta_push(&member_cur); if (ecs_meta_dotmember(&member_cur, token) != 0) { ecs_parser_error(name, expr, ptr - expr, "failed to assign member '%s'", token); goto error; } result.type = ecs_meta_get_type(&member_cur); result.ptr = ecs_meta_get_ptr(&member_cur); } } } 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 = flecs_parse_ws_eol(ptr); } if (!value->ptr) { value->type = result.type; value->ptr = flecs_expr_value_new(stack, result.type); } if (value->ptr != result.ptr) { cur = ecs_meta_cursor(world, value->type, value->ptr); ecs_meta_set_value(&cur, &result); } ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return ptr; error: return NULL; } const char* ecs_script_expr_run( ecs_world_t *world, const char *ptr, ecs_value_t *value, const ecs_script_expr_run_desc_t *desc) { ecs_script_expr_run_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.lookup_action) { priv_desc.lookup_action = flecs_script_default_lookup; } /* 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_script_expr_run( world, &stack, ptr, value, EcsExprOperUnknown, &priv_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/script/interpolate.c * @brief String interpolation. */ #ifdef FLECS_SCRIPT #include static const char* flecs_parse_var_name( const char *ptr, char *token_out) { char ch, *bptr = token_out; while ((ch = *ptr)) { if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { goto error; } if (isalpha(ch) || isdigit(ch) || ch == '_') { *bptr = ch; bptr ++; ptr ++; } else { break; } } if (bptr == token_out) { goto error; } *bptr = '\0'; return ptr; error: return NULL; } static const char* flecs_parse_interpolated_str( const char *ptr, char *token_out) { char ch, *bptr = token_out; while ((ch = *ptr)) { if (bptr - token_out > ECS_MAX_TOKEN_SIZE) { goto error; } if (ch == '\\') { if (ptr[1] == '}') { *bptr = '}'; bptr ++; ptr += 2; continue; } } if (ch != '}') { *bptr = ch; bptr ++; ptr ++; } else { ptr ++; break; } } if (bptr == token_out) { goto error; } *bptr = '\0'; return ptr; error: return NULL; } char* ecs_script_string_interpolate( ecs_world_t *world, const char *str, const ecs_script_vars_t *vars) { char token[ECS_MAX_TOKEN_SIZE]; ecs_strbuf_t result = ECS_STRBUF_INIT; const char *ptr; char ch; for(ptr = str; (ch = *ptr); ptr++) { if (ch == '\\') { ptr ++; if (ptr[0] == '$') { ecs_strbuf_appendch(&result, '$'); continue; } if (ptr[0] == '\\') { ecs_strbuf_appendch(&result, '\\'); continue; } if (ptr[0] == '{') { ecs_strbuf_appendch(&result, '{'); continue; } if (ptr[0] == '}') { ecs_strbuf_appendch(&result, '}'); continue; } ptr --; } if (ch == '$') { ptr = flecs_parse_var_name(ptr + 1, token); if (!ptr) { ecs_parser_error(NULL, str, ptr - str, "invalid variable name '%s'", ptr); goto error; } ecs_script_var_t *var = ecs_script_vars_lookup(vars, token); if (!var) { ecs_parser_error(NULL, str, ptr - str, "unresolved variable '%s'", token); goto error; } if (ecs_ptr_to_str_buf( world, var->value.type, var->value.ptr, &result)) { goto error; } ptr --; } else if (ch == '{') { ptr = flecs_parse_interpolated_str(ptr + 1, token); if (!ptr) { ecs_parser_error(NULL, str, ptr - str, "invalid interpolated expression"); goto error; } ecs_script_expr_run_desc_t expr_desc = { .vars = ECS_CONST_CAST(ecs_script_vars_t*, vars) }; ecs_value_t expr_result = {0}; if (!ecs_script_expr_run(world, token, &expr_result, &expr_desc)) { goto error; } if (ecs_ptr_to_str_buf( world, expr_result.type, expr_result.ptr, &result)) { goto error; } ecs_value_free(world, expr_result.type, expr_result.ptr); ptr --; } else { ecs_strbuf_appendch(&result, ch); } } return ecs_strbuf_get(&result); error: return NULL; } #endif /** * @file addons/script/parser.c * @brief Script grammar parser. */ #ifdef FLECS_SCRIPT /** * @file addons/script/parser.h * @brief Script grammar parser. * * Macro utilities that facilitate a simple recursive descent parser. */ #ifndef FLECS_SCRIPT_PARSER_H #define FLECS_SCRIPT_PARSER_H #if defined(ECS_TARGET_CLANG) /* Ignore unused enum constants in switch as it would blow up the parser code */ #pragma clang diagnostic ignored "-Wswitch-enum" /* To allow for nested Parse statements */ #pragma clang diagnostic ignored "-Wshadow" #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #pragma GCC diagnostic ignored "-Wswitch-enum" #pragma GCC diagnostic ignored "-Wshadow" #elif defined(ECS_TARGET_MSVC) /* Allow for variable shadowing */ #pragma warning(disable : 4456) #endif /* Create script & parser structs with static token buffer */ #define EcsParserFixedBuffer(w, script_name, expr, tokens, tokens_len)\ ecs_script_impl_t script = {\ .pub.world = ECS_CONST_CAST(ecs_world_t*, w),\ .pub.name = script_name,\ .pub.code = expr\ };\ ecs_script_parser_t parser = {\ .script = flecs_script_impl(&script),\ .pos = expr,\ .token_cur = tokens\ } /* Definitions for parser functions */ #define ParserBegin\ ecs_script_tokens_t token_stack = {0};\ ecs_script_token_t *tokens = token_stack.tokens;\ (void)tokens #define ParserEnd\ Error("unexpected end of rule (parser error)");\ error:\ return NULL /* Get token */ #define Token(n) (tokens[n].value) /* Error */ #define Error(...)\ ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ goto error /* Parse expression */ #define Expr(until, ...)\ {\ ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ if (!(pos = flecs_script_expr(parser, pos, t, until))) {\ goto error;\ }\ if (!t->value[0] && (until == '\n' || until == '{')) {\ pos ++;\ Error("empty expression");\ }\ }\ Parse_1(until, __VA_ARGS__) /* Parse token until character */ #define Until(until, ...)\ {\ ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ if (!(pos = flecs_script_until(parser, pos, t, until))) {\ goto error;\ }\ }\ Parse_1(until, __VA_ARGS__) /* Parse next token */ #define Parse(...)\ {\ ecs_assert(token_stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ ecs_script_token_t *t = &token_stack.tokens[token_stack.count ++];\ if (!(pos = flecs_script_token(parser, pos, t, false))) {\ goto error;\ }\ switch(t->kind) {\ __VA_ARGS__\ default:\ if (t->value) {\ Error("unexpected %s'%s'", \ flecs_script_token_kind_str(t->kind), t->value);\ } else {\ Error("unexpected %s", \ flecs_script_token_kind_str(t->kind));\ }\ }\ } /* Parse N consecutive tokens */ #define Parse_1(tok, ...)\ Parse(\ case tok: {\ __VA_ARGS__\ }\ ) #define Parse_2(tok1, tok2, ...)\ Parse_1(tok1, \ Parse(\ case tok2: {\ __VA_ARGS__\ }\ )\ ) #define Parse_3(tok1, tok2, tok3, ...)\ Parse_2(tok1, tok2, \ Parse(\ case tok3: {\ __VA_ARGS__\ }\ )\ ) #define Parse_4(tok1, tok2, tok3, tok4, ...)\ Parse_3(tok1, tok2, tok3, \ Parse(\ case tok4: {\ __VA_ARGS__\ }\ )\ ) #define Parse_5(tok1, tok2, tok3, tok4, tok5, ...)\ Parse_4(tok1, tok2, tok3, tok4, \ Parse(\ case tok5: {\ __VA_ARGS__\ }\ )\ ) #define LookAhead_Keep() \ pos = lookahead;\ parser->token_keep = parser->token_cur /* Same as Parse, but doesn't error out if token is not in handled cases */ #define LookAhead(...)\ const char *lookahead;\ ecs_script_token_t lookahead_token;\ const char *old_lh_token_cur = parser->token_cur;\ if ((lookahead = flecs_script_token(parser, pos, &lookahead_token, true))) {\ token_stack.tokens[token_stack.count ++] = lookahead_token;\ switch(lookahead_token.kind) {\ __VA_ARGS__\ default:\ token_stack.count --;\ break;\ }\ if (old_lh_token_cur > parser->token_keep) {\ parser->token_cur = ECS_CONST_CAST(char*, old_lh_token_cur);\ } else {\ parser->token_cur = parser->token_keep;\ }\ } /* Lookahead N consecutive tokens */ #define LookAhead_1(tok, ...)\ LookAhead(\ case tok: {\ __VA_ARGS__\ }\ ) #define LookAhead_2(tok1, tok2, ...)\ LookAhead_1(tok1, \ const char *old_ptr = pos;\ pos = lookahead;\ LookAhead(\ case tok2: {\ __VA_ARGS__\ }\ )\ pos = old_ptr;\ ) #define LookAhead_3(tok1, tok2, tok3, ...)\ LookAhead_2(tok1, tok2, \ const char *old_ptr = pos;\ pos = lookahead;\ LookAhead(\ case tok3: {\ __VA_ARGS__\ }\ )\ pos = old_ptr;\ ) /* Open scope */ #define Scope(s, ...) {\ ecs_script_scope_t *old_scope = parser->scope;\ parser->scope = s;\ __VA_ARGS__\ parser->scope = old_scope;\ } /* Parser loop */ #define Loop(...)\ int32_t token_stack_count = token_stack.count;\ do {\ token_stack.count = token_stack_count;\ __VA_ARGS__\ } while (true); #define EndOfRule return pos #endif #define EcsTokEndOfStatement\ case ';':\ case '\n':\ case '\0' static const char* flecs_script_stmt( ecs_script_parser_t *parser, const char *pos); /* Parse scope (statements inside {}) */ static const char* flecs_script_scope( ecs_script_parser_t *parser, ecs_script_scope_t *scope, const char *pos) { ParserBegin; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pos[-1] == '{', ECS_INTERNAL_ERROR, NULL); ecs_script_scope_t *prev = parser->scope; parser->scope = scope; Loop( LookAhead( case EcsTokScopeClose: pos = lookahead; goto scope_close; case EcsTokEnd: Error("unexpected end of script"); goto error; ) pos = flecs_script_stmt(parser, pos); if (!pos) { goto error; } ) scope_close: parser->scope = prev; ecs_assert(pos[-1] == '}', ECS_INTERNAL_ERROR, NULL); return pos; ParserEnd; } /* Parse comma expression (expressions separated by ',') */ static const char* flecs_script_comma_expr( ecs_script_parser_t *parser, const char *pos, bool is_base_list) { ParserBegin; Loop( LookAhead( case '\n': pos = lookahead; continue; case EcsTokIdentifier: LookAhead_Keep(); if (is_base_list) { flecs_script_insert_pair_tag(parser, "IsA", Token(0)); } else { flecs_script_insert_entity(parser, Token(0)); } LookAhead_1(',', pos = lookahead; continue; ) ) break; ) return pos; } /* Parse with expression (expression after 'with' keyword) */ static const char* flecs_script_with_expr( ecs_script_parser_t *parser, const char *pos) { ParserBegin; Parse( // Position case EcsTokIdentifier: { // Position ( LookAhead_1('(', pos = lookahead; // Position ( expr ) Expr(')', ecs_script_component_t *component = flecs_script_insert_component(parser, Token(0)); component->node.kind = EcsAstWithComponent; component->expr = Token(2); EndOfRule; ) ) if (Token(0)[0] == '$') { ecs_script_var_component_t *var = flecs_script_insert_var_component(parser, &Token(0)[1]); var->node.kind = EcsAstWithVar; } else { ecs_script_tag_t *tag = flecs_script_insert_tag(parser, Token(0)); tag->node.kind = EcsAstWithTag; } EndOfRule; } // ( case '(': // (Eats, Apples) Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', // (Eats, Apples) ( expr LookAhead_1('(', pos = lookahead; // (Eats, Apples) ( expr ) Expr(')', ecs_script_component_t *component = flecs_script_insert_pair_component(parser, Token(1), Token(3)); component->node.kind = EcsAstWithComponent; component->expr = Token(6); EndOfRule; ) ) ecs_script_tag_t *tag = flecs_script_insert_pair_tag(parser, Token(1), Token(3)); tag->node.kind = EcsAstWithTag; EndOfRule; ) ) ParserEnd; } /* Parse with expression list (expression list after 'with' keyword) */ static const char* flecs_script_with( ecs_script_parser_t *parser, ecs_script_with_t *with, const char *pos) { ParserBegin; bool has_next; do { Scope(with->expressions, pos = flecs_script_with_expr(parser, pos); ) if (!pos) { goto error; } Parse( case ',': { has_next = true; break; } case '{': { return flecs_script_scope(parser, with->scope, pos); } ) } while (has_next); ParserEnd; } /* Parenthesis expression */ static const char* flecs_script_paren_expr( ecs_script_parser_t *parser, const char *kind, ecs_script_entity_t *entity, const char *pos) { ParserBegin; Expr(')', entity->kind_w_expr = true; Scope(entity->scope, ecs_script_component_t *component = flecs_script_insert_component(parser, kind); component->expr = Token(0); ) Parse( // Position spaceship (expr)\n EcsTokEndOfStatement: { EndOfRule; } // Position spaceship (expr) { case '{': { return flecs_script_scope(parser, entity->scope, pos); } ) ) ParserEnd; } /* Parse a single statement */ static const char* flecs_script_stmt( ecs_script_parser_t *parser, const char *pos) { ParserBegin; Parse( case EcsTokIdentifier: goto identifier; case '{': return flecs_script_scope(parser, flecs_script_insert_scope(parser), pos); case '(': goto paren; case '@': goto annotation; case EcsTokKeywordWith: goto with_stmt; case EcsTokKeywordModule: goto module_stmt; case EcsTokKeywordUsing: goto using_stmt; case EcsTokKeywordTemplate: goto template_stmt; case EcsTokKeywordProp: goto prop_var; case EcsTokKeywordConst: goto const_var; case EcsTokKeywordIf: goto if_stmt; EcsTokEndOfStatement: EndOfRule; ); identifier: { // enterprise } (end of scope) LookAhead_1('}', goto insert_tag; ) Parse( // enterprise { case '{': { return flecs_script_scope(parser, flecs_script_insert_entity( parser, Token(0))->scope, pos); } // Red, case ',': { flecs_script_insert_entity(parser, Token(0)); pos = flecs_script_comma_expr(parser, pos, false); EndOfRule; } // Npc\n EcsTokEndOfStatement: { // Npc\n{ LookAhead_1('{', pos = lookahead; return flecs_script_scope(parser, flecs_script_insert_entity( parser, Token(0))->scope, pos); ) goto insert_tag; } // auto_override | case '|': { goto identifier_flag; } // Position: case ':': { goto identifier_colon; } // x = case '=': { goto identifier_assign; } // SpaceShip( case '(': { goto identifier_paren; } // Spaceship enterprise case EcsTokIdentifier: { goto identifier_identifier; } ) } insert_tag: { if (Token(0)[0] == '$') { if (!flecs_script_insert_var_component(parser, &Token(0)[1])) { Error( "invalid context for variable component '%s': must be " "part of entity", tokens[0].value); } } else { if (!flecs_script_insert_tag(parser, Token(0))) { Error( "invalid context for tag '%s': must be part of entity", tokens[0].value); } } EndOfRule; } // @ annotation: { // @brief Parse_1(EcsTokIdentifier, // $brief expr Until('\n', flecs_script_insert_annot(parser, Token(1), Token(2)); EndOfRule; ) ) } // with with_stmt: { ecs_script_with_t *with = flecs_script_insert_with(parser); pos = flecs_script_with(parser, with, pos); EndOfRule; } // using using_stmt: { // using flecs.meta\n Parse_2(EcsTokIdentifier, '\n', flecs_script_insert_using(parser, Token(1)); EndOfRule; ) } // module module_stmt: { // using flecs.meta\n Parse_2(EcsTokIdentifier, '\n', flecs_script_insert_module(parser, Token(1)); EndOfRule; ) } // template template_stmt: { // template SpaceShip Parse_1(EcsTokIdentifier, ecs_script_template_node_t *template = flecs_script_insert_template( parser, Token(1)); Parse( // template SpaceShip { case '{': return flecs_script_scope(parser, template->scope, pos); // template SpaceShip\n EcsTokEndOfStatement: EndOfRule; ) ) } // prop prop_var: { // prop color = Color: Parse_4(EcsTokIdentifier, '=', EcsTokIdentifier, ':', ecs_script_var_node_t *var = flecs_script_insert_var( parser, Token(1)); var->node.kind = EcsAstProp; var->type = Token(3); // prop color = Color : { LookAhead_1('{', // prop color = Color: {expr} pos = lookahead; Expr('}', var->expr = Token(6); EndOfRule; ) ) // prop color = Color : expr\n Expr('\n', var->expr = Token(5); EndOfRule; ) ) } // const const_var: { // const color Parse_1(EcsTokIdentifier, ecs_script_var_node_t *var = flecs_script_insert_var( parser, Token(1)); var->node.kind = EcsAstConst; Parse( // const color = case '=': { // const color = Color : LookAhead_2(EcsTokIdentifier, ':', pos = lookahead; var->type = Token(3); // const color = Color: { LookAhead_1('{', // const color = Color: {expr} pos = lookahead; Expr('}', var->expr = Token(6); EndOfRule; ) ) // const color = Color: expr\n Expr('\n', var->expr = Token(5); EndOfRule; ) ) // const PI = expr\n Expr('\n', var->expr = Token(3); EndOfRule; ) } ) ) } // if if_stmt: { // if expr { Expr('{', ecs_script_if_t *stmt = flecs_script_insert_if(parser); stmt->expr = Token(1); pos = flecs_script_scope(parser, stmt->if_true, pos); if (!pos) { goto error; } // if expr { } else LookAhead_1(EcsTokKeywordElse, pos = lookahead; // if expr { } else { Parse_1('{', return flecs_script_scope(parser, stmt->if_false, pos); ) ) EndOfRule; ) } // ( paren: { // (Likes, Apples) Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', goto pair; ) } // (Likes, Apples) pair: { // (Likes, Apples) } (end of scope) LookAhead_1('}', flecs_script_insert_pair_tag(parser, Token(1), Token(3)); EndOfRule; ) Parse( // (Likes, Apples)\n EcsTokEndOfStatement: { flecs_script_insert_pair_tag(parser, Token(1), Token(3)); EndOfRule; } // (Eats, Apples): case ':': { // (Eats, Apples): { Parse_1('{', // (Eats, Apples): { expr } Expr('}', ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); comp->expr = Token(7); EndOfRule; ) ) } // (IsA, Machine) { case '{': { ecs_script_pair_scope_t *ps = flecs_script_insert_pair_scope( parser, Token(1), Token(3)); return flecs_script_scope(parser, ps->scope, pos); } ) } // auto_override | identifier_flag: { ecs_id_t flag; if (!ecs_os_strcmp(Token(0), "auto_override")) { flag = ECS_AUTO_OVERRIDE; } else { Error("invalid flag '%s'", Token(0)); } Parse( // auto_override | ( case '(': // auto_override | (Rel, Tgt) Parse_4(EcsTokIdentifier, ',', EcsTokIdentifier, ')', ecs_script_tag_t *tag = flecs_script_insert_pair_tag( parser, Token(3), Token(5)); tag->id.flag = flag; Parse( // auto_override | (Rel, Tgt)\n EcsTokEndOfStatement: { EndOfRule; } // auto_override | (Rel, Tgt): case ':': { Parse_1('{', // auto_override | (Rel, Tgt): {expr} Expr('}', { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(3), Token(5)); comp->expr = Token(9); EndOfRule; }) ) } ) ) // auto_override | Position case EcsTokIdentifier: { ecs_script_tag_t *tag = flecs_script_insert_tag( parser, Token(2)); tag->id.flag = flag; Parse( // auto_override | Position\n EcsTokEndOfStatement: { EndOfRule; } // auto_override | Position: case ':': { Parse_1('{', // auto_override | Position: {expr} Expr('}', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(2)); comp->expr = Token(5); EndOfRule; }) ) } ) } ) } // Position: identifier_colon: { { // Position: { LookAhead_1('{', pos = lookahead; goto component_expr_scope; ) } { // Position: [ LookAhead_1('[', pos = lookahead; goto component_expr_collection; ) } // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(0)); Scope(entity->scope, flecs_script_insert_pair_tag(parser, "IsA", Token(2)); LookAhead_1(',', { pos = lookahead; pos = flecs_script_comma_expr(parser, pos, true); }) ) Parse( // enterprise : SpaceShip\n EcsTokEndOfStatement: EndOfRule; // enterprise : SpaceShip { case '{': return flecs_script_scope(parser, entity->scope, pos); ) }) } // x = identifier_assign: { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(0)); // x = Position: LookAhead_2(EcsTokIdentifier, ':', pos = lookahead; // x = Position: { Parse_1('{', { // x = Position: {expr} Expr('}', Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(2)); comp->expr = Token(5); ) // x = Position: {expr}\n Parse( EcsTokEndOfStatement: EndOfRule; ) ) }) ) // x = f32\n Expr('\n', Scope(entity->scope, ecs_script_default_component_t *comp = flecs_script_insert_default_component(parser); comp->expr = Token(2); ) EndOfRule; ) } // Spaceship enterprise identifier_identifier: { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(1)); entity->kind = Token(0); // Spaceship enterprise : LookAhead_1(':', pos = lookahead; Parse_1(EcsTokIdentifier, { Scope(entity->scope, flecs_script_insert_pair_tag(parser, "IsA", Token(3)); LookAhead_1(',', { pos = lookahead; pos = flecs_script_comma_expr(parser, pos, true); }) ) goto identifier_identifier_x; }) ) identifier_identifier_x: Parse( // Spaceship enterprise\n EcsTokEndOfStatement: { EndOfRule; } // Spaceship enterprise { case '{': { return flecs_script_scope(parser, entity->scope, pos); } // Spaceship enterprise( case '(': { return flecs_script_paren_expr(parser, Token(0), entity, pos); } ) } // SpaceShip( identifier_paren: { // SpaceShip() Expr(')', Parse( // SpaceShip(expr)\n EcsTokEndOfStatement: { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, NULL); Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); comp->expr = Token(2); if (!ecs_os_strcmp(comp->expr, "{}")) { comp->expr = NULL; } ) EndOfRule; } // SpaceShip(expr) { case '{': { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, NULL); Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); comp->expr = Token(2); ) return flecs_script_scope(parser, entity->scope, pos); } ) ) } // Position: { component_expr_scope: { // Position: {expr} Expr('}', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); comp->expr = Token(3); EndOfRule; }) } // Points: [ component_expr_collection: { // Position: [expr] Expr(']', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); comp->expr = Token(3); comp->is_collection = true; EndOfRule; }) } ParserEnd; } /* Parse script */ ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, const char *code) { if (!code) { code = ""; } ecs_script_t *script = flecs_script_new(world); script->name = ecs_os_strdup(name); script->code = ecs_os_strdup(code); ecs_script_impl_t *impl = flecs_script_impl(script); ecs_script_parser_t parser = { .script = impl, .scope = impl->root, .significant_newline = true }; /* Allocate a buffer that is able to store all parsed tokens. Multiply the * size of the script by two so that there is enough space to add \0 * terminators and expression deliminators ('""') * The token buffer will exist for as long as the script object exists, and * ensures that AST nodes don't need to do separate allocations for the data * they contain. */ impl->token_buffer_size = ecs_os_strlen(code) * 2 + 1; impl->token_buffer = flecs_alloc( &impl->allocator, impl->token_buffer_size); parser.token_cur = impl->token_buffer; /* Start parsing code */ const char *pos = script->code; do { pos = flecs_script_stmt(&parser, pos); if (!pos) { /* NULL means error */ goto error; } if (!pos[0]) { /* \0 means end of input */ break; } } while (true); return script; error: ecs_script_free(script); return NULL; } #endif /** * @file addons/script/query_parser.c * @brief Script grammar parser. */ #ifdef FLECS_SCRIPT #define EcsTokTermIdentifier\ EcsTokIdentifier:\ case EcsTokNumber:\ case EcsTokMul #define EcsTokEndOfTerm\ '}':\ pos --; /* Give token back to parser */\ case EcsTokOr:\ if (t->kind == EcsTokOr) {\ if (parser->term->oper != EcsAnd) {\ Error("cannot mix operators in || expression");\ }\ parser->term->oper = EcsOr;\ }\ case ',':\ case '\n':\ case '\0' // $this == static const char* flecs_term_parse_equality_pred( ecs_script_parser_t *parser, const char *pos, ecs_entity_t pred) { ParserBegin; if (parser->term->oper != EcsAnd) { Error("cannot mix operator with equality expression"); } parser->term->src = parser->term->first; parser->term->first = (ecs_term_ref_t){0}; parser->term->first.id = pred; Parse( // $this == foo // ^ case EcsTokTermIdentifier: { parser->term->second.name = Token(0); Parse( case EcsTokEndOfTerm: EndOfRule; ) } // $this == "foo" // ^ case EcsTokString: { parser->term->second.name = Token(0); parser->term->second.id = EcsIsName; if (pred == EcsPredMatch) { if (Token(0)[0] == '!') { /* If match expression starts with !, set Not operator. The * reason the ! is embedded in the expression is because * there is only a single match (~=) operator. */ parser->term->second.name ++; parser->term->oper = EcsNot; } } Parse( case EcsTokEndOfTerm: EndOfRule; ) } ) ParserEnd; } static ecs_entity_t flecs_query_parse_trav_flags( const char *tok) { if (!ecs_os_strcmp(tok, "self")) return EcsSelf; else if (!ecs_os_strcmp(tok, "up")) return EcsUp; else if (!ecs_os_strcmp(tok, "cascade")) return EcsCascade; else if (!ecs_os_strcmp(tok, "desc")) return EcsDesc; else return 0; } static const char* flecs_term_parse_trav( ecs_script_parser_t *parser, ecs_term_ref_t *ref, const char *pos) { ParserBegin; Loop( // self Parse_1(EcsTokIdentifier, ref->id |= flecs_query_parse_trav_flags(Token(0)); LookAhead( // self| case '|': pos = lookahead; continue; // self IsA case EcsTokIdentifier: pos = lookahead; parser->term->trav = ecs_lookup( parser->script->pub.world, Token(1)); if (!parser->term->trav) { Error( "unresolved traversal relationship '%s'", Token(1)); goto error; } EndOfRule; ) EndOfRule; ) ) ParserEnd; } // Position( static const char* flecs_term_parse_arg( ecs_script_parser_t *parser, const char *pos, int32_t arg) { ParserBegin; ecs_term_ref_t *ref = NULL; // Position(src if (arg == 0) { ref = &parser->term->src; // Position(src, tgt } else if (arg == 1) { ref = &parser->term->second; } else { if (arg > FLECS_TERM_ARG_COUNT_MAX) { Error("too many arguments in term"); } ref = &parser->extra_args[arg - 2]; } bool is_trav_flag = false; LookAhead_1(EcsTokIdentifier, is_trav_flag = flecs_query_parse_trav_flags(Token(0)) != 0; ) if (is_trav_flag) { // Position(self|up // ^ pos = flecs_term_parse_trav(parser, ref, pos); if (!pos) { goto error; } } else { // Position(src // ^ Parse( case EcsTokTermIdentifier: { ref->name = Token(0); // Position(src| // ^ LookAhead_1('|', pos = lookahead; pos = flecs_term_parse_trav(parser, ref, pos); if (!pos) { goto error; } // Position(src|up IsA // ^ LookAhead_1(EcsTokIdentifier, pos = lookahead; parser->term->trav = ecs_lookup( parser->script->pub.world, Token(1)); if (!parser->term->trav) { Error( "unresolved trav identifier '%s'", Token(1)); } ) ) break; } ) } Parse( // Position(src, // ^ case ',': if ((arg > 1) && parser->extra_oper != EcsAnd) { Error("cannot mix operators in extra term arguments"); } parser->extra_oper = EcsAnd; return flecs_term_parse_arg(parser, pos, arg + 1); // Position(src, second || // ^ case EcsTokOr: if ((arg > 1) && parser->extra_oper != EcsOr) { Error("cannot mix operators in extra term arguments"); } parser->extra_oper = EcsOr; return flecs_term_parse_arg(parser, pos, arg + 1); // Position(src) // ^ case ')': Parse( case EcsTokEndOfTerm: EndOfRule; ) ) ParserEnd; } // Position static const char* flecs_term_parse_id( ecs_script_parser_t *parser, const char *pos) { ParserBegin; Parse( case EcsTokEq: return flecs_term_parse_equality_pred( parser, pos, EcsPredEq); case EcsTokNeq: { const char *ret = flecs_term_parse_equality_pred( parser, pos, EcsPredEq); if (ret) { parser->term->oper = EcsNot; } return ret; } case EcsTokMatch: return flecs_term_parse_equality_pred( parser, pos, EcsPredMatch); // Position| case '|': { pos = flecs_term_parse_trav(parser, &parser->term->first, pos); if (!pos) { goto error; } // Position|self( Parse( case '(': return flecs_term_parse_arg(parser, pos, 0); case EcsTokEndOfTerm: EndOfRule; ) } // Position( case '(': { // Position() LookAhead_1(')', pos = lookahead; parser->term->src.id = EcsIsEntity; Parse( case EcsTokEndOfTerm: EndOfRule; ) ) return flecs_term_parse_arg(parser, pos, 0); } case EcsTokEndOfTerm: EndOfRule; ) ParserEnd; } // ( static const char* flecs_term_parse_pair( ecs_script_parser_t *parser, const char *pos) { ParserBegin; // (Position // ^ Parse( case EcsTokTermIdentifier: { parser->term->first.name = Token(0); LookAhead_1('|', // (Position|self pos = lookahead; pos = flecs_term_parse_trav( parser, &parser->term->first, pos); if (!pos) { goto error; } ) // (Position, Parse_1(',', return flecs_term_parse_arg(parser, pos, 1); ) } ) ParserEnd; } // AND static const char* flecs_term_parse_flags( ecs_script_parser_t *parser, const char *token_0, const char *pos) { ecs_assert(token_0 != NULL, ECS_INTERNAL_ERROR, NULL); ParserBegin; ecs_id_t flag = 0; int16_t oper = 0; ecs_term_t *term = parser->term; // AND if (!ecs_os_strcmp(token_0, "and")) oper = EcsAndFrom; else if (!ecs_os_strcmp(token_0, "or")) oper = EcsOrFrom; else if (!ecs_os_strcmp(token_0, "not")) oper = EcsNotFrom; else if (!ecs_os_strcmp(token_0, "auto_override")) flag = ECS_AUTO_OVERRIDE; else if (!ecs_os_strcmp(token_0, "toggle")) flag = ECS_TOGGLE; else { // Position term->first.name = token_0; return flecs_term_parse_id(parser, pos); } if (oper || flag) { // and | // ^ Parse_1('|', Parse( // and | Position // ^ case EcsTokTermIdentifier: { if (oper) { term->oper = oper; } else if (flag) { term->id = flag; } term->first.name = Token(1); return flecs_term_parse_id(parser, pos); } // and | ( // ^ case '(': { return flecs_term_parse_pair(parser, pos); } ) ) } ParserEnd; } // ! static const char* flecs_term_parse_unary( ecs_script_parser_t *parser, const char *pos) { ParserBegin; Parse( // !( case '(': { return flecs_term_parse_pair(parser, pos); } // !{ case '{': { parser->term->first.id = EcsScopeOpen; parser->term->src.id = EcsIsEntity; parser->term->inout = EcsInOutNone; EndOfRule; } // !Position // ^ case EcsTokTermIdentifier: { parser->term->first.name = Token(0); return flecs_term_parse_id(parser, pos); } ) ParserEnd; } // [ static const char* flecs_term_parse_inout( ecs_script_parser_t *parser, const char *pos) { ParserBegin; ecs_term_t *term = parser->term; // [inout] // ^ Parse_2(EcsTokIdentifier, ']', if (!ecs_os_strcmp(Token(0), "default")) term->inout = EcsInOutDefault; else if (!ecs_os_strcmp(Token(0), "none")) term->inout = EcsInOutNone; else if (!ecs_os_strcmp(Token(0), "filter")) term->inout = EcsInOutFilter; else if (!ecs_os_strcmp(Token(0), "inout")) term->inout = EcsInOut; else if (!ecs_os_strcmp(Token(0), "in")) term->inout = EcsIn; else if (!ecs_os_strcmp(Token(0), "out")) term->inout = EcsOut; Parse( // [inout] Position // ^ case EcsTokTermIdentifier: { return flecs_term_parse_flags(parser, Token(2), pos); } // [inout] !Position // ^ case '!': term->oper = EcsNot; return flecs_term_parse_unary(parser, pos); case '?': term->oper = EcsOptional; return flecs_term_parse_unary(parser, pos); // [inout] ( // ^ case '(': { return flecs_term_parse_pair(parser, pos); } ) ) ParserEnd; } static const char* flecs_query_term_parse( ecs_script_parser_t *parser, const char *pos) { ParserBegin; Parse( case '[': return flecs_term_parse_inout(parser, pos); case EcsTokTermIdentifier: return flecs_term_parse_flags(parser, Token(0), pos); case '(': return flecs_term_parse_pair(parser, pos); case '!': parser->term->oper = EcsNot; return flecs_term_parse_unary(parser, pos); case '?': parser->term->oper = EcsOptional; return flecs_term_parse_unary(parser, pos); case '{': parser->term->first.id = EcsScopeOpen; parser->term->src.id = EcsIsEntity; parser->term->inout = EcsInOutNone; EndOfRule; case '}': parser->term->first.id = EcsScopeClose; parser->term->src.id = EcsIsEntity; parser->term->inout = EcsInOutNone; LookAhead_1(',', pos = lookahead; ) EndOfRule; case '\n':\ case '\0': EndOfRule; ); ParserEnd; } int flecs_terms_parse( ecs_script_t *script, ecs_term_t *terms, int32_t *term_count_out) { if (!ecs_os_strcmp(script->code, "0")) { *term_count_out = 0; return 0; } ecs_script_parser_t parser = { .script = flecs_script_impl(script), .pos = script->code }; parser.token_cur = flecs_script_impl(script)->token_buffer; int32_t term_count = 0; const char *ptr = script->code; ecs_term_ref_t extra_args[FLECS_TERM_ARG_COUNT_MAX]; ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); parser.extra_args = extra_args; parser.extra_oper = 0; do { if (term_count == FLECS_TERM_COUNT_MAX) { ecs_err("max number of terms (%d) reached, increase " "FLECS_TERM_COUNT_MAX to support more", FLECS_TERM_COUNT_MAX); goto error; } /* Parse next term */ ecs_term_t *term = &terms[term_count]; parser.term = term; ecs_os_memset_t(term, 0, ecs_term_t); ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); parser.extra_oper = 0; ptr = flecs_query_term_parse(&parser, ptr); if (!ptr) { /* Parser error */ goto error; } if (!ecs_term_is_initialized(term)) { /* Last term parsed */ break; } term_count ++; /* Unpack terms with more than two args into multiple terms so that: * Rel(X, Y, Z) * becomes: * Rel(X, Y), Rel(Y, Z) */ int32_t arg = 0; while (ecs_term_ref_is_set(&extra_args[arg ++])) { ecs_assert(arg <= FLECS_TERM_ARG_COUNT_MAX, ECS_INTERNAL_ERROR, NULL); if (term_count == FLECS_TERM_COUNT_MAX) { ecs_err("max number of terms (%d) reached, increase " "FLECS_TERM_COUNT_MAX to support more", FLECS_TERM_COUNT_MAX); goto error; } term = &terms[term_count ++]; *term = term[-1]; if (parser.extra_oper == EcsAnd) { term->src = term[-1].second; term->second = extra_args[arg - 1]; } else if (parser.extra_oper == EcsOr) { term->src = term[-1].src; term->second = extra_args[arg - 1]; term[-1].oper = EcsOr; } if (term->first.name != NULL) { term->first.name = term->first.name; } if (term->src.name != NULL) { term->src.name = term->src.name; } } if (arg) { ecs_os_memset_n(extra_args, 0, ecs_term_ref_t, FLECS_TERM_ARG_COUNT_MAX); } } while (ptr[0]); (*term_count_out) += term_count; return 0; error: return -1; } const char* flecs_term_parse( ecs_world_t *world, const char *name, const char *expr, ecs_term_t *term, char *token_buffer) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); ecs_assert(term != NULL, ECS_INVALID_PARAMETER, NULL); EcsParserFixedBuffer(world, name, expr, token_buffer, 256); parser.term = term; const char *result = flecs_query_term_parse(&parser, expr); if (!result) { return NULL; } ecs_os_memset_t(term, 0, ecs_term_t); return flecs_query_term_parse(&parser, expr); } const char* flecs_id_parse( const ecs_world_t *world, const char *name, const char *expr, ecs_id_t *id) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(expr != NULL, ECS_INVALID_PARAMETER, name); ecs_assert(id != NULL, ECS_INVALID_PARAMETER, NULL); char token_buffer[256]; ecs_term_t term = {0}; EcsParserFixedBuffer(world, name, expr, token_buffer, 256); parser.term = &term; expr = flecs_scan_whitespace(&parser, expr); if (!ecs_os_strcmp(expr, "#0")) { *id = 0; return &expr[1]; } const char *result = flecs_query_term_parse(&parser, expr); if (!result) { return NULL; } if (ecs_term_finalize(world, &term)) { return NULL; } if (!ecs_id_is_valid(world, term.id)) { ecs_parser_error(name, expr, (result - expr), "invalid term for add expression"); return NULL; } if (term.oper != EcsAnd) { ecs_parser_error(name, expr, (result - expr), "invalid operator for add expression"); return NULL; } if ((term.src.id & ~EcsTraverseFlags) != (EcsThis|EcsIsVariable)) { ecs_parser_error(name, expr, (result - expr), "invalid source for add expression (must be $this)"); return NULL; } *id = term.id; return result; } static const char* flecs_query_arg_parse( ecs_script_parser_t *parser, ecs_query_t *q, ecs_iter_t *it, const char *pos) { ParserBegin; Parse_3(EcsTokIdentifier, ':', EcsTokIdentifier, { int var = ecs_query_find_var(q, Token(0)); if (var == -1) { Error("unknown variable '%s'", Token(0)); } ecs_entity_t val = ecs_lookup(q->world, Token(2)); if (!val) { Error("unresolved entity '%s'", Token(2)); } ecs_iter_set_var(it, var, val); EndOfRule; }) ParserEnd; } static const char* flecs_query_args_parse( ecs_script_parser_t *parser, ecs_query_t *q, ecs_iter_t *it, const char *pos) { ParserBegin; bool has_paren = false; LookAhead( case '\0': pos = lookahead; EndOfRule; case '(': { pos = lookahead; has_paren = true; LookAhead_1(')', pos = lookahead; EndOfRule; ) } ) Loop( pos = flecs_query_arg_parse(parser, q, it, pos); if (!pos) { goto error; } Parse( case ',': continue; case '\0': EndOfRule; case ')': if (!has_paren) { Error("unexpected ')' without opening '(')"); } EndOfRule; ) ) ParserEnd; } const char* ecs_query_args_parse( ecs_query_t *q, ecs_iter_t *it, const char *expr) { flecs_poly_assert(q, ecs_query_t); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(expr != NULL, ECS_INVALID_PARAMETER, NULL); const char *q_name = q->entity ? ecs_get_name(q->world, q->entity) : NULL; if (ecs_os_strlen(expr) > 512) { ecs_parser_error(q_name, expr, 0, "query argument expression too long"); return NULL; } char token_buffer[1024]; EcsParserFixedBuffer(q->world, q_name, expr, token_buffer, 256); return flecs_query_args_parse(&parser, q, it, expr); error: return NULL; } #endif /** * @file addons/script/script.c * @brief Script API. */ #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScript); static ECS_MOVE(EcsScript, dst, src, { if (dst->script && (dst->script != src->script)) { if (dst->template_ && (dst->template_ != src->template_)) { flecs_script_template_fini( flecs_script_impl(dst->script), dst->template_); } ecs_script_free(dst->script); } dst->script = src->script; dst->template_ = src->template_; src->script = NULL; src->template_ = NULL; }) static ECS_DTOR(EcsScript, ptr, { if (ptr->template_) { flecs_script_template_fini( flecs_script_impl(ptr->script), ptr->template_); } if (ptr->script) { ecs_script_free(ptr->script); } }) static ecs_id_t flecs_script_tag( ecs_entity_t script, ecs_entity_t instance) { if (!instance) { return ecs_pair_t(EcsScript, script); } else { return ecs_pair(EcsChildOf, instance); } } ecs_script_t* flecs_script_new( ecs_world_t *world) { ecs_script_impl_t *result = ecs_os_calloc_t(ecs_script_impl_t); flecs_allocator_init(&result->allocator); ecs_script_parser_t parser = { .script = result }; result->root = flecs_script_scope_new(&parser); result->pub.world = world; result->refcount = 1; return &result->pub; } void ecs_script_clear( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance) { ecs_delete_with(world, flecs_script_tag(script, instance)); } int ecs_script_run( ecs_world_t *world, const char *name, const char *code) { ecs_script_t *script = ecs_script_parse(world, name, code); if (!script) { goto error; } ecs_entity_t prev_scope = ecs_set_scope(world, 0); if (ecs_script_eval(script)) { goto error_free; } ecs_set_scope(world, prev_scope); ecs_script_free(script); return 0; error_free: ecs_script_free(script); error: return -1; } int ecs_script_run_file( ecs_world_t *world, const char *filename) { char *script = flecs_load_from_file(filename); if (!script) { return -1; } int result = ecs_script_run(world, filename, script); ecs_os_free(script); return result; } void ecs_script_free( ecs_script_t *script) { ecs_script_impl_t *impl = flecs_script_impl(script); ecs_check(impl->refcount > 0, ECS_INVALID_OPERATION, NULL); if (!--impl->refcount) { flecs_script_visit_free(script); flecs_free(&impl->allocator, impl->token_buffer_size, impl->token_buffer); flecs_allocator_fini(&impl->allocator); ecs_os_free(ECS_CONST_CAST(char*, impl->pub.name)); /* safe, owned value */ ecs_os_free(ECS_CONST_CAST(char*, impl->pub.code)); /* safe, owned value */ ecs_os_free(impl); } error: return; } int ecs_script_update( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *code) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(code != NULL, ECS_INTERNAL_ERROR, NULL); const char *name = ecs_get_name(world, e); EcsScript *s = ecs_ensure(world, e, EcsScript); if (s->template_) { char *template_name = ecs_get_path(world, s->template_->entity); ecs_err("cannot update scripts for individual templates, " "update parent script instead (tried to update '%s')", template_name); ecs_os_free(template_name); return -1; } if (s->script) { ecs_script_free(s->script); } s->script = ecs_script_parse(world, name, code); if (!s->script) { return -1; } int result = 0; bool is_defer = ecs_is_deferred(world); ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_script_clear(world, e, instance); ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); if (ecs_script_eval(s->script)) { ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } ecs_set_with(world, prev); if (is_defer) { flecs_resume_readonly(real_world, &srs); } return result; } ecs_entity_t ecs_script_init( ecs_world_t *world, const ecs_script_desc_t *desc) { const char *script = NULL; ecs_entity_t e = desc->entity; ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); if (!e) { if (desc->filename) { e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); } else { e = ecs_new(world); } } script = desc->code; if (!script && desc->filename) { script = flecs_load_from_file(desc->filename); if (!script) { goto error; } } if (ecs_script_update(world, e, 0, script)) { goto error; } if (script != desc->code) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free(ECS_CONST_CAST(char*, script)); } return e; error: if (script != desc->code) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free(ECS_CONST_CAST(char*, script)); } if (!desc->entity) { ecs_delete(world, e); } return 0; } static int EcsScript_serialize(const ecs_serializer_t *ser, const void *ptr) { const EcsScript *data = ptr; if (data->script) { ser->member(ser, "name"); ser->value(ser, ecs_id(ecs_string_t), &data->script->name); ser->member(ser, "code"); ser->value(ser, ecs_id(ecs_string_t), &data->script->code); char *ast = ecs_script_ast_to_str(data->script); ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &ast); ecs_os_free(ast); } else { char *nullString = NULL; ser->member(ser, "name"); ser->value(ser, ecs_id(ecs_string_t), &nullString); ser->member(ser, "code"); ser->value(ser, ecs_id(ecs_string_t), &nullString); ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &nullString); } return 0; } void FlecsScriptImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScript); ECS_IMPORT(world, FlecsMeta); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsScript), "Module with components for managing Flecs scripts"); #endif ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); ecs_set_hooks(world, EcsScript, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScript), .dtor = ecs_dtor(EcsScript) }); ECS_COMPONENT(world, ecs_script_t); ecs_struct(world, { .entity = ecs_id(ecs_script_t), .members = { { .name = "name", .type = ecs_id(ecs_string_t) }, { .name = "code", .type = ecs_id(ecs_string_t) }, { .name = "ast", .type = ecs_id(ecs_string_t) } } }); ecs_opaque(world, { .entity = ecs_id(EcsScript), .type.as_type = ecs_id(ecs_script_t), .type.serialize = EcsScript_serialize }); ecs_add_id(world, ecs_id(EcsScript), EcsPairIsTag); ecs_add_id(world, ecs_id(EcsScript), EcsPrivate); ecs_add_pair(world, ecs_id(EcsScript), EcsOnInstantiate, EcsDontInherit); } #endif /** * @file addons/script/serialize.c * @brief Serialize values to string. */ #ifdef FLECS_SCRIPT static int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str, bool is_expr); static int flecs_expr_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array, bool is_expr); static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str, bool is_expr); static ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { return kind - EcsOpPrimitive; } /* Serialize enumeration */ static int flecs_expr_ser_enum( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); int32_t val = *(const int32_t*)base; /* Enumeration constants are stored in a map that is keyed on the * enumeration value. */ ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants, ecs_enum_constant_t, (ecs_map_key_t)val); if (!c) { char *path = ecs_get_path(world, op->type); ecs_err("value %d is not valid for enum type '%s'", val, path); ecs_os_free(path); goto error; } ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); return 0; error: return -1; } /* Serialize bitmask */ static int flecs_expr_ser_bitmask( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); uint32_t value = *(const uint32_t*)ptr; ecs_strbuf_list_push(str, "", "|"); /* Multiple flags can be set at a given time. Iterate through all the flags * and append the ones that are set. */ ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); int count = 0; while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); count ++; value -= (uint32_t)key; } } if (value != 0) { /* All bits must have been matched by a constant */ char *path = ecs_get_path(world, op->type); ecs_err( "value for bitmask %s contains bits (%u) that cannot be mapped to constant", path, value); ecs_os_free(path); goto error; } if (!count) { ecs_strbuf_list_appendstr(str, "0"); } ecs_strbuf_list_pop(str, ""); return 0; error: return -1; } /* Serialize elements of a contiguous array */ static int expr_ser_elements( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, int32_t elem_count, int32_t elem_size, ecs_strbuf_t *str, bool is_array) { ecs_strbuf_list_push(str, "[", ", "); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (flecs_expr_ser_type_ops( world, ops, op_count, ptr, str, is_array, true)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } ecs_strbuf_list_pop(str, "]"); return 0; } static int expr_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str, bool is_array) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vec_count(&ser->ops); return expr_ser_elements( world, ops, op_count, base, elem_count, comp->size, str, is_array); } /* Serialize array */ static int expr_ser_array( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsArray *a = ecs_get(world, op->type, EcsArray); ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); return expr_ser_type_elements( world, a->type, ptr, a->count, str, true); } /* Serialize vector */ static int expr_ser_vector( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const ecs_vec_t *value = base; const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(value); void *array = ecs_vec_first(value); /* Serialize contiguous buffer of vector */ return expr_ser_type_elements(world, v->type, array, count, str, false); } /* Forward serialization to the different type kinds */ static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str, bool is_expr) { switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpEnum: if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpBitmask: if (flecs_expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpArray: if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpVector: if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpScope: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str, is_expr)) { /* Unknown operation */ ecs_err("unknown serializer operation kind (%d)", op->kind); goto error; } break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int flecs_expr_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array, bool is_expr) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (in_array <= 0) { if (op->name) { ecs_strbuf_list_next(str); ecs_strbuf_append(str, "%s: ", op->name); } int32_t elem_count = op->count; if (elem_count > 1) { /* Serialize inline array */ if (expr_ser_elements(world, op, op->op_count, base, elem_count, op->size, str, true)) { return -1; } i += op->op_count - 1; continue; } } switch(op->kind) { case EcsOpPush: ecs_strbuf_list_push(str, "{", ", "); in_array --; break; case EcsOpPop: ecs_strbuf_list_pop(str, "}"); in_array ++; break; case EcsOpArray: case EcsOpVector: case EcsOpEnum: case EcsOpBitmask: case EcsOpScope: case EcsOpPrimitive: case EcsOpBool: case EcsOpChar: case EcsOpByte: case EcsOpU8: case EcsOpU16: case EcsOpU32: case EcsOpU64: case EcsOpI8: case EcsOpI16: case EcsOpI32: case EcsOpI64: case EcsOpF32: case EcsOpF64: case EcsOpUPtr: case EcsOpIPtr: case EcsOpEntity: case EcsOpId: case EcsOpString: case EcsOpOpaque: if (flecs_expr_ser_type_op(world, op, base, str, is_expr)) { goto error; } break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid operation"); } } return 0; error: return -1; } /* Iterate over the type ops of a type */ static int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str, bool is_expr) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); return flecs_expr_ser_type_ops(world, ops, count, base, str, 0, is_expr); } int ecs_ptr_to_expr_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (ser == NULL) { char *path = ecs_get_path(world, type); ecs_err("cannot serialize value for type '%s'", path); ecs_os_free(path); goto error; } if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, true)) { goto error; } return 0; error: return -1; } char* ecs_ptr_to_expr( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_str_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { const EcsTypeSerializer *ser = ecs_get( world, type, EcsTypeSerializer); if (ser == NULL) { char *path = ecs_get_path(world, type); ecs_err("cannot serialize value for type '%s'", path); ecs_os_free(path); goto error; } if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out, false)) { goto error; } return 0; error: return -1; } char* ecs_ptr_to_str( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_ptr_to_str_buf(world, type, ptr, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } #endif /** * @file addons/script/template.c * @brief Script template implementation. */ #ifdef FLECS_SCRIPT /* Template ctor to initialize with default property values */ static void flecs_script_template_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.ctx; ecs_entity_t template_entity = ti->component; const EcsStruct *st = ecs_get(world, template_entity, EcsStruct); if (!st) { ecs_os_memset(ptr, 0, count * ti->size); return; } const EcsScript *script = ecs_get(world, template_entity, EcsScript); if (!script) { ecs_err("template '%s' is not a script, cannot construct", ti->name); return; } ecs_script_template_t *template = script->template_; ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); if (st->members.count != template->prop_defaults.count) { ecs_err("number of props (%d) of template '%s' does not match members" " (%d), cannot construct", template->prop_defaults.count, ti->name, st->members.count); return; } const ecs_member_t *members = st->members.array; int32_t i, m, member_count = st->members.count; ecs_script_var_t *values = template->prop_defaults.array; for (m = 0; m < member_count; m ++) { const ecs_member_t *member = &members[m]; ecs_script_var_t *value = &values[m]; const ecs_type_info_t *mti = value->type_info; ecs_assert(mti != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < count; i ++) { void *el = ECS_ELEM(ptr, ti->size, i); ecs_value_copy_w_type_info(world, mti, ECS_OFFSET(el, member->offset), value->value.ptr); } } } /* Template on_set handler to update contents for new property values */ static void flecs_script_template_on_set( ecs_iter_t *it) { if (it->table->flags & EcsTableIsPrefab) { /* Don't instantiate templates for prefabs */ return; } ecs_world_t *world = it->world; ecs_entity_t template_entity = ecs_field_id(it, 0); ecs_record_t *r = ecs_record_find(world, template_entity); if (!r) { ecs_err("template entity is empty (should never happen)"); return; } const EcsScript *script = ecs_record_get(world, r, EcsScript); if (!script) { ecs_err("template is missing script component"); return; } ecs_script_template_t *template = script->template_; ecs_assert(template != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = template->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); const EcsStruct *st = ecs_record_get(world, r, EcsStruct); void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); ecs_script_eval_visitor_t v; flecs_script_eval_visit_init(flecs_script_impl(script->script), &v); ecs_vec_t prev_using = v.using; v.using = template->using_; ecs_script_scope_t *scope = template->node->scope; /* Dummy entity node for instance */ ecs_script_entity_t instance_node = { .node = { .kind = EcsAstEntity, .pos = template->node->node.pos }, .scope = scope }; v.entity = &instance_node; int32_t i, m; for (i = 0; i < it->count; i ++) { v.parent = it->entities[i]; instance_node.eval = it->entities[i]; /* Create variables to hold template properties */ ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v.stack, v.allocator); vars->parent = template->vars; /* Include hoisted variables */ /* Populate properties from template members */ if (st) { const ecs_member_t *members = st->members.array; for (m = 0; m < st->members.count; m ++) { const ecs_member_t *member = &members[m]; /* Assign template property from template instance */ ecs_script_var_t *var = ecs_script_vars_declare(vars, member->name); var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); } } /* Populate $this variable with instance entity */ ecs_entity_t instance = it->entities[i]; ecs_script_var_t *var = ecs_script_vars_declare(vars, "this"); var->value.type = ecs_id(ecs_entity_t); var->value.ptr = &instance; bool is_defer = ecs_is_deferred(world); ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_script_clear(world, template_entity, instance); /* Run template code */ v.vars = vars; ecs_script_visit_scope(&v, scope); if (is_defer) { flecs_resume_readonly(real_world, &srs); } /* Pop variable scope */ ecs_script_vars_pop(vars); data = ECS_OFFSET(data, ti->size); } v.using = prev_using; flecs_script_eval_visit_fini(&v); } static int flecs_script_eval_prop( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_template_t *template = v->template; ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); return -1; } if (node->type) { ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); if (!type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); return -1; } const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); if (!ti) { return -1; } var->value.type = type; var->value.ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); var->type_info = ti; if (flecs_script_eval_expr(v, node->expr, &var->value)) { return -1; } ecs_script_var_t *value = ecs_vec_append_t(&v->base.script->allocator, &template->prop_defaults, ecs_script_var_t); value->value.ptr = flecs_calloc(&v->base.script->allocator, ti->size); value->value.type = type; value->type_info = ti; ecs_value_copy_w_type_info( v->world, ti, value->value.ptr, var->value.ptr); ecs_entity_t mbr = ecs_entity(v->world, { .name = node->name, .parent = template->entity }); ecs_set(v->world, mbr, EcsMember, { .type = var->value.type }); } return 0; } static int flecs_script_template_eval( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { switch(node->kind) { case EcsAstProp: return flecs_script_eval_prop(v, (ecs_script_var_node_t*)node); case EcsAstScope: case EcsAstTag: case EcsAstComponent: case EcsAstVarComponent: case EcsAstDefaultComponent: case EcsAstWithVar: case EcsAstWithTag: case EcsAstWithComponent: case EcsAstWith: case EcsAstUsing: case EcsAstModule: case EcsAstAnnotation: case EcsAstTemplate: case EcsAstConst: case EcsAstEntity: case EcsAstPairScope: case EcsAstIf: break; } return flecs_script_eval_node(v, node); } static int flecs_script_template_preprocess( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { v->template = template; v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->stack, v->allocator); int result = ecs_script_visit_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->template = NULL; return result; } static int flecs_script_template_hoist_using( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { if (v->module) { ecs_vec_append_t( v->allocator, &template->using_, ecs_entity_t)[0] = v->module; } int i, count = ecs_vec_count(&v->using); for (i = 0; i < count; i ++) { ecs_vec_append_t(v->allocator, &template->using_, ecs_entity_t)[0] = ecs_vec_get_t(&v->using, ecs_entity_t, i)[0]; } return 0; } static int flecs_script_template_hoist_vars( ecs_script_eval_visitor_t *v, ecs_script_template_t *template, ecs_script_vars_t *vars) { if (vars->parent) { flecs_script_template_hoist_vars(v, template, vars); } int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *src = &src_vars[i]; ecs_script_var_t *dst = ecs_script_vars_define_id( template->vars, src->name, src->value.type); ecs_value_copy(v->world, src->value.type, dst->value.ptr, src->value.ptr); } return 0; } ecs_script_template_t* flecs_script_template_init( ecs_script_impl_t *script) { ecs_allocator_t *a = &script->allocator; ecs_script_template_t *result = flecs_alloc_t(a, ecs_script_template_t); ecs_vec_init_t(NULL, &result->prop_defaults, ecs_script_var_t, 0); ecs_vec_init_t(NULL, &result->using_, ecs_entity_t, 0); result->vars = ecs_script_vars_init(script->pub.world); return result; } void flecs_script_template_fini( ecs_script_impl_t *script, ecs_script_template_t *template) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &script->allocator; int32_t i, count = ecs_vec_count(&template->prop_defaults); ecs_script_var_t *values = ecs_vec_first(&template->prop_defaults); for (i = 0; i < count; i ++) { ecs_script_var_t *value = &values[i]; const ecs_type_info_t *ti = value->type_info; if (ti->hooks.dtor) { ti->hooks.dtor(value->value.ptr, 1, ti); } flecs_free(a, ti->size, value->value.ptr); } ecs_vec_fini_t(a, &template->prop_defaults, ecs_script_var_t); ecs_vec_fini_t(a, &template->using_, ecs_entity_t); ecs_script_vars_fini(template->vars); flecs_free_t(a, ecs_script_template_t, template); } /* Create new template */ int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *node) { ecs_entity_t template_entity = flecs_script_create_entity(v, node->name); if (!template_entity) { return -1; } ecs_script_template_t *template = flecs_script_template_init(v->base.script); template->entity = template_entity; template->node = node; if (flecs_script_template_preprocess(v, template)) { goto error; } if (flecs_script_template_hoist_using(v, template)) { goto error; } if (flecs_script_template_hoist_vars(v, template, v->vars)) { goto error; } /* If template has no props, give template dummy size so we can register * hooks for it. */ if (!ecs_has(v->world, template_entity, EcsComponent)) { ecs_set(v->world, template_entity, EcsComponent, {1, 1}); } template->type_info = ecs_get_type_info(v->world, template_entity); ecs_add_pair(v->world, template_entity, EcsOnInstantiate, EcsOverride); EcsScript *script = ecs_ensure(v->world, template_entity, EcsScript); if (script->script) { if (script->template_) { flecs_script_template_fini( flecs_script_impl(script->script), script->template_); } ecs_script_free(script->script); } script->script = &v->base.script->pub; script->template_ = template; ecs_modified(v->world, template_entity, EcsScript); ecs_set_hooks_id(v->world, template_entity, &(ecs_type_hooks_t) { .ctor = flecs_script_template_ctor, .on_set = flecs_script_template_on_set, .ctx = v->world }); /* Keep script alive for as long as template is alive */ v->base.script->refcount ++; return 0; error: flecs_script_template_fini(v->base.script, template); return -1; } #endif /** * @file addons/script/tokenizer.c * @brief Script tokenizer. */ #ifdef FLECS_SCRIPT #define Keyword(keyword, _kind)\ } else if (!ecs_os_strncmp(pos, keyword " ", ecs_os_strlen(keyword) + 1)) {\ out->value = keyword;\ out->kind = _kind;\ return pos + ecs_os_strlen(keyword); #define OperatorMultiChar(oper, _kind)\ } else if (!ecs_os_strncmp(pos, oper, ecs_os_strlen(oper))) {\ out->value = oper;\ out->kind = _kind;\ return pos + ecs_os_strlen(oper); #define Operator(oper, _kind)\ } else if (pos[0] == oper[0]) {\ out->value = oper;\ out->kind = _kind;\ return pos + 1; const char* flecs_script_token_kind_str( ecs_script_token_kind_t kind) { switch(kind) { case EcsTokUnknown: return "unknown token "; case EcsTokColon: case EcsTokScopeOpen: case EcsTokScopeClose: case EcsTokParenOpen: case EcsTokParenClose: case EcsTokBracketOpen: case EcsTokBracketClose: case EcsTokAnnotation: case EcsTokComma: case EcsTokSemiColon: case EcsTokMul: case EcsTokAssign: case EcsTokBitwiseOr: case EcsTokNot: case EcsTokOptional: case EcsTokEq: case EcsTokNeq: case EcsTokMatch: case EcsTokOr: return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordModule: return "keyword "; case EcsTokIdentifier: return "identifier "; case EcsTokString: return "string "; case EcsTokNumber: return "number "; case EcsTokNewline: return "newline"; case EcsTokEnd: return "end of script"; default: return ""; } } const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos) { (void)parser; if (parser->significant_newline) { while (pos[0] && isspace(pos[0]) && pos[0] != '\n') { pos ++; } } else { while (pos[0] && isspace(pos[0])) { pos ++; } } return pos; } static const char* flecs_scan_whitespace_and_comment( ecs_script_parser_t *parser, const char *pos) { repeat_skip_whitespace_comment: pos = flecs_scan_whitespace(parser, pos); if (pos[0] == '/') { if (pos[1] == '/') { for (pos = pos + 2; pos[0] && pos[0] != '\n'; pos ++) { } if (pos[0] == '\n') { pos ++; goto repeat_skip_whitespace_comment; } } else if (pos[1] == '*') { for (pos = &pos[2]; pos[0] != 0; pos ++) { if (pos[0] == '*' && pos[1] == '/') { pos += 2; goto repeat_skip_whitespace_comment; } } ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "missing */ for multiline comment"); } } return pos; } // Identifier token static bool flecs_script_is_identifier( char c) { return isalpha(c) || (c == '_') || (c == '$') || (c == '#'); } static const char* flecs_script_identifier( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { out->kind = EcsTokIdentifier; out->value = parser->token_cur; ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || isdigit(c) || (c == '.') || (c == '*'); /* Retain \. for name lookup operation */ if (!is_ident && c == '\\' && pos[1] == '.') { is_ident = true; } if (!is_ident) { if (c == '\\') { pos ++; } else if (c == '<') { int32_t indent = 0; do { c = *pos; if (c == '<') { indent ++; } else if (c == '>') { indent --; } else if (!c) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "< without > in identifier"); return NULL; } *outpos = c; outpos ++; pos ++; if (!indent) { break; } } while (true); *outpos = '\0'; parser->token_cur = outpos + 1; return pos; } else if (c == '>') { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "> without < in identifier"); return NULL; } else { *outpos = '\0'; parser->token_cur = outpos + 1; return pos; } } *outpos = *pos; outpos ++; pos ++; } while (true); } // Number token static static bool flecs_script_is_number( char c) { return isdigit(c) || (c == '-'); } static const char* flecs_script_number( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { out->kind = EcsTokNumber; out->value = parser->token_cur; ecs_assert(flecs_script_is_number(pos[0]), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; do { char c = pos[0]; if (!isdigit(c)) { *outpos = '\0'; parser->token_cur = outpos + 1; break; } outpos[0] = pos[0]; outpos ++; pos ++; } while (true); return pos; } static const char* flecs_script_skip_string( ecs_script_parser_t *parser, const char *pos, char delim) { char ch; for (; (ch = pos[0]) && pos[0] != delim; pos ++) { if (ch == '\\') { pos ++; } } if (!pos[0]) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "unterminated string"); return NULL; } return pos; } static const char* flecs_script_string( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { const char *end = flecs_script_skip_string(parser, pos + 1, '"'); if (!end) { return NULL; } ecs_assert(end[0] == '"', ECS_INTERNAL_ERROR, NULL); end --; int32_t len = flecs_ito(int32_t, end - pos); ecs_os_memcpy(parser->token_cur, pos + 1, len); parser->token_cur[len] = '\0'; out->kind = EcsTokString; out->value = parser->token_cur; parser->token_cur += len + 1; return end + 2; } const char* flecs_script_expr( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out, char until) { parser->pos = pos; int32_t scope_depth = until == '}' ? 1 : 0; int32_t paren_depth = until == ')' ? 1 : 0; const char *start = pos = flecs_scan_whitespace(parser, pos); char ch; for (; (ch = pos[0]); pos ++) { if (ch == '{') { if (ch == until) { break; } scope_depth ++; } else if (ch == '}') { scope_depth --; if (!scope_depth && until == '}') { break; } if (scope_depth < 0) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "mismatching { }"); return NULL; } } else if (ch == '(') { paren_depth ++; } else if (ch == ')') { paren_depth --; if (!paren_depth && until == ')') { break; } if (paren_depth < 0) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "mismatching ( )"); return NULL; } } else if (ch == '"') { pos = flecs_script_skip_string(parser, pos + 1, '"'); if (!pos) { return NULL; } } else if (ch == '`') { pos = flecs_script_skip_string(parser, pos + 1, '`'); if (!pos) { return NULL; } } else if (ch == until) { break; } } if (!pos[0]) { if (until == '\0') { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "expected end of script"); return NULL; } else if (until == '\n') { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "expected newline"); return NULL; } else { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "expected '%c'", until); return NULL; } } if (scope_depth) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "mismatching { }"); return NULL; } if (paren_depth) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "mismatching ( )"); return NULL; } if (until != ']') { parser->token_cur[0] = '{'; } else { parser->token_cur[0] = '['; } int32_t len = flecs_ito(int32_t, pos - start); ecs_os_memcpy(parser->token_cur + 1, start, len); out->value = parser->token_cur; parser->token_cur += len + 1; while (isspace(parser->token_cur[-1])) { parser->token_cur --; } if (until != ']') { parser->token_cur[0] = '}'; } else { parser->token_cur[0] = ']'; } parser->token_cur ++; parser->token_cur[0] = '\0'; parser->token_cur ++; return pos; } const char* flecs_script_until( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out, char until) { parser->pos = pos; const char *start = pos = flecs_scan_whitespace(parser, pos); char ch; for (; (ch = pos[0]); pos ++) { if (ch == until) { break; } } if (!pos[0]) { if (until == '\0') { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "expected end of script"); return NULL; } else if (until == '\n') { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "expected newline"); return NULL; } else { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "expected '%c'", until); return NULL; } } int32_t len = flecs_ito(int32_t, pos - start); ecs_os_memcpy(parser->token_cur, start, len); out->value = parser->token_cur; parser->token_cur += len; while (isspace(parser->token_cur[-1])) { parser->token_cur --; } parser->token_cur[0] = '\0'; parser->token_cur ++; return pos; } const char* flecs_script_token( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out, bool is_lookahead) { parser->pos = pos; // Skip whitespace and comments pos = flecs_scan_whitespace_and_comment(parser, pos); out->kind = EcsTokUnknown; out->value = NULL; if (pos[0] == '\0') { out->kind = EcsTokEnd; return pos; } else if (pos[0] == '\n') { out->kind = EcsTokNewline; // Parse multiple newlines/whitespaces as a single token pos = flecs_scan_whitespace_and_comment(parser, pos + 1); if (pos[0] == '\n') { pos ++; } return pos; Operator (":", EcsTokColon) Operator ("{", EcsTokScopeOpen) Operator ("}", EcsTokScopeClose) Operator ("(", EcsTokParenOpen) Operator (")", EcsTokParenClose) Operator ("[", EcsTokBracketOpen) Operator ("]", EcsTokBracketClose) Operator ("@", EcsTokAnnotation) Operator (",", EcsTokComma) Operator (";", EcsTokSemiColon) Operator ("*", EcsTokMul) Operator ("?", EcsTokOptional) OperatorMultiChar ("==", EcsTokEq) OperatorMultiChar ("!=", EcsTokNeq) OperatorMultiChar ("~=", EcsTokMatch) OperatorMultiChar ("||", EcsTokOr) OperatorMultiChar ("!", EcsTokNot) OperatorMultiChar ("=", EcsTokAssign) OperatorMultiChar ("|", EcsTokBitwiseOr) Keyword ("with", EcsTokKeywordWith) Keyword ("using", EcsTokKeywordUsing) Keyword ("template", EcsTokKeywordTemplate) Keyword ("prop", EcsTokKeywordProp) Keyword ("const", EcsTokKeywordConst) Keyword ("if", EcsTokKeywordIf) Keyword ("else", EcsTokKeywordElse) Keyword ("module", EcsTokKeywordModule) } else if (pos[0] == '"') { return flecs_script_string(parser, pos, out); } else if (flecs_script_is_number(pos[0])) { return flecs_script_number(parser, pos, out); } else if (flecs_script_is_identifier(pos[0])) { return flecs_script_identifier(parser, pos, out); } if (!is_lookahead) { ecs_parser_error(parser->script->pub.name, parser->script->pub.code, pos - parser->script->pub.code, "unknown token '%c'", pos[0]); } return NULL; } #endif /** * @file addons/script/vars.c * @brief Script variables. */ #ifdef FLECS_SCRIPT ecs_script_vars_t* flecs_script_vars_push( ecs_script_vars_t *parent, ecs_stack_t *stack, ecs_allocator_t *allocator) { ecs_check(stack || parent, ECS_INVALID_PARAMETER, "must provide either parent scope or stack allocator"); ecs_check(allocator || parent, ECS_INVALID_PARAMETER, "must provide either parent scope or allocator"); if (!stack) { stack = parent->stack; } else if (parent) { ecs_check(stack == parent->stack, ECS_INVALID_PARAMETER, "provided stack allocator is different from parent scope"); } if (!allocator) { allocator = parent->allocator; } else if (parent) { ecs_check(allocator == parent->allocator, ECS_INVALID_PARAMETER, "provided allocator is different from parent scope"); } ecs_stack_cursor_t *cursor = flecs_stack_get_cursor(stack); ecs_script_vars_t *result = flecs_stack_calloc_t(stack, ecs_script_vars_t); ecs_vec_init_t(allocator, &result->vars, ecs_script_var_t, 0); result->parent = parent; if (parent) { result->world = parent->world; } result->stack = stack; result->allocator = allocator; result->cursor = cursor; return result; error: return NULL; } ecs_script_vars_t* ecs_script_vars_init( ecs_world_t *world) { ecs_script_vars_t *result = flecs_script_vars_push(NULL, flecs_stage_get_stack_allocator(world), flecs_stage_get_allocator(world)); result->world = ecs_get_world(world); /* Provided world can be stage */ return result; } void ecs_script_vars_fini( ecs_script_vars_t *vars) { ecs_check(vars->parent == NULL, ECS_INVALID_PARAMETER, "ecs_script_vars_fini can only be called on the roots cope"); ecs_script_vars_pop(vars); error: return; } ecs_script_vars_t* ecs_script_vars_push( ecs_script_vars_t *parent) { ecs_check(parent != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stack_t *stack = parent->stack; ecs_allocator_t *allocator = parent->allocator; ecs_check(stack != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(allocator != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_script_vars_push(parent, stack, allocator); error: return NULL; } ecs_script_vars_t* ecs_script_vars_pop( ecs_script_vars_t *vars) { ecs_script_vars_t *parent = vars->parent; ecs_stack_cursor_t *cursor = vars->cursor; int32_t i, count = ecs_vec_count(&vars->vars); if (count) { ecs_script_var_t *var_array = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *var = &var_array[i]; if (!var->value.ptr) { continue; } if (!var->type_info || !var->type_info->hooks.dtor) { continue; } var->type_info->hooks.dtor(var->value.ptr, 1, var->type_info); } flecs_name_index_fini(&vars->var_index); } ecs_vec_fini_t(vars->allocator, &vars->vars, ecs_script_var_t); flecs_stack_restore_cursor(vars->stack, cursor); return parent; } ecs_script_var_t* ecs_script_vars_declare( ecs_script_vars_t *vars, const char *name) { if (!ecs_vec_count(&vars->vars)) { flecs_name_index_init(&vars->var_index, vars->allocator); } else { if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { goto error; } } ecs_script_var_t *var = ecs_vec_append_t( vars->allocator, &vars->vars, ecs_script_var_t); var->name = name; var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; flecs_name_index_ensure(&vars->var_index, flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); return var; error: return NULL; } ecs_script_var_t* ecs_script_vars_define_id( ecs_script_vars_t *vars, const char *name, ecs_entity_t type) { ecs_check(vars->world != NULL, ECS_INVALID_OPERATION, "variable scope is " "not associated with world, create scope with ecs_script_vars_init"); const ecs_type_info_t *ti = ecs_get_type_info(vars->world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "the entity provided for the type parameter is not a type"); ecs_script_var_t *result = ecs_script_vars_declare(vars, name); if (!result) { return NULL; } result->value.type = type; result->value.ptr = flecs_stack_alloc(vars->stack, ti->size, ti->alignment); result->type_info = ti; if (ti->hooks.ctor) { ti->hooks.ctor(result->value.ptr, 1, ti); } return result; error: return NULL; } ecs_script_var_t* ecs_script_vars_lookup( const ecs_script_vars_t *vars, const char *name) { uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); } if (!var_id) { if (vars->parent) { return ecs_script_vars_lookup(vars->parent, name); } return NULL; } return ecs_vec_get_t(&vars->vars, ecs_script_var_t, flecs_uto(int32_t, var_id - 1)); } /* Static names for iterator fields */ static const char* flecs_script_iter_field_names[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }; void ecs_script_vars_from_iter( const ecs_iter_t *it, ecs_script_vars_t *vars, int offset) { ecs_check(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!offset || offset < it->count, ECS_INVALID_PARAMETER, NULL); /* Set variable for $this */ if (it->count) { ecs_script_var_t *var = ecs_script_vars_lookup(vars, "this"); if (!var) { var = ecs_script_vars_declare(vars, "this"); var->value.type = ecs_id(ecs_entity_t); } var->value.ptr = &it->entities[offset]; } /* Set variables for fields */ { int32_t i, field_count = it->field_count; for (i = 0; i < field_count; i ++) { ecs_size_t size = it->sizes[i]; if (!size) { continue; } void *ptr = it->ptrs[i]; if (!ptr) { continue; } ptr = ECS_OFFSET(ptr, offset * size); const char *name = flecs_script_iter_field_names[i]; ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); if (!var) { var = ecs_script_vars_declare(vars, name); ecs_assert(ecs_script_vars_lookup(vars, name) != NULL, ECS_INTERNAL_ERROR, NULL); var->value.type = it->ids[i]; } else { ecs_check(var->value.type == it->ids[i], ECS_INVALID_PARAMETER, NULL); } var->value.ptr = ptr; } } /* Set variables for query variables */ { int32_t i, var_count = it->variable_count; for (i = 1 /* skip this variable */ ; i < var_count; i ++) { ecs_entity_t *e_ptr = NULL; ecs_var_t *query_var = &it->variables[i]; if (query_var->entity) { e_ptr = &query_var->entity; } else { ecs_table_range_t *range = &query_var->range; if (range->count == 1) { ecs_entity_t *entities = range->table->data.entities.array; e_ptr = &entities[range->offset]; } } if (!e_ptr) { continue; } ecs_script_var_t *var = ecs_script_vars_lookup( vars, it->variable_names[i]); if (!var) { var = ecs_script_vars_declare(vars, it->variable_names[i]); var->value.type = ecs_id(ecs_entity_t); } else { ecs_check(var->value.type == ecs_id(ecs_entity_t), ECS_INVALID_PARAMETER, NULL); } var->value.ptr = e_ptr; } } error: return; } #endif /** * @file addons/script/visit.c * @brief Script AST visitor utilities. */ #ifdef FLECS_SCRIPT ecs_script_node_t* ecs_script_parent_node_( ecs_script_visit_t *v) { if (v->depth > 1) { return v->nodes[v->depth - 2]; /* Last node is current node */ } else { return NULL; } } ecs_script_scope_t* ecs_script_current_scope_( ecs_script_visit_t *v) { int32_t depth; for(depth = v->depth - 1; depth >= 0; depth --) { ecs_script_node_t *node = v->nodes[depth]; if (node->kind == EcsAstScope) { return (ecs_script_scope_t*)node; } } return NULL; } ecs_script_node_t* ecs_script_parent_( ecs_script_visit_t *v, ecs_script_node_t *child) { int32_t depth; for(depth = v->depth - 1; depth >= 0; depth --) { ecs_script_node_t *node = v->nodes[depth]; if (node == child && depth) { return v->nodes[depth - 1]; } } return NULL; } int32_t ecs_script_node_line_number_( ecs_script_impl_t *script, ecs_script_node_t *node) { const char *ptr; int32_t line_count = 1; for (ptr = script->pub.code; ptr < node->pos; ptr ++) { ecs_assert(ptr[0] != 0, ECS_INTERNAL_ERROR, NULL); if (ptr[0] == '\n') { line_count ++; } } return line_count; } int ecs_script_visit_scope_( ecs_script_visit_t *v, ecs_script_scope_t *scope) { ecs_script_node_t **nodes = ecs_vec_first_t( &scope->stmts, ecs_script_node_t*); v->nodes[v->depth ++] = (ecs_script_node_t*)scope; int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { if (!i) { v->prev = NULL; } else { v->prev = nodes[i - 1]; } if (i != (count - 1)) { v->next = nodes[i + 1]; } else { v->next = NULL; } v->nodes[v->depth ++] = nodes[i]; if (v->visit(v, nodes[i])) { return -1; } v->depth --; } v->depth --; return 0; } int ecs_script_visit_node_( ecs_script_visit_t *v, ecs_script_node_t *node) { v->nodes[v->depth ++] = node; if (v->visit(v, node)) { return -1; } v->depth --; return 0; } int ecs_script_visit_( ecs_script_visit_t *visitor, ecs_visit_action_t visit, ecs_script_impl_t *script) { visitor->script = script; visitor->visit = visit; visitor->depth = 0; int result = ecs_script_visit_node(visitor, script->root); if (result) { return -1; } if (visitor->depth) { ecs_parser_error(script->pub.name, NULL, 0, "unexpected end of script"); return -1; } return 0; } #endif /** * @file addons/script/visit_eval.c * @brief Script evaluation visitor. */ #ifdef FLECS_SCRIPT void flecs_script_eval_error_( ecs_script_eval_visitor_t *v, ecs_script_node_t *node, const char *fmt, ...) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); if (node) { int32_t line = ecs_script_node_line_number(v->base.script, node); ecs_parser_error(v->base.script->pub.name, NULL, 0, "%d: %s", line, msg); } else { ecs_parser_error(v->base.script->pub.name, NULL, 0, "%s", msg); } ecs_os_free(msg); } static ecs_value_t* flecs_script_with_append( ecs_allocator_t *a, ecs_script_eval_visitor_t *v) { if (ecs_vec_count(&v->with)) { ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_remove_last(&v->with); } ecs_vec_append_t(a, &v->with, ecs_value_t); ecs_value_t *last = ecs_vec_append_t(a, &v->with, ecs_value_t); ecs_os_memset_t(last, 0, ecs_value_t); return ecs_vec_get_t(&v->with, ecs_value_t, ecs_vec_count(&v->with) - 2); } static void flecs_script_with_set_count( ecs_allocator_t *a, ecs_script_eval_visitor_t *v, int32_t count) { if (count) { ecs_value_t *last = ecs_vec_get_t(&v->with, ecs_value_t, count); ecs_os_memset_t(last, 0, ecs_value_t); ecs_vec_set_count_t(a, &v->with, ecs_value_t, count + 1); } else { ecs_vec_set_count_t(a, &v->with, ecs_value_t, 0); } } static ecs_value_t* flecs_script_with_last( ecs_script_eval_visitor_t *v) { int32_t count = ecs_vec_count(&v->with); if (count) { return ecs_vec_get_t(&v->with, ecs_value_t, count - 2); } return NULL; } static int32_t flecs_script_with_count( ecs_script_eval_visitor_t *v) { if (ecs_vec_count(&v->with)) { ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_last_t(&v->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); return ecs_vec_count(&v->with) - 1; } return 0; } const ecs_type_info_t* flecs_script_get_type_info( ecs_script_eval_visitor_t *v, void *node, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_ensure(v->world, id); if (!idr) { goto error; } if (!idr->type_info) { goto error; } return idr->type_info; error: { char *idstr = ecs_id_str(v->world, id); flecs_script_eval_error(v, node, "cannot set value of '%s': not a component", idstr); ecs_os_free(idstr); } return NULL; } ecs_entity_t flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, const char *path) { if (!path) { return 0; } if (path[0] == '$') { const ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, &path[1]); if (!var) { return 0; } if (var->value.type != ecs_id(ecs_entity_t)) { char *type_str = ecs_id_str(v->world, var->value.type); flecs_script_eval_error(v, NULL, "variable '%s' must be of type entity, got '%s'", path, type_str); ecs_os_free(type_str); return 0; } if (var->value.ptr == NULL) { flecs_script_eval_error(v, NULL, "variable '%s' is not initialized", path); return 0; } ecs_entity_t result = *(ecs_entity_t*)var->value.ptr; if (!result) { flecs_script_eval_error(v, NULL, "variable '%s' contains invalid entity id (0)", path); return 0; } return result; } if (from) { return ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); } else { int32_t i, using_count = ecs_vec_count(&v->using); if (using_count) { ecs_entity_t *using = ecs_vec_first(&v->using); for (i = using_count - 1; i >= 0; i --) { ecs_entity_t e = ecs_lookup_path_w_sep( v->world, using[i], path, NULL, NULL, false); if (e) { return e; } } } return ecs_lookup_path_w_sep( v->world, v->parent, path, NULL, NULL, true); } } ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, const char *name) { ecs_value_t *with = NULL; if (flecs_script_with_count(v)) { with = ecs_vec_first_t(&v->with, ecs_value_t); } if (name || with) { ecs_entity_desc_t desc = {0}; desc.name = name; desc.parent = v->parent; desc.set = with; return ecs_entity_init(v->world, &desc); } else if (v->parent) { return ecs_new_w_pair(v->world, EcsChildOf, v->parent); } else { return ecs_new(v->world); } } static ecs_entity_t flecs_script_find_entity_action( const ecs_world_t *world, const char *path, void *ctx) { (void)world; ecs_script_eval_visitor_t *v = ctx; return flecs_script_find_entity(v, 0, path); } static int flecs_script_eval_id( ecs_script_eval_visitor_t *v, void *node, ecs_script_id_t *id) { ecs_entity_t second_from = 0; if (!id->first) { flecs_script_eval_error(v, node, "invalid component/tag identifier"); return -1; } if (v->template) { /* Can't resolve variables while preprocessing template scope */ if (id->first[0] == '$') { return 0; } if (id->second && id->second[0] == '$') { return 0; } } ecs_entity_t first = flecs_script_find_entity(v, 0, id->first); if (!first) { if (id->first[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->first); return -1; } flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->first); return -1; } else if (id->second) { second_from = flecs_get_oneof(v->world, first); } if (id->second) { ecs_entity_t second = flecs_script_find_entity( v, second_from, id->second); if (!second) { if (id->second[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->second); return -1; } /* Tags/components must be created in advance for templates */ if (v->template) { flecs_script_eval_error(v, node, "'%s' must be defined outside of template scope", id->second); return -1; } if (second_from) { char *parent_str = ecs_id_str(v->world, second_from); flecs_script_eval_error(v, node, "target '%s' not found in " "parent '%s'", id->second, parent_str); ecs_os_free(parent_str); return -1; } flecs_script_eval_error(v, node, "unresolved identifier '%s'", id->second); return -1; } if (first == EcsAny || second == EcsAny) { flecs_script_eval_error(v, node, "cannot use anonymous entity as element of pair"); return -1; } id->eval = id->flag | ecs_pair(first, second); } else { if (first == EcsAny) { flecs_script_eval_error(v, node, "cannot use anonymous entity as component or tag"); return -1; } id->eval = id->flag | first; } return 0; } int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, const char *expr, ecs_value_t *value) { if (!value->type && expr[0] == '{') { expr ++; } ecs_script_expr_run_desc_t desc = { .name = v->base.script->pub.name, .expr = expr, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, .vars = v->vars }; if (!ecs_script_expr_run(v->world, expr, value, &desc)) { return -1; } return 0; } static int flecs_script_eval_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node) { ecs_script_node_t *scope_parent = ecs_script_parent_node(v); ecs_entity_t prev_eval_parent = v->parent; int32_t prev_using_count = ecs_vec_count(&v->using); for (int i = v->base.depth - 2; i >= 0; i --) { if (v->base.nodes[i]->kind == EcsAstScope) { node->parent = (ecs_script_scope_t*)v->base.nodes[i]; break; } } ecs_allocator_t *a = v->allocator; v->vars = flecs_script_vars_push(v->vars, &v->stack, a); if (scope_parent && (scope_parent->kind == EcsAstEntity)) { v->parent = ecs_script_node(entity, scope_parent)->eval; } int result = ecs_script_visit_scope(v, node); ecs_vec_set_count_t(a, &v->using, ecs_entity_t, prev_using_count); v->vars = ecs_script_vars_pop(v->vars); v->parent = prev_eval_parent; return result; } static int flecs_script_apply_annot( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_script_annot_t *node) { if (!ecs_os_strcmp(node->name, "name")) { ecs_doc_set_name(v->world, entity, node->expr); } else if (!ecs_os_strcmp(node->name, "brief")) { ecs_doc_set_brief(v->world, entity, node->expr); } else if (!ecs_os_strcmp(node->name, "detail")) { ecs_doc_set_detail(v->world, entity, node->expr); } else if (!ecs_os_strcmp(node->name, "link")) { ecs_doc_set_link(v->world, entity, node->expr); } else if (!ecs_os_strcmp(node->name, "color")) { ecs_doc_set_color(v->world, entity, node->expr); } else { flecs_script_eval_error(v, node, "unknown annotation '%s'", node->name); return -1; } return 0; } static int flecs_script_eval_entity( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node) { bool is_slot = false; if (node->kind) { ecs_script_id_t id = { .first = node->kind }; if (!ecs_os_strcmp(node->kind, "prefab")) { id.eval = EcsPrefab; } else if (!ecs_os_strcmp(node->kind, "slot")) { is_slot = true; } else if (flecs_script_eval_id(v, node, &id)) { return -1; } node->eval_kind = id.eval; } else { /* Inherit kind from parent kind's DefaultChildComponent, if it existst */ ecs_script_scope_t *scope = ecs_script_current_scope(v); if (scope && scope->default_component_eval) { node->eval_kind = scope->default_component_eval; } } if (v->template) { return 0; } node->eval = flecs_script_create_entity(v, node->name); node->parent = v->entity; if (is_slot) { ecs_entity_t parent = ecs_get_target( v->world, node->eval, EcsChildOf, 0); if (!parent) { flecs_script_eval_error(v, node, "slot entity must have a parent"); return -1; } ecs_add_pair(v->world, node->eval, EcsSlotOf, parent); } const EcsDefaultChildComponent *default_comp = NULL; ecs_script_entity_t *old_entity = v->entity; v->entity = node; if (node->eval_kind) { ecs_add_id(v->world, node->eval, node->eval_kind); default_comp = ecs_get(v->world, node->eval_kind, EcsDefaultChildComponent); if (default_comp) { if (!default_comp->component) { flecs_script_eval_error(v, node, "entity '%s' has kind '%s' " "with uninitialized DefaultChildComponent", node->name, node->kind); return -1; } node->scope->default_component_eval = default_comp->component; } } int32_t i, count = ecs_vec_count(&v->annot); if (count) { ecs_script_annot_t **annots = ecs_vec_first(&v->annot); for (i = 0; i < count ; i ++) { flecs_script_apply_annot(v, node->eval, annots[i]); } ecs_vec_clear(&v->annot); } if (ecs_script_visit_node(v, node->scope)) { return -1; } if (node->eval_kind) { if (!node->kind_w_expr) { if (ecs_get_type_info(v->world, node->eval_kind) != NULL) { ecs_modified_id(v->world, node->eval, node->eval_kind); } } } v->entity = old_entity; return 0; } static ecs_entity_t flecs_script_get_src( ecs_script_eval_visitor_t *v, ecs_entity_t entity, ecs_id_t id) { if (entity == EcsVariable) { // Singleton ($) if (ECS_IS_PAIR(id)) { return ecs_pair_first(v->world, id); } else { return id & ECS_COMPONENT_MASK; } } return entity; } static int flecs_script_eval_tag( ecs_script_eval_visitor_t *v, ecs_script_tag_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } if (v->template) { return 0; } if (!v->entity) { if (node->id.second) { flecs_script_eval_error( v, node, "missing entity for pair (%s, %s)", node->id.first, node->id.second); } else { flecs_script_eval_error(v, node, "missing entity for tag %s", node->id.first); } return -1; } ecs_entity_t src = flecs_script_get_src( v, v->entity->eval, node->id.eval); ecs_add_id(v->world, src, node->id.eval); return 0; } static int flecs_script_eval_component( ecs_script_eval_visitor_t *v, ecs_script_component_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } if (v->template) { return 0; } if (!v->entity) { if (node->id.second) { flecs_script_eval_error(v, node, "missing entity for pair (%s, %s)", node->id.first, node->id.second); } else { flecs_script_eval_error(v, node, "missing entity for component %s", node->id.first); } return -1; } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); if (node->expr && node->expr[0]) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { return -1; } const EcsType *type = ecs_get(v->world, ti->component, EcsType); if (type) { bool is_collection = false; switch(type->kind) { case EcsPrimitiveType: case EcsBitmaskType: case EcsEnumType: case EcsStructType: case EcsOpaqueType: break; case EcsArrayType: case EcsVectorType: is_collection = true; break; } if (node->is_collection != is_collection) { char *id_str = ecs_id_str(v->world, ti->component); if (node->is_collection && !is_collection) { flecs_script_eval_error(v, node, "type %s is not a collection (use '%s: {...}')", id_str, id_str); } else { flecs_script_eval_error(v, node, "type %s is a collection (use '%s: [...]')", id_str, id_str); } ecs_os_free(id_str); return -1; } } ecs_value_t value = { .ptr = ecs_ensure_id(v->world, src, node->id.eval), .type = ti->component }; if (ecs_os_strcmp(node->expr, "{}")) { if (flecs_script_eval_expr(v, node->expr, &value)) { return -1; } } ecs_modified_id(v->world, src, node->id.eval); } else { ecs_add_id(v->world, src, node->id.eval); } return 0; } static int flecs_script_eval_var_component( ecs_script_eval_visitor_t *v, ecs_script_var_component_t *node) { ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); return -1; } ecs_id_t var_id = var->value.type; if (var->value.ptr) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, var_id); if (!ti) { return -1; } ecs_value_t value = { .ptr = ecs_ensure_id(v->world, v->entity->eval, var_id), .type = var_id }; ecs_value_copy_w_type_info(v->world, ti, value.ptr, var->value.ptr); ecs_modified_id(v->world, v->entity->eval, var_id); } else { ecs_add_id(v->world, v->entity->eval, var_id); } return 0; } static int flecs_script_eval_default_component( ecs_script_eval_visitor_t *v, ecs_script_default_component_t *node) { if (!v->entity) { flecs_script_eval_error(v, node, "missing entity for default component"); return -1; } ecs_script_scope_t *scope = ecs_script_current_scope(v); ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(scope->node.kind == EcsAstScope, ECS_INTERNAL_ERROR, NULL); scope = scope->parent; if (!scope) { flecs_script_eval_error(v, node, "entity '%s' is in root scope which cannot have a default type", v->entity->name); return -1; } ecs_id_t default_type = scope->default_component_eval; if (!default_type) { flecs_script_eval_error(v, node, "scope for entity '%s' does not have a default type", v->entity->name); return -1; } if (ecs_get_type_info(v->world, default_type) == NULL) { char *id_str = ecs_id_str(v->world, default_type); flecs_script_eval_error(v, node, "cannot use tag '%s' as default type in assignment", id_str); ecs_os_free(id_str); return -1; } ecs_value_t value = { .ptr = ecs_ensure_id(v->world, v->entity->eval, default_type), .type = default_type }; if (flecs_script_eval_expr(v, node->expr, &value)) { return -1; } ecs_modified_id(v->world, v->entity->eval, default_type); return 0; } static int flecs_script_eval_with_var( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_var_t *var = ecs_script_vars_lookup(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); return -1; } ecs_allocator_t *a = v->allocator; ecs_value_t *value = flecs_script_with_append(a, v); *value = var->value; return 0; } static int flecs_script_eval_with_tag( ecs_script_eval_visitor_t *v, ecs_script_tag_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } ecs_allocator_t *a = v->allocator; ecs_value_t *value = flecs_script_with_append(a, v); value->type = node->id.eval; value->ptr = NULL; return 0; } static int flecs_script_eval_with_component( ecs_script_eval_visitor_t *v, ecs_script_component_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } ecs_allocator_t *a = v->allocator; ecs_value_t *value = flecs_script_with_append(a, v); value->type = node->id.eval; value->ptr = NULL; if (node->expr && node->expr[0]) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { return -1; } value->ptr = flecs_stack_alloc(&v->stack, ti->size, ti->alignment); value->type = ti->component; // Expression parser needs actual type if (flecs_script_eval_expr(v, node->expr, value)) { return -1; } value->type = node->id.eval; // Restore so we're adding actual id } return 0; } static int flecs_script_eval_with( ecs_script_eval_visitor_t *v, ecs_script_with_t *node) { ecs_allocator_t *a = v->allocator; int32_t prev_with_count = flecs_script_with_count(v); ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->stack); int result = 0; if (ecs_script_visit_scope(v, node->expressions)) { result = -1; goto error; } ecs_value_t *value = flecs_script_with_last(v); if (!value->ptr) { if (ecs_is_valid(v->world, value->type)) { node->scope->default_component_eval = value->type; } } if (ecs_script_visit_scope(v, node->scope)) { result = -1; goto error; } error: flecs_script_with_set_count(a, v, prev_with_count); flecs_stack_restore_cursor(&v->stack, prev_stack_cursor); return result; } static int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) { ecs_allocator_t *a = v->allocator; int32_t len = ecs_os_strlen(node->name); if (len > 2 && !ecs_os_strcmp(&node->name[len - 2], ".*")) { char *path = flecs_strdup(a, node->name); path[len - 2] = '\0'; ecs_entity_t from = ecs_lookup(v->world, path); if (!from) { flecs_script_eval_error(v, node, "unresolved path '%s' in using statement", path); flecs_strfree(a, path); return -1; } /* Add each child of the scope to using stack */ ecs_iter_t it = ecs_children(v->world, from); while (ecs_children_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_vec_append_t( a, &v->using, ecs_entity_t)[0] = it.entities[i]; } } flecs_strfree(a, path); } else { ecs_entity_t from = ecs_lookup_path_w_sep( v->world, 0, node->name, NULL, NULL, false); if (!from) { from = ecs_entity(v->world, { .name = node->name, .root_sep = "" }); if (!from) { return -1; } } ecs_vec_append_t(a, &v->using, ecs_entity_t)[0] = from; } return 0; } static int flecs_script_eval_module( ecs_script_eval_visitor_t *v, ecs_script_module_t *node) { ecs_entity_t m = flecs_script_create_entity(v, node->name); if (!m) { return -1; } ecs_add_id(v->world, m, EcsModule); v->module = m; v->parent = m; return 0; } static int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); return -1; } if (node->type) { ecs_entity_t type = flecs_script_find_entity(v, 0, node->type); if (!type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); return -1; } const ecs_type_info_t *ti = flecs_script_get_type_info(v, node, type); if (!ti) { flecs_script_eval_error(v, node, "failed to retrieve type info for '%s' for const variable '%s'", node->type, node->name); return -1; } var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); var->value.type = type; var->type_info = ti; if (flecs_script_eval_expr(v, node->expr, &var->value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for const variable '%s'", node->name); return -1; } } else { /* We don't know the type yet, so we can't create a storage for it yet. * Run the expression first to deduce the type. */ ecs_value_t value = {0}; if (flecs_script_eval_expr(v, node->expr, &value)) { flecs_script_eval_error(v, node, "failed to evaluate expression for const variable '%s'", node->name); return -1; } ecs_assert(value.type != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_stack_calloc(&v->stack, ti->size, ti->alignment); var->value.type = value.type; var->type_info = ti; ecs_value_copy_w_type_info(v->world, ti, var->value.ptr, value.ptr); ecs_value_fini_w_type_info(v->world, ti, value.ptr); flecs_free(&v->world->allocator, ti->size, value.ptr); } return 0; } static int flecs_script_eval_pair_scope( ecs_script_eval_visitor_t *v, ecs_script_pair_scope_t *node) { ecs_entity_t first = flecs_script_find_entity(v, 0, node->id.first); if (!first) { first = flecs_script_create_entity(v, node->id.first); if (!first) { return -1; } } ecs_entity_t second = flecs_script_create_entity(v, node->id.second); if (!second) { return -1; } ecs_allocator_t *a = v->allocator; ecs_entity_t prev_first = v->with_relationship; ecs_entity_t prev_second = 0; int32_t prev_with_relationship_sp = v->with_relationship_sp; v->with_relationship = first; if (prev_first != first) { /* Append new element to with stack */ ecs_value_t *value = flecs_script_with_append(a, v); value->type = ecs_pair(first, second); value->ptr = NULL; v->with_relationship_sp = flecs_script_with_count(v) - 1; } else { /* Get existing with element for current relationhip stack */ ecs_value_t *value = ecs_vec_get_t( &v->with, ecs_value_t, v->with_relationship_sp); ecs_assert(ECS_PAIR_FIRST(value->type) == (uint32_t)first, ECS_INTERNAL_ERROR, NULL); prev_second = ECS_PAIR_SECOND(value->type); value->type = ecs_pair(first, second); value->ptr = NULL; } if (ecs_script_visit_scope(v, node->scope)) { return -1; } if (prev_second) { ecs_value_t *value = ecs_vec_get_t( &v->with, ecs_value_t, v->with_relationship_sp); value->type = ecs_pair(first, prev_second); } else { flecs_script_with_set_count(a, v, v->with_relationship_sp); } v->with_relationship = prev_first; v->with_relationship_sp = prev_with_relationship_sp; return 0; } static int flecs_script_eval_if( ecs_script_eval_visitor_t *v, ecs_script_if_t *node) { ecs_value_t condval = { .type = 0, .ptr = NULL }; if (flecs_script_eval_expr(v, node->expr, &condval)) { return -1; } bool cond; if (condval.type == ecs_id(ecs_bool_t)) { cond = *(bool*)(condval.ptr); } else { ecs_meta_cursor_t cur = ecs_meta_cursor( v->world, condval.type, condval.ptr); cond = ecs_meta_get_bool(&cur); } ecs_value_free(v->world, condval.type, condval.ptr); if (flecs_script_eval_scope(v, cond ? node->if_true : node->if_false)) { return -1; } return 0; } static int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, ecs_script_annot_t *node) { if (!v->base.next) { flecs_script_eval_error(v, node, "annotation '%s' is not applied to anything", node->name); return -1; } ecs_script_node_kind_t kind = v->base.next->kind; if (kind != EcsAstEntity && kind != EcsAstAnnotation) { flecs_script_eval_error(v, node, "annotation must be applied to an entity"); return -1; } ecs_allocator_t *a = v->allocator; ecs_vec_append_t(a, &v->annot, ecs_script_annot_t*)[0] = node; return 0; } int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { switch(node->kind) { case EcsAstScope: return flecs_script_eval_scope( v, (ecs_script_scope_t*)node); case EcsAstTag: return flecs_script_eval_tag( v, (ecs_script_tag_t*)node); case EcsAstComponent: return flecs_script_eval_component( v, (ecs_script_component_t*)node); case EcsAstVarComponent: return flecs_script_eval_var_component( v, (ecs_script_var_component_t*)node); case EcsAstDefaultComponent: return flecs_script_eval_default_component( v, (ecs_script_default_component_t*)node); case EcsAstWithVar: return flecs_script_eval_with_var( v, (ecs_script_var_node_t*)node); case EcsAstWithTag: return flecs_script_eval_with_tag( v, (ecs_script_tag_t*)node); case EcsAstWithComponent: return flecs_script_eval_with_component( v, (ecs_script_component_t*)node); case EcsAstWith: return flecs_script_eval_with( v, (ecs_script_with_t*)node); case EcsAstUsing: return flecs_script_eval_using( v, (ecs_script_using_t*)node); case EcsAstModule: return flecs_script_eval_module( v, (ecs_script_module_t*)node); case EcsAstAnnotation: return flecs_script_eval_annot( v, (ecs_script_annot_t*)node); case EcsAstTemplate: return flecs_script_eval_template( v, (ecs_script_template_node_t*)node); case EcsAstProp: return 0; case EcsAstConst: return flecs_script_eval_const( v, (ecs_script_var_node_t*)node); case EcsAstEntity: return flecs_script_eval_entity( v, (ecs_script_entity_t*)node); case EcsAstPairScope: return flecs_script_eval_pair_scope( v, (ecs_script_pair_scope_t*)node); case EcsAstIf: return flecs_script_eval_if( v, (ecs_script_if_t*)node); } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); } void flecs_script_eval_visit_init( ecs_script_impl_t *script, ecs_script_eval_visitor_t *v) { *v = (ecs_script_eval_visitor_t){ .base = { .script = script, .visit = (ecs_visit_action_t)flecs_script_eval_node }, .world = script->pub.world, .allocator = &script->allocator }; flecs_stack_init(&v->stack); ecs_vec_init_t(v->allocator, &v->using, ecs_entity_t, 0); ecs_vec_init_t(v->allocator, &v->with, ecs_value_t, 0); ecs_vec_init_t(v->allocator, &v->annot, ecs_script_annot_t*, 0); /* Always include flecs.meta */ ecs_vec_append_t(v->allocator, &v->using, ecs_entity_t)[0] = ecs_lookup(v->world, "flecs.meta"); } void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v) { ecs_vec_fini_t(v->allocator, &v->annot, ecs_script_annot_t*); ecs_vec_fini_t(v->allocator, &v->with, ecs_value_t); ecs_vec_fini_t(v->allocator, &v->using, ecs_entity_t); flecs_stack_fini(&v->stack); } int ecs_script_eval( ecs_script_t *script) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl(script); flecs_script_eval_visit_init(impl, &v); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v); return result; } #endif /** * @file addons/script/visit_free.c * @brief Script free visitor (frees AST resources). */ #ifdef FLECS_SCRIPT static void flecs_script_scope_free( ecs_script_visit_t *v, ecs_script_scope_t *node) { ecs_script_visit_scope(v, node); ecs_vec_fini_t(&v->script->allocator, &node->stmts, ecs_script_node_t*); flecs_free_t(&v->script->allocator, ecs_script_scope_t, node); } static void flecs_script_with_free( ecs_script_visit_t *v, ecs_script_with_t *node) { flecs_script_scope_free(v, node->expressions); flecs_script_scope_free(v, node->scope); } static void flecs_script_template_free( ecs_script_visit_t *v, ecs_script_template_node_t *node) { flecs_script_scope_free(v, node->scope); } static void flecs_script_entity_free( ecs_script_visit_t *v, ecs_script_entity_t *node) { flecs_script_scope_free(v, node->scope); } static void flecs_script_pair_scope_free( ecs_script_visit_t *v, ecs_script_pair_scope_t *node) { flecs_script_scope_free(v, node->scope); } static void flecs_script_if_free( ecs_script_visit_t *v, ecs_script_if_t *node) { flecs_script_scope_free(v, node->if_true); flecs_script_scope_free(v, node->if_false); } static int flecs_script_stmt_free( ecs_script_visit_t *v, ecs_script_node_t *node) { ecs_allocator_t *a = &v->script->allocator; switch(node->kind) { case EcsAstScope: flecs_script_scope_free(v, (ecs_script_scope_t*)node); break; case EcsAstWith: flecs_script_with_free(v, (ecs_script_with_t*)node); flecs_free_t(a, ecs_script_with_t, node); break; case EcsAstTemplate: flecs_script_template_free(v, (ecs_script_template_node_t*)node); flecs_free_t(a, ecs_script_template_node_t, node); break; case EcsAstEntity: flecs_script_entity_free(v, (ecs_script_entity_t*)node); flecs_free_t(a, ecs_script_entity_t, node); break; case EcsAstPairScope: flecs_script_pair_scope_free(v, (ecs_script_pair_scope_t*)node); flecs_free_t(a, ecs_script_pair_scope_t, node); break; case EcsAstIf: flecs_script_if_free(v, (ecs_script_if_t*)node); flecs_free_t(a, ecs_script_if_t, node); break; case EcsAstTag: flecs_free_t(a, ecs_script_tag_t, node); break; case EcsAstComponent: flecs_free_t(a, ecs_script_component_t, node); break; case EcsAstDefaultComponent: flecs_free_t(a, ecs_script_default_component_t, node); break; case EcsAstVarComponent: flecs_free_t(a, ecs_script_var_component_t, node); break; case EcsAstWithVar: flecs_free_t(a, ecs_script_var_component_t, node); break; case EcsAstWithTag: flecs_free_t(a, ecs_script_tag_t, node); break; case EcsAstWithComponent: flecs_free_t(a, ecs_script_component_t, node); break; case EcsAstUsing: flecs_free_t(a, ecs_script_using_t, node); break; case EcsAstModule: flecs_free_t(a, ecs_script_module_t, node); break; case EcsAstAnnotation: flecs_free_t(a, ecs_script_annot_t, node); break; case EcsAstProp: case EcsAstConst: flecs_free_t(a, ecs_script_var_node_t, node); break; } return 0; } int flecs_script_visit_free( ecs_script_t *script) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_visit_t v = { .script = flecs_script_impl(script) }; if (ecs_script_visit( flecs_script_impl(script), &v, flecs_script_stmt_free)) { goto error; } return 0; error: return - 1; } #endif /** * @file addons/script/visit_to_str.c * @brief Script AST to string visitor. */ #ifdef FLECS_SCRIPT typedef struct ecs_script_str_visitor_t { ecs_script_visit_t base; ecs_strbuf_t *buf; int32_t depth; bool newline; } ecs_script_str_visitor_t; static int flecs_script_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_scope_t *scope); static void flecs_scriptbuf_append( ecs_script_str_visitor_t *v, const char *fmt, ...) { if (v->newline) { ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); v->newline = false; } va_list args; va_start(args, fmt); ecs_strbuf_vappend(v->buf, fmt, args); va_end(args); if (fmt[strlen(fmt) - 1] == '\n') { v->newline = true; } } static void flecs_scriptbuf_appendstr( ecs_script_str_visitor_t *v, const char *str) { if (v->newline) { ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); v->newline = false; } ecs_strbuf_appendstr(v->buf, str); if (str[strlen(str) - 1] == '\n') { v->newline = true; } } static void flecs_script_id_to_str( ecs_script_str_visitor_t *v, ecs_script_id_t *id) { if (id->flag) { if (id->flag == ECS_AUTO_OVERRIDE) { flecs_scriptbuf_appendstr(v, "auto_override | "); } else { flecs_scriptbuf_appendstr(v, "??? | "); } } if (id->second) { flecs_scriptbuf_append(v, "(%s, %s)", id->first, id->second); } else { flecs_scriptbuf_appendstr(v, id->first); } } static void flecs_script_expr_to_str( ecs_script_str_visitor_t *v, const char *expr) { if (expr) { flecs_scriptbuf_append(v, "%s%s%s", ECS_GREEN, expr, ECS_NORMAL); } else { flecs_scriptbuf_appendstr(v, "{}"); } } static const char* flecs_script_node_to_str( ecs_script_node_t *node) { switch(node->kind) { case EcsAstScope: return "scope"; case EcsAstWithTag: case EcsAstTag: return "tag"; case EcsAstWithComponent: case EcsAstComponent: return "component"; case EcsAstWithVar: case EcsAstVarComponent: return "var"; case EcsAstDefaultComponent: return "default_component"; case EcsAstWith: return "with"; case EcsAstUsing: return "using"; case EcsAstModule: return "module"; case EcsAstAnnotation: return "annot"; case EcsAstTemplate: return "template"; case EcsAstProp: return "prop"; case EcsAstConst: return "const"; case EcsAstEntity: return "entity"; case EcsAstPairScope: return "pair_scope"; case EcsAstIf: return "if"; } return "???"; } static void flecs_scriptbuf_node( ecs_script_str_visitor_t *v, ecs_script_node_t *node) { flecs_scriptbuf_append(v, "%s%s%s: ", ECS_BLUE, flecs_script_node_to_str(node), ECS_NORMAL); } static void flecs_script_tag_to_str( ecs_script_str_visitor_t *v, ecs_script_tag_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_script_id_to_str(v, &node->id); flecs_scriptbuf_appendstr(v, "\n"); } static void flecs_script_component_to_str( ecs_script_str_visitor_t *v, ecs_script_component_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_script_id_to_str(v, &node->id); if (node->expr) { flecs_scriptbuf_appendstr(v, ": "); flecs_script_expr_to_str(v, node->expr); } flecs_scriptbuf_appendstr(v, "\n"); } static void flecs_script_default_component_to_str( ecs_script_str_visitor_t *v, ecs_script_default_component_t *node) { flecs_scriptbuf_node(v, &node->node); if (node->expr) { flecs_script_expr_to_str(v, node->expr); } flecs_scriptbuf_appendstr(v, "\n"); } static void flecs_script_with_var_to_str( ecs_script_str_visitor_t *v, ecs_script_var_component_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_append(v, "%s ", node->name); flecs_scriptbuf_appendstr(v, "\n"); } static void flecs_script_with_to_str( ecs_script_str_visitor_t *v, ecs_script_with_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; flecs_scriptbuf_append(v, "%sexpressions%s: ", ECS_CYAN, ECS_NORMAL); flecs_script_scope_to_str(v, node->expressions); flecs_scriptbuf_append(v, "%sscope%s: ", ECS_CYAN, ECS_NORMAL); flecs_script_scope_to_str(v, node->scope); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); } static void flecs_script_using_to_str( ecs_script_str_visitor_t *v, ecs_script_using_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_append(v, "%s\n", node->name); } static void flecs_script_module_to_str( ecs_script_str_visitor_t *v, ecs_script_module_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_append(v, "%s\n", node->name); } static void flecs_script_annot_to_str( ecs_script_str_visitor_t *v, ecs_script_annot_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_append(v, "%s = %s\"%s\"%s", node->name, ECS_GREEN, node->expr, ECS_NORMAL); flecs_scriptbuf_appendstr(v, "\n"); } static void flecs_script_template_to_str( ecs_script_str_visitor_t *v, ecs_script_template_node_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_append(v, "%s ", node->name); flecs_script_scope_to_str(v, node->scope); } static void flecs_script_var_node_to_str( ecs_script_str_visitor_t *v, ecs_script_var_node_t *node) { flecs_scriptbuf_node(v, &node->node); if (node->type) { flecs_scriptbuf_append(v, "%s : %s = ", node->name, node->type); } else { flecs_scriptbuf_append(v, "%s = ", node->name); } flecs_script_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, "\n"); } static void flecs_script_entity_to_str( ecs_script_str_visitor_t *v, ecs_script_entity_t *node) { flecs_scriptbuf_node(v, &node->node); if (node->kind) { flecs_scriptbuf_append(v, "%s ", node->kind); } if (node->name) { flecs_scriptbuf_append(v, "%s ", node->name); } else { flecs_scriptbuf_appendstr(v, " "); } if (!flecs_scope_is_empty(node->scope)) { flecs_script_scope_to_str(v, node->scope); } else { flecs_scriptbuf_appendstr(v, "\n"); } } static void flecs_script_pair_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_pair_scope_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_script_id_to_str(v, &node->id); flecs_scriptbuf_appendstr(v, " "); flecs_script_scope_to_str(v, node->scope); } static void flecs_script_if_to_str( ecs_script_str_visitor_t *v, ecs_script_if_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_script_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; flecs_scriptbuf_append(v, "%strue%s: ", ECS_CYAN, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_true); flecs_scriptbuf_append(v, "%sfalse%s: ", ECS_CYAN, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_false); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); } static int flecs_script_scope_to_str( ecs_script_str_visitor_t *v, ecs_script_scope_t *scope) { if (!ecs_vec_count(&scope->stmts)) { flecs_scriptbuf_appendstr(v, "{}\n"); return 0; } flecs_scriptbuf_appendstr(v, "{\n"); v->depth ++; if (ecs_script_visit_scope(v, scope)) { return -1; } v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); return 0; } static int flecs_script_stmt_to_str( ecs_script_str_visitor_t *v, ecs_script_node_t *node) { switch(node->kind) { case EcsAstScope: if (flecs_script_scope_to_str(v, (ecs_script_scope_t*)node)) { return -1; } break; case EcsAstTag: case EcsAstWithTag: flecs_script_tag_to_str(v, (ecs_script_tag_t*)node); break; case EcsAstComponent: case EcsAstWithComponent: flecs_script_component_to_str(v, (ecs_script_component_t*)node); break; case EcsAstVarComponent: case EcsAstWithVar: flecs_script_with_var_to_str(v, (ecs_script_var_component_t*)node); break; case EcsAstDefaultComponent: flecs_script_default_component_to_str(v, (ecs_script_default_component_t*)node); break; case EcsAstWith: flecs_script_with_to_str(v, (ecs_script_with_t*)node); break; case EcsAstUsing: flecs_script_using_to_str(v, (ecs_script_using_t*)node); break; case EcsAstModule: flecs_script_module_to_str(v, (ecs_script_module_t*)node); break; case EcsAstAnnotation: flecs_script_annot_to_str(v, (ecs_script_annot_t*)node); break; case EcsAstTemplate: flecs_script_template_to_str(v, (ecs_script_template_node_t*)node); break; case EcsAstConst: case EcsAstProp: flecs_script_var_node_to_str(v, (ecs_script_var_node_t*)node); break; case EcsAstEntity: flecs_script_entity_to_str(v, (ecs_script_entity_t*)node); break; case EcsAstPairScope: flecs_script_pair_scope_to_str(v, (ecs_script_pair_scope_t*)node); break; case EcsAstIf: flecs_script_if_to_str(v, (ecs_script_if_t*)node); break; } return 0; } int ecs_script_ast_to_buf( ecs_script_t *script, ecs_strbuf_t *buf) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_str_visitor_t v = { .buf = buf }; if (ecs_script_visit(flecs_script_impl(script), &v, flecs_script_stmt_to_str)) { goto error; } return 0; error: ecs_strbuf_reset(buf); return - 1; } char* ecs_script_ast_to_str( ecs_script_t *script) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_script_ast_to_buf(script, &buf)) { goto error; } return ecs_strbuf_get(&buf); error: return NULL; } #endif /** * @file addons/monitor.c * @brief Stats addon module. */ /** * @file addons/stats/stats.h * @brief Internal functions/types for stats addon. */ #ifndef FLECS_STATS_PRIVATE_H #define FLECS_STATS_PRIVATE_H typedef struct { /* Statistics API interface */ void (*copy_last)(void *stats, void *src); void (*get)(ecs_world_t *world, ecs_entity_t res, void *stats); void (*reduce)(void *stats, void *src); void (*reduce_last)(void *stats, void *last, int32_t reduce_count); void (*repeat_last)(void* stats); void (*set_t)(void *stats, int32_t t); void (*fini)(void *stats); /* Size of statistics type */ ecs_size_t stats_size; /* Id of component that contains the statistics */ ecs_entity_t monitor_component_id; /* Id of component used to query for monitored resources (optional) */ ecs_id_t query_component_id; } ecs_stats_api_t; void flecs_stats_api_import( ecs_world_t *world, ecs_stats_api_t *api); void FlecsWorldSummaryImport( ecs_world_t *world); void FlecsWorldMonitorImport( ecs_world_t *world); void FlecsSystemMonitorImport( ecs_world_t *world); void FlecsPipelineMonitorImport( ecs_world_t *world); #endif #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(FlecsStats); ecs_entity_t EcsPeriod1s = 0; ecs_entity_t EcsPeriod1m = 0; ecs_entity_t EcsPeriod1h = 0; ecs_entity_t EcsPeriod1d = 0; ecs_entity_t EcsPeriod1w = 0; #define FlecsDayIntervalCount (24) #define FlecsWeekIntervalCount (168) typedef struct { ecs_stats_api_t api; ecs_query_t *query; } ecs_monitor_stats_ctx_t; typedef struct { ecs_stats_api_t api; } ecs_reduce_stats_ctx_t; typedef struct { ecs_stats_api_t api; int32_t interval; } ecs_aggregate_stats_ctx_t; static void MonitorStats(ecs_iter_t *it) { ecs_world_t *world = it->real_world; ecs_monitor_stats_ctx_t *ctx = it->ctx; EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 0); ecs_ftime_t elapsed = hdr->elapsed; hdr->elapsed += it->delta_time; int32_t t_last = (int32_t)(elapsed * 60); int32_t t_next = (int32_t)(hdr->elapsed * 60); int32_t i, dif = t_next - t_last; void *stats_storage = ecs_os_alloca(ctx->api.stats_size); void *last = NULL; if (!dif) { hdr->reduce_count ++; } ecs_iter_t qit; int32_t cur = -1, count = 0; void *stats = NULL; ecs_map_t *stats_map = NULL; if (ctx->query) { /* Query results are stored in a map */ qit = ecs_query_iter(it->world, ctx->query); stats_map = ECS_OFFSET_T(hdr, EcsStatsHeader); } else { /* No query, so tracking stats for single element */ stats = ECS_OFFSET_T(hdr, EcsStatsHeader); } do { ecs_entity_t res = 0; if (ctx->query) { /* Query, fetch resource entity & stats pointer */ if (cur == (count - 1)) { if (!ecs_query_next(&qit)) { break; } cur = 0; count = qit.count; } else { cur ++; } res = qit.entities[cur]; stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); } if (!dif) { /* Copy last value so we can pass it to reduce_last */ last = stats_storage; ecs_os_memset(last, 0, ctx->api.stats_size); ctx->api.copy_last(last, stats); } ctx->api.get(world, res, stats); if (!dif) { /* Still in same interval, combine with last measurement */ ctx->api.reduce_last(stats, last, hdr->reduce_count); } else if (dif > 1) { /* More than 16ms has passed, backfill */ for (i = 1; i < dif; i ++) { ctx->api.repeat_last(stats); } } if (last && ctx->api.fini) { ctx->api.fini(last); } if (!ctx->query) { break; } } while (true); if (dif > 1) { hdr->reduce_count = 0; } } static void ReduceStats(ecs_iter_t *it) { ecs_reduce_stats_ctx_t *ctx = it->ctx; void *dst = ecs_field_w_size(it, 0, 0); void *src = ecs_field_w_size(it, 0, 1); dst = ECS_OFFSET_T(dst, EcsStatsHeader); src = ECS_OFFSET_T(src, EcsStatsHeader); if (!ctx->api.query_component_id) { ctx->api.reduce(dst, src); } else { ecs_map_iter_t mit = ecs_map_iter(src); while (ecs_map_next(&mit)) { void *src_el = ecs_map_ptr(&mit); void *dst_el = ecs_map_ensure_alloc( dst, ctx->api.stats_size, ecs_map_key(&mit)); ctx->api.reduce(dst_el, src_el); } } } static void AggregateStats(ecs_iter_t *it) { ecs_aggregate_stats_ctx_t *ctx = it->ctx; int32_t interval = ctx->interval; EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 0); EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 1); void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); void *dst_map = NULL; void *src_map = NULL; if (ctx->api.query_component_id) { dst_map = dst; src_map = src; dst = NULL; src = NULL; } void *stats_storage = ecs_os_alloca(ctx->api.stats_size); void *last = NULL; ecs_map_iter_t mit; if (src_map) { mit = ecs_map_iter(src_map); } do { if (src_map) { if (!ecs_map_next(&mit)) { break; } src = ecs_map_ptr(&mit); dst = ecs_map_ensure_alloc( dst_map, ctx->api.stats_size, ecs_map_key(&mit)); } if (dst_hdr->reduce_count != 0) { /* Copy last value so we can pass it to reduce_last */ last = stats_storage; ecs_os_memset(last, 0, ctx->api.stats_size); ctx->api.copy_last(last, dst); } /* Reduce from minutes to the current day */ ctx->api.reduce(dst, src); if (dst_hdr->reduce_count != 0) { ctx->api.reduce_last(dst, last, dst_hdr->reduce_count); } if (last && ctx->api.fini != NULL) { ctx->api.fini(last); } if (!src_map) { break; } } while (true); /* A day has 60 24 minute intervals */ dst_hdr->reduce_count ++; if (dst_hdr->reduce_count >= interval) { dst_hdr->reduce_count = 0; } } static void flecs_monitor_ctx_free( void *ptr) { ecs_monitor_stats_ctx_t *ctx = ptr; if (ctx->query) { ecs_query_fini(ctx->query); } ecs_os_free(ctx); } static void flecs_reduce_ctx_free( void *ptr) { ecs_os_free(ptr); } static void flecs_aggregate_ctx_free( void *ptr) { ecs_os_free(ptr); } void flecs_stats_api_import( ecs_world_t *world, ecs_stats_api_t *api) { ecs_entity_t kind = api->monitor_component_id; ecs_entity_t prev = ecs_set_scope(world, kind); ecs_query_t *q = NULL; if (api->query_component_id) { q = ecs_query(world, { .terms = {{ .id = api->query_component_id }}, .cache_kind = EcsQueryCacheNone, .flags = EcsQueryMatchDisabled }); } // Called each frame, collects 60 measurements per second { ecs_monitor_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_monitor_stats_ctx_t); ctx->api = *api; ctx->query = q; ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1s", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), .query.terms = {{ .id = ecs_pair(kind, EcsPeriod1s), .src.id = EcsWorld }}, .callback = MonitorStats, .ctx = ctx, .ctx_free = flecs_monitor_ctx_free }); } // Called each second, reduces into 60 measurements per minute ecs_entity_t mw1m; { ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); ctx->api = *api; mw1m = ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1m", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), .query.terms = {{ .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1s), .src.id = EcsWorld }}, .callback = ReduceStats, .interval = 1.0, .ctx = ctx, .ctx_free = flecs_reduce_ctx_free }); } // Called each minute, reduces into 60 measurements per hour { ecs_reduce_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_reduce_stats_ctx_t); ctx->api = *api; ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1h", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), .query.terms = {{ .id = ecs_pair(kind, EcsPeriod1h), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }}, .callback = ReduceStats, .rate = 60, .tick_source = mw1m, .ctx = ctx, .ctx_free = flecs_reduce_ctx_free }); } // Called each minute, reduces into 60 measurements per day { ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); ctx->api = *api; ctx->interval = FlecsDayIntervalCount; ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1d", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), .query.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 = ctx, .ctx_free = flecs_aggregate_ctx_free }); } // Called each hour, reduces into 60 measurements per week { ecs_aggregate_stats_ctx_t *ctx = ecs_os_calloc_t(ecs_aggregate_stats_ctx_t); ctx->api = *api; ctx->interval = FlecsWeekIntervalCount; ecs_system(world, { .entity = ecs_entity(world, { .name = "Monitor1w", .add = ecs_ids(ecs_dependson(EcsPreFrame)) }), .query.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 = ctx, .ctx_free = flecs_aggregate_ctx_free }); } ecs_set_scope(world, prev); ecs_add_pair(world, EcsWorld, kind, EcsPeriod1s); ecs_add_pair(world, EcsWorld, kind, EcsPeriod1m); ecs_add_pair(world, EcsWorld, kind, EcsPeriod1h); ecs_add_pair(world, EcsWorld, kind, EcsPeriod1d); ecs_add_pair(world, EcsWorld, kind, EcsPeriod1w); } void FlecsStatsImport( ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsStats); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsTimer); #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_UNITS ECS_IMPORT(world, FlecsUnits); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsStats), "Module that automatically monitors statistics for the world & systems"); #endif ecs_set_name_prefix(world, "Ecs"); EcsPeriod1s = ecs_entity(world, { .name = "EcsPeriod1s" }); EcsPeriod1m = ecs_entity(world, { .name = "EcsPeriod1m" }); EcsPeriod1h = ecs_entity(world, { .name = "EcsPeriod1h" }); EcsPeriod1d = ecs_entity(world, { .name = "EcsPeriod1d" }); EcsPeriod1w = ecs_entity(world, { .name = "EcsPeriod1w" }); FlecsWorldSummaryImport(world); FlecsWorldMonitorImport(world); FlecsSystemMonitorImport(world); FlecsPipelineMonitorImport(world); if (ecs_os_has_time()) { ecs_measure_frame_time(world, true); ecs_measure_system_time(world, true); } } #endif /** * @file addons/stats/pipeline_monitor.c * @brief Stats addon pipeline monitor */ #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsPipelineStats); static void flecs_pipeline_monitor_dtor(EcsPipelineStats *ptr) { ecs_map_iter_t it = ecs_map_iter(&ptr->stats); while (ecs_map_next(&it)) { ecs_pipeline_stats_t *stats = ecs_map_ptr(&it); ecs_pipeline_stats_fini(stats); ecs_os_free(stats); } ecs_map_fini(&ptr->stats); } static ECS_CTOR(EcsPipelineStats, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->stats, NULL); }) static ECS_COPY(EcsPipelineStats, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); }) static ECS_MOVE(EcsPipelineStats, dst, src, { flecs_pipeline_monitor_dtor(dst); ecs_os_memcpy_t(dst, src, EcsPipelineStats); ecs_os_zeromem(src); }) static ECS_DTOR(EcsPipelineStats, ptr, { flecs_pipeline_monitor_dtor(ptr); }) static void flecs_pipeline_stats_set_t( void *stats, int32_t t) { ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); ((ecs_pipeline_stats_t*)stats)->t = t; } static void flecs_pipeline_stats_copy_last( void *stats, void *src) { ecs_pipeline_stats_copy_last(stats, src); } static void flecs_pipeline_stats_get( ecs_world_t *world, ecs_entity_t res, void *stats) { ecs_pipeline_stats_get(world, res, stats); } static void flecs_pipeline_stats_reduce( void *stats, void *src) { ecs_pipeline_stats_reduce(stats, src); } static void flecs_pipeline_stats_reduce_last( void *stats, void *last, int32_t reduce_count) { ecs_pipeline_stats_reduce_last(stats, last, reduce_count); } static void flecs_pipeline_stats_repeat_last( void* stats) { ecs_pipeline_stats_repeat_last(stats); } static void flecs_pipeline_stats_fini( void *stats) { ecs_pipeline_stats_fini(stats); } void FlecsPipelineMonitorImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsPipelineStats); ecs_set_hooks(world, EcsPipelineStats, { .ctor = ecs_ctor(EcsPipelineStats), .copy = ecs_copy(EcsPipelineStats), .move = ecs_move(EcsPipelineStats), .dtor = ecs_dtor(EcsPipelineStats) }); ecs_stats_api_t api = { .copy_last = flecs_pipeline_stats_copy_last, .get = flecs_pipeline_stats_get, .reduce = flecs_pipeline_stats_reduce, .reduce_last = flecs_pipeline_stats_reduce_last, .repeat_last = flecs_pipeline_stats_repeat_last, .set_t = flecs_pipeline_stats_set_t, .fini = flecs_pipeline_stats_fini, .stats_size = ECS_SIZEOF(ecs_pipeline_stats_t), .monitor_component_id = ecs_id(EcsPipelineStats), .query_component_id = ecs_id(EcsPipeline) }; flecs_stats_api_import(world, &api); } #endif /** * @file addons/stats.c * @brief Stats addon. */ #ifdef FLECS_STATS #define ECS_GAUGE_RECORD(m, t, value)\ flecs_gauge_record(m, t, (ecs_float_t)(value)) #define ECS_COUNTER_RECORD(m, t, value)\ flecs_counter_record(m, t, (double)(value)) #define ECS_METRIC_FIRST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) #define ECS_METRIC_LAST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) static int32_t t_next( int32_t t) { return (t + 1) % ECS_STAT_WINDOW; } static int32_t t_prev( int32_t t) { return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } static void flecs_gauge_record( ecs_metric_t *m, int32_t t, ecs_float_t value) { m->gauge.avg[t] = value; m->gauge.min[t] = value; m->gauge.max[t] = value; } static double flecs_counter_record( ecs_metric_t *m, int32_t t, double value) { int32_t tp = t_prev(t); double prev = m->counter.value[tp]; m->counter.value[t] = value; double gauge_value = value - prev; if (gauge_value < 0) { gauge_value = 0; /* Counters are monotonically increasing */ } flecs_gauge_record(m, t, (ecs_float_t)gauge_value); return gauge_value; } static void flecs_metric_print( const char *name, ecs_float_t value) { ecs_size_t len = ecs_os_strlen(name); ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); } static void flecs_gauge_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->gauge.avg[t]); } static void flecs_counter_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->counter.rate.avg[t]); } void ecs_metric_reduce( ecs_metric_t *dst, const ecs_metric_t *src, int32_t t_dst, int32_t t_src) { ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); bool min_set = false; dst->gauge.avg[t_dst] = 0; dst->gauge.min[t_dst] = 0; dst->gauge.max[t_dst] = 0; ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; int32_t i; for (i = 0; i < ECS_STAT_WINDOW; i ++) { int32_t t = (t_src + i) % ECS_STAT_WINDOW; dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { dst->gauge.min[t_dst] = src->gauge.min[t]; min_set = true; } if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { dst->gauge.max[t_dst] = src->gauge.max[t]; } } dst->counter.value[t_dst] = src->counter.value[t_src]; error: return; } void ecs_metric_reduce_last( ecs_metric_t *m, int32_t prev, int32_t count) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); int32_t t = t_next(prev); if (m->gauge.min[t] < m->gauge.min[prev]) { m->gauge.min[prev] = m->gauge.min[t]; } if (m->gauge.max[t] > m->gauge.max[prev]) { m->gauge.max[prev] = m->gauge.max[t]; } ecs_float_t fcount = (ecs_float_t)(count + 1); ecs_float_t cur = m->gauge.avg[prev]; ecs_float_t next = m->gauge.avg[t]; cur *= ((fcount - 1) / fcount); next *= 1 / fcount; m->gauge.avg[prev] = cur + next; m->counter.value[prev] = m->counter.value[t]; error: return; } void ecs_metric_copy( ecs_metric_t *m, int32_t dst, int32_t src) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); m->gauge.avg[dst] = m->gauge.avg[src]; m->gauge.min[dst] = m->gauge.min[src]; m->gauge.max[dst] = m->gauge.max[src]; m->counter.value[dst] = m->counter.value[src]; error: return; } static void flecs_stats_reduce( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } } static void flecs_stats_reduce_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src, int32_t count) { int32_t t_dst_next = t_next(t_dst); for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { /* Reduce into previous value */ ecs_metric_reduce_last(dst_cur, t_dst, count); /* Restore old value */ dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; } } static void flecs_stats_repeat_last( ecs_metric_t *cur, ecs_metric_t *last, int32_t t) { int32_t prev = t_prev(t); for (; cur <= last; cur ++) { ecs_metric_copy(cur, t, prev); } } static void flecs_stats_copy_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; } } void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t t = s->t = t_next(s->t); double delta_frame_count = ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); double delta_world_time = ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); if (ECS_NEQZERO(delta_world_time) && ECS_NEQZERO(delta_frame_count)) { ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); } else { ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } ECS_GAUGE_RECORD(&s->entities.count, t, flecs_entities_count(world)); ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_entities_not_alive_count(world)); ECS_GAUGE_RECORD(&s->components.tag_count, t, world->info.tag_id_count); ECS_GAUGE_RECORD(&s->components.component_count, t, world->info.component_id_count); ECS_GAUGE_RECORD(&s->components.pair_count, t, world->info.pair_id_count); ECS_GAUGE_RECORD(&s->components.type_count, t, ecs_sparse_count(&world->type_info)); ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); if (ecs_is_alive(world, EcsSystem)) { ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); int64_t outstanding_allocs = ecs_os_api_malloc_count + ecs_os_api_calloc_count - ecs_os_api_free_count; ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); #ifdef FLECS_HTTP ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif error: return; } void ecs_world_stats_reduce( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_world_stats_reduce_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_world_stats_repeat_last( ecs_world_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_world_stats_copy_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) { ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; int32_t t = s->t = t_next(s->t); ecs_query_count_t counts = ecs_query_count(query); ECS_GAUGE_RECORD(&s->result_count, t, counts.results); ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); error: return; } void ecs_query_cache_stats_reduce( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_query_cache_stats_reduce_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_query_cache_stats_repeat_last( ecs_query_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_query_cache_stats_copy_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } #ifdef FLECS_SYSTEM bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); if (!ptr) { return false; } ecs_query_stats_get(world, ptr->query, &s->query); int32_t t = s->query.t; ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); s->task = !(ptr->query->flags & EcsQueryMatchThis); return true; error: return false; } void ecs_system_stats_reduce( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_cache_stats_reduce(&dst->query, &src->query); dst->task = src->task; flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } void ecs_system_stats_reduce_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src, int32_t count) { ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); dst->task = src->task; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } void ecs_system_stats_repeat_last( ecs_system_stats_t *stats) { ecs_query_cache_stats_repeat_last(&stats->query); flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->query.t)); } void ecs_system_stats_copy_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_cache_stats_copy_last(&dst->query, &src->query); dst->task = src->task; flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } #endif #ifdef FLECS_PIPELINE bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); if (!pqc) { return false; } ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); int32_t sys_count = 0, active_sys_count = 0; /* 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 (op) { ecs_entity_t *systems = NULL; if (pip_count) { ecs_vec_init_if_t(&s->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); systems = ecs_vec_first_t(&s->systems, ecs_entity_t); /* Populate systems vector, keep track of sync points */ it = ecs_query_iter(stage, pq->query); int32_t i, i_system = 0, ran_since_merge = 0; while (ecs_query_next(&it)) { if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { continue; } for (i = 0; i < it.count; i ++) { systems[i_system ++] = it.entities[i]; ran_since_merge ++; if (op != op_last && ran_since_merge == op->count) { ran_since_merge = 0; op++; systems[i_system ++] = 0; /* 0 indicates a merge point */ } } } systems[i_system ++] = 0; /* Last merge */ ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); } /* Get sync point statistics */ int32_t i, count = ecs_vec_count(ops); if (count) { ecs_vec_init_if_t(&s->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &s->sync_points, ecs_sync_stats_t, count); op = ecs_vec_first_t(ops, ecs_pipeline_op_t); for (i = 0; i < count; i ++) { ecs_pipeline_op_t *cur = &op[i]; ecs_sync_stats_t *el = ecs_vec_get_t(&s->sync_points, ecs_sync_stats_t, i); ECS_COUNTER_RECORD(&el->time_spent, s->t, cur->time_spent); ECS_COUNTER_RECORD(&el->commands_enqueued, s->t, cur->commands_enqueued); el->system_count = cur->count; el->multi_threaded = cur->multi_threaded; el->immediate = cur->immediate; } } } s->t = t_next(s->t); return true; error: return false; } void ecs_pipeline_stats_fini( ecs_pipeline_stats_t *stats) { ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); ecs_vec_fini_t(NULL, &stats->sync_points, ecs_sync_stats_t); } void ecs_pipeline_stats_reduce( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t system_count = ecs_vec_count(&src->systems); ecs_vec_init_if_t(&dst->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_reduce(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, src->t); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->immediate = src_el->immediate; } dst->t = t_next(dst->t); } void ecs_pipeline_stats_reduce_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src, int32_t count) { int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, src->t, count); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->immediate = src_el->immediate; } dst->t = t_prev(dst->t); } void ecs_pipeline_stats_repeat_last( ecs_pipeline_stats_t *stats) { int32_t i, sync_count = ecs_vec_count(&stats->sync_points); ecs_sync_stats_t *syncs = ecs_vec_first_t(&stats->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *el = &syncs[i]; flecs_stats_repeat_last(ECS_METRIC_FIRST(el), ECS_METRIC_LAST(el), (stats->t)); } stats->t = t_next(stats->t); } void ecs_pipeline_stats_copy_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t i, sync_count = ecs_vec_count(&src->sync_points); ecs_vec_init_if_t(&dst->sync_points, ecs_sync_stats_t); ecs_vec_set_min_count_zeromem_t(NULL, &dst->sync_points, ecs_sync_stats_t, sync_count); ecs_sync_stats_t *dst_syncs = ecs_vec_first_t(&dst->sync_points, ecs_sync_stats_t); ecs_sync_stats_t *src_syncs = ecs_vec_first_t(&src->sync_points, ecs_sync_stats_t); for (i = 0; i < sync_count; i ++) { ecs_sync_stats_t *dst_el = &dst_syncs[i]; ecs_sync_stats_t *src_el = &src_syncs[i]; flecs_stats_copy_last(ECS_METRIC_FIRST(dst_el), ECS_METRIC_LAST(dst_el), ECS_METRIC_FIRST(src_el), dst->t, t_next(src->t)); dst_el->system_count = src_el->system_count; dst_el->multi_threaded = src_el->multi_threaded; dst_el->immediate = src_el->immediate; } } #endif void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { int32_t t = s->t; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_counter_print("Frame", t, &s->frame.frame_count); ecs_trace("-------------------------------------"); flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); flecs_counter_print("systems ran", t, &s->frame.systems_ran); ecs_trace(""); flecs_metric_print("target FPS", (ecs_float_t)world->info.target_fps); flecs_metric_print("time scale", (ecs_float_t)world->info.time_scale); ecs_trace(""); flecs_gauge_print("actual FPS", t, &s->performance.fps); flecs_counter_print("frame time", t, &s->performance.frame_time); flecs_counter_print("system time", t, &s->performance.system_time); flecs_counter_print("merge time", t, &s->performance.merge_time); flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); ecs_trace(""); flecs_gauge_print("tag id count", t, &s->components.tag_count); flecs_gauge_print("component id count", t, &s->components.component_count); flecs_gauge_print("pair id count", t, &s->components.pair_count); flecs_gauge_print("type count", t, &s->components.type_count); flecs_counter_print("id create count", t, &s->components.create_count); flecs_counter_print("id delete count", t, &s->components.delete_count); ecs_trace(""); flecs_gauge_print("alive entity count", t, &s->entities.count); flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); ecs_trace(""); flecs_gauge_print("query count", t, &s->queries.query_count); flecs_gauge_print("observer count", t, &s->queries.observer_count); flecs_gauge_print("system count", t, &s->queries.system_count); ecs_trace(""); flecs_gauge_print("table count", t, &s->tables.count); flecs_gauge_print("empty table count", t, &s->tables.empty_count); flecs_counter_print("table create count", t, &s->tables.create_count); flecs_counter_print("table delete count", t, &s->tables.delete_count); ecs_trace(""); flecs_counter_print("add commands", t, &s->commands.add_count); flecs_counter_print("remove commands", t, &s->commands.remove_count); flecs_counter_print("delete commands", t, &s->commands.delete_count); flecs_counter_print("clear commands", t, &s->commands.clear_count); flecs_counter_print("set commands", t, &s->commands.set_count); flecs_counter_print("ensure commands", t, &s->commands.ensure_count); flecs_counter_print("modified commands", t, &s->commands.modified_count); flecs_counter_print("other commands", t, &s->commands.other_count); flecs_counter_print("discarded commands", t, &s->commands.discard_count); flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); flecs_counter_print("batched commands", t, &s->commands.batched_count); ecs_trace(""); error: return; } #endif /** * @file addons/stats/system_monitor.c * @brief Stats addon system monitor */ #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsSystemStats); static void flecs_system_monitor_dtor(EcsSystemStats *ptr) { ecs_map_iter_t it = ecs_map_iter(&ptr->stats); while (ecs_map_next(&it)) { ecs_system_stats_t *stats = ecs_map_ptr(&it); ecs_os_free(stats); } ecs_map_fini(&ptr->stats); } static ECS_CTOR(EcsSystemStats, ptr, { ecs_os_zeromem(ptr); ecs_map_init(&ptr->stats, NULL); }) static ECS_COPY(EcsSystemStats, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "cannot copy system stats component"); }) static ECS_MOVE(EcsSystemStats, dst, src, { flecs_system_monitor_dtor(dst); ecs_os_memcpy_t(dst, src, EcsSystemStats); ecs_os_zeromem(src); }) static ECS_DTOR(EcsSystemStats, ptr, { flecs_system_monitor_dtor(ptr); }) static void flecs_system_stats_set_t( void *stats, int32_t t) { ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); ((ecs_system_stats_t*)stats)->query.t = t; } static void flecs_system_stats_copy_last( void *stats, void *src) { ecs_system_stats_copy_last(stats, src); } static void flecs_system_stats_get( ecs_world_t *world, ecs_entity_t res, void *stats) { ecs_system_stats_get(world, res, stats); } static void flecs_system_stats_reduce( void *stats, void *src) { ecs_system_stats_reduce(stats, src); } static void flecs_system_stats_reduce_last( void *stats, void *last, int32_t reduce_count) { ecs_system_stats_reduce_last(stats, last, reduce_count); } static void flecs_system_stats_repeat_last( void* stats) { ecs_system_stats_repeat_last(stats); } void FlecsSystemMonitorImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsSystemStats); ecs_set_hooks(world, EcsSystemStats, { .ctor = ecs_ctor(EcsSystemStats), .copy = ecs_copy(EcsSystemStats), .move = ecs_move(EcsSystemStats), .dtor = ecs_dtor(EcsSystemStats) }); ecs_stats_api_t api = { .copy_last = flecs_system_stats_copy_last, .get = flecs_system_stats_get, .reduce = flecs_system_stats_reduce, .reduce_last = flecs_system_stats_reduce_last, .repeat_last = flecs_system_stats_repeat_last, .set_t = flecs_system_stats_set_t, .stats_size = ECS_SIZEOF(ecs_system_stats_t), .monitor_component_id = ecs_id(EcsSystemStats), .query_component_id = EcsSystem }; flecs_stats_api_import(world, &api); } #endif /** * @file addons/stats/world_monitor.c * @brief Stats addon world monitor. */ #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsWorldStats); static void flecs_world_stats_get( ecs_world_t *world, ecs_entity_t res, void *stats) { (void)res; ecs_world_stats_get(world, stats); } static void flecs_world_stats_set_t( void *stats, int32_t t) { ecs_assert(t >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(t < ECS_STAT_WINDOW, ECS_INTERNAL_ERROR, NULL); ((ecs_world_stats_t*)stats)->t = t; } static void flecs_world_stats_copy_last( void *stats, void *src) { ecs_world_stats_copy_last(stats, src); } static void flecs_world_stats_reduce( void *stats, void *src) { ecs_world_stats_reduce(stats, src); } static void flecs_world_stats_reduce_last( void *stats, void *last, int32_t reduce_count) { ecs_world_stats_reduce_last(stats, last, reduce_count); } static void flecs_world_stats_repeat_last( void* stats) { ecs_world_stats_repeat_last(stats); } void FlecsWorldMonitorImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldStats); ecs_set_hooks(world, EcsWorldStats, { .ctor = flecs_default_ctor }); ecs_stats_api_t api = { .copy_last = flecs_world_stats_copy_last, .get = flecs_world_stats_get, .reduce = flecs_world_stats_reduce, .reduce_last = flecs_world_stats_reduce_last, .repeat_last = flecs_world_stats_repeat_last, .set_t = flecs_world_stats_set_t, .fini = NULL, .stats_size = ECS_SIZEOF(ecs_world_stats_t), .monitor_component_id = ecs_id(EcsWorldStats) }; flecs_stats_api_import(world, &api); } #endif /** * @file addons/world_summary.c * @brief Monitor addon. */ #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsWorldSummary); static void UpdateWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); const ecs_world_info_t *info = ecs_get_world_info(it->world); int32_t i, count = it->count; for (i = 0; i < count; i ++) { summary[i].target_fps = (double)info->target_fps; summary[i].time_scale = (double)info->time_scale; summary[i].frame_time_last = (double)info->frame_time_total - summary[i].frame_time_total; summary[i].system_time_last = (double)info->system_time_total - summary[i].system_time_total; summary[i].merge_time_last = (double)info->merge_time_total - summary[i].merge_time_total; summary[i].frame_time_total = (double)info->frame_time_total; summary[i].system_time_total = (double)info->system_time_total; summary[i].merge_time_total = (double)info->merge_time_total; summary[i].frame_count ++; summary[i].command_count += info->cmd.add_count + info->cmd.remove_count + info->cmd.delete_count + info->cmd.clear_count + info->cmd.set_count + info->cmd.ensure_count + info->cmd.modified_count + info->cmd.discard_count + info->cmd.event_count + info->cmd.other_count; summary[i].build_info = *ecs_get_build_info(); } } static void OnSetWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); } } void FlecsWorldSummaryImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldSummary); #if defined(FLECS_META) && defined(FLECS_UNITS) ecs_entity_t build_info = ecs_lookup(world, "flecs.core.build_info_t"); ecs_struct(world, { .entity = ecs_id(EcsWorldSummary), .members = { { .name = "target_fps", .type = ecs_id(ecs_f64_t), .unit = EcsHertz }, { .name = "time_scale", .type = ecs_id(ecs_f64_t) }, { .name = "frame_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "system_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_time_total", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "frame_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "system_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "merge_time_last", .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }, { .name = "frame_count", .type = ecs_id(ecs_u64_t) }, { .name = "command_count", .type = ecs_id(ecs_u64_t) }, { .name = "build_info", .type = build_info } } }); #endif const ecs_world_info_t *info = ecs_get_world_info(world); ecs_system(world, { .entity = ecs_entity(world, { .name = "UpdateWorldSummary", .add = ecs_ids(ecs_pair(EcsDependsOn, EcsPreFrame)) }), .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, .callback = UpdateWorldSummary }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "OnSetWorldSummary" }), .events = { EcsOnSet }, .query.terms = {{ .id = ecs_id(EcsWorldSummary) }}, .callback = OnSetWorldSummary }); ecs_set(world, EcsWorld, EcsWorldSummary, { .target_fps = (double)info->target_fps, .time_scale = (double)info->time_scale }); } #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 flecs_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, 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_path(world, system); ecs_dbg_3("worker %d: %s", stage_index, path); ecs_os_free(path); } ecs_time_t time_start; bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); if (measure_time) { ecs_os_get_time(&time_start); } 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 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.param = param; qit.ctx = system_data->ctx; qit.callback_ctx = system_data->callback_ctx; qit.run_ctx = system_data->run_ctx; flecs_defer_begin(world, stage); if (stage_count > 1 && system_data->multi_threaded) { wit = ecs_worker_iter(it, stage_index, stage_count); it = &wit; } ecs_entity_t old_system = flecs_stage_set_system(stage, system); ecs_iter_action_t action = system_data->action; it->callback = action; ecs_run_action_t run = system_data->run; if (run) { if (!system_data->query->term_count) { it->next = flecs_default_next_callback; /* Return once */ run(it); ecs_iter_fini(&qit); } else { run(it); } } else { if (system_data->query->term_count) { if (it == &qit) { while (ecs_query_next(&qit)) { action(&qit); } } else { while (ecs_iter_next(it)) { action(it); } } } else { action(&qit); ecs_iter_fini(&qit); } } flecs_stage_set_system(stage, old_system); if (measure_time) { system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); } flecs_defer_end(world, stage); return it->interrupted_by; } /* -- Public API -- */ ecs_entity_t ecs_run_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 = flecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_run_intern( world, stage, system, system_data, stage_index, stage_count, delta_time, param); } ecs_entity_t ecs_run( ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, void *param) { ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = flecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_run_intern( world, stage, system, system_data, 0, 0, delta_time, param); } /* System deinitialization */ static void flecs_system_fini(ecs_system_t *sys) { if (sys->ctx_free) { sys->ctx_free(sys->ctx); } if (sys->callback_ctx_free) { sys->callback_ctx_free(sys->callback_ctx); } if (sys->run_ctx_free) { sys->run_ctx_free(sys->run_ctx); } flecs_poly_free(sys, ecs_system_t); } /* ecs_poly_dtor_t-compatible wrapper */ static void flecs_system_poly_fini(void *sys) { flecs_system_fini(sys); } static void flecs_system_init_timer( ecs_world_t *world, ecs_entity_t entity, const ecs_system_desc_t *desc) { if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || ECS_NEQZERO(desc->tick_source)) { #ifdef FLECS_TIMER if (ECS_NEQZERO(desc->interval)) { ecs_set_interval(world, entity, desc->interval); } if (desc->rate) { ecs_entity_t tick_source = desc->tick_source; if (!tick_source) { tick_source = entity; } ecs_set_rate(world, entity, desc->rate, tick_source); } else if (desc->tick_source) { ecs_set_tick_source(world, entity, desc->tick_source); } #else (void)world; (void)entity; ecs_abort(ECS_UNSUPPORTED, "timer module not available"); #endif } } ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_system_desc_t was not initialized to zero"); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t entity = desc->entity; if (!entity) { entity = ecs_entity(world, {0}); } EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); if (!poly->poly) { ecs_system_t *system = flecs_poly_new(ecs_system_t); ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); poly->poly = system; system->world = world; system->dtor = flecs_system_poly_fini; system->entity = entity; ecs_query_desc_t query_desc = desc->query; query_desc.entity = entity; ecs_query_t *query = ecs_query_init(world, &query_desc); if (!query) { ecs_delete(world, entity); return 0; } /* Prevent the system from moving while we're initializing */ flecs_defer_begin(world, world->stages[0]); system->query = query; system->query_entity = query->entity; system->run = desc->run; system->action = desc->callback; system->ctx = desc->ctx; system->callback_ctx = desc->callback_ctx; system->run_ctx = desc->run_ctx; system->ctx_free = desc->ctx_free; system->callback_ctx_free = desc->callback_ctx_free; system->run_ctx_free = desc->run_ctx_free; system->tick_source = desc->tick_source; system->multi_threaded = desc->multi_threaded; system->immediate = desc->immediate; flecs_system_init_timer(world, entity, desc); if (ecs_get_name(world, entity)) { ecs_trace("#[green]system#[reset] %s created", ecs_get_name(world, entity)); } ecs_defer_end(world); } else { flecs_poly_assert(poly->poly, ecs_system_t); ecs_system_t *system = (ecs_system_t*)poly->poly; if (system->ctx_free) { if (system->ctx && system->ctx != desc->ctx) { system->ctx_free(system->ctx); } } if (system->callback_ctx_free) { if (system->callback_ctx && system->callback_ctx != desc->callback_ctx) { system->callback_ctx_free(system->callback_ctx); system->callback_ctx_free = NULL; system->callback_ctx = NULL; } } if (system->run_ctx_free) { if (system->run_ctx && system->run_ctx != desc->run_ctx) { system->run_ctx_free(system->run_ctx); system->run_ctx_free = NULL; system->run_ctx = NULL; } } if (desc->run) { system->run = desc->run; if (!desc->callback) { system->action = NULL; } } if (desc->callback) { system->action = desc->callback; if (!desc->run) { system->run = NULL; } } if (desc->ctx) { system->ctx = desc->ctx; } if (desc->callback_ctx) { system->callback_ctx = desc->callback_ctx; } if (desc->run_ctx) { system->run_ctx = desc->run_ctx; } if (desc->ctx_free) { system->ctx_free = desc->ctx_free; } if (desc->callback_ctx_free) { system->callback_ctx_free = desc->callback_ctx_free; } if (desc->run_ctx_free) { system->run_ctx_free = desc->run_ctx_free; } if (desc->query.flags & EcsQueryIsInstanced) { ECS_BIT_SET(system->query->flags, EcsQueryIsInstanced); } if (desc->multi_threaded) { system->multi_threaded = desc->multi_threaded; } if (desc->immediate) { system->immediate = desc->immediate; } flecs_system_init_timer(world, entity, desc); } flecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } const ecs_system_t* ecs_system_get( const ecs_world_t *world, ecs_entity_t entity) { return flecs_poly_get(world, entity, ecs_system_t); } void FlecsSystemImport( ecs_world_t *world) { ECS_MODULE(world, FlecsSystem); #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, ecs_id(FlecsSystem), "Module that implements Flecs systems"); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_tag(world, EcsSystem); flecs_bootstrap_component(world, EcsTickSource); /* Make sure to never inherit system component. This makes sure that any * term created for the System component will default to 'self' traversal, * which improves efficiency of the query. */ ecs_add_pair(world, EcsSystem, EcsOnInstantiate, EcsDontInherit); } #endif /** * @file query/compiler/compile.c * @brief Compile query program from query. */ static bool flecs_query_var_is_anonymous( const ecs_query_impl_t *query, ecs_var_id_t var_id) { ecs_query_var_t *var = &query->vars[var_id]; return var->anonymous; } ecs_var_id_t flecs_query_add_var( ecs_query_impl_t *query, const char *name, ecs_vec_t *vars, ecs_var_kind_t kind) { const char *dot = NULL; if (name) { dot = strchr(name, '.'); if (dot) { kind = EcsVarEntity; /* lookup variables are always entities */ } } ecs_hashmap_t *var_index = NULL; ecs_var_id_t var_id = EcsVarNone; if (name) { if (kind == EcsVarAny) { var_id = flecs_query_find_var_id(query, name, EcsVarEntity); if (var_id != EcsVarNone) { return var_id; } var_id = flecs_query_find_var_id(query, name, EcsVarTable); if (var_id != EcsVarNone) { return var_id; } kind = EcsVarTable; } else { var_id = flecs_query_find_var_id(query, name, kind); if (var_id != EcsVarNone) { return var_id; } } if (kind == EcsVarTable) { var_index = &query->tvar_index; } else { var_index = &query->evar_index; } /* If we're creating an entity var, check if it has a table variant */ if (kind == EcsVarEntity && var_id == EcsVarNone) { var_id = flecs_query_find_var_id(query, name, EcsVarTable); } } ecs_query_var_t *var; ecs_var_id_t result; if (vars) { var = ecs_vec_append_t(NULL, vars, ecs_query_var_t); result = var->id = flecs_itovar(ecs_vec_count(vars)); } else { ecs_dbg_assert(query->var_count < query->var_size, ECS_INTERNAL_ERROR, NULL); var = &query->vars[query->var_count]; result = var->id = flecs_itovar(query->var_count); query->var_count ++; } var->kind = flecs_ito(int8_t, kind); var->name = name; var->table_id = var_id; var->base_id = 0; var->lookup = NULL; flecs_set_var_label(var, NULL); if (name) { flecs_name_index_init_if(var_index, NULL); flecs_name_index_ensure(var_index, var->id, name, 0, 0); var->anonymous = name[0] == '_'; /* Handle variables that require a by-name lookup, e.g. $this.wheel */ if (dot != NULL) { ecs_assert(var->table_id == EcsVarNone, ECS_INTERNAL_ERROR, NULL); var->lookup = dot + 1; } } return result; } static ecs_var_id_t flecs_query_add_var_for_term_id( ecs_query_impl_t *query, ecs_term_ref_t *term_id, ecs_vec_t *vars, ecs_var_kind_t kind) { const char *name = flecs_term_ref_var_name(term_id); if (!name) { return EcsVarNone; } return flecs_query_add_var(query, name, vars, kind); } /* This function walks over terms to discover which variables are used in the * query. It needs to provide the following functionality: * - create table vars for all variables used as source * - create entity vars for all variables not used as source * - create entity vars for all non-$this vars * - create anonymous vars to store the content of wildcards * - create anonymous vars to store result of lookups (for $var.child_name) * - create anonymous vars for resolving component inheritance * - create array that stores the source variable for each field * - ensure table vars for non-$this variables are anonymous * - ensure variables created inside scopes are anonymous * - place anonymous variables after public variables in vars array */ static int flecs_query_discover_vars( ecs_stage_t *stage, ecs_query_impl_t *query) { ecs_vec_t *vars = &stage->variables; /* Buffer to reduce allocs */ ecs_vec_reset_t(NULL, vars, ecs_query_var_t); ecs_term_t *terms = query->pub.terms; int32_t a, i, anonymous_count = 0, count = query->pub.term_count; int32_t anonymous_table_count = 0, scope = 0, scoped_var_index = 0; bool table_this = false, entity_before_table_this = false; /* For This table lookups during discovery. This will be overwritten after * discovery with whether the query actually has a This table variable. */ query->pub.flags |= EcsQueryHasTableThisVar; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *second = &term->second; ecs_term_ref_t *src = &term->src; if (ECS_TERM_REF_ID(first) == EcsScopeOpen) { /* Keep track of which variables are first used in scope, so that we * can mark them as anonymous. Terms inside a scope are collapsed * into a single result, which means that outside of the scope the * value of those variables is undefined. */ if (!scope) { scoped_var_index = ecs_vec_count(vars); } scope ++; continue; } else if (ECS_TERM_REF_ID(first) == EcsScopeClose) { if (!--scope) { /* Any new variables declared after entering a scope should be * marked as anonymous. */ int32_t v; for (v = scoped_var_index; v < ecs_vec_count(vars); v ++) { ecs_vec_get_t(vars, ecs_query_var_t, v)->anonymous = true; } } continue; } ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( query, first, vars, EcsVarEntity); if (first_var_id == EcsVarNone) { /* If first is not a variable, check if we need to insert anonymous * variable for resolving component inheritance */ if (term->flags_ & EcsTermIdInherited) { anonymous_count += 2; /* table & entity variable */ } /* If first is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(first)) { anonymous_count ++; } } if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) != EcsThis)) { const char *var_name = flecs_term_ref_var_name(src); if (var_name) { ecs_var_id_t var_id = flecs_query_find_var_id( query, var_name, EcsVarEntity); if (var_id == EcsVarNone || var_id == first_var_id) { var_id = flecs_query_add_var( query, var_name, vars, EcsVarEntity); } if (var_id != EcsVarNone) { /* Mark variable as one for which we need to create a table * variable. Don't create table variable now, so that we can * store it in the non-public part of the variable array. */ ecs_query_var_t *var = ecs_vec_get_t( vars, ecs_query_var_t, (int32_t)var_id - 1); ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); if (!var->lookup) { var->kind = EcsVarAny; anonymous_table_count ++; } if (!(term->flags_ & EcsTermNoData)) { /* Can't have an anonymous variable as source of a term * that returns a component. We need to return each * instance of the component, whereas anonymous * variables are not guaranteed to be resolved to * individual entities. */ if (var->anonymous) { ecs_err( "can't use anonymous variable '%s' as source of " "data term", var->name); goto error; } } /* Track which variable ids are used as field source */ if (!query->src_vars) { query->src_vars = flecs_calloc_n(&stage->allocator, ecs_var_id_t, query->pub.field_count); } query->src_vars[term->field_index] = var_id; } } else { if (flecs_term_ref_is_wildcard(src)) { anonymous_count ++; } } } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); } if (term->flags_ & EcsTermIsSparse) { /* If this is a sparse $this term entities have to be returned * one by one. */ flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); /* Track if query contains $this sparse terms. Queries with * sparse $this fields need to return results one by one. */ if ((ECS_TERM_REF_ID(&term->src) == EcsThis) && ((term->src.id & (EcsSelf|EcsIsVariable)) == (EcsSelf|EcsIsVariable))) { query->pub.flags |= EcsQueryHasSparseThis; } } } if (flecs_query_add_var_for_term_id( query, second, vars, EcsVarEntity) == EcsVarNone) { /* If second is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(second)) { anonymous_count ++; } } if (src->id & EcsIsVariable && second->id & EcsIsVariable) { if (term->flags_ & EcsTermTransitive) { /* Anonymous variable to store temporary id for finding * targets for transitive relationship, see compile_term. */ anonymous_count ++; } } /* If member term, make sure source is available as entity */ if (term->flags_ & EcsTermIsMember) { flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); } /* Track if a This entity variable is used before a potential This table * variable. If this happens, the query has no This table variable */ if (ECS_TERM_REF_ID(src) == EcsThis) { table_this = true; } if (ECS_TERM_REF_ID(first) == EcsThis || ECS_TERM_REF_ID(second) == EcsThis) { if (!table_this) { entity_before_table_this = true; } } } int32_t var_count = ecs_vec_count(vars); ecs_var_id_t placeholder = EcsVarNone - 1; bool replace_placeholders = false; /* Ensure lookup variables have table and/or entity variables */ for (i = 0; i < var_count; i ++) { ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); if (var->lookup) { char *var_name = ecs_os_strdup(var->name); var_name[var->lookup - var->name - 1] = '\0'; ecs_var_id_t base_table_id = flecs_query_find_var_id( query, var_name, EcsVarTable); if (base_table_id != EcsVarNone) { var->table_id = base_table_id; } else if (anonymous_table_count) { /* Scan for implicit anonymous table variables that haven't been * inserted yet (happens after this step). Doing this here vs. * ensures that anonymous variables are appended at the end of * the variable array, while also ensuring that variable ids are * stable (no swapping of table var ids that are in use). */ for (a = 0; a < var_count; a ++) { ecs_query_var_t *avar = ecs_vec_get_t( vars, ecs_query_var_t, a); if (avar->kind == EcsVarAny) { if (!ecs_os_strcmp(avar->name, var_name)) { base_table_id = (ecs_var_id_t)(a + 1); break; } } } if (base_table_id != EcsVarNone) { /* Set marker so we can set the new table id afterwards */ var->table_id = placeholder; replace_placeholders = true; } } ecs_var_id_t base_entity_id = flecs_query_find_var_id( query, var_name, EcsVarEntity); if (base_entity_id == EcsVarNone) { /* Get name from table var (must exist). We can't use allocated * name since variables don't own names. */ const char *base_name = NULL; if (base_table_id != EcsVarNone && base_table_id) { ecs_query_var_t *base_table_var = ecs_vec_get_t( vars, ecs_query_var_t, (int32_t)base_table_id - 1); base_name = base_table_var->name; } else { base_name = EcsThisName; } base_entity_id = flecs_query_add_var( query, base_name, vars, EcsVarEntity); var = ecs_vec_get_t(vars, ecs_query_var_t, i); } var->base_id = base_entity_id; ecs_os_free(var_name); } } var_count = ecs_vec_count(vars); /* Add non-This table variables */ if (anonymous_table_count) { anonymous_table_count = 0; for (i = 0; i < var_count; i ++) { ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); if (var->kind == EcsVarAny) { var->kind = EcsVarEntity; ecs_var_id_t var_id = flecs_query_add_var( query, var->name, vars, EcsVarTable); ecs_vec_get_t(vars, ecs_query_var_t, i)->table_id = var_id; anonymous_table_count ++; } } var_count = ecs_vec_count(vars); } /* If any forward references to newly added anonymous tables exist, replace * them with the actual table variable ids. */ if (replace_placeholders) { for (i = 0; i < var_count; i ++) { ecs_query_var_t *var = ecs_vec_get_t(vars, ecs_query_var_t, i); if (var->table_id == placeholder) { char *var_name = ecs_os_strdup(var->name); var_name[var->lookup - var->name - 1] = '\0'; var->table_id = flecs_query_find_var_id( query, var_name, EcsVarTable); ecs_assert(var->table_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); ecs_os_free(var_name); } } } /* Always include spot for This variable, even if query doesn't use it */ var_count ++; ecs_query_var_t *query_vars = &query->vars_cache.var; if ((var_count + anonymous_count) > 1) { query_vars = flecs_alloc(&stage->allocator, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * (var_count + anonymous_count)); } query->vars = query_vars; query->var_count = var_count; query->pub.var_count = flecs_ito(int16_t, var_count); ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, !entity_before_table_this); query->var_size = var_count + anonymous_count; char **var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), var_count + anonymous_count); query->pub.vars = (char**)var_names; query_vars[0].kind = EcsVarTable; query_vars[0].name = NULL; flecs_set_var_label(&query_vars[0], NULL); query_vars[0].id = 0; query_vars[0].table_id = EcsVarNone; query_vars[0].lookup = NULL; var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); query_vars ++; var_names ++; var_count --; if (var_count) { ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); for (i = 0; i < var_count; i ++) { var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); } } /* Hide anonymous table variables from application */ query->pub.var_count = flecs_ito(int16_t, query->pub.var_count - anonymous_table_count); /* Sanity check to make sure that the public part of the variable array only * contains entity variables. */ #ifdef FLECS_DEBUG for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } #endif return 0; error: return -1; } static bool flecs_query_var_is_unknown( ecs_query_impl_t *query, ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx) { ecs_query_var_t *vars = query->vars; if (ctx->written & (1ull << var_id)) { return false; } else { ecs_var_id_t table_var = vars[var_id].table_id; if (table_var != EcsVarNone) { return flecs_query_var_is_unknown(query, table_var, ctx); } } return true; } /* Returns whether term is unknown. A term is unknown when it has variable * elements (first, second, src) that are all unknown. */ static bool flecs_query_term_is_unknown( ecs_query_impl_t *query, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t dummy = {0}; flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); bool has_vars = dummy.flags & ((EcsQueryIsVar << EcsQueryFirst) | (EcsQueryIsVar << EcsQuerySecond) | (EcsQueryIsVar << EcsQuerySrc)); if (!has_vars) { /* If term has no variables (typically terms with a static src) there * can't be anything that's unknown. */ return false; } if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { return false; } } if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { return false; } } if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { return false; } } return true; } /* Find the next known term from specified offset. This function is used to find * a term that can be evaluated before a term that is unknown. Evaluating known * before unknown terms can significantly decrease the search space. */ static int32_t flecs_query_term_next_known( ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, int32_t offset, ecs_flags64_t compiled) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = offset; i < count; i ++) { ecs_term_t *term = &terms[i]; if (compiled & (1ull << i)) { continue; } /* Only evaluate And terms */ if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ continue; } /* Don't reorder terms in scopes */ if (term->flags_ & EcsTermIsScope) { continue; } if (flecs_query_term_is_unknown(query, term, ctx)) { continue; } return i; } return -1; } /* If the first part of a query contains more than one trivial term, insert a * special instruction which batch-evaluates multiple terms. */ static void flecs_query_insert_trivial_search( ecs_query_impl_t *query, ecs_flags64_t *compiled, ecs_flags64_t *populated, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; int32_t i, term_count = q->term_count; ecs_flags64_t trivial_set = 0; /* Trivial search always ignores prefabs and disabled entities */ if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { return; } /* Find trivial terms, which can be handled in single instruction */ int32_t trivial_wildcard_terms = 0; int32_t trivial_data_terms = 0; int32_t trivial_terms = 0; bool populate = true; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->flags_ & (EcsTermIsToggle|EcsTermIsMember|EcsTermIsSparse)) { /* If query returns individual entities, let dedicated populate * instruction handle populating data fields */ populate = false; break; } } for (i = 0; i < term_count; i ++) { /* Term is already compiled */ if (*compiled & (1ull << i)) { continue; } ecs_term_t *term = &terms[i]; if (!(term->flags_ & EcsTermIsTrivial)) { continue; } /* We can only add trivial terms to plan if they no up traversal */ if ((term->src.id & EcsTraverseFlags) != EcsSelf) { continue; } trivial_set |= (1llu << i); if (ecs_id_is_wildcard(term->id)) { trivial_wildcard_terms ++; } if (populate) { if (q->data_fields & (1llu << term->field_index)) { trivial_data_terms ++; } } trivial_terms ++; } if (trivial_terms >= 2) { /* Mark terms as compiled & populated */ for (i = 0; i < q->term_count; i ++) { if (trivial_set & (1llu << i)) { *compiled |= (1ull << i); if (populate) { *populated |= (1ull << terms[i].field_index); } } } /* If there's more than 1 trivial term, batch them in trivial search */ ecs_query_op_t trivial = {0}; if (trivial_wildcard_terms) { trivial.kind = EcsQueryTrivWildcard; } else { if (trivial_data_terms) { trivial.kind = EcsQueryTrivData; } if (!trivial.kind) { trivial.kind = EcsQueryTriv; } } /* Store the bitset with trivial terms on the instruction */ trivial.src.entity = trivial_set; flecs_query_op_insert(&trivial, ctx); /* Mark $this as written */ ctx->written |= (1llu << 0); } } static void flecs_query_insert_cache_search( ecs_query_impl_t *query, ecs_flags64_t *compiled, ecs_flags64_t *populated, ecs_query_compile_ctx_t *ctx) { if (!query->cache) { return; } ecs_query_t *q = &query->pub; bool populate = true; int32_t populate_count = 0; if (q->cache_kind == EcsQueryCacheAll) { /* If all terms are cacheable, make sure no other terms are compiled */ *compiled = 0xFFFFFFFFFFFFFFFF; /* If query has a sparse $this field, fields can't be populated by the cache * instruction. The reason for this is that sparse $this fields have to be * returned one at a time. This means that the same needs to happen for * cached fields, and the cache instruction only returns entire arrays. */ if (!(query->pub.flags & EcsQueryHasSparseThis)) { *populated = 0xFFFFFFFFFFFFFFFF; populate_count = query->pub.field_count; } } else if (q->cache_kind == EcsQueryCacheAuto) { /* The query is partially cacheable */ ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (term->flags_ & (EcsTermIsToggle|EcsTermIsMember|EcsTermIsSparse)) { /* If query returns individual entities, let dedicated populate * instruction handle populating data fields */ populate = false; break; } } for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; int16_t field = term->field_index; if ((*compiled) & (1ull << i)) { continue; } if (!(term->flags_ & EcsTermIsCacheable)) { continue; } *compiled |= (1ull << i); if (populate) { *populated |= (1ull << field); populate_count ++; } } } /* Insert the operation for cache traversal */ ecs_query_op_t op = {0}; if (!populate_count || (q->flags & EcsQueryNoData)) { if (q->flags & EcsQueryIsCacheable) { op.kind = EcsQueryIsCache; } else { op.kind = EcsQueryCache; } } else { if (q->flags & EcsQueryIsCacheable) { op.kind = EcsQueryIsCacheData; } else { op.kind = EcsQueryCacheData; } } flecs_query_write(0, &op.written); flecs_query_write_ctx(0, ctx, false); flecs_query_op_insert(&op, ctx); } static bool flecs_term_ref_match_multiple( ecs_term_ref_t *ref) { return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); } static bool flecs_term_match_multiple( ecs_term_t *term) { return flecs_term_ref_match_multiple(&term->first) || flecs_term_ref_match_multiple(&term->second); } /* Insert instruction to populate data fields. */ void flecs_query_insert_populate( ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, ecs_flags64_t populated) { ecs_query_t *q = &query->pub; int32_t i, term_count = q->term_count; if (populated && !(q->flags & EcsQueryNoData)) { ecs_flags64_t sparse_fields = 0; int32_t populate_count = 0; int32_t self_count = 0; /* Figure out which populate instruction to use */ for (i = 0; i < term_count; i ++) { ecs_term_t *term = &q->terms[i]; int16_t field = term->field_index; ecs_flags64_t field_bit = (1ull << field); if (!(populated & field_bit)) { /* Only check fields that need to be populated */ continue; } populate_count ++; /* Callee is asking us to populate a term without data */ if (ecs_term_match_this(term) && !(term->src.id & EcsUp)) { self_count ++; } /* Track sparse fields as they require a separate instruction */ if (term->flags_ & EcsTermIsSparse) { sparse_fields |= field_bit; } } ecs_assert(populate_count != 0, ECS_INTERNAL_ERROR, NULL); /* Mask out the sparse fields as they are populated separately. */ populated &= ~sparse_fields; if (populated) { ecs_query_op_kind_t kind = EcsQueryPopulate; if (populate_count == self_count) { kind = EcsQueryPopulateSelf; } ecs_query_op_t op = {0}; op.kind = flecs_ito(uint8_t, kind); op.src.entity = populated; /* Abuse for bitset w/fields to populate */ flecs_query_op_insert(&op, ctx); } if (sparse_fields) { ecs_query_op_t op = {0}; op.kind = EcsQueryPopulateSparse; op.src.entity = sparse_fields; /* Abuse for bitset w/fields to populate */ flecs_query_op_insert(&op, ctx); } } } static int flecs_query_insert_toggle( ecs_query_impl_t *impl, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &impl->pub; int32_t i, j, term_count = q->term_count; ecs_term_t *terms = q->terms; ecs_flags64_t fields_done = 0; for (i = 0; i < term_count; i ++) { if (fields_done & (1llu << i)) { continue; } ecs_term_t *term = &terms[i]; if (term->flags_ & EcsTermIsToggle) { ecs_query_op_t cur = {0}; flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); ecs_flags64_t and_toggles = 0; ecs_flags64_t not_toggles = 0; ecs_flags64_t optional_toggles = 0; for (j = i; j < term_count; j ++) { if (fields_done & (1llu << j)) { continue; } /* Also includes term[i], so flags get set correctly */ term = &terms[j]; /* If term is not for the same src, skip */ ecs_query_op_t next = {0}; flecs_query_compile_term_ref(NULL, impl, &next, &term->src, &next.src, EcsQuerySrc, EcsVarAny, ctx, false); if (next.src.entity != cur.src.entity || next.flags != cur.flags) { continue; } /* Source matches, set flag */ if (term->oper == EcsNot) { not_toggles |= (1llu << j); } else if (term->oper == EcsOptional) { optional_toggles |= (1llu << j); } else { and_toggles |= (1llu << j); } fields_done |= (1llu << j); } if (and_toggles || not_toggles) { ecs_query_op_t op = {0}; op.kind = EcsQueryToggle; op.src = cur.src; op.flags = cur.flags; if (op.flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_write(op.src.var, &op.written); } /* Encode fields: * - first.entity is the fields that match enabled bits * - second.entity is the fields that match disabled bits */ op.first.entity = and_toggles; op.second.entity = not_toggles; flecs_query_op_insert(&op, ctx); } /* Insert separate instructions for optional terms. To make sure * entities are returned in batches where fields are never partially * set or unset, the result must be split up into batches that have * the exact same toggle masks. Instead of complicating the toggle * instruction with code to scan for blocks that have the same bits * set, separate instructions let the query engine backtrack to get * the right results. */ if (optional_toggles) { for (j = i; j < term_count; j ++) { uint64_t field_bit = 1ull << j; if (!(optional_toggles & field_bit)) { continue; } ecs_query_op_t op = {0}; op.kind = EcsQueryToggleOption; op.src = cur.src; op.first.entity = field_bit; op.flags = cur.flags; flecs_query_op_insert(&op, ctx); } } } } return 0; } static int flecs_query_insert_fixed_src_terms( ecs_world_t *world, ecs_query_impl_t *impl, ecs_flags64_t *compiled, ecs_flags64_t *populated_out, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &impl->pub; int32_t i, term_count = q->term_count; ecs_term_t *terms = q->terms; ecs_flags64_t populated = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper == EcsNot) { /* If term has not operator and variables for first/second, we can't * put the term first as this could prevent us from getting back * valid results. For example: * !$var(e), Tag($var) * * Here, the first term would evaluate to false (and cause the * entire query not to match) if 'e' has any components. * * However, when reordering we get results: * Tag($var), !$var(e) * * Now the query returns all entities with Tag, that 'e' does not * have as component. For this reason, queries should never use * unwritten variables in not terms- and we should also not reorder * terms in a way that results in doing this. */ if (flecs_term_match_multiple(term)) { continue; } } /* Don't reorder terms in scopes */ if (term->flags_ & EcsTermIsScope) { continue; } if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { if (flecs_query_compile_term(world, impl, term, populated_out, ctx)) { return -1; } *compiled |= (1llu << i); /* If this is a data field, track it. This will let us insert an * instruction specifically for populating data fields of terms with * fixed source (see below). */ if (q->data_fields & (1llu << term->field_index)) { populated |= (1llu << term->field_index); } } } if (populated) { /* If data fields with a fixed source were evaluated, insert a populate * instruction that just populates those fields. The advantage of doing * this before the rest of the query is evaluated, is that we only * populate data for static fields once vs. for each returned result of * the query, which would be wasteful since this data doesn't change. */ flecs_query_insert_populate(impl, ctx, populated); } *populated_out |= populated; return 0; } int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; ecs_query_compile_ctx_t ctx = {0}; ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); ctx.ops = &stage->operations; ctx.cur = ctx.ctrlflow; ctx.cur->lbl_begin = -1; ctx.cur->lbl_begin = -1; ecs_vec_clear(ctx.ops); /* Find all variables defined in query */ if (flecs_query_discover_vars(stage, query)) { return -1; } /* If query contains fixed source terms, insert operation to set sources */ int32_t i, term_count = q->term_count; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->src.id & EcsIsEntity) { ecs_query_op_t set_fixed = {0}; set_fixed.kind = EcsQuerySetFixed; flecs_query_op_insert(&set_fixed, &ctx); break; } } /* If the query contains terms with fixed ids (no wildcards, variables), * insert instruction that initializes ecs_iter_t::ids. This allows for the * insertion of simpler instructions later on. * If the query is entirely cacheable, ids are populated by the cache. */ if (q->cache_kind != EcsQueryCacheAll) { for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (flecs_term_is_fixed_id(q, term) || (term->src.id & EcsIsEntity && !(term->src.id & ~EcsTermRefFlags))) { ecs_query_op_t set_ids = {0}; set_ids.kind = EcsQuerySetIds; flecs_query_op_insert(&set_ids, &ctx); break; } } } ecs_flags64_t compiled = 0; ecs_flags64_t populated = 0; /* Always evaluate terms with fixed source before other terms */ flecs_query_insert_fixed_src_terms( world, query, &compiled, &populated, &ctx); /* Compile cacheable terms */ flecs_query_insert_cache_search( query, &compiled, &populated, &ctx); /* Insert trivial term search if query allows for it */ flecs_query_insert_trivial_search( query, &compiled, &populated, &ctx); /* If a query starts with one or more optional terms, first compile the non * optional terms. This prevents having to insert an instruction that * matches the query against every entity in the storage. * Only skip optional terms at the start of the query so that any * short-circuiting behavior isn't affected (a non-optional term can become * optional if it uses a variable set in an optional term). */ int32_t start_term = 0; for (; start_term < term_count; start_term ++) { if (terms[start_term].oper != EcsOptional) { break; } } do { /* Compile remaining query terms to instructions */ for (i = start_term; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int32_t compile = i; if (compiled & (1ull << i)) { continue; /* Already compiled */ } if (term->oper == EcsOptional && start_term) { /* Don't reorder past the first optional term that's not in the * initial list of optional terms. This protects short * circuiting branching in the query. * A future algorithm could look at which variables are * accessed by optional terms, and continue reordering terms * that don't access those variables. */ break; } bool can_reorder = true; if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ can_reorder = false; } /* If variables have been written, but this term has no known variables, * first try to resolve terms that have known variables. This can * significantly reduce the search space. * Only perform this optimization after at least one variable has been * written to, as all terms are unknown otherwise. */ if (can_reorder && ctx.written && flecs_query_term_is_unknown(query, term, &ctx)) { int32_t term_index = flecs_query_term_next_known( query, &ctx, i + 1, compiled); if (term_index != -1) { term = &q->terms[term_index]; compile = term_index; i --; /* Repeat current term */ } } if (flecs_query_compile_term(world, query, term, &populated, &ctx)) { return -1; } compiled |= (1ull << compile); } if (start_term) { start_term = 0; /* Repeat, now also insert optional terms */ } else { break; } } while (true); ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); if (this_id != EcsVarNone) { /* If query has a $this term with a sparse component value and so far * we've only accessed $this as table, we need to insert an each * instruction. This is because we can't return multiple sparse * component instances in a single query result. */ if ((query->pub.flags & EcsQueryHasSparseThis) && !(ctx.written & (1ull << this_id))) { flecs_query_insert_each(0, this_id, &ctx, false); } /* If This variable has been written as entity, insert an operation to * assign it to it.entities for consistency. */ if (ctx.written & (1ull << this_id)) { ecs_query_op_t set_this = {0}; set_this.kind = EcsQuerySetThis; set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); set_this.first.var = this_id; flecs_query_op_insert(&set_this, &ctx); } } /* Make sure non-This variables are written as entities */ if (query->vars) { for (i = 0; i < query->var_count; i ++) { ecs_query_var_t *var = &query->vars[i]; if (var->id && var->kind == EcsVarTable && var->name) { ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, EcsVarEntity); if (!flecs_query_is_written(var_id, ctx.written)) { /* Skip anonymous variables */ if (!flecs_query_var_is_anonymous(query, var_id)) { flecs_query_insert_each(var->id, var_id, &ctx, false); } } } } } /* If query contains non-This variables as term source, build lookup array */ if (query->src_vars) { ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); bool only_anonymous = true; for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; if (!var_id) { continue; } if (!flecs_query_var_is_anonymous(query, var_id)) { only_anonymous = false; break; } else { /* Don't fetch component data for anonymous variables. Because * not all metadata (such as it.sources) is initialized for * anonymous variables, and because they may only be available * as table variables (each is not guaranteed to be inserted for * anonymous variables) the iterator may not have sufficient * information to resolve component data. */ for (int32_t t = 0; t < q->term_count; t ++) { ecs_term_t *term = &q->terms[t]; if (term->field_index == i) { term->inout = EcsInOutNone; } } } } /* Don't insert setvar instruction if all vars are anonymous */ if (!only_anonymous) { ecs_query_op_t set_vars = {0}; set_vars.kind = EcsQuerySetVars; flecs_query_op_insert(&set_vars, &ctx); } for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; if (!var_id) { continue; } if (query->vars[var_id].kind == EcsVarTable) { var_id = flecs_query_find_var_id(query, query->vars[var_id].name, EcsVarEntity); /* Variables used as source that aren't This must be entities */ ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } query->src_vars[i] = var_id; } } ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); /* If query is empty, insert Nothing instruction */ if (!(term_count - ctx.skipped)) { ecs_vec_clear(ctx.ops); ecs_query_op_t nothing = {0}; nothing.kind = EcsQueryNothing; flecs_query_op_insert(¬hing, &ctx); } else { /* If query contains terms for toggleable components, insert toggle */ if (!(q->flags & EcsQueryTableOnly)) { flecs_query_insert_toggle(query, &ctx); } /* Insert instruction to populate remaining data fields */ ecs_flags64_t remaining = q->data_fields & ~populated; flecs_query_insert_populate(query, &ctx, remaining); /* Insert yield. If program reaches this operation, a result was found */ ecs_query_op_t yield = {0}; yield.kind = EcsQueryYield; flecs_query_op_insert(&yield, &ctx); } int32_t op_count = ecs_vec_count(ctx.ops); if (op_count) { query->op_count = op_count; query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); } return 0; } /** * @file query/compiler/compiler_term.c * @brief Compile query term. */ #define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ ecs_var_id_t flecs_query_find_var_id( const ecs_query_impl_t *query, const char *name, ecs_var_kind_t kind) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (kind == EcsVarTable) { if (!ecs_os_strcmp(name, EcsThisName)) { if (query->pub.flags & EcsQueryHasTableThisVar) { return 0; } else { return EcsVarNone; } } if (!flecs_name_index_is_init(&query->tvar_index)) { return EcsVarNone; } uint64_t index = flecs_name_index_find( &query->tvar_index, name, 0, 0); if (index == 0) { return EcsVarNone; } return flecs_utovar(index); } if (kind == EcsVarEntity) { if (!flecs_name_index_is_init(&query->evar_index)) { return EcsVarNone; } uint64_t index = flecs_name_index_find( &query->evar_index, name, 0, 0); if (index == 0) { return EcsVarNone; } return flecs_utovar(index); } ecs_assert(kind == EcsVarAny, ECS_INTERNAL_ERROR, NULL); /* If searching for any kind of variable, start with most specific */ ecs_var_id_t index = flecs_query_find_var_id(query, name, EcsVarEntity); if (index != EcsVarNone) { return index; } return flecs_query_find_var_id(query, name, EcsVarTable); } static ecs_var_id_t flecs_query_most_specific_var( ecs_query_impl_t *query, const char *name, ecs_var_kind_t kind, ecs_query_compile_ctx_t *ctx) { if (kind == EcsVarTable || kind == EcsVarEntity) { return flecs_query_find_var_id(query, name, kind); } ecs_var_id_t evar = flecs_query_find_var_id(query, name, EcsVarEntity); if ((evar != EcsVarNone) && flecs_query_is_written(evar, ctx->written)) { /* If entity variable is available and written to, it contains the most * specific result and should be used. */ return evar; } ecs_var_id_t tvar = flecs_query_find_var_id(query, name, EcsVarTable); if ((tvar != EcsVarNone) && !flecs_query_is_written(tvar, ctx->written)) { /* If variable of any kind is requested and variable hasn't been written * yet, write to table variable */ return tvar; } /* If table var is written, and entity var doesn't exist or is not written, * return table var */ if (tvar != EcsVarNone) { return tvar; } else { return evar; } } ecs_query_lbl_t flecs_query_op_insert( ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *elem = ecs_vec_append_t(NULL, ctx->ops, ecs_query_op_t); int32_t count = ecs_vec_count(ctx->ops); *elem = *op; if (count > 1) { if (ctx->cur->lbl_begin == -1) { /* Variables written by previous instruction can't be written by * this instruction, except when this is part of an OR chain. */ elem->written &= ~elem[-1].written; } } elem->next = flecs_itolbl(count); elem->prev = flecs_itolbl(count - 2); return flecs_itolbl(count - 1); } ecs_query_op_t* flecs_query_begin_block( ecs_query_op_kind_t kind, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t op = {0}; op.kind = flecs_ito(uint8_t, kind); ctx->cur->lbl_begin = flecs_query_op_insert(&op, ctx); return ecs_vec_get_t(ctx->ops, ecs_query_op_t, ctx->cur->lbl_begin); } void flecs_query_end_block( ecs_query_compile_ctx_t *ctx, bool reset) { ecs_query_op_t new_op = {0}; new_op.kind = EcsQueryEnd; ecs_query_lbl_t end = flecs_query_op_insert(&new_op, ctx); ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); ops[ctx->cur->lbl_begin].next = end; ecs_query_op_t *end_op = &ops[end]; if (reset && ctx->cur->lbl_query != -1) { ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; end_op->prev = ctx->cur->lbl_begin; end_op->src = query_op->src; end_op->first = query_op->first; end_op->second = query_op->second; end_op->flags = query_op->flags; end_op->field_index = query_op->field_index; } else { end_op->prev = ctx->cur->lbl_begin; end_op->field_index = -1; } ctx->cur->lbl_begin = -1; } static void flecs_query_begin_block_cond_eval( ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx, ecs_write_flags_t cond_write_state) { ecs_var_id_t first_var = EcsVarNone, second_var = EcsVarNone, src_var = EcsVarNone; ecs_write_flags_t cond_mask = 0; if (flecs_query_ref_flags(op->flags, EcsQueryFirst) == EcsQueryIsVar) { first_var = op->first.var; ecs_assert(first_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << first_var); } if (flecs_query_ref_flags(op->flags, EcsQuerySecond) == EcsQueryIsVar) { second_var = op->second.var; ecs_assert(second_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << second_var); } if (flecs_query_ref_flags(op->flags, EcsQuerySrc) == EcsQueryIsVar) { src_var = op->src.var; ecs_assert(src_var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); cond_mask |= (1ull << src_var); } /* Variables set in an OR chain are marked as conditional writes. However, * writes from previous terms in the current OR chain shouldn't be treated * as variables that are conditionally set, so instead use the write mask * from before the chain started. */ if (ctx->ctrlflow->in_or) { cond_write_state = ctx->ctrlflow->cond_written_or; } /* If this term uses conditionally set variables, insert instruction that * jumps over the term if the variables weren't set yet. */ if (cond_mask & cond_write_state) { ctx->cur->lbl_cond_eval = flecs_itolbl(ecs_vec_count(ctx->ops)); ecs_query_op_t jmp_op = {0}; jmp_op.kind = EcsQueryIfVar; if ((first_var != EcsVarNone) && cond_write_state & (1ull << first_var)) { jmp_op.flags |= (EcsQueryIsVar << EcsQueryFirst); jmp_op.first.var = first_var; } if ((second_var != EcsVarNone) && cond_write_state & (1ull << second_var)) { jmp_op.flags |= (EcsQueryIsVar << EcsQuerySecond); jmp_op.second.var = second_var; } if ((src_var != EcsVarNone) && cond_write_state & (1ull << src_var)) { jmp_op.flags |= (EcsQueryIsVar << EcsQuerySrc); jmp_op.src.var = src_var; } flecs_query_op_insert(&jmp_op, ctx); } else { ctx->cur->lbl_cond_eval = -1; } } static void flecs_query_end_block_cond_eval( ecs_query_compile_ctx_t *ctx) { if (ctx->cur->lbl_cond_eval == -1) { return; } ecs_assert(ctx->cur->lbl_query >= 0, ECS_INTERNAL_ERROR, NULL); ecs_query_op_t end_op = {0}; end_op.kind = EcsQueryEnd; ecs_query_lbl_t end = flecs_query_op_insert(&end_op, ctx); ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); ops[ctx->cur->lbl_cond_eval].next = end; ecs_query_op_t *end_op_ptr = &ops[end]; ecs_query_op_t *query_op = &ops[ctx->cur->lbl_query]; end_op_ptr->prev = ctx->cur->lbl_cond_eval; end_op_ptr->src = query_op->src; end_op_ptr->first = query_op->first; end_op_ptr->second = query_op->second; end_op_ptr->flags = query_op->flags; end_op_ptr->field_index = query_op->field_index; } static void flecs_query_begin_block_or( ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *or_op = flecs_query_begin_block(EcsQueryNot, ctx); or_op->kind = EcsQueryOr; /* Set the source of the evaluate terms as source of the Or instruction. * This lets the engine determine whether the variable has already been * written. When the source is not yet written, an OR operation needs to * take the union of all the terms in the OR chain. When the variable is * known, it will return after the first matching term. * * In case a term in the OR expression is an equality predicate which * compares the left hand side with a variable, the variable acts as an * alias, so we can always assume that it's written. */ bool add_src = true; if (ECS_TERM_REF_ID(&term->first) == EcsPredEq && term->second.id & EcsIsVariable) { if (!(flecs_query_is_written(op->src.var, ctx->written))) { add_src = false; } } if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { if (add_src) { or_op->flags = (EcsQueryIsVar << EcsQuerySrc); or_op->src = op->src; ctx->cur->src_or = op->src; } ctx->cur->src_written_or = flecs_query_is_written( op->src.var, ctx->written); } } static void flecs_query_end_block_or( ecs_query_impl_t *impl, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t op = {0}; op.kind = EcsQueryEnd; ecs_query_lbl_t end = flecs_query_op_insert(&op, ctx); ecs_query_op_t *ops = ecs_vec_first_t(ctx->ops, ecs_query_op_t); int32_t i, prev_or = ctx->cur->lbl_begin + 1; for (i = ctx->cur->lbl_begin + 1; i < end; i ++) { if (ops[i].next == FlecsRuleOrMarker) { if (i == (end - 1)) { ops[prev_or].prev = ctx->cur->lbl_begin; } else { ops[prev_or].prev = flecs_itolbl(i + 1); } ops[i].next = flecs_itolbl(end); prev_or = i + 1; } } ecs_query_op_t *first = &ops[ctx->cur->lbl_begin]; bool src_is_var = first->flags & (EcsQueryIsVar << EcsQuerySrc); first->next = flecs_itolbl(end); ops[end].prev = ctx->cur->lbl_begin; ops[end - 1].prev = ctx->cur->lbl_begin; ctx->ctrlflow->in_or = false; ctx->cur->lbl_begin = -1; if (src_is_var) { ecs_var_id_t src_var = first->src.var; ctx->written |= (1llu << src_var); /* If src is a table variable, it is possible that this was resolved to * an entity variable in all of the OR terms. If this is the case, mark * entity variable as written as well. */ ecs_query_var_t *var = &impl->vars[src_var]; if (var->kind == EcsVarTable) { const char *name = var->name; if (!name) { name = "this"; } ecs_var_id_t evar = flecs_query_find_var_id( impl, name, EcsVarEntity); if (evar != EcsVarNone && (ctx->cond_written & (1llu << evar))) { ctx->written |= (1llu << evar); ctx->cond_written &= ~(1llu << evar); } } } ctx->written |= ctx->cond_written; /* Scan which variables were conditionally written in the OR chain and * reset instructions after the OR chain. If a variable is set in part one * of a chain but not part two, there would be nothing writing to the * variable in part two, leaving it to the previous value. To address this * a reset is inserted that resets the variable value on redo. */ for (i = 1; i < (8 * ECS_SIZEOF(ecs_write_flags_t)); i ++) { ecs_write_flags_t prev = 1 & (ctx->ctrlflow->cond_written_or >> i); ecs_write_flags_t cur = 1 & (ctx->cond_written >> i); /* Skip variable if it's the source for the OR chain */ if (src_is_var && (i == first->src.var)) { continue; } if (!prev && cur) { ecs_query_op_t reset_op = {0}; reset_op.kind = EcsQueryReset; reset_op.flags |= (EcsQueryIsVar << EcsQuerySrc); reset_op.src.var = flecs_itovar(i); flecs_query_op_insert(&reset_op, ctx); } } } void flecs_query_insert_each( ecs_var_id_t tvar, ecs_var_id_t evar, ecs_query_compile_ctx_t *ctx, bool cond_write) { ecs_query_op_t each = {0}; each.kind = EcsQueryEach; each.src.var = evar; each.first.var = tvar; each.flags = (EcsQueryIsVar << EcsQuerySrc) | (EcsQueryIsVar << EcsQueryFirst); flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &each.written); flecs_query_op_insert(&each, ctx); } static void flecs_query_insert_lookup( ecs_var_id_t base_var, ecs_var_id_t evar, ecs_query_compile_ctx_t *ctx, bool cond_write) { ecs_query_op_t lookup = {0}; lookup.kind = EcsQueryLookup; lookup.src.var = evar; lookup.first.var = base_var; lookup.flags = (EcsQueryIsVar << EcsQuerySrc) | (EcsQueryIsVar << EcsQueryFirst); flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &lookup.written); flecs_query_op_insert(&lookup, ctx); } static void flecs_query_insert_unconstrained_transitive( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx, bool cond_write) { /* Create anonymous variable to store the target ids. This will return the * list of targets without constraining the variable of the term, which * needs to stay variable to find all transitive relationships for a src. */ ecs_var_id_t tgt = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); flecs_set_var_label(&query->vars[tgt], query->vars[op->second.var].name); /* First, find ids to start traversal from. This fixes op.second. */ ecs_query_op_t find_ids = {0}; find_ids.kind = EcsQueryIdsRight; find_ids.field_index = -1; find_ids.first = op->first; find_ids.second = op->second; find_ids.flags = op->flags; find_ids.flags &= (ecs_flags8_t)~((EcsQueryIsVar|EcsQueryIsEntity) << EcsQuerySrc); find_ids.second.var = tgt; flecs_query_write_ctx(tgt, ctx, cond_write); flecs_query_write(tgt, &find_ids.written); flecs_query_op_insert(&find_ids, ctx); /* Next, iterate all tables for the ids. This fixes op.src */ ecs_query_op_t and_op = {0}; and_op.kind = EcsQueryAnd; and_op.field_index = op->field_index; and_op.first = op->first; and_op.second = op->second; and_op.src = op->src; and_op.flags = op->flags | EcsQueryIsSelf; and_op.second.var = tgt; flecs_query_write_ctx(and_op.src.var, ctx, cond_write); flecs_query_write(and_op.src.var, &and_op.written); flecs_query_op_insert(&and_op, ctx); } static void flecs_query_insert_inheritance( ecs_query_impl_t *query, ecs_term_t *term, ecs_query_op_t *op, ecs_query_compile_ctx_t *ctx, bool cond_write) { /* Anonymous variable to store the resolved component ids */ ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, ECS_TERM_REF_ID(&term->first))); flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, ECS_TERM_REF_ID(&term->first))); ecs_query_op_t trav_op = {0}; trav_op.kind = EcsQueryTrav; trav_op.field_index = -1; trav_op.first.entity = EcsIsA; trav_op.second.entity = ECS_TERM_REF_ID(&term->first); trav_op.src.var = tvar; trav_op.flags = EcsQueryIsSelf; trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); trav_op.written |= (1ull << tvar); if (term->first.id & EcsSelf) { trav_op.match_flags |= EcsTermReflexive; } flecs_query_op_insert(&trav_op, ctx); flecs_query_insert_each(tvar, evar, ctx, cond_write); ecs_query_ref_t r = { .var = evar }; op->first = r; op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); op->flags |= (EcsQueryIsVar << EcsQueryFirst); } void flecs_query_compile_term_ref( ecs_world_t *world, ecs_query_impl_t *query, ecs_query_op_t *op, ecs_term_ref_t *term_ref, ecs_query_ref_t *ref, ecs_flags8_t ref_kind, ecs_var_kind_t kind, ecs_query_compile_ctx_t *ctx, bool create_wildcard_vars) { (void)world; if (!ecs_term_ref_is_set(term_ref)) { return; } if (term_ref->id & EcsIsVariable) { op->flags |= (ecs_flags8_t)(EcsQueryIsVar << ref_kind); const char *name = flecs_term_ref_var_name(term_ref); if (name) { ref->var = flecs_query_most_specific_var(query, name, kind, ctx); ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } else if (create_wildcard_vars) { bool is_wildcard = flecs_term_ref_is_wildcard(term_ref); if (is_wildcard && (kind == EcsVarAny)) { ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarTable); } else { ref->var = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); } if (is_wildcard) { flecs_set_var_label(&query->vars[ref->var], ecs_get_name(world, ECS_TERM_REF_ID(term_ref))); } ecs_assert(ref->var != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } } if (term_ref->id & EcsIsEntity) { op->flags |= (ecs_flags8_t)(EcsQueryIsEntity << ref_kind); ref->entity = ECS_TERM_REF_ID(term_ref); } } static int flecs_query_compile_ensure_vars( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_query_ref_t *ref, ecs_flags16_t ref_kind, ecs_query_compile_ctx_t *ctx, bool cond_write, bool *written_out) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); bool written = false; if (flags & EcsQueryIsVar) { ecs_var_id_t var_id = ref->var; ecs_query_var_t *var = &query->vars[var_id]; if (var->kind == EcsVarEntity && !flecs_query_is_written(var_id, ctx->written)) { /* If entity variable is not yet written but a table variant exists * that has been written, insert each operation to translate from * entity variable to table */ ecs_var_id_t tvar = var->table_id; if ((tvar != EcsVarNone) && flecs_query_is_written(tvar, ctx->written)) { if (var->lookup) { if (!flecs_query_is_written(tvar, ctx->written)) { ecs_err("dependent variable of '$%s' is not written", var->name); return -1; } if (!flecs_query_is_written(var->base_id, ctx->written)) { flecs_query_insert_each( tvar, var->base_id, ctx, cond_write); } } else { flecs_query_insert_each(tvar, var_id, ctx, cond_write); } /* Variable was written, just not as entity */ written = true; } else if (var->lookup) { if (!flecs_query_is_written(var->base_id, ctx->written)) { ecs_err("dependent variable of '$%s' is not written", var->name); return -1; } } } written |= flecs_query_is_written(var_id, ctx->written); } else { /* If it's not a variable, it's always written */ written = true; } if (written_out) { *written_out = written; } return 0; } static bool flecs_query_compile_lookup( ecs_query_impl_t *query, ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx, bool cond_write) { ecs_query_var_t *var = &query->vars[var_id]; if (var->lookup) { flecs_query_insert_lookup(var->base_id, var_id, ctx, cond_write); return true; } else { return false; } } static void flecs_query_insert_contains( ecs_query_impl_t *query, ecs_var_id_t src_var, ecs_var_id_t other_var, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t contains = {0}; if ((src_var != other_var) && (src_var == query->vars[other_var].table_id)) { contains.kind = EcsQueryContain; contains.src.var = src_var; contains.first.var = other_var; contains.flags |= (EcsQueryIsVar << EcsQuerySrc) | (EcsQueryIsVar << EcsQueryFirst); flecs_query_op_insert(&contains, ctx); } } static void flecs_query_insert_pair_eq( int32_t field_index, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t contains = {0}; contains.kind = EcsQueryPairEq; contains.field_index = flecs_ito(int8_t, field_index); flecs_query_op_insert(&contains, ctx); } static int flecs_query_compile_builtin_pred( ecs_query_t *q, ecs_term_t *term, ecs_query_op_t *op, ecs_write_flags_t write_state) { ecs_entity_t id = ECS_TERM_REF_ID(&term->first); ecs_query_op_kind_t eq[] = {EcsQueryPredEq, EcsQueryPredNeq}; ecs_query_op_kind_t eq_name[] = {EcsQueryPredEqName, EcsQueryPredNeqName}; ecs_query_op_kind_t eq_match[] = {EcsQueryPredEqMatch, EcsQueryPredNeqMatch}; ecs_flags16_t flags_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); if (id == EcsPredEq) { if (term->second.id & EcsIsName) { op->kind = flecs_ito(uint8_t, eq_name[term->oper == EcsNot]); } else { op->kind = flecs_ito(uint8_t, eq[term->oper == EcsNot]); } } else if (id == EcsPredMatch) { op->kind = flecs_ito(uint8_t, eq_match[term->oper == EcsNot]); } if (flags_2nd & EcsQueryIsVar) { if (!(write_state & (1ull << op->second.var))) { ecs_err("uninitialized variable '%s' on right-hand side of " "equality operator", ecs_query_var_name(q, op->second.var)); return -1; } } ecs_assert(flags_src & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); (void)flags_src; if (!(write_state & (1ull << op->src.var))) { /* If this is an == operator with a right-hand side that resolves to a * single entity, the left-hand side is allowed to be undefined, as the * instruction will be evaluated as an assignment. */ if (op->kind != EcsQueryPredEq && op->kind != EcsQueryPredEqName) { ecs_err("uninitialized variable '%s' on left-hand side of " "equality operator", ecs_query_var_name(q, op->src.var)); return -1; } } return 0; } static int flecs_query_ensure_scope_var( ecs_query_impl_t *query, ecs_query_op_t *op, ecs_query_ref_t *ref, ecs_flags16_t ref_kind, ecs_query_compile_ctx_t *ctx) { ecs_var_id_t var = ref->var; if (query->vars[var].kind == EcsVarEntity && !flecs_query_is_written(var, ctx->written)) { ecs_var_id_t table_var = query->vars[var].table_id; if (table_var != EcsVarNone && flecs_query_is_written(table_var, ctx->written)) { if (flecs_query_compile_ensure_vars( query, op, ref, ref_kind, ctx, false, NULL)) { goto error; } } } return 0; error: return -1; } static int flecs_query_ensure_scope_vars( ecs_world_t *world, ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, ecs_term_t *term) { /* If the scope uses variables as entity that have only been written as * table, resolve them as entities before entering the scope. */ ecs_term_t *cur = term; while(ECS_TERM_REF_ID(&cur->first) != EcsScopeClose) { /* Dummy operation to obtain variable information for term */ ecs_query_op_t op = {0}; flecs_query_compile_term_ref(world, query, &op, &cur->first, &op.first, EcsQueryFirst, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(world, query, &op, &cur->second, &op.second, EcsQuerySecond, EcsVarEntity, ctx, false); if (op.flags & (EcsQueryIsVar << EcsQueryFirst)) { if (flecs_query_ensure_scope_var( query, &op, &op.first, EcsQueryFirst, ctx)) { goto error; } } if (op.flags & (EcsQueryIsVar << EcsQuerySecond)) { if (flecs_query_ensure_scope_var( query, &op, &op.second, EcsQuerySecond, ctx)) { goto error; } } cur ++; } return 0; error: return -1; } static void flecs_query_compile_push( ecs_query_compile_ctx_t *ctx) { ctx->cur = &ctx->ctrlflow[++ ctx->scope]; ctx->cur->lbl_begin = -1; ctx->cur->lbl_begin = -1; } static void flecs_query_compile_pop( ecs_query_compile_ctx_t *ctx) { ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } static int flecs_query_compile_0_src( ecs_world_t *world, ecs_query_impl_t *impl, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { /* If the term has a 0 source, check if it's a scope open/close */ if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { goto error; } if (term->oper == EcsNot) { ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); flecs_query_begin_block(EcsQueryNot, ctx); } else { ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); } flecs_query_compile_push(ctx); } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { flecs_query_compile_pop(ctx); if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { flecs_query_end_block(ctx, false); } } else { /* Noop */ } return 0; error: return -1; } static bool flecs_query_select_all( ecs_term_t *term, ecs_query_op_t *op, ecs_var_id_t src_var, ecs_query_compile_ctx_t *ctx) { bool builtin_pred = flecs_term_is_builtin_pred(term); bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; if (term->oper == EcsNot || term->oper == EcsOptional || term->oper == EcsNotFrom || pred_match) { ecs_query_op_t match_any = {0}; match_any.kind = EcsAnd; match_any.flags = EcsQueryIsSelf | (EcsQueryIsEntity << EcsQueryFirst); match_any.flags |= (EcsQueryIsVar << EcsQuerySrc); match_any.src = op->src; match_any.field_index = -1; if (!pred_match) { match_any.first.entity = EcsAny; } else { /* If matching by name, instead of finding all tables, just find * the ones with a name. */ match_any.first.entity = ecs_id(EcsIdentifier); match_any.second.entity = EcsName; match_any.flags |= (EcsQueryIsEntity << EcsQuerySecond); } match_any.written = (1ull << src_var); flecs_query_op_insert(&match_any, ctx); flecs_query_write_ctx(op->src.var, ctx, false); /* Update write administration */ return true; } return false; } #ifdef FLECS_META static int flecs_query_compile_begin_member_term( ecs_world_t *world, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_entity_t first_id) { ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); first_id = ECS_TERM_REF_ID(&term->first); /* First compile as if it's a regular term, to match the component */ term->flags_ &= (uint16_t)~EcsTermIsMember; /* Replace term id with member parent (the component) */ ecs_entity_t component = ecs_get_parent(world, first_id); if (!component) { ecs_err("member without parent in query"); return -1; } if (!ecs_has(world, component, EcsComponent)) { ecs_err("parent of member is not a component"); return -1; } bool second_wildcard = (ECS_TERM_REF_ID(&term->second) == EcsWildcard || ECS_TERM_REF_ID(&term->second) == EcsAny) && (term->second.id & EcsIsVariable) && !term->second.name; term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); term->second.id = 0; term->id = component; ctx->oper = (ecs_oper_kind_t)term->oper; if (term->oper == EcsNot && !second_wildcard) { /* When matching a member term with not operator, we need to cover both * the case where an entity doesn't have the component, and where it * does have the component, but doesn't match the member. */ term->oper = EcsOptional; } return 0; } static int flecs_query_compile_end_member_term( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_id_t term_id, ecs_entity_t first_id, ecs_entity_t second_id, bool cond_write) { ecs_entity_t component = ECS_TERM_REF_ID(&term->first); const EcsComponent *comp = ecs_get(world, component, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); /* Restore term values */ term->id = term_id; term->first.id = first_id; term->second.id = second_id; term->flags_ |= EcsTermIsMember; term->oper = flecs_ito(int16_t, ctx->oper); first_id = ECS_TERM_REF_ID(&term->first); const EcsMember *member = ecs_get(world, first_id, EcsMember); ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_var_t *var = &impl->vars[op->src.var]; const char *var_name = flecs_term_ref_var_name(&term->src); ecs_var_id_t evar = flecs_query_find_var_id( impl, var_name, EcsVarEntity); bool second_wildcard = (ECS_TERM_REF_ID(&term->second) == EcsWildcard || ECS_TERM_REF_ID(&term->second) == EcsAny) && (term->second.id & EcsIsVariable) && !term->second.name; if (term->oper == EcsOptional) { second_wildcard = true; } ecs_query_op_t mbr_op = *op; mbr_op.kind = EcsQueryMemberEq; mbr_op.first.entity = /* Encode type size and member offset */ flecs_ito(uint32_t, member->offset) | (flecs_ito(uint64_t, comp->size) << 32); /* If this is a term with a Not operator, conditionally evaluate member on * whether term was set by previous operation (see begin_member_term). */ if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { if (second_wildcard && ctx->oper == EcsNot) { /* A !(T.value, *) term doesn't need special operations */ return 0; } /* Resolve to entity variable before entering if block, so that we * don't have different branches of the query working with different * versions of the same variable. */ if (var->kind == EcsVarTable) { flecs_query_insert_each(op->src.var, evar, ctx, cond_write); var = &impl->vars[evar]; } ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); if_op->other = term->field_index; if (ctx->oper == EcsNot) { mbr_op.kind = EcsQueryMemberNeq; } } if (var->kind == EcsVarTable) { /* If MemberEq is called on table variable, store it on .other member. * This causes MemberEq to do double duty as 'each' instruction, * which is faster than having to go back & forth between instructions * while finding matching values. */ mbr_op.other = flecs_itolbl(op->src.var + 1); /* Mark entity variable as written */ flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &mbr_op.written); } flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); if (second_wildcard) { mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); mbr_op.second.entity = EcsWildcard; } else { flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); if (term->second.id & EcsIsVariable) { if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, EcsQuerySecond, ctx, cond_write, NULL)) { goto error; } flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); flecs_query_write(mbr_op.second.var, &mbr_op.written); } } flecs_query_op_insert(&mbr_op, ctx); if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { flecs_query_end_block(ctx, false); } return 0; error: return -1; } #else static int flecs_query_compile_begin_member_term( ecs_world_t *world, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_entity_t first_id) { (void)world; (void)term; (void)ctx; (void)first_id; return 0; } static int flecs_query_compile_end_member_term( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_id_t term_id, ecs_entity_t first_id, ecs_entity_t second_id, bool cond_write) { (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; (void)first_id; (void)second_id; (void)cond_write; return 0; } #endif static void flecs_query_mark_last_or_op( ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); op_ptr->next = FlecsRuleOrMarker; } static void flecs_query_set_op_kind( ecs_query_t *q, ecs_query_op_t *op, ecs_term_t *term, bool src_is_var, bool member_term) { /* Default instruction for And operators. If the source is fixed (like for * singletons or terms with an entity source), use With, which like And but * just matches against a source (vs. finding a source). */ op->kind = src_is_var ? EcsQueryAnd : EcsQueryWith; /* Ignore cascade flag */ ecs_entity_t trav_flags = EcsTraverseFlags & ~(EcsCascade|EcsDesc); /* Handle *From operators */ if (term->oper == EcsAndFrom) { op->kind = EcsQueryAndFrom; } else if (term->oper == EcsOrFrom) { op->kind = EcsQueryOrFrom; } else if (term->oper == EcsNotFrom) { op->kind = EcsQueryNotFrom; /* If query is transitive, use Trav(ersal) instruction */ } else if (term->flags_ & EcsTermTransitive) { ecs_assert(ecs_term_ref_is_set(&term->second), ECS_INTERNAL_ERROR, NULL); op->kind = EcsQueryTrav; /* If term queries for union pair, use union instruction */ } else if (term->flags_ & EcsTermIsUnion) { if (op->kind == EcsQueryAnd) { op->kind = EcsQueryUnionEq; if (term->oper == EcsNot) { if (!ecs_id_is_wildcard(ECS_TERM_REF_ID(&term->second))) { term->oper = EcsAnd; op->kind = EcsQueryUnionNeq; } } } else { op->kind = EcsQueryUnionEqWith; } if ((term->src.id & trav_flags) == EcsUp) { if (op->kind == EcsQueryUnionEq) { op->kind = EcsQueryUnionEqUp; } } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { if (op->kind == EcsQueryUnionEq) { op->kind = EcsQueryUnionEqSelfUp; } } } else { if ((term->src.id & trav_flags) == EcsUp) { op->kind = EcsQueryUp; } else if ((term->src.id & trav_flags) == (EcsSelf|EcsUp)) { op->kind = EcsQuerySelfUp; } else if (term->flags_ & (EcsTermMatchAny|EcsTermMatchAnySrc)) { op->kind = EcsQueryAndAny; } } /* If term has fixed id, insert simpler instruction that skips dealing with * wildcard terms and variables */ if (flecs_term_is_fixed_id(q, term) && !member_term) { if (op->kind == EcsQueryAnd) { op->kind = EcsQueryAndId; } else if (op->kind == EcsQuerySelfUp) { op->kind = EcsQuerySelfUpId; } else if (op->kind == EcsQueryUp) { op->kind = EcsQueryUpId; } } } int flecs_query_compile_term( ecs_world_t *world, ecs_query_impl_t *query, ecs_term_t *term, ecs_flags64_t *populated, ecs_query_compile_ctx_t *ctx) { ecs_id_t term_id = term->id; ecs_entity_t first_id = term->first.id; ecs_entity_t second_id = term->second.id; bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; bool member_term = (term->flags_ & EcsTermIsMember) != 0; if (member_term) { (*populated) |= (1llu << term->field_index); flecs_query_compile_begin_member_term(world, term, ctx, first_id); } ecs_query_t *q = &query->pub; bool first_term = term == q->terms; bool first_is_var = term->first.id & EcsIsVariable; bool second_is_var = term->second.id & EcsIsVariable; bool src_is_var = term->src.id & EcsIsVariable; bool src_is_wildcard = src_is_var && (ECS_TERM_REF_ID(&term->src) == EcsWildcard || ECS_TERM_REF_ID(&term->src) == EcsAny); bool src_is_lookup = false; bool builtin_pred = flecs_term_is_builtin_pred(term); bool is_optional = (term->oper == EcsOptional); bool is_or = flecs_term_is_or(q, term); bool first_or = false, last_or = false; bool cond_write = term->oper == EcsOptional || is_or; ecs_query_op_t op = {0}; if (is_or) { first_or = first_term || (term[-1].oper != EcsOr); last_or = term->oper != EcsOr; } if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { const ecs_type_t *type = ecs_get_type(world, term->id); if (!type) { /* Empty type for id in *From operation is a noop */ ctx->skipped ++; return 0; } int32_t i, count = type->count; ecs_id_t *ti_ids = type->array; for (i = 0; i < count; i ++) { ecs_id_t ti_id = ti_ids[i]; ecs_id_record_t *idr = flecs_id_record_get(world, ti_id); if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { break; } } if (i == count) { /* Type did not contain any ids to perform operation on */ ctx->skipped ++; return 0; } } /* !_ (don't match anything) terms always return nothing. */ if (term->oper == EcsNot && term->id == EcsAny) { op.kind = EcsQueryNothing; flecs_query_op_insert(&op, ctx); return 0; } if (first_or) { ctx->ctrlflow->cond_written_or = ctx->cond_written; ctx->ctrlflow->in_or = true; } else if (is_or) { ctx->written = ctx->ctrlflow->written_or; } if (!ECS_TERM_REF_ID(&term->src) && term->src.id & EcsIsEntity) { if (flecs_query_compile_0_src(world, query, term, ctx)) { goto error; } return 0; } if (builtin_pred) { ecs_entity_t id_noflags = ECS_TERM_REF_ID(&term->second); if (id_noflags == EcsWildcard || id_noflags == EcsAny) { /* Noop */ return 0; } } op.field_index = flecs_ito(int8_t, term->field_index); op.term_index = flecs_ito(int8_t, term - q->terms); flecs_query_set_op_kind(q, &op, term, src_is_var, member_term); bool is_not = (term->oper == EcsNot) && !builtin_pred; /* Save write state at start of term so we can use it to reliably track * variables got written by this term. */ ecs_write_flags_t cond_write_state = ctx->cond_written; /* Resolve variables and entities for operation arguments */ flecs_query_compile_term_ref(world, query, &op, &term->first, &op.first, EcsQueryFirst, EcsVarEntity, ctx, true); flecs_query_compile_term_ref(world, query, &op, &term->second, &op.second, EcsQuerySecond, EcsVarEntity, ctx, true); flecs_query_compile_term_ref(world, query, &op, &term->src, &op.src, EcsQuerySrc, EcsVarAny, ctx, true); bool src_written = true; if (src_is_var) { src_is_lookup = query->vars[op.src.var].lookup != NULL; src_written = flecs_query_is_written(op.src.var, ctx->written); } /* Insert each instructions for table -> entity variable if needed */ bool first_written, second_written; if (flecs_query_compile_ensure_vars( query, &op, &op.first, EcsQueryFirst, ctx, cond_write, &first_written)) { goto error; } if (flecs_query_compile_ensure_vars( query, &op, &op.second, EcsQuerySecond, ctx, cond_write, &second_written)) { goto error; } /* Store write state of variables for first OR term in chain which will get * restored for the other terms in the chain, so that all OR terms make the * same assumptions about which variables were already written. */ if (first_or) { ctx->ctrlflow->written_or = ctx->written; } /* If an optional or not term is inserted for a source that's not been * written to yet, insert instruction that selects all entities so we have * something to match the optional/not against. */ if (src_is_var && !src_written && !src_is_wildcard && !src_is_lookup) { src_written = flecs_query_select_all(term, &op, op.src.var, ctx); } /* A bit of special logic for OR expressions and equality predicates. If the * left-hand of an equality operator is a table, and there are multiple * operators in an Or expression, the Or chain should match all entities in * the table that match the right hand sides of the operator expressions. * For this to work, the src variable needs to be resolved as entity, as an * Or chain would otherwise only yield the first match from a table. */ if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { if (query->vars[op.src.var].kind == EcsVarTable) { flecs_query_compile_term_ref(world, query, &op, &term->src, &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); ctx->ctrlflow->written_or |= (1llu << op.src.var); } } if (flecs_query_compile_ensure_vars( query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) { goto error; } /* If source is Any (_) and first and/or second are unconstrained, insert an * ids instruction instead of an And */ if (term->flags_ & EcsTermMatchAnySrc) { op.kind = EcsQueryIds; /* Use up-to-date written values after potentially inserting each */ if (!first_written || !second_written) { if (!first_written) { /* If first is unknown, traverse left: <- (*, t) */ if (ECS_TERM_REF_ID(&term->first) != EcsAny) { op.kind = EcsQueryIdsLeft; } } else { /* If second is wildcard, traverse right: (r, *) -> */ if (ECS_TERM_REF_ID(&term->second) != EcsAny) { op.kind = EcsQueryIdsRight; } } op.src.entity = 0; src_is_var = false; op.flags &= (ecs_flags8_t)~(EcsQueryIsVar << EcsQuerySrc); /* ids has no src */ op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQuerySrc); } /* If source variable is not written and we're querying just for Any, insert * a dedicated instruction that uses the Any record in the id index. Any * queries that are evaluated against written sources can use Wildcard * records, which is what the AndAny instruction does. */ } else if (!src_written && term->id == EcsAny && op.kind == EcsQueryAndAny) { /* Lookup variables ($var.child_name) are always written */ if (!src_is_lookup) { op.kind = EcsQueryOnlyAny; /* Uses Any (_) id record */ } } /* If this is a transitive term and both the target and source are unknown, * find the targets for the relationship first. This clusters together * tables for the same target, which allows for more efficient usage of the * traversal caches. */ if (term->flags_ & EcsTermTransitive && src_is_var && second_is_var) { if (!src_written && !second_written) { flecs_query_insert_unconstrained_transitive( query, &op, ctx, cond_write); } } /* Check if this term has variables that have been conditionally written, * like variables written by an optional term. */ if (ctx->cond_written) { if (!is_or || first_or) { flecs_query_begin_block_cond_eval(&op, ctx, cond_write_state); } } /* If term can toggle and is Not, change operator to Optional as we * have to match entities that have the component but disabled. */ if (toggle_term && is_not) { is_not = false; is_optional = true; } /* Handle Not, Optional, Or operators */ if (is_not) { flecs_query_begin_block(EcsQueryNot, ctx); } else if (is_optional) { flecs_query_begin_block(EcsQueryOptional, ctx); } else if (first_or) { flecs_query_begin_block_or(&op, term, ctx); } /* If term has component inheritance enabled, insert instruction to walk * down the relationship tree of the id. */ if (term->flags_ & EcsTermIdInherited) { flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); } op.match_flags = term->flags_; ecs_write_flags_t write_state = ctx->written; if (first_is_var) { op.flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); op.flags |= (EcsQueryIsVar << EcsQueryFirst); } if (term->src.id & EcsSelf) { op.flags |= EcsQueryIsSelf; } /* Insert instructions for lookup variables */ if (first_is_var) { if (flecs_query_compile_lookup(query, op.first.var, ctx, cond_write)) { write_state |= (1ull << op.first.var); // lookups are resolved inline } } if (src_is_var) { if (flecs_query_compile_lookup(query, op.src.var, ctx, cond_write)) { write_state |= (1ull << op.src.var); // lookups are resolved inline } } if (second_is_var) { if (flecs_query_compile_lookup(query, op.second.var, ctx, cond_write)) { write_state |= (1ull << op.second.var); // lookups are resolved inline } } if (builtin_pred) { if (flecs_query_compile_builtin_pred(q, term, &op, write_state)) { goto error; } } /* If we're writing the $this variable, filter out disabled/prefab entities * unless the query explicitly matches them. * This could've been done with regular With instructions, but since * filtering out disabled/prefab entities is the default and this check is * cheap to perform on table flags, it's worth special casing. */ if (!src_written && op.src.var == 0) { ecs_flags32_t query_flags = q->flags; if (!(query_flags & EcsQueryMatchDisabled) || !(query_flags & EcsQueryMatchPrefab)) { ecs_flags32_t table_flags = EcsTableNotQueryable; if (!(query_flags & EcsQueryMatchDisabled)) { table_flags |= EcsTableIsDisabled; } if (!(query_flags & EcsQueryMatchPrefab)) { table_flags |= EcsTableIsPrefab; } op.other = flecs_itolbl(table_flags); } } /* After evaluating a term, a used variable is always written */ if (src_is_var) { flecs_query_write(op.src.var, &op.written); flecs_query_write_ctx(op.src.var, ctx, cond_write); } if (first_is_var) { flecs_query_write(op.first.var, &op.written); flecs_query_write_ctx(op.first.var, ctx, cond_write); } if (second_is_var) { flecs_query_write(op.second.var, &op.written); flecs_query_write_ctx(op.second.var, ctx, cond_write); } flecs_query_op_insert(&op, ctx); ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); if (is_or && !member_term) { flecs_query_mark_last_or_op(ctx); } /* Handle self-references between src and first/second variables */ if (src_is_var) { if (first_is_var) { flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); } if (second_is_var && op.first.var != op.second.var) { flecs_query_insert_contains(query, op.src.var, op.second.var, ctx); } } /* Handle self references between first and second variables */ if (!ecs_id_is_wildcard(first_id)) { if (first_is_var && !first_written && (op.first.var == op.second.var)) { flecs_query_insert_pair_eq(term->field_index, ctx); } } /* Handle closing of Not, Optional and Or operators */ if (is_not) { flecs_query_end_block(ctx, true); } else if (is_optional) { flecs_query_end_block(ctx, true); } /* Now that the term is resolved, evaluate member of component */ if (member_term) { flecs_query_compile_end_member_term(world, query, &op, term, ctx, term_id, first_id, second_id, cond_write); if (is_or) { flecs_query_mark_last_or_op(ctx); } } if (last_or) { flecs_query_end_block_or(query, ctx); } /* Handle closing of conditional evaluation */ if (ctx->cur->lbl_cond_eval && (first_is_var || second_is_var || src_is_var)) { if (!is_or || last_or) { flecs_query_end_block_cond_eval(ctx); } } /* Ensure that term id is set after evaluating Not */ if (term->flags_ & EcsTermIdInherited) { if (is_not) { ecs_query_op_t set_id = {0}; set_id.kind = EcsQuerySetId; set_id.first.entity = term->id; set_id.flags = (EcsQueryIsEntity << EcsQueryFirst); set_id.field_index = flecs_ito(int8_t, term->field_index); flecs_query_op_insert(&set_id, ctx); } } return 0; error: return -1; } /** * @file query/engine/cache.c * @brief Cached query implementation. */ int32_t flecs_query_cache_table_count( ecs_query_cache_t *cache) { ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); return cache->cache.tables.count; } int32_t flecs_query_cache_empty_table_count( ecs_query_cache_t *cache) { ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); return cache->cache.empty_tables.count; } int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache) { ecs_run_aperiodic(cache->query->world, EcsAperiodicEmptyTables); int32_t result = 0; ecs_table_cache_hdr_t *cur, *last = cache->cache.tables.last; if (!last) { return 0; } for (cur = cache->cache.tables.first; cur != NULL; cur = cur->next) { result += ecs_table_count(cur->table); } return result; } static uint64_t flecs_query_cache_get_group_id( ecs_query_cache_t *cache, ecs_table_t *table) { if (cache->group_by_callback) { return cache->group_by_callback(cache->query->world, table, cache->group_by, cache->group_by_ctx); } else { return 0; } } static void flecs_query_cache_compute_group_id( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (cache->group_by_callback) { ecs_table_t *table = match->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); match->group_id = flecs_query_cache_get_group_id(cache, table); } else { match->group_id = 0; } } static ecs_query_cache_table_list_t* flecs_query_cache_get_group( const ecs_query_cache_t *cache, uint64_t group_id) { return ecs_map_get_deref( &cache->groups, ecs_query_cache_table_list_t, group_id); } static ecs_query_cache_table_list_t* flecs_query_cache_ensure_group( ecs_query_cache_t *cache, uint64_t id) { ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, ecs_query_cache_table_list_t, id); if (!group) { group = ecs_map_insert_alloc_t(&cache->groups, ecs_query_cache_table_list_t, id); ecs_os_zeromem(group); if (cache->on_group_create) { group->info.ctx = cache->on_group_create( cache->query->world, id, cache->group_by_ctx); } } return group; } static void flecs_query_cache_remove_group( ecs_query_cache_t *cache, uint64_t id) { if (cache->on_group_delete) { ecs_query_cache_table_list_t *group = ecs_map_get_deref(&cache->groups, ecs_query_cache_table_list_t, id); if (group) { cache->on_group_delete(cache->query->world, id, group->info.ctx, cache->group_by_ctx); } } ecs_map_remove_free(&cache->groups, id); } static uint64_t flecs_query_cache_default_group_by( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, void *ctx) { (void)ctx; ecs_id_t match; if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) { 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_cache_table_match_t* flecs_query_cache_find_group_insertion_node( ecs_query_cache_t *cache, uint64_t group_id) { /* Grouping must be enabled */ ecs_assert(cache->group_by_callback != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_iter_t it = ecs_map_iter(&cache->groups); ecs_query_cache_table_list_t *list, *closest_list = NULL; uint64_t id, closest_id = 0; bool desc = false; if (cache->cascade_by) { desc = (cache->query->terms[ cache->cascade_by - 1].src.id & EcsDesc) != 0; } /* Find closest smaller group id */ while (ecs_map_next(&it)) { id = ecs_map_key(&it); if (!desc) { if (id >= group_id) { continue; } } else { if (id <= group_id) { continue; } } list = ecs_map_ptr(&it); if (!list->last) { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); continue; } bool comp; if (!desc) { comp = ((group_id - id) < (group_id - closest_id)); } else { comp = ((group_id - id) > (group_id - closest_id)); } if (!closest_list || comp) { closest_id = id; closest_list = list; } } if (closest_list) { return closest_list->last; } else { return NULL; /* Group should be first in query */ } } /* Initialize group with first node */ static void flecs_query_cache_create_group( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *match) { uint64_t group_id = match->group_id; /* If query has grouping enabled & this is a new/empty group, find * the insertion point for the group */ ecs_query_cache_table_match_t *insert_after = flecs_query_cache_find_group_insertion_node(cache, group_id); if (!insert_after) { /* This group should appear first in the query list */ ecs_query_cache_table_match_t *query_first = cache->list.first; if (query_first) { /* If this is not the first match for the query, insert before it */ match->next = query_first; query_first->prev = match; cache->list.first = match; } else { /* If this is the first match of the query, initialize its list */ ecs_assert(cache->list.last == NULL, ECS_INTERNAL_ERROR, NULL); cache->list.first = match; cache->list.last = match; } } else { ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); /* This group should appear after another group */ ecs_query_cache_table_match_t *insert_before = insert_after->next; match->prev = insert_after; insert_after->next = match; match->next = insert_before; if (insert_before) { insert_before->prev = match; } else { ecs_assert(cache->list.last == insert_after, ECS_INTERNAL_ERROR, NULL); /* This group should appear last in the query list */ cache->list.last = match; } } } /* Find the list the node should be part of */ static ecs_query_cache_table_list_t* flecs_query_cache_get_node_list( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *match) { if (cache->group_by_callback) { return flecs_query_cache_get_group(cache, match->group_id); } else { return &cache->list; } } /* Find or create the list the node should be part of */ static ecs_query_cache_table_list_t* flecs_query_cache_ensure_node_list( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *match) { if (cache->group_by_callback) { return flecs_query_cache_ensure_group(cache, match->group_id); } else { return &cache->list; } } /* Remove node from list */ static void flecs_query_cache_remove_table_node( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *match) { ecs_query_cache_table_match_t *prev = match->prev; ecs_query_cache_table_match_t *next = match->next; ecs_assert(prev != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(next != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(!prev || prev != next, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_table_list_t *list = flecs_query_cache_get_node_list(cache, match); if (!list || !list->first) { /* If list contains no matches, the match must be empty */ ecs_assert(!list || list->last == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); return; } ecs_assert(prev != NULL || cache->list.first == match, ECS_INTERNAL_ERROR, NULL); ecs_assert(next != NULL || cache->list.last == match, ECS_INTERNAL_ERROR, NULL); if (prev) { prev->next = next; } if (next) { next->prev = prev; } ecs_assert(list->info.table_count > 0, ECS_INTERNAL_ERROR, NULL); list->info.table_count --; if (cache->group_by_callback) { uint64_t group_id = match->group_id; /* Make sure query.list is updated if this is the first or last group */ if (cache->list.first == match) { ecs_assert(prev == NULL, ECS_INTERNAL_ERROR, NULL); cache->list.first = next; prev = next; } if (cache->list.last == match) { ecs_assert(next == NULL, ECS_INTERNAL_ERROR, NULL); cache->list.last = prev; next = prev; } ecs_assert(cache->list.info.table_count > 0, ECS_INTERNAL_ERROR, NULL); cache->list.info.table_count --; list->info.match_count ++; /* Make sure group list only contains nodes that belong to the group */ if (prev && prev->group_id != group_id) { /* The previous node belonged to another group */ prev = next; } if (next && next->group_id != group_id) { /* The next node belonged to another group */ next = prev; } /* Do check again, in case both prev & next belonged to another group */ if ((!prev && !next) || (prev && prev->group_id != group_id)) { /* There are no more matches left in this group */ flecs_query_cache_remove_group(cache, group_id); list = NULL; } } if (list) { if (list->first == match) { list->first = next; } if (list->last == match) { list->last = prev; } } match->prev = NULL; match->next = NULL; cache->match_count ++; } /* Add node to list */ static void flecs_query_cache_insert_table_node( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *match) { /* Node should not be part of an existing list */ ecs_assert(match->prev == NULL && match->next == NULL, ECS_INTERNAL_ERROR, NULL); /* If this is the first match, activate system */ if (!cache->list.first && cache->entity) { ecs_remove_id(cache->query->world, cache->entity, EcsEmpty); } flecs_query_cache_compute_group_id(cache, match); ecs_query_cache_table_list_t *list = flecs_query_cache_ensure_node_list(cache, match); if (list->last) { ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_table_match_t *last = list->last; ecs_query_cache_table_match_t *last_next = last->next; match->prev = last; match->next = last_next; last->next = match; if (last_next) { last_next->prev = match; } list->last = match; if (cache->group_by_callback) { /* Make sure to update query list if this is the last group */ if (cache->list.last == last) { cache->list.last = match; } } } else { ecs_assert(list->first == NULL, ECS_INTERNAL_ERROR, NULL); list->first = match; list->last = match; if (cache->group_by_callback) { /* Initialize group with its first node */ flecs_query_cache_create_group(cache, match); } } if (cache->group_by_callback) { list->info.table_count ++; list->info.match_count ++; } cache->list.info.table_count ++; cache->match_count ++; ecs_assert(match->prev != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(match->next != match, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(list->last == match, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.first != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.last != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.first->prev == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->list.last->next == NULL, ECS_INTERNAL_ERROR, NULL); } static ecs_query_cache_table_match_t* flecs_query_cache_cache_add( ecs_world_t *world, ecs_query_cache_table_t *elem) { ecs_query_cache_table_match_t *result = flecs_bcalloc(&world->allocators.query_table_match); if (!elem->first) { elem->first = result; elem->last = result; } else { ecs_assert(elem->last != NULL, ECS_INTERNAL_ERROR, NULL); elem->last->next_match = result; elem->last = result; } return result; } /* 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_cache_group_by_cascade( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, void *ctx) { (void)id; ecs_term_t *term = ctx; ecs_entity_t rel = term->trav; int32_t depth = flecs_relation_depth(world, rel, table); return flecs_ito(uint64_t, depth); } static void flecs_query_cache_add_ref( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_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 }; } impl->pub.flags |= EcsQueryHasRefs; } static ecs_query_cache_table_match_t* flecs_query_cache_add_table_match( ecs_query_cache_t *cache, ecs_query_cache_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_cache_table_match_t *qm = flecs_query_cache_cache_add( cache->query->world, qt); qm->table = table; qm->columns = flecs_balloc(&cache->allocators.columns); qm->storage_columns = flecs_balloc(&cache->allocators.columns); qm->ids = flecs_balloc(&cache->allocators.ids); qm->sources = flecs_balloc(&cache->allocators.sources); /* Insert match to iteration list if table is not empty */ if (!table || ecs_table_count(table) != 0 || (cache->query->flags & EcsQueryCacheYieldEmptyTables)) { flecs_query_cache_insert_table_node(cache, qm); } return qm; } static void flecs_query_cache_set_table_match( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache, ecs_query_cache_table_match_t *qm, ecs_table_t *table, ecs_iter_t *it) { ecs_allocator_t *a = &world->allocator; ecs_query_t *query = cache->query; int32_t i, term_count = query->term_count; int32_t field_count = query->field_count; ecs_term_t *terms = query->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); qm->set_fields = it->set_fields; qm->up_fields = it->up_fields; if (table) { /* Initialize storage columns for faster access to component storage */ for (i = 0; i < field_count; i ++) { int32_t column = it->columns[i]; if (!ecs_field_is_set(it, i) || terms[i].inout == EcsInOutNone) { qm->storage_columns[i] = -1; continue; } ecs_entity_t src = it->sources[i]; if (!src) { qm->storage_columns[i] = ecs_table_type_to_column_index( table, column); } else { /* Shared field (not from table) */ qm->storage_columns[i] = -2; } } } /* Add references for substituted terms */ for (i = 0; i < term_count; i ++) { 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_cache_add_ref( world, impl, qm, id, src, size); /* Use column index to bind term and ref */ if (qm->set_fields & (1llu << field)) { qm->columns[field] = ecs_vec_count(&qm->refs) - 1; } } } } } static ecs_query_cache_table_t* flecs_query_cache_table_insert( ecs_world_t *world, ecs_query_cache_t *cache, ecs_table_t *table) { ecs_query_cache_table_t *qt = flecs_bcalloc(&world->allocators.query_table); if (table) { qt->table_id = table->id; } else { qt->table_id = 0; } if (cache->query->flags & EcsQueryCacheYieldEmptyTables) { ecs_table_cache_insert_w_empty(&cache->cache, table, &qt->hdr, false); } else { ecs_table_cache_insert(&cache->cache, table, &qt->hdr); } return qt; } /** Populate query cache with tables */ static void flecs_query_cache_match_tables( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache) { ecs_table_t *table = NULL; ecs_query_cache_table_t *qt = NULL; ecs_iter_t it = ecs_query_iter(world, cache->query); ECS_BIT_SET(it.flags, EcsIterIsInstanced); ECS_BIT_SET(it.flags, EcsIterNoData); ECS_BIT_SET(it.flags, EcsIterTableOnly); while (ecs_query_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { /* New table matched, add record to cache */ table = it.table; qt = flecs_query_cache_table_insert(world, cache, table); } ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match(cache, qt, table); flecs_query_cache_set_table_match(world, impl, cache, qm, table, &it); } } static bool flecs_query_cache_match_table( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache, ecs_table_t *table) { if (!ecs_map_is_init(&cache->cache.index)) { return false; } ecs_query_cache_table_t *qt = NULL; ecs_query_t *q = cache->query; /* Iterate uncached query for table to check if it matches. If this is a * wildcard query, a table can match multiple times. */ ecs_iter_t it = flecs_query_iter(world, q); it.flags |= EcsIterIsInstanced; it.flags |= EcsIterNoData; ecs_iter_set_var_as_table(&it, 0, table); while (ecs_query_next(&it)) { ecs_assert(it.table == table, ECS_INTERNAL_ERROR, NULL); if (qt == NULL) { table = it.table; qt = flecs_query_cache_table_insert(world, cache, table); } ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match( cache, qt, table); flecs_query_cache_set_table_match(world, impl, cache, qm, table, &it); } return qt != NULL; } static bool flecs_query_cache_has_refs( ecs_query_cache_t *cache) { ecs_term_t *terms = cache->query->terms; int32_t i, count = cache->query->term_count; for (i = 0; i < count; i ++) { if (terms[i].src.id & (EcsUp | EcsIsEntity)) { return true; } } return false; } static void flecs_query_cache_for_each_component_monitor( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache, void(*callback)( ecs_world_t* world, ecs_id_t id, ecs_query_t *q)) { ecs_query_t *q = &impl->pub; ecs_term_t *terms = cache->query->terms; int32_t i, count = cache->query->term_count; for (i = 0; i < count; i++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *src = &term->src; if (src->id & EcsUp) { callback(world, ecs_pair(term->trav, EcsWildcard), q); if (term->trav != EcsIsA) { callback(world, ecs_pair(EcsIsA, EcsWildcard), q); } callback(world, term->id, q); } else if (src->id & EcsSelf && !ecs_term_match_this(term)) { callback(world, term->id, q); } } } static bool flecs_query_cache_is_term_ref_supported( ecs_term_ref_t *ref) { if (!(ref->id & EcsIsVariable)) { return true; } if (ecs_id_is_wildcard(ref->id)) { return true; } return false; } static int flecs_query_cache_process_signature( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_cache_t *cache) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_term_t *terms = cache->query->terms; int32_t i, count = cache->query->term_count; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *src = &term->src; ecs_term_ref_t *second = &term->second; bool is_src_ok = flecs_query_cache_is_term_ref_supported(src); bool is_first_ok = flecs_query_cache_is_term_ref_supported(first); bool is_second_ok = flecs_query_cache_is_term_ref_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(term->inout != EcsInOutFilter, ECS_INVALID_PARAMETER, "invalid usage of InOutFilter for query"); if (src->id & EcsCascade) { ecs_assert(cache->cascade_by == 0, ECS_INVALID_PARAMETER, "query can only have one cascade term"); cache->cascade_by = i + 1; } } impl->pub.flags |= (ecs_flags32_t)(flecs_query_cache_has_refs(cache) * EcsQueryHasRefs); flecs_query_cache_for_each_component_monitor( world, impl, cache, 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_cache_update_table( ecs_query_cache_t *cache, ecs_table_t *table, bool empty) { int32_t prev_count = flecs_query_cache_table_count(cache); ecs_table_cache_set_empty(&cache->cache, table, empty); int32_t cur_count = flecs_query_cache_table_count(cache); if (prev_count != cur_count) { ecs_query_cache_table_t *qt = ecs_table_cache_get(&cache->cache, table); ecs_assert(qt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_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_cache_remove_table_node(cache, cur); } else { ecs_assert(ecs_table_count(table) != 0, ECS_INTERNAL_ERROR, NULL); flecs_query_cache_insert_table_node(cache, cur); } } } ecs_assert(cur_count || cache->list.first == NULL, ECS_INTERNAL_ERROR, NULL); } /* Remove table */ static void flecs_query_cache_table_match_free( ecs_query_cache_t *cache, ecs_query_cache_table_t *elem, ecs_query_cache_table_match_t *first) { ecs_query_cache_table_match_t *cur, *next; ecs_world_t *world = cache->query->world; for (cur = first; cur != NULL; cur = next) { flecs_bfree(&cache->allocators.columns, cur->columns); flecs_bfree(&cache->allocators.columns, cur->storage_columns); flecs_bfree(&cache->allocators.ids, cur->ids); flecs_bfree(&cache->allocators.sources, cur->sources); if (cur->monitor) { flecs_bfree(&cache->allocators.monitors, cur->monitor); } if (!elem->hdr.empty) { flecs_query_cache_remove_table_node(cache, cur); } ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); next = cur->next_match; flecs_bfree(&world->allocators.query_table_match, cur); } } static void flecs_query_cache_table_free( ecs_query_cache_t *cache, ecs_query_cache_table_t *elem) { flecs_query_cache_table_match_free(cache, elem, elem->first); flecs_bfree(&cache->query->world->allocators.query_table, elem); } static void flecs_query_cache_unmatch_table( ecs_query_cache_t *cache, ecs_table_t *table, ecs_query_cache_table_t *elem) { if (!elem) { elem = ecs_table_cache_get(&cache->cache, table); } if (elem) { ecs_table_cache_remove(&cache->cache, elem->table_id, &elem->hdr); flecs_query_cache_table_free(cache, elem); } } /* Rematch system with tables after a change happened to a watched entity */ static void flecs_query_cache_rematch_tables( ecs_world_t *world, ecs_query_impl_t *impl) { ecs_iter_t it; ecs_table_t *table = NULL; ecs_query_cache_table_t *qt = NULL; ecs_query_cache_table_match_t *qm = NULL; ecs_query_cache_t *cache = impl->cache; if (cache->monitor_generation == world->monitor_generation) { return; } cache->monitor_generation = world->monitor_generation; it = ecs_query_iter(world, cache->query); ECS_BIT_SET(it.flags, EcsIterIsInstanced); ECS_BIT_SET(it.flags, EcsIterNoData); world->info.rematch_count_total ++; int32_t rematch_count = ++ cache->rematch_count; ecs_time_t t = {0}; if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_measure(&t); } while (ecs_query_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { if (qm && qm->next_match) { flecs_query_cache_table_match_free(cache, qt, qm->next_match); qm->next_match = NULL; } table = it.table; qt = ecs_table_cache_get(&cache->cache, table); if (!qt) { qt = flecs_query_cache_table_insert(world, cache, 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_cache_add_table_match(cache, qt, table); } flecs_query_cache_set_table_match(world, impl, cache, qm, table, &it); if (table && ecs_table_count(table) && cache->group_by_callback) { if (flecs_query_cache_get_group_id(cache, table) != qm->group_id) { /* Update table group */ flecs_query_cache_remove_table_node(cache, qm); flecs_query_cache_insert_table_node(cache, qm); } } } if (qm && qm->next_match) { flecs_query_cache_table_match_free(cache, 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(&cache->cache, &cache_it)) { while ((qt = flecs_table_cache_next(&cache_it, ecs_query_cache_table_t))) { if (qt->rematch_count != rematch_count) { flecs_query_cache_unmatch_table(cache, qt->hdr.table, qt); } } } if (world->flags & EcsWorldMeasureFrameTime) { world->info.rematch_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } /* -- Private API -- */ void flecs_query_cache_notify( ecs_world_t *world, ecs_query_t *q, ecs_query_cache_event_t *event) { flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_cache_t *cache = impl->cache; switch(event->kind) { case EcsQueryTableMatch: /* Creation of new table */ flecs_query_cache_match_table(world, impl, cache, event->table); break; case EcsQueryTableUnmatch: /* Deletion of table */ flecs_query_cache_unmatch_table(cache, event->table, NULL); break; case EcsQueryTableRematch: /* Rematch tables of query */ flecs_query_cache_rematch_tables(world, impl); break; } } static int flecs_query_cache_order_by( ecs_world_t *world, ecs_query_impl_t *impl, ecs_entity_t order_by, ecs_order_by_action_t order_by_callback, ecs_sort_table_action_t action) { ecs_check(impl != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_cache_t *cache = impl->cache; ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_id_is_wildcard(order_by), ECS_INVALID_PARAMETER, NULL); /* Find order_by term & make sure it is queried for */ const ecs_query_t *query = cache->query; int32_t i, count = query->term_count; int32_t order_by_term = -1; if (order_by) { for (i = 0; i < count; i ++) { const ecs_term_t *term = &query->terms[i]; /* Only And terms are supported */ if (term->id == order_by && term->oper == EcsAnd) { order_by_term = i; break; } } if (order_by_term == -1) { char *id_str = ecs_id_str(world, order_by); ecs_err("order_by component '%s' is not queried for", id_str); ecs_os_free(id_str); goto error; } } cache->order_by = order_by; cache->order_by_callback = order_by_callback; cache->order_by_term = order_by_term; cache->order_by_table_callback = action; ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); flecs_query_cache_sort_tables(world, impl); if (!cache->table_slices.array) { flecs_query_cache_build_sorted_tables(cache); } return 0; error: return -1; } static void flecs_query_cache_group_by( ecs_query_cache_t *cache, ecs_entity_t sort_component, ecs_group_by_action_t group_by) { ecs_check(cache->group_by == 0, ECS_INVALID_OPERATION, "query is already grouped"); ecs_check(cache->group_by_callback == 0, ECS_INVALID_OPERATION, "query is already grouped"); if (!group_by) { /* Builtin function that groups by relationship */ group_by = flecs_query_cache_default_group_by; } cache->group_by = sort_component; cache->group_by_callback = group_by; ecs_map_init_w_params(&cache->groups, &cache->query->world->allocators.query_table_list); error: return; } static void flecs_query_cache_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; ecs_observer_impl_t *o_impl = flecs_observer_impl(o); if (o_impl->last_event_id) { if (o_impl->last_event_id[0] == world->event_id) { return; } o_impl->last_event_id[0] = world->event_id; } ecs_query_impl_t *impl = o->ctx; flecs_poly_assert(impl, ecs_query_t); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = it->table; ecs_entity_t event = it->event; if (event == EcsOnTableCreate) { /* Creation of new table */ if (flecs_query_cache_match_table(world, impl, cache, table)) { if (ecs_should_log_3()) { char *table_str = ecs_table_str(world, table); ecs_dbg_3("query cache event: %s for [%s]", ecs_get_name(world, event), table_str); ecs_os_free(table_str); } } return; } ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); /* The observer isn't doing the matching because the query can do it more * efficiently by checking the table with the query cache. */ if (ecs_table_cache_get(&cache->cache, table) == NULL) { return; } if (ecs_should_log_3()) { char *table_str = ecs_table_str(world, table); ecs_dbg_3("query cache event: %s for [%s]", ecs_get_name(world, event), table_str); ecs_os_free(table_str); } if (event == EcsOnTableEmpty) { flecs_query_cache_update_table(cache, table, true); } else if (event == EcsOnTableFill) { flecs_query_cache_update_table(cache, table, false); } else if (event == EcsOnTableDelete) { /* Deletion of table */ flecs_query_cache_unmatch_table(cache, table, NULL); return; } } static void flecs_query_cache_table_cache_free( ecs_query_cache_t *cache) { ecs_table_cache_iter_t it; ecs_query_cache_table_t *qt; if (flecs_table_cache_all_iter(&cache->cache, &it)) { while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { flecs_query_cache_table_free(cache, qt); } } ecs_table_cache_fini(&cache->cache); } static void flecs_query_cache_allocators_init( ecs_query_cache_t *cache) { int32_t field_count = cache->query->field_count; if (field_count) { flecs_ballocator_init(&cache->allocators.columns, field_count * ECS_SIZEOF(int32_t)); flecs_ballocator_init(&cache->allocators.ids, field_count * ECS_SIZEOF(ecs_id_t)); flecs_ballocator_init(&cache->allocators.sources, field_count * ECS_SIZEOF(ecs_entity_t)); flecs_ballocator_init(&cache->allocators.monitors, (1 + field_count) * ECS_SIZEOF(int32_t)); } } static void flecs_query_cache_allocators_fini( ecs_query_cache_t *cache) { int32_t field_count = cache->query->field_count; if (field_count) { flecs_ballocator_fini(&cache->allocators.columns); flecs_ballocator_fini(&cache->allocators.ids); flecs_ballocator_fini(&cache->allocators.sources); flecs_ballocator_fini(&cache->allocators.monitors); } } void flecs_query_cache_fini( ecs_query_impl_t *impl) { ecs_world_t *world = impl->pub.world; ecs_stage_t *stage = impl->stage; ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); if (cache->observer) { flecs_observer_fini(cache->observer); } ecs_group_delete_action_t on_delete = cache->on_group_delete; if (on_delete) { ecs_map_iter_t it = ecs_map_iter(&cache->groups); while (ecs_map_next(&it)) { ecs_query_cache_table_list_t *group = ecs_map_ptr(&it); uint64_t group_id = ecs_map_key(&it); on_delete(world, group_id, group->info.ctx, cache->group_by_ctx); } cache->on_group_delete = NULL; } if (cache->group_by_ctx_free) { if (cache->group_by_ctx) { cache->group_by_ctx_free(cache->group_by_ctx); } } flecs_query_cache_for_each_component_monitor(world, impl, cache, flecs_monitor_unregister); flecs_query_cache_table_cache_free(cache); ecs_map_fini(&cache->groups); ecs_vec_fini_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); flecs_query_cache_allocators_fini(cache); ecs_query_fini(cache->query); flecs_bfree(&stage->allocators.query_cache, cache); } /* -- Public API -- */ ecs_query_cache_t* flecs_query_cache_init( ecs_query_impl_t *impl, const ecs_query_desc_t *const_desc) { ecs_world_t *world = impl->pub.real_world; ecs_stage_t *stage = impl->stage; ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_query_desc_t was not initialized to zero"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot create query during world fini"); /* Create private version of desc to create the uncached query that will * populate the query cache. */ ecs_query_desc_t desc = *const_desc; ecs_entity_t entity = desc.entity; desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ desc.group_by_callback = NULL; desc.group_by = 0; desc.order_by_callback = NULL; desc.order_by = 0; desc.entity = 0; /* Don't pass ctx/binding_ctx to uncached query */ desc.ctx = NULL; desc.binding_ctx = NULL; desc.ctx_free = NULL; desc.binding_ctx_free = NULL; ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); result->entity = entity; impl->cache = result; ecs_observer_desc_t observer_desc = { .query = desc }; ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly; ecs_query_t *q = result->query = ecs_query_init(world, &desc); if (!q) { goto error; } /* The uncached query used to populate the cache always matches empty * tables. This flag determines whether the empty tables are stored * separately in the cache or are treated as regular tables. This is only * enabled if the user requested that the query matches empty tables. */ ECS_BIT_COND(q->flags, EcsQueryCacheYieldEmptyTables, !!(query_flags & EcsQueryMatchEmptyTables)); flecs_query_cache_allocators_init(result); if (q->term_count) { observer_desc.run = flecs_query_cache_on_event; observer_desc.ctx = impl; int32_t event_index = 0; if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { observer_desc.events[event_index ++] = EcsOnTableEmpty; observer_desc.events[event_index ++] = EcsOnTableFill; } observer_desc.events[event_index ++] = EcsOnTableCreate; observer_desc.events[event_index ++] = EcsOnTableDelete; observer_desc.query.flags |= EcsQueryNoData|EcsQueryIsInstanced; observer_desc.flags_ = EcsObserverBypassQuery; /* ecs_query_init could have moved away resources from the terms array * in the descriptor, so use the terms array from the query. */ ecs_os_memcpy_n(observer_desc.query.terms, q->terms, ecs_term_t, FLECS_TERM_COUNT_MAX); observer_desc.query.expr = NULL; /* Already parsed */ result->observer = flecs_observer_init(world, entity, &observer_desc); if (!result->observer) { goto error; } } result->prev_match_count = -1; if (ecs_should_log_1()) { char *query_expr = ecs_query_str(result->query); ecs_dbg_1("#[green]query#[normal] [%s] created", query_expr ? query_expr : ""); ecs_os_free(query_expr); } ecs_log_push_1(); if (flecs_query_cache_process_signature(world, impl, result)) { goto error; } /* Group before matching so we won't have to move tables around later */ int32_t cascade_by = result->cascade_by; if (cascade_by) { flecs_query_cache_group_by(result, result->query->terms[cascade_by - 1].id, flecs_query_cache_group_by_cascade); result->group_by_ctx = &result->query->terms[cascade_by - 1]; } if (const_desc->group_by_callback || const_desc->group_by) { ecs_check(!result->cascade_by, ECS_INVALID_PARAMETER, "cannot mix cascade and group_by"); flecs_query_cache_group_by(result, const_desc->group_by, const_desc->group_by_callback); result->group_by_ctx = const_desc->group_by_ctx; result->on_group_create = const_desc->on_group_create; result->on_group_delete = const_desc->on_group_delete; result->group_by_ctx_free = const_desc->group_by_ctx_free; } /* 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); flecs_query_cache_match_tables(world, impl, result); if (const_desc->order_by_callback) { if (flecs_query_cache_order_by(world, impl, const_desc->order_by, const_desc->order_by_callback, const_desc->order_by_table_callback)) { goto error; } } if (entity) { if (!flecs_query_cache_table_count(result) && result->query->term_count){ ecs_add_id(world, entity, EcsEmpty); } } ecs_log_pop_1(); return result; error: return NULL; } ecs_query_cache_table_t* flecs_query_cache_get_table( ecs_query_cache_t *cache, ecs_table_t *table) { return ecs_table_cache_get(&cache->cache, table); } void ecs_iter_set_group( ecs_iter_t *it, uint64_t group_id) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_check(!(it->flags & EcsIterIsValid), ECS_INVALID_PARAMETER, "cannot set group during iteration"); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *q = flecs_query_impl(qit->query); ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); flecs_poly_assert(q, ecs_query_t); ecs_query_cache_t *cache = q->cache; ecs_check(cache != NULL, ECS_INVALID_PARAMETER, NULL); ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( cache, group_id); if (!node) { qit->node = NULL; qit->last = NULL; return; } ecs_query_cache_table_match_t *first = node->first; if (first) { qit->node = node->first; qit->last = node->last; } else { qit->node = NULL; qit->last = NULL; } error: return; } const ecs_query_group_info_t* ecs_query_get_group_info( const ecs_query_t *query, uint64_t group_id) { flecs_poly_assert(query, ecs_query_t); ecs_query_cache_table_list_t *node = flecs_query_cache_get_group( flecs_query_impl(query)->cache, group_id); if (!node) { return NULL; } return &node->info; } void* ecs_query_get_group_ctx( const ecs_query_t *query, uint64_t group_id) { flecs_poly_assert(query, ecs_query_t); const ecs_query_group_info_t *info = ecs_query_get_group_info( query, group_id); if (!info) { return NULL; } else { return info->ctx; } } /** * @file query/engine/cache_iter.c * @brief Compile query term. */ static ecs_query_cache_table_match_t* flecs_query_next( const ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_cache_table_match_t *node = qit->node; ecs_query_cache_table_match_t *prev = qit->prev; if (prev != qit->last) { ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); ctx->vars[0].range.table = node->table; it->group_id = node->group_id; it->instance_count = 0; qit->node = node->next; qit->prev = node; return node; } return NULL; } static ecs_query_cache_table_match_t* flecs_query_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_iter_t *it = ctx->it; if (!redo) { ecs_var_t *var = &ctx->vars[0]; ecs_table_t *table = var->range.table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the variable set on the iterator is missing a table"); ecs_query_cache_table_t *qt = flecs_query_cache_get_table( ctx->query->cache, table); if (!qt) { return NULL; } ecs_query_iter_t *qit = &it->priv_.iter.query; qit->prev = NULL; qit->node = qt->first; qit->last = qt->last; } return flecs_query_next(ctx); } static void flecs_query_populate_ptrs( ecs_iter_t *it, ecs_table_t *table, int32_t offset, ecs_query_cache_table_match_t *node) { int32_t i, field_count = it->field_count; ecs_data_t *data = &table->data; for (i = 0; i < field_count; i ++) { ECS_TERMSET_CLEAR(it->shared_fields, 1u << i); int32_t storage_column = node->storage_columns[i]; ecs_size_t size = it->sizes[i]; if (storage_column < 0 || !size) { /* Tag / no data */ it->ptrs[i] = NULL; continue; } it->ptrs[i] = ecs_vec_get(&data->columns[storage_column].data, it->sizes[i], offset); } } static void flecs_query_populate_ptrs_w_field_map( ecs_iter_t *it, ecs_table_t *table, int32_t offset, ecs_query_cache_table_match_t *node, int8_t *field_map, int32_t field_count) { int32_t i; ecs_data_t *data = &table->data; for (i = 0; i < field_count; i ++) { int32_t storage_column = node->storage_columns[i]; int8_t field_index = field_map[i]; ecs_size_t size = it->sizes[field_index]; if (storage_column < 0 || !size) { /* Tag / no data */ it->ptrs[field_index] = NULL; continue; } it->ptrs[field_index] = ecs_vec_get(&data->columns[storage_column].data, size, offset); } } static void flecs_query_populate_ptrs_w_shared( ecs_iter_t *it, ecs_table_t *table, int32_t offset, ecs_query_cache_table_match_t *node, int8_t *field_map, int8_t field_count) { ecs_ref_t *refs = ecs_vec_first(&node->refs); int8_t i; for (i = 0; i < field_count; i ++) { int32_t column = node->columns[i]; int8_t field_index = i; if (field_map) { field_index = field_map[i]; } ecs_size_t size = it->sizes[field_index]; if (!ecs_field_is_set(it, i) || !size) { /* Tag / no data */ it->ptrs[field_index] = NULL; continue; } ecs_entity_t src = node->sources[i]; if (src != 0) { ecs_ref_t *ref = &refs[column]; if (ref->id) { it->ptrs[field_index] = (void*)ecs_ref_get_id( it->real_world, ref, ref->id); ECS_TERMSET_SET(it->shared_fields, 1u << i); } else { it->ptrs[field_index] = NULL; } } else { int32_t storage_column = node->storage_columns[i]; if (storage_column < 0) { it->ptrs[field_index] = NULL; continue; } ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); it->ptrs[field_index] = ecs_vec_get( &table->data.columns[storage_column].data, size, offset); ECS_TERMSET_CLEAR(it->shared_fields, 1u << i); } } } /* Populate for query that is partially cached. * This requires a mapping from cached fields to query fields. */ static void flecs_query_cache_data_populate( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx, ecs_query_cache_table_match_t *node) { ecs_table_t *table = node->table; ecs_var_t *var = &ctx->vars[0]; int32_t count = var->range.count, offset = var->range.offset; if (!count) { count = ecs_table_count(table); } /* If NoData flag is set on iterator, don't populate fields */ if (ECS_BIT_IS_SET(it->flags, EcsIterNoData) || !count) { return; } const ecs_query_impl_t *impl = ctx->query; ecs_query_t *cache_query = impl->cache->query; if (!ecs_vec_count(&node->refs)) { flecs_query_populate_ptrs_w_field_map( it, table, offset, node, impl->field_map, cache_query->field_count); } else { flecs_query_populate_ptrs_w_shared( it, table, offset, node, impl->field_map, cache_query->field_count); } } /* Populate for query that is entirely cached. * Cached fields the same as query fields. */ static void flecs_query_is_cache_data_populate( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx, ecs_query_cache_table_match_t *node) { ecs_table_t *table = node->table; ecs_var_t *var = &ctx->vars[0]; int32_t count = var->range.count, offset = var->range.offset; if (!count) { count = ecs_table_count(table); } /* If NoData flag is set on iterator, don't populate fields */ if (ECS_BIT_IS_SET(it->flags, EcsIterNoData) || !count) { return; } if (!ecs_vec_count(&node->refs)) { flecs_query_populate_ptrs(it, table, offset, node); } else { flecs_query_populate_ptrs_w_shared(it, table, offset, node, NULL, flecs_ito(int8_t, it->field_count)); } } static void flecs_query_cache_init_mapped_fields( const ecs_query_run_ctx_t *ctx, ecs_query_cache_table_match_t *node) { ecs_iter_t *it = ctx->it; const ecs_query_impl_t *impl = ctx->query; ecs_query_cache_t *cache = impl->cache; int32_t i, field_count = cache->query->field_count; int8_t *field_map = impl->field_map; for (i = 0; i < field_count; i ++) { int8_t field_index = field_map[i]; it->columns[field_index] = node->columns[i]; it->ids[field_index] = node->ids[i]; it->sources[field_index] = node->sources[i]; ecs_termset_t bit = (ecs_termset_t)(1u << i); ecs_termset_t field_bit = (ecs_termset_t)(1u << field_index); ECS_TERMSET_COND(it->set_fields, field_bit, node->set_fields & bit); ECS_TERMSET_COND(it->up_fields, field_bit, node->up_fields & bit); } } /* Iterate cache for query that's partially cached */ bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx) { ecs_query_cache_table_match_t *node = flecs_query_next(ctx); if (!node) { return false; } flecs_query_cache_init_mapped_fields(ctx, node); ctx->vars[0].range.count = node->count; ctx->vars[0].range.offset = node->offset; return true; } /* Iterate cache for query that's entirely cached */ bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx) { ecs_query_cache_table_match_t *node = flecs_query_next(ctx); if (!node) { return false; } ecs_iter_t *it = ctx->it; it->columns = node->columns; it->ids = node->ids; it->sources = node->sources; it->set_fields = node->set_fields; it->up_fields = node->up_fields; ctx->vars[0].range.count = node->count; ctx->vars[0].range.offset = node->offset; return true; } /* Iterate cache for query that's partially cached with data */ bool flecs_query_cache_data_search( const ecs_query_run_ctx_t *ctx) { if (!flecs_query_cache_search(ctx)) { return false; } ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; flecs_query_cache_data_populate(it, ctx, qit->prev); return true; } /* Iterate cache for query that's entirely cached with data */ bool flecs_query_is_cache_data_search( const ecs_query_run_ctx_t *ctx) { if (!flecs_query_is_cache_search(ctx)) { return false; } ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; flecs_query_is_cache_data_populate(it, ctx, qit->prev); return true; } /* Test if query that is entirely cached matches constrained $this */ bool flecs_query_cache_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); if (!node) { return false; } flecs_query_cache_init_mapped_fields(ctx, node); return true; } /* Test if query that is entirely cached matches constrained $this */ bool flecs_query_is_cache_test( const ecs_query_run_ctx_t *ctx, bool redo) { ecs_query_cache_table_match_t *node = flecs_query_test(ctx, redo); if (!node) { return false; } ecs_iter_t *it = ctx->it; it->columns = node->columns; it->ids = node->ids; it->sources = node->sources; return true; } /* Test if query that is partially cached matches constrained $this */ bool flecs_query_cache_data_test( const ecs_query_run_ctx_t *ctx, bool redo) { if (!flecs_query_cache_test(ctx, redo)) { return false; } ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; flecs_query_cache_data_populate(it, ctx, qit->prev); return true; } /* Test if query that is entirely cached matches constrained $this with data */ bool flecs_query_is_cache_data_test( const ecs_query_run_ctx_t *ctx, bool redo) { if (!flecs_query_is_cache_test(ctx, redo)) { return false; } ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; flecs_query_is_cache_data_populate(it, ctx, qit->prev); return true; } /** * @file query/engine/cache_order_by.c * @brief Order by implementation */ ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) static void flecs_query_cache_sort_table( ecs_world_t *world, ecs_table_t *table, int32_t column_index, ecs_order_by_action_t compare, ecs_sort_table_action_t sort) { ecs_data_t *data = &table->data; if (!ecs_vec_count(&data->entities)) { /* Nothing to sort */ return; } int32_t count = flecs_table_data_count(data); if (count < 2) { return; } ecs_entity_t *entities = ecs_vec_first(&data->entities); void *ptr = NULL; int32_t size = 0; if (column_index != -1) { ecs_column_t *column = &data->columns[column_index]; ecs_type_info_t *ti = column->ti; size = ti->size; ptr = ecs_vec_first(&column->data); } if (sort) { sort(world, table, entities, ptr, size, 0, count - 1, compare); } else { flecs_query_cache_sort_table_generic( world, table, entities, ptr, size, 0, count - 1, compare); } } /* Helper struct for building sorted table ranges */ typedef struct sort_helper_t { ecs_query_cache_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_cache_build_sorted_table_range( ecs_query_cache_t *cache, ecs_query_cache_table_list_t *list) { ecs_world_t *world = cache->query->world; ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, "cannot sort query in multithreaded mode"); ecs_entity_t id = cache->order_by; ecs_order_by_action_t compare = cache->order_by_callback; int32_t table_count = list->info.table_count; if (!table_count) { return; } ecs_vec_init_if_t(&cache->table_slices, ecs_query_cache_table_match_t); int32_t to_sort = 0; int32_t order_by_term = cache->order_by_term; sort_helper_t *helper = flecs_alloc_n( &world->allocator, sort_helper_t, table_count); ecs_query_cache_table_match_t *cur, *end = list->last->next; for (cur = list->first; cur != end; cur = cur->next) { ecs_table_t *table = cur->table; ecs_data_t *data = &table->data; if (ecs_table_count(table) == 0) { continue; } if (id) { const ecs_term_t *term = &cache->query->terms[order_by_term]; int32_t field = term->field_index; int32_t column = cur->columns[field]; ecs_size_t size = cache->query->sizes[field]; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t src = cur->sources[field]; if (src == 0) { column = table->column_map[column]; ecs_vec_t *vec = &data->columns[column].data; helper[to_sort].ptr = ecs_vec_first(vec); helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else { ecs_record_t *r = flecs_entities_get(world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); if (term->src.id & EcsUp) { ecs_entity_t base = 0; ecs_search_relation(world, r->table, 0, id, EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); if (base && base != src) { /* Component could be inherited */ r = flecs_entities_get(world, base); } } helper[to_sort].ptr = ecs_table_get_id( world, r->table, id, ECS_RECORD_TO_ROW(r->row)); helper[to_sort].elem_size = size; helper[to_sort].shared = true; } ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); } else { helper[to_sort].ptr = NULL; helper[to_sort].elem_size = 0; helper[to_sort].shared = false; } helper[to_sort].match = cur; helper[to_sort].entities = ecs_vec_first(&data->entities); helper[to_sort].row = 0; helper[to_sort].count = ecs_table_count(table); to_sort ++; } ecs_assert(to_sort != 0, ECS_INTERNAL_ERROR, NULL); bool proceed; do { int32_t j, min = 0; proceed = true; ecs_entity_t e1; while (!(e1 = e_from_helper(&helper[min]))) { min ++; if (min == to_sort) { proceed = false; break; } } if (!proceed) { break; } for (j = min + 1; j < to_sort; j++) { ecs_entity_t e2 = e_from_helper(&helper[j]); if (!e2) { continue; } const void *ptr1 = ptr_from_helper(&helper[min]); const void *ptr2 = ptr_from_helper(&helper[j]); if (compare(e1, ptr1, e2, ptr2) > 0) { min = j; e1 = e_from_helper(&helper[min]); } } sort_helper_t *cur_helper = &helper[min]; if (!cur || cur->columns != cur_helper->match->columns) { cur = ecs_vec_append_t(NULL, &cache->table_slices, ecs_query_cache_table_match_t); *cur = *(cur_helper->match); cur->offset = cur_helper->row; cur->count = 1; } else { cur->count ++; } cur_helper->row ++; } while (proceed); /* Iterate through the vector of slices to set the prev/next ptrs. This * can't be done while building the vector, as reallocs may occur */ int32_t i, count = ecs_vec_count(&cache->table_slices); ecs_query_cache_table_match_t *nodes = ecs_vec_first(&cache->table_slices); for (i = 0; i < count; i ++) { nodes[i].prev = &nodes[i - 1]; nodes[i].next = &nodes[i + 1]; } nodes[0].prev = NULL; nodes[i - 1].next = NULL; flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } void flecs_query_cache_build_sorted_tables( ecs_query_cache_t *cache) { ecs_vec_clear(&cache->table_slices); if (cache->group_by_callback) { /* Populate sorted node list in grouping order */ ecs_query_cache_table_match_t *cur = cache->list.first; if (cur) { do { /* Find list for current group */ uint64_t group_id = cur->group_id; ecs_query_cache_table_list_t *list = ecs_map_get_deref( &cache->groups, ecs_query_cache_table_list_t, group_id); ecs_assert(list != NULL, ECS_INTERNAL_ERROR, NULL); /* Sort tables in current group */ flecs_query_cache_build_sorted_table_range(cache, list); /* Find next group to sort */ cur = list->last->next; } while (cur); } } else { flecs_query_cache_build_sorted_table_range(cache, &cache->list); } } void flecs_query_cache_sort_tables( ecs_world_t *world, ecs_query_impl_t *impl) { ecs_query_cache_t *cache = impl->cache; ecs_order_by_action_t compare = cache->order_by_callback; if (!compare) { return; } ecs_sort_table_action_t sort = cache->order_by_table_callback; ecs_entity_t order_by = cache->order_by; int32_t order_by_term = cache->order_by_term; /* Iterate over non-empty tables. Don't bother with empty tables as they * have nothing to sort */ bool tables_sorted = false; ecs_id_record_t *idr = flecs_id_record_get(world, order_by); ecs_table_cache_iter_t it; ecs_query_cache_table_t *qt; flecs_table_cache_iter(&cache->cache, &it); while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { ecs_table_t *table = qt->hdr.table; bool dirty = false; if (flecs_query_check_table_monitor(impl, qt, 0)) { tables_sorted = true; dirty = true; } int32_t column = -1; if (order_by) { if (flecs_query_check_table_monitor(impl, qt, order_by_term + 1)) { dirty = true; } if (dirty) { column = -1; const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); if (tr) { column = tr->column; } if (column == -1) { /* Component is shared, no sorting is needed */ dirty = false; } } } if (!dirty) { continue; } /* Something has changed, sort the table. Prefers using * flecs_query_cache_sort_table when available */ flecs_query_cache_sort_table(world, table, column, compare, sort); tables_sorted = true; } if (tables_sorted || cache->match_count != cache->prev_match_count) { flecs_query_cache_build_sorted_tables(cache); cache->match_count ++; /* Increase version if tables changed */ } } /** * @file query/engine/change_detection.c * @brief Compile query term. */ typedef struct { ecs_table_t *table; int32_t column; } flecs_table_column_t; static void flecs_query_get_column_for_field( const ecs_query_t *q, ecs_query_cache_table_match_t *match, int32_t field, flecs_table_column_t *out) { ecs_assert(field >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field < q->field_count, ECS_INTERNAL_ERROR, NULL); ecs_entity_t src = match->sources[field]; ecs_table_t *table = NULL; int32_t column = -1; if (!src) { table = match->table; column = match->storage_columns[field]; if (column == -2) { /* Shared field */ column = -1; } } else { ecs_record_t *r = flecs_entities_get(q->world, src); table = r->table; int32_t ref_index = match->columns[field]; ecs_ref_t *ref = ecs_vec_get_t(&match->refs, ecs_ref_t, ref_index); if (ref->id != 0) { ecs_ref_update(q->world, ref); column = ref->tr->column; } } out->table = table; out->column = column; } /* Get match monitor. Monitors are used to keep track of whether components * matched by the query in a table have changed. */ static bool flecs_query_get_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_table_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (match->monitor) { return false; } ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *monitor = flecs_balloc(&cache->allocators.monitors); monitor[0] = 0; /* Mark terms that don't need to be monitored. This saves time when reading * and/or updating the monitor. */ const ecs_query_t *q = cache->query; int32_t i, field = -1, term_count = q->term_count; flecs_table_column_t tc; for (i = 0; i < term_count; i ++) { if (field == q->terms[i].field_index) { if (monitor[field + 1] != -1) { continue; } } field = q->terms[i].field_index; monitor[field + 1] = -1; /* If term isn't read, don't monitor */ if (q->terms[i].inout != EcsIn && q->terms[i].inout != EcsInOut && q->terms[i].inout != EcsInOutDefault) { continue; } /* Don't track fields that aren't set */ if (!(match->set_fields & (1llu << field))) { continue; } ecs_assert(match->columns != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = match->columns[field]; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); (void)column; flecs_query_get_column_for_field(q, match, field, &tc); if (tc.column == -1) { continue; /* Don't track terms that aren't stored */ } monitor[field + 1] = 0; } match->monitor = monitor; impl->pub.flags |= EcsQueryHasMonitor; return true; } /* Get monitor for fixed query terms. Fixed terms are handled separately as they * don't require a query cache, and fixed terms aren't stored in the cache. */ static bool flecs_query_get_fixed_monitor( ecs_query_impl_t *impl, bool check) { ecs_query_t *q = &impl->pub; ecs_world_t *world = q->world; ecs_term_t *terms = q->terms; int32_t i, term_count = q->term_count; if (!impl->monitor) { impl->monitor = flecs_alloc_n(&impl->stage->allocator, int32_t, q->field_count); check = false; /* If the monitor is new, initialize it with dirty state */ } for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int16_t field_index = term->field_index; if (!(q->read_fields & flecs_ito(uint32_t, 1 << field_index))) { continue; /* If term doesn't read data there's nothing to track */ } if (!(term->src.id & EcsIsEntity)) { continue; /* Not a term with a fixed source */ } ecs_entity_t src = ECS_TERM_REF_ID(&term->src); ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, src); if (!r || !r->table) { continue; /* Entity is empty, nothing to track */ } ecs_id_record_t *idr = flecs_id_record_get(world, term->id); if (!idr) { continue; /* If id doesn't exist, entity can't have it */ } ecs_table_record_t *tr = flecs_id_record_get_table(idr, r->table); if (!tr) { continue; /* Entity doesn't have the component */ } /* Copy/check column dirty state from table */ int32_t *dirty_state = flecs_table_get_dirty_state(world, r->table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (!check) { impl->monitor[field_index] = dirty_state[tr->column + 1]; } else { if (impl->monitor[field_index] != dirty_state[tr->column + 1]) { return true; } } } return !check; } bool flecs_query_update_fixed_monitor( ecs_query_impl_t *impl) { return flecs_query_get_fixed_monitor(impl, false); } bool flecs_query_check_fixed_monitor( ecs_query_impl_t *impl) { return flecs_query_get_fixed_monitor(impl, true); } /* Check if single match term has changed */ static bool flecs_query_check_match_monitor_term( ecs_query_impl_t *impl, ecs_query_cache_table_match_t *match, int32_t field) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_get_match_monitor(impl, match)) { return true; } int32_t *monitor = match->monitor; int32_t state = monitor[field]; if (state == -1) { return false; } ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = match->table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( cache->query->world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (!field) { return monitor[0] != dirty_state[0]; } } else if (!field) { return false; } flecs_table_column_t cur; flecs_query_get_column_for_field( &impl->pub, match, field - 1, &cur); ecs_assert(cur.column != -1, ECS_INTERNAL_ERROR, NULL); return monitor[field] != flecs_table_get_dirty_state( cache->query->world, cur.table)[cur.column + 1]; } static bool flecs_query_check_cache_monitor( ecs_query_impl_t *impl) { ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); /* If the match count changed, tables got matched/unmatched for the * cache, so return that the query has changed. */ if (cache->match_count != cache->prev_match_count) { return true; } ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&cache->cache, &it)) { ecs_query_cache_table_t *qt; while ((qt = flecs_table_cache_next(&it, ecs_query_cache_table_t))) { if (flecs_query_check_table_monitor(impl, qt, -1)) { return true; } } } return false; } static void flecs_query_init_query_monitors( ecs_query_impl_t *impl) { /* Change monitor for cache */ ecs_query_cache_t *cache = impl->cache; if (cache) { ecs_query_cache_table_match_t *cur = cache->list.first; /* Ensure each match has a monitor */ for (; cur != NULL; cur = cur->next) { ecs_query_cache_table_match_t *match = (ecs_query_cache_table_match_t*)cur; flecs_query_get_match_monitor(impl, match); } } } static bool flecs_query_check_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_table_match_t *match, const ecs_iter_t *it) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_query_get_match_monitor(impl, match)) { return true; } ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *monitor = match->monitor; ecs_table_t *table = match->table; int32_t *dirty_state = NULL; if (table) { dirty_state = flecs_table_get_dirty_state( cache->query->world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (monitor[0] != dirty_state[0]) { return true; } } bool is_this = false; const ecs_query_t *query = cache->query; ecs_world_t *world = query->world; int32_t i, j, field_count = query->field_count; int32_t *storage_columns = match->storage_columns; ecs_entity_t *sources = match->sources; int32_t *columns = it ? it->columns : match->columns; ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *refs = &match->refs; for (i = 0; i < field_count; i ++) { int32_t mon = monitor[i + 1]; if (mon == -1) { continue; } if (!(set_fields & (1llu << i))) { continue; } int32_t column = storage_columns[i]; ecs_entity_t src = sources[i]; if (!src) { if (column >= 0) { /* owned component */ ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (mon != dirty_state[column + 1]) { return true; } continue; } else if (column == -1) { continue; /* owned but not a component */ } } column = columns[i]; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); /* Find term index from field index, which differ when using || */ int32_t term_index = i; if (query->terms[i].field_index != i) { for (j = i; j < query->term_count; j ++) { if (query->terms[j].field_index == i) { term_index = j; break; } } } is_this = ecs_term_match_this(&query->terms[term_index]); /* Flattened fields are encoded by adding field_count to the column * index of the parent component. */ ecs_assert(column <= field_count, ECS_INTERNAL_ERROR, NULL); if (is_this) { /* Component reached through traversal from this */ int32_t ref_index = column; ecs_ref_t *ref = ecs_vec_get_t(refs, ecs_ref_t, ref_index); if (ref->id != 0) { ecs_ref_update(world, ref); ecs_table_record_t *tr = ref->tr; ecs_table_t *src_table = tr->hdr.table; column = tr->index; column = ecs_table_type_to_column_index(src_table, column); int32_t *src_dirty_state = flecs_table_get_dirty_state( world, src_table); if (mon != src_dirty_state[column + 1]) { return true; } } } else { /* Component from static source */ ecs_entity_t fixed_src = match->sources[i]; ecs_table_t *src_table = ecs_get_table(world, fixed_src); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); column = ecs_table_type_to_column_index(src_table, column); int32_t *src_dirty_state = flecs_table_get_dirty_state( world, src_table); if (mon != src_dirty_state[column + 1]) { return true; } } } return false; } /* Check if any term for matched table has changed */ bool flecs_query_check_table_monitor( ecs_query_impl_t *impl, ecs_query_cache_table_t *table, int32_t field) { ecs_query_cache_table_match_t *cur, *end = table->last->next; for (cur = table->first; cur != end; cur = cur->next) { ecs_query_cache_table_match_t *match = (ecs_query_cache_table_match_t*)cur; if (field == -1) { if (flecs_query_check_match_monitor(impl, match, NULL)) { return true; } } else { if (flecs_query_check_match_monitor_term(impl, match, field)) { return true; } } } return false; } void flecs_query_mark_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it) { ecs_query_t *q = &impl->pub; /* Evaluate all writeable non-fixed fields, set fields */ ecs_termset_t write_fields = (ecs_termset_t)(q->write_fields & ~q->fixed_fields & it->set_fields); if (!write_fields || (it->flags & EcsIterNoData)) { return; } ecs_world_t *world = q->world; int16_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { if (!(write_fields & flecs_ito(uint32_t, 1 << i))) { continue; /* If term doesn't write data there's nothing to track */ } ecs_entity_t src = it->sources[i]; ecs_table_t *table; if (!src) { table = it->table; } else { ecs_record_t *r = flecs_entities_get(world, src); if (!r || !(table = r->table)) { continue; } if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { /* Shared fields that aren't marked explicitly as out/inout * default to readonly */ continue; } } int32_t type_index = it->columns[i]; ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *dirty_state = table->dirty_state; if (!dirty_state) { continue; } ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[type_index]; dirty_state[column + 1] ++; } } void flecs_query_mark_fixed_fields_dirty( ecs_query_impl_t *impl, ecs_iter_t *it) { /* This function marks fields dirty for terms with fixed sources. */ ecs_query_t *q = &impl->pub; ecs_termset_t fixed_write_fields = q->write_fields & q->fixed_fields; if (!fixed_write_fields) { return; } ecs_world_t *world = q->world; int32_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { if (!(fixed_write_fields & flecs_ito(uint32_t, 1 << i))) { continue; /* If term doesn't write data there's nothing to track */ } ecs_entity_t src = it->sources[i]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, src); ecs_table_t *table; if (!r || !(table = r->table)) { /* If the field is optional, it's possible that it didn't match */ continue; } int32_t *dirty_state = table->dirty_state; if (!dirty_state) { continue; } ecs_assert(it->columns[i] >= 0, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[it->columns[i]]; dirty_state[column + 1] ++; } } /* Synchronize match monitor with table dirty state */ void flecs_query_sync_match_monitor( ecs_query_impl_t *impl, ecs_query_cache_table_match_t *match) { ecs_assert(match != NULL, ECS_INTERNAL_ERROR, NULL); if (!match->monitor) { if (impl->pub.flags & EcsQueryHasMonitor) { flecs_query_get_match_monitor(impl, match); } else { return; } } ecs_query_cache_t *cache = impl->cache; ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *monitor = match->monitor; ecs_table_t *table = match->table; if (table) { int32_t *dirty_state = flecs_table_get_dirty_state( cache->query->world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); monitor[0] = dirty_state[0]; /* Did table gain/lose entities */ } ecs_query_t *q = cache->query; { flecs_table_column_t tc; int32_t t, term_count = q->term_count; for (t = 0; t < term_count; t ++) { int32_t field = q->terms[t].field_index; if (monitor[field + 1] == -1) { continue; } flecs_query_get_column_for_field(q, match, field, &tc); monitor[field + 1] = flecs_table_get_dirty_state( q->world, tc.table)[tc.column + 1]; } } cache->prev_match_count = cache->match_count; } bool ecs_query_changed( ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); ecs_assert(q->cache_kind != EcsQueryCacheNone, ECS_INVALID_OPERATION, "change detection is only supported on cached queries"); /* If query reads terms with fixed sources, check those first as that's * cheaper than checking entries in the cache. */ if (impl->monitor) { if (flecs_query_check_fixed_monitor(impl)) { return true; } } /* Check cache for changes. We can't detect changes for terms that are not * cached/cacheable and don't have a fixed source, since that requires * storing state per result, which doesn't happen for uncached queries. */ if (impl->cache) { /* If we're checking the cache, make sure that tables are in the correct * empty/non-empty lists. */ flecs_process_pending_tables(q->world); if (!(impl->pub.flags & EcsQueryHasMonitor)) { flecs_query_init_query_monitors(impl); } /* Check cache entries for changes */ return flecs_query_check_cache_monitor(impl); } return false; } bool ecs_iter_changed( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_query_next, ECS_UNSUPPORTED, NULL); ecs_check(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = flecs_query_impl(qit->query); ecs_query_t *q = &impl->pub; /* First check for changes for terms with fixed sources, if query has any */ if (q->read_fields & q->fixed_fields) { /* Detecting changes for uncached terms is costly, so only do it once * per iteration. */ if (!(it->flags & EcsIterFixedInChangeComputed)) { it->flags |= EcsIterFixedInChangeComputed; ECS_BIT_COND(it->flags, EcsIterFixedInChanged, flecs_query_check_fixed_monitor(impl)); } if (it->flags & EcsIterFixedInChanged) { return true; } } /* If query has a cache, check for changes in current matched result */ if (impl->cache) { ecs_query_cache_table_match_t *qm = (ecs_query_cache_table_match_t*)it->priv_.iter.query.prev; ecs_check(qm != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_query_check_match_monitor(impl, qm, it); } error: return false; } void ecs_iter_skip( ecs_iter_t *it) { ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_assert(ECS_BIT_IS_SET(it->flags, EcsIterIsValid), ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; if (it->instance_count > it->count) { qit->skip_count ++; if (qit->skip_count == it->instance_count) { /* For non-instanced queries, make sure all entities are skipped */ it->flags |= EcsIterSkip; } } else { it->flags |= EcsIterSkip; } } /** * @file query/engine/eval.c * @brief Query engine implementation. */ // #define FLECS_QUERY_TRACE #ifdef FLECS_QUERY_TRACE static int flecs_query_trace_indent = 0; #endif static bool flecs_query_dispatch( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_select_w_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_id_t id, ecs_flags32_t filter_mask) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_id_record_t *idr = op_ctx->idr; ecs_table_record_t *tr; ecs_table_t *table; if (!redo) { if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { return false; } } else { if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { return false; } } } repeat: if (!redo || !op_ctx->remaining) { tr = flecs_table_cache_next(&op_ctx->it, ecs_table_record_t); if (!tr) { return false; } op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count - 1); table = tr->hdr.table; flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); } else { tr = (ecs_table_record_t*)op_ctx->it.cur; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); table = tr->hdr.table; op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); op_ctx->remaining --; } if (flecs_query_table_filter(table, op->other, filter_mask)) { goto repeat; } flecs_query_set_match(op, table, op_ctx->column, ctx); return true; } bool flecs_query_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_id_t id = 0; if (!redo) { id = flecs_query_op_get_id(op, ctx); } return flecs_query_select_w_id(op, redo, ctx, id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } static bool flecs_query_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_id_record_t *idr = op_ctx->idr; const ecs_table_record_t *tr; ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); if (!table) { return false; } if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } tr = flecs_id_record_get_table(idr, table); if (!tr) { return false; } op_ctx->column = flecs_ito(int16_t, tr->index); op_ctx->remaining = flecs_ito(int16_t, tr->count); } else { ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); if (--op_ctx->remaining <= 0) { return false; } op_ctx->column = flecs_query_next_column(table, idr->id, op_ctx->column); ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } flecs_query_set_match(op, table, op_ctx->column, ctx); return true; } static bool flecs_query_and( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_with(op, redo, ctx); } else { return flecs_query_select(op, redo, ctx); } } static bool flecs_query_select_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_filter) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; int8_t field = op->field_index; ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); if (!redo) { ecs_id_t id = it->ids[field]; ecs_id_record_t *idr = op_ctx->idr; if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } if (ctx->query->pub.flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)) { return false; } } else { if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { return false; } } } repeat: {} const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (flecs_query_table_filter(table, op->other, table_filter)) { goto repeat; } flecs_query_var_set_range(op, op->src.var, table, 0, 0, ctx); flecs_query_it_set_column(it, field, tr->index); return true; } static bool flecs_query_with_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); ecs_iter_t *it = ctx->it; int8_t field = op->field_index; ecs_assert(field != -1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = flecs_query_get_table(op, &op->src, EcsQuerySrc, ctx); if (!table) { return false; } ecs_id_t id = it->ids[field]; ecs_id_record_t *idr = op_ctx->idr; if (!idr || idr->id != id) { idr = op_ctx->idr = flecs_id_record_get(ctx->world, id); if (!idr) { return false; } } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return false; } flecs_query_it_set_column(it, field, tr->index); return true; } static bool flecs_query_and_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_with_id(op, redo, ctx); } else { return flecs_query_select_id(op, redo, ctx, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } } bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_world_t *world = ctx->world; ecs_iter_t *it = ctx->it; bool redo_select = redo; const ecs_query_t *q = &ctx->query->pub; bool self = trav_kind == FlecsQueryUpSelectSelfUp; /* Early out if traversal relationship doesn't exist */ op_ctx->trav = q->terms[op->term_index].trav; if (!op_ctx->idr_trav) { op_ctx->idr_trav = flecs_id_record_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)){ if (!self) { return false; } else if (kind == FlecsQueryUpSelectId) { return flecs_query_select_id(op, redo, ctx, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } else if (kind == FlecsQueryUpSelectDefault) { return flecs_query_select(op, redo, ctx); } else if (kind == FlecsQueryUpSelectUnion) { return flecs_query_union_select(op, redo, ctx); } else { /* Invalid select kind */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } } if (!redo) { op_ctx->with = flecs_query_op_get_id(op, ctx); op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); if (!op_ctx->idr_with) { return false; } op_ctx->down = NULL; op_ctx->cache_elem = 0; } ecs_trav_down_t *down = op_ctx->down; do { while (!down) { ecs_table_t *table = op_ctx->table; if (!table) { ecs_table_range_t range; it->sources[op->field_index] = 0; do { bool result; if (kind == FlecsQueryUpSelectId) { result = flecs_query_select_id(op, redo_select, ctx, 0); } else if (kind == FlecsQueryUpSelectDefault) { result = flecs_query_select_w_id(op, redo_select, ctx, op_ctx->with, 0); } else if (kind == FlecsQueryUpSelectUnion) { result = flecs_query_union_select(op, redo_select, ctx); } else { ecs_abort(ECS_INTERNAL_ERROR, NULL); } if (!result) { return false; } redo_select = true; range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); } while (!self && range.table->_->traversable_count == 0); if (!range.count) { range.count = ecs_table_count(range.table); } table = op_ctx->table = range.table; op_ctx->row = range.offset; op_ctx->end = range.offset + range.count; op_ctx->matched = it->ids[op->field_index]; if (self) { if (!flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { flecs_reset_source_set_flag(it, op->field_index); op_ctx->row --; return true; } } redo_select = true; } else { op_ctx->row ++; } if (table->_->traversable_count == 0) { op_ctx->table = NULL; continue; } else { int32_t row; ecs_entity_t entity = 0; ecs_entity_t *entities = flecs_table_entities_array(table); for (row = op_ctx->row; row < op_ctx->end; row ++) { entity = entities[row]; ecs_record_t *record = flecs_entities_get(world, entity); if (record->row & EcsEntityIsTraversable) { it->sources[op->field_index] = entity; break; } } if (row == op_ctx->end) { op_ctx->table = NULL; continue; } op_ctx->row = row; bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; down = op_ctx->down = flecs_query_get_down_cache(ctx, &op_ctx->cache, op_ctx->trav, entity, op_ctx->idr_with, self, match_empty); op_ctx->cache_elem = -1; } } next_elem: if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { down = NULL; continue; } ecs_trav_down_elem_t *elem = ecs_vec_get_t( &down->elems, ecs_trav_down_elem_t, op_ctx->cache_elem); flecs_query_var_set_range(op, op->src.var, elem->table, 0, 0, ctx); flecs_query_set_vars(op, op_ctx->matched, ctx); if (flecs_query_table_filter(elem->table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { goto next_elem; } break; } while (true); flecs_set_source_set_flag(it, op->field_index); return true; } bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { const ecs_query_t *q = &ctx->query->pub; ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; /* Early out if traversal relationship doesn't exist */ op_ctx->trav = q->terms[op->term_index].trav; if (!op_ctx->idr_trav) { op_ctx->idr_trav = flecs_id_record_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } if (!op_ctx->idr_trav || !flecs_table_cache_all_count(&op_ctx->idr_trav->cache)){ return false; } if (!redo) { op_ctx->trav = q->terms[op->term_index].trav; op_ctx->with = flecs_query_op_get_id(op, ctx); op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); if (!op_ctx->idr_with) { return false; } ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!range.table) { return false; } ecs_trav_up_t *up = flecs_query_get_up_cache(ctx, &op_ctx->cache, range.table, op_ctx->with, op_ctx->trav, op_ctx->idr_with, op_ctx->idr_trav); if (!up) { return false; } it->sources[op->field_index] = flecs_entities_get_alive( ctx->world, up->src); it->columns[op->field_index] = up->column; it->ids[op->field_index] = up->id; flecs_query_set_vars(op, up->id, ctx); flecs_set_source_set_flag(it, op->field_index); return true; } else { return false; } } bool flecs_query_self_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool id_only) { if (!redo) { bool result; if (id_only) { result = flecs_query_with_id(op, redo, ctx); ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); op_ctx->remaining = 1; } else { result = flecs_query_with(op, redo, ctx); } flecs_reset_source_set_flag(ctx->it, op->field_index); if (result) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); op_ctx->trav = 0; if (flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar) { ecs_iter_t *it = ctx->it; it->sources[op->field_index] = 0; } return true; } return flecs_query_up_with(op, redo, ctx); } else { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); if (op_ctx->trav == 0) { return flecs_query_with(op, redo, ctx); } } return false; } static bool flecs_query_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { return flecs_query_up_with(op, redo, ctx); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectUp, FlecsQueryUpSelectDefault); } } static bool flecs_query_self_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { return flecs_query_self_up_with(op, redo, ctx, false); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectDefault); } } static bool flecs_query_up_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { return flecs_query_up_with(op, redo, ctx); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectUp, FlecsQueryUpSelectId); } } static bool flecs_query_self_up_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { return flecs_query_self_up_with(op, redo, ctx, true); } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectId); } } static bool flecs_query_select_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_select_w_id(op, redo, ctx, EcsAny, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } static bool flecs_query_and_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t match_flags = op->match_flags; if (redo) { if (match_flags & EcsTermMatchAnySrc) { return false; } } uint64_t written = ctx->written[ctx->op_index]; int32_t remaining = 1; bool result; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { result = flecs_query_with(op, redo, ctx); } else { result = flecs_query_select(op, redo, ctx); remaining = 0; } if (!redo) { ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); if (match_flags & EcsTermMatchAny && op_ctx->remaining) { op_ctx->remaining = flecs_ito(int16_t, remaining); } } int32_t field = op->field_index; if (field != -1) { ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); } return result; } static bool flecs_query_only_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { return flecs_query_and_any(op, redo, ctx); } else { return flecs_query_select_any(op, redo, ctx); } } static bool flecs_query_triv( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); ecs_flags64_t termset = op->src.entity; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { flecs_query_set_iter_this(ctx->it, ctx); return flecs_query_trivial_test(ctx, redo, termset); } else { return flecs_query_trivial_search_nodata(ctx, op_ctx, redo, termset); } } static bool flecs_query_triv_data( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); ecs_flags64_t termset = op->src.entity; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { flecs_query_set_iter_this(ctx->it, ctx); return flecs_query_trivial_test(ctx, redo, termset); } else { return flecs_query_trivial_search(ctx, op_ctx, redo, termset); } } static bool flecs_query_triv_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); ecs_flags64_t termset = op->src.entity; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { flecs_query_set_iter_this(ctx->it, ctx); return flecs_query_trivial_test_w_wildcards( ctx, redo, termset); } else { return flecs_query_trivial_search_w_wildcards( ctx, op_ctx, redo, termset); } } static bool flecs_query_cache( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { (void)op; (void)redo; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_query_cache_test(ctx, redo); } else { return flecs_query_cache_search(ctx); } } static bool flecs_query_cache_data( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { (void)op; (void)redo; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_query_cache_data_test(ctx, redo); } else { return flecs_query_cache_data_search(ctx); } } static bool flecs_query_is_cache( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { (void)op; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_query_is_cache_test(ctx, redo); } else { return flecs_query_is_cache_search(ctx); } } static bool flecs_query_is_cache_data( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { (void)op; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { return flecs_query_is_cache_data_test(ctx, redo); } else { return flecs_query_is_cache_data_search(ctx); } } static int32_t flecs_query_next_inheritable_id( ecs_world_t *world, ecs_type_t *type, int32_t index) { int32_t i; for (i = index; i < type->count; i ++) { ecs_id_record_t *idr = flecs_id_record_get(world, type->array[i]); if (!(idr->flags & EcsIdOnInstantiateDontInherit)) { return i; } } return -1; } static bool flecs_query_x_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_oper_kind_t oper) { ecs_query_xfrom_ctx_t *op_ctx = flecs_op_ctx(ctx, xfrom); ecs_world_t *world = ctx->world; ecs_type_t *type; int32_t i; if (!redo) { /* Find entity that acts as the template from which we match the ids */ ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_assert(ecs_is_alive(world, id), ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, id); ecs_table_t *table; if (!r || !(table = r->table)) { /* Nothing to match */ return false; } /* Find first id to test against. Skip ids with DontInherit flag. */ type = op_ctx->type = &table->type; op_ctx->first_id_index = flecs_query_next_inheritable_id( world, type, 0); op_ctx->cur_id_index = op_ctx->first_id_index; if (op_ctx->cur_id_index == -1) { return false; /* No ids to filter on */ } } else { type = op_ctx->type; } ecs_id_t *ids = type->array; /* Check if source is variable, and if it's already written */ bool src_written = true; if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { uint64_t written = ctx->written[ctx->op_index]; src_written = written & (1ull << op->src.var); } do { int32_t id_index = op_ctx->cur_id_index; /* If source is not yet written, find tables with first id */ if (!src_written) { ecs_entity_t first_id = ids[id_index]; if (!flecs_query_select_w_id(op, redo, ctx, first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { if (oper == EcsOrFrom) { id_index = flecs_query_next_inheritable_id( world, type, id_index + 1); if (id_index != -1) { op_ctx->cur_id_index = id_index; redo = false; continue; } } return false; } id_index ++; /* First id got matched */ } else if (redo && src_written) { return false; } ecs_table_t *src_table = flecs_query_get_table( op, &op->src, EcsQuerySrc, ctx); if (!src_table) { continue; } redo = true; if (!src_written && oper == EcsOrFrom) { /* Eliminate duplicate matches from tables that have multiple * components from the type list */ if (op_ctx->cur_id_index != op_ctx->first_id_index) { for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); if (!idr) { continue; } if (idr->flags & EcsIdOnInstantiateDontInherit) { continue; } if (flecs_id_record_get_table(idr, src_table) != NULL) { /* Already matched */ break; } } if (i != op_ctx->cur_id_index) { continue; } } return true; } if (oper == EcsAndFrom || oper == EcsNotFrom || src_written) { for (i = id_index; i < type->count; i ++) { ecs_id_record_t *idr = flecs_id_record_get(world, ids[i]); if (!idr) { if (oper == EcsAndFrom) { return false; } else { continue; } } if (idr->flags & EcsIdOnInstantiateDontInherit) { continue; } if (flecs_id_record_get_table(idr, src_table) == NULL) { if (oper == EcsAndFrom) { break; /* Must have all ids */ } } else { if (oper == EcsNotFrom) { break; /* Must have none of the ids */ } else if (oper == EcsOrFrom) { return true; /* Single match is enough */ } } } if (i == type->count) { if (oper == EcsAndFrom || oper == EcsNotFrom) { break; /* All ids matched */ } } } } while (true); return true; } static bool flecs_query_and_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsAndFrom); } static bool flecs_query_not_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsNotFrom); } static bool flecs_query_or_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsOrFrom); } static bool flecs_query_ids( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_id_record_t *cur; ecs_id_t id = flecs_query_op_get_id(op, ctx); { cur = flecs_id_record_get(ctx->world, id); if (!cur || !cur->cache.tables.count) { return false; } } flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; it->columns[op->field_index] = -1; /* Mark field as set */ } return true; } static bool flecs_query_idsright( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_id_record_t *cur; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (!ecs_id_is_wildcard(id)) { /* If id is not a wildcard, we can directly return it. This can * happen if a variable was constrained by an iterator. */ op_ctx->cur = NULL; flecs_query_set_vars(op, id, ctx); return true; } cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); if (!cur) { return false; } } else { if (!op_ctx->cur) { return false; } } do { cur = op_ctx->cur = op_ctx->cur->first.next; } while (cur && !cur->cache.tables.count); /* Skip empty ids */ if (!cur) { return false; } flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } return true; } static bool flecs_query_idsleft( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_id_record_t *cur; if (!redo) { ecs_id_t id = flecs_query_op_get_id(op, ctx); if (!ecs_id_is_wildcard(id)) { /* If id is not a wildcard, we can directly return it. This can * happen if a variable was constrained by an iterator. */ op_ctx->cur = NULL; flecs_query_set_vars(op, id, ctx); return true; } cur = op_ctx->cur = flecs_id_record_get(ctx->world, id); if (!cur) { return false; } } else { if (!op_ctx->cur) { return false; } } do { cur = op_ctx->cur = op_ctx->cur->second.next; } while (cur && !cur->cache.tables.count); /* Skip empty ids */ if (!cur) { return false; } flecs_query_set_vars(op, cur->id, ctx); if (op->field_index != -1) { ecs_iter_t *it = ctx->it; ecs_id_t id = flecs_query_op_get_id_w_written(op, op->written, ctx); it->ids[op->field_index] = id; it->sources[op->field_index] = EcsWildcard; ECS_TERMSET_SET(it->set_fields, 1u << op->field_index); } return true; } static bool flecs_query_each( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_each_ctx_t *op_ctx = flecs_op_ctx(ctx, each); int32_t row; ecs_table_range_t range = flecs_query_var_get_range(op->first.var, ctx); ecs_table_t *table = range.table; if (!table) { return false; } if (!redo) { row = op_ctx->row = range.offset; } else { int32_t end = range.count; if (end) { end += range.offset; } else { end = table->data.entities.count; } row = ++ op_ctx->row; if (op_ctx->row >= end) { return false; } } ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = table->data.entities.array; flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); return true; } static bool flecs_query_store( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (!redo) { flecs_query_var_set_entity(op, op->src.var, op->first.entity, ctx); return true; } else { return false; } } static bool flecs_query_reset( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (!redo) { return true; } else { flecs_query_var_reset(op->src.var, ctx); return false; } } static bool flecs_query_lookup( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } const ecs_query_impl_t *query = ctx->query; ecs_entity_t first = flecs_query_var_get_entity(op->first.var, ctx); ecs_query_var_t *var = &query->vars[op->src.var]; ecs_entity_t result = ecs_lookup_path_w_sep(ctx->world, first, var->lookup, NULL, NULL, false); if (!result) { flecs_query_var_set_entity(op, op->src.var, EcsWildcard, ctx); return false; } flecs_query_var_set_entity(op, op->src.var, result, ctx); return true; } static bool flecs_query_setvars( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; ecs_var_id_t *src_vars = query->src_vars; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; ecs_flags32_t up_fields = it->up_fields; for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = src_vars[i]; if (!var_id) { continue; } if (up_fields & (1u << i)) { continue; } it->sources[i] = flecs_query_var_get_entity(var_id, ctx); } return true; } static bool flecs_query_setthis( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_query_setthis_ctx_t *op_ctx = flecs_op_ctx(ctx, setthis); ecs_var_t *vars = ctx->vars; ecs_var_t *this_var = &vars[op->first.var]; if (!redo) { /* Save values so we can restore them later */ op_ctx->range = vars[0].range; /* Constrain This table variable to a single entity from the table */ vars[0].range = flecs_range_from_entity(this_var->entity, ctx); vars[0].entity = this_var->entity; return true; } else { /* Restore previous values, so that instructions that are operating on * the table variable use all the entities in the table. */ vars[0].range = op_ctx->range; vars[0].entity = 0; return false; } } static bool flecs_query_setfixed( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; for (i = 0; i < q->term_count; i ++) { const ecs_term_t *term = &q->terms[i]; const ecs_term_ref_t *src = &term->src; if (src->id & EcsIsEntity) { it->sources[term->field_index] = ECS_TERM_REF_ID(src); } } return true; } bool flecs_query_setids( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; ecs_iter_t *it = ctx->it; if (redo) { return false; } int32_t i; for (i = 0; i < q->term_count; i ++) { const ecs_term_t *term = &q->terms[i]; it->ids[term->field_index] = term->id; } return true; } static bool flecs_query_setid( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_assert(op->field_index != -1, ECS_INTERNAL_ERROR, NULL); ctx->it->ids[op->field_index] = op->first.entity; return true; } /* Check if entity is stored in table */ static bool flecs_query_contain( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_var_id_t src_id = op->src.var; ecs_var_id_t first_id = op->first.var; ecs_table_t *table = flecs_query_var_get_table(src_id, ctx); ecs_entity_t e = flecs_query_var_get_entity(first_id, ctx); return table == ecs_get_table(ctx->world, e); } /* Check if first and second id of pair from last operation are the same */ static bool flecs_query_pair_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } ecs_iter_t *it = ctx->it; ecs_id_t id = it->ids[op->field_index]; return ECS_PAIR_FIRST(id) == ECS_PAIR_SECOND(id); } static void flecs_query_reset_after_block( const ecs_query_op_t *start_op, ecs_query_run_ctx_t *ctx, ecs_query_ctrl_ctx_t *op_ctx, bool result) { ecs_query_lbl_t op_index = start_op->next; const ecs_query_op_t *op = &ctx->qit->ops[op_index]; int32_t field = op->field_index; if (field == -1) { goto done; } /* Set/unset field */ ecs_iter_t *it = ctx->it; if (result) { ECS_TERMSET_SET(it->set_fields, 1u << field); return; } /* Reset state after a field was not matched */ ctx->written[op_index] = ctx->written[ctx->op_index]; ctx->op_index = op_index; ECS_TERMSET_CLEAR(it->set_fields, 1u << field); /* Ignore variables written by Not operation */ uint64_t *written = ctx->written; uint64_t written_cur = written[op->prev + 1]; ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); /* Overwrite id with cleared out variables */ ecs_id_t id = flecs_query_op_get_id(op, ctx); if (id) { it->ids[field] = id; } /* Reset variables */ if (flags_1st & EcsQueryIsVar) { if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ flecs_query_var_reset(op->first.var, ctx); } } if (flags_2nd & EcsQueryIsVar) { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ flecs_query_var_reset(op->second.var, ctx); } } /* If term has entity src, set it because no other instruction might */ if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { it->sources[field] = op->src.entity; } done: op_ctx->op_index = op_index; } static bool flecs_query_run_block( bool redo, ecs_query_run_ctx_t *ctx, ecs_query_ctrl_ctx_t *op_ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; if (!redo) { op_ctx->op_index = flecs_itolbl(ctx->op_index + 1); } else if (ctx->qit->ops[op_ctx->op_index].kind == EcsQueryEnd) { return false; } ctx->written[ctx->op_index + 1] = ctx->written[ctx->op_index]; const ecs_query_op_t *op = &ctx->qit->ops[ctx->op_index]; bool result = flecs_query_run_until( redo, ctx, qit->ops, ctx->op_index, op_ctx->op_index, op->next); op_ctx->op_index = flecs_itolbl(ctx->op_index - 1); return result; } static ecs_query_lbl_t flecs_query_last_op_for_or_cond( const ecs_query_op_t *ops, ecs_query_lbl_t cur, ecs_query_lbl_t last) { const ecs_query_op_t *cur_op, *last_op = &ops[last]; do { cur_op = &ops[cur]; cur ++; } while (cur_op->next != last && cur_op != last_op); return cur; } static bool flecs_query_run_until_for_select_or( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last) { ecs_query_lbl_t last_for_cur = flecs_query_last_op_for_or_cond( ops, cur, flecs_itolbl(last)); if (redo) { /* If redoing, start from the last instruction of the last executed * sequence */ cur = flecs_itolbl(last_for_cur - 1); } flecs_query_run_until(redo, ctx, ops, first, cur, last_for_cur); #ifdef FLECS_QUERY_TRACE printf("%*s%s (or)\n", (flecs_query_trace_indent + 1)*2, "", ctx->op_index == last ? "true" : "false"); #endif return ctx->op_index == last; } static bool flecs_query_select_or( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); ecs_query_lbl_t first = flecs_itolbl(ctx->op_index + 1); if (!redo) { op_ctx->op_index = first; } const ecs_query_op_t *ops = qit->ops; const ecs_query_op_t *first_op = &ops[first - 1]; ecs_query_lbl_t last = first_op->next; const ecs_query_op_t *last_op = &ops[last]; const ecs_query_op_t *cur_op = &ops[op_ctx->op_index]; bool result = false; do { ecs_query_lbl_t cur = op_ctx->op_index; ctx->op_index = cur; ctx->written[cur] = op->written; result = flecs_query_run_until_for_select_or( redo, ctx, ops, flecs_itolbl(first - 1), cur, last); if (result) { if (first == cur) { break; } /* If a previous operation in the OR chain returned a result for the * same matched source, skip it so we don't yield for each matching * element in the chain. */ /* Copy written status so that the variables we just wrote will show * up as written for the test. This ensures that the instructions * match against the result we already found, vs. starting a new * search (the difference between select & with). */ ecs_query_lbl_t prev = first; bool dup_found = false; /* While terms of an OR chain always operate on the same source, it * is possible that a table variable is resolved to an entity * variable. When checking for duplicates, copy the entity variable * to the table, to ensure we're only testing the found entity. */ const ecs_query_op_t *prev_op = &ops[cur - 1]; ecs_var_t old_table_var; ecs_os_memset_t(&old_table_var, 0, ecs_var_t); bool restore_table_var = false; if (prev_op->flags & (EcsQueryIsVar << EcsQuerySrc)) { if (first_op->src.var != prev_op->src.var) { restore_table_var = true; old_table_var = ctx->vars[first_op->src.var]; ctx->vars[first_op->src.var] = ctx->vars[prev_op->src.var]; } } do { ctx->written[prev] = ctx->written[last]; flecs_query_run_until(false, ctx, ops, flecs_itolbl(first - 1), prev, cur); if (ctx->op_index == last) { /* Duplicate match was found, find next result */ redo = true; dup_found = true; break; } break; } while (true); /* Restore table variable to full range for next result */ if (restore_table_var) { ctx->vars[first_op->src.var] = old_table_var; } if (dup_found) { continue; } break; } /* No result was found, go to next operation in chain */ op_ctx->op_index = flecs_query_last_op_for_or_cond( ops, op_ctx->op_index, last); cur_op = &qit->ops[op_ctx->op_index]; redo = false; } while (cur_op != last_op); return result; } static bool flecs_query_with_or( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); bool result = flecs_query_run_block(redo, ctx, op_ctx); if (result) { /* If a match was found, no need to keep searching for this source */ op_ctx->op_index = op->next; } return result; } static bool flecs_query_or( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { uint64_t written = ctx->written[ctx->op_index]; if (!(written & (1ull << op->src.var))) { return flecs_query_select_or(op, redo, ctx); } } return flecs_query_with_or(op, redo, ctx); } static bool flecs_query_run_block_w_reset( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); bool result = flecs_query_run_block(redo, ctx, op_ctx); flecs_query_reset_after_block(op, ctx, op_ctx, result); return result; } static bool flecs_query_not( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (redo) { return false; } return !flecs_query_run_block_w_reset(op, redo, ctx); } static bool flecs_query_optional( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { bool result = flecs_query_run_block_w_reset(op, redo, ctx); if (!redo) { return true; /* Return at least once */ } else { return result; } } static bool flecs_query_eval_if( const ecs_query_op_t *op, ecs_query_run_ctx_t *ctx, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind) { bool result = true; if (flecs_query_ref_flags(op->flags, ref_kind) == EcsQueryIsVar) { result = ctx->vars[ref->var].entity != EcsWildcard; ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); flecs_query_reset_after_block(op, ctx, op_ctx, result); return result; } return true; } static bool flecs_query_if_var( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { if (!redo) { if (!flecs_query_eval_if(op, ctx, &op->src, EcsQuerySrc) || !flecs_query_eval_if(op, ctx, &op->first, EcsQueryFirst) || !flecs_query_eval_if(op, ctx, &op->second, EcsQuerySecond)) { return true; } } ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); return flecs_query_run_block(redo, ctx, op_ctx); } static bool flecs_query_if_set( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; uint8_t field_index = flecs_ito(uint8_t, op->other); ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); if (!redo) { op_ctx->is_set = ecs_field_is_set(it, field_index); } if (!op_ctx->is_set) { return !redo; } return flecs_query_run_block(redo, ctx, op_ctx); } static bool flecs_query_end( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; (void)ctx; return !redo; } static bool flecs_query_dispatch( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { switch(op->kind) { case EcsQueryAnd: return flecs_query_and(op, redo, ctx); case EcsQueryAndId: return flecs_query_and_id(op, redo, ctx); case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); case EcsQueryTrivData: return flecs_query_triv_data(op, redo, ctx); case EcsQueryTrivWildcard: return flecs_query_triv_wildcard(op, redo, ctx); case EcsQueryCache: return flecs_query_cache(op, redo, ctx); case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); case EcsQueryCacheData: return flecs_query_cache_data(op, redo, ctx); case EcsQueryIsCacheData: return flecs_query_is_cache_data(op, redo, ctx); case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); case EcsQueryUp: return flecs_query_up(op, redo, ctx); case EcsQueryUpId: return flecs_query_up_id(op, redo, ctx); case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); case EcsQuerySelfUpId: return flecs_query_self_up_id(op, redo, ctx); case EcsQueryWith: return flecs_query_with(op, redo, ctx); case EcsQueryTrav: return flecs_query_trav(op, redo, ctx); case EcsQueryAndFrom: return flecs_query_and_from(op, redo, ctx); case EcsQueryNotFrom: return flecs_query_not_from(op, redo, ctx); case EcsQueryOrFrom: return flecs_query_or_from(op, redo, ctx); case EcsQueryIds: return flecs_query_ids(op, redo, ctx); case EcsQueryIdsRight: return flecs_query_idsright(op, redo, ctx); case EcsQueryIdsLeft: return flecs_query_idsleft(op, redo, ctx); case EcsQueryEach: return flecs_query_each(op, redo, ctx); case EcsQueryStore: return flecs_query_store(op, redo, ctx); case EcsQueryReset: return flecs_query_reset(op, redo, ctx); case EcsQueryOr: return flecs_query_or(op, redo, ctx); case EcsQueryOptional: return flecs_query_optional(op, redo, ctx); case EcsQueryIfVar: return flecs_query_if_var(op, redo, ctx); case EcsQueryIfSet: return flecs_query_if_set(op, redo, ctx); case EcsQueryEnd: return flecs_query_end(op, redo, ctx); case EcsQueryNot: return flecs_query_not(op, redo, ctx); case EcsQueryPredEq: return flecs_query_pred_eq(op, redo, ctx); case EcsQueryPredNeq: return flecs_query_pred_neq(op, redo, ctx); case EcsQueryPredEqName: return flecs_query_pred_eq_name(op, redo, ctx); case EcsQueryPredNeqName: return flecs_query_pred_neq_name(op, redo, ctx); case EcsQueryPredEqMatch: return flecs_query_pred_eq_match(op, redo, ctx); case EcsQueryPredNeqMatch: return flecs_query_pred_neq_match(op, redo, ctx); case EcsQueryMemberEq: return flecs_query_member_eq(op, redo, ctx); case EcsQueryMemberNeq: return flecs_query_member_neq(op, redo, ctx); case EcsQueryToggle: return flecs_query_toggle(op, redo, ctx); case EcsQueryToggleOption: return flecs_query_toggle_option(op, redo, ctx); case EcsQueryUnionEq: return flecs_query_union(op, redo, ctx); case EcsQueryUnionEqWith: return flecs_query_union_with(op, redo, ctx, false); case EcsQueryUnionNeq: return flecs_query_union_neq(op, redo, ctx); case EcsQueryUnionEqUp: return flecs_query_union_up(op, redo, ctx); case EcsQueryUnionEqSelfUp: return flecs_query_union_self_up(op, redo, ctx); case EcsQueryLookup: return flecs_query_lookup(op, redo, ctx); case EcsQuerySetVars: return flecs_query_setvars(op, redo, ctx); case EcsQuerySetThis: return flecs_query_setthis(op, redo, ctx); case EcsQuerySetFixed: return flecs_query_setfixed(op, redo, ctx); case EcsQuerySetIds: return flecs_query_setids(op, redo, ctx); case EcsQuerySetId: return flecs_query_setid(op, redo, ctx); case EcsQueryContain: return flecs_query_contain(op, redo, ctx); case EcsQueryPairEq: return flecs_query_pair_eq(op, redo, ctx); case EcsQueryPopulate: return flecs_query_populate(op, redo, ctx); case EcsQueryPopulateSelf: return flecs_query_populate_self(op, redo, ctx); case EcsQueryPopulateSparse: return flecs_query_populate_sparse(op, redo, ctx); case EcsQueryYield: return false; case EcsQueryNothing: return false; } return false; } bool flecs_query_run_until( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last) { ecs_assert(first >= -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur > first, ECS_INTERNAL_ERROR, NULL); ctx->op_index = cur; const ecs_query_op_t *op = &ops[ctx->op_index]; const ecs_query_op_t *last_op = &ops[last]; ecs_assert(last > first, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_QUERY_TRACE printf("%*sblock:\n", flecs_query_trace_indent*2, ""); flecs_query_trace_indent ++; #endif do { #ifdef FLECS_DEBUG ctx->qit->profile[ctx->op_index].count[redo] ++; #endif #ifdef FLECS_QUERY_TRACE printf("%*s%d: %s\n", flecs_query_trace_indent*2, "", ctx->op_index, flecs_query_op_str(op->kind)); #endif bool result = flecs_query_dispatch(op, redo, ctx); cur = (&op->prev)[result]; redo = cur < ctx->op_index; if (!redo) { ctx->written[cur] |= ctx->written[ctx->op_index] | op->written; } ctx->op_index = cur; op = &ops[ctx->op_index]; if (cur <= first) { #ifdef FLECS_QUERY_TRACE printf("%*sfalse\n", flecs_query_trace_indent*2, ""); flecs_query_trace_indent --; #endif return false; } } while (op < last_op); #ifdef FLECS_QUERY_TRACE printf("%*strue\n", flecs_query_trace_indent*2, ""); flecs_query_trace_indent --; #endif return true; } /** * @file query/engine/eval_iter.c * @brief Query iterator. */ static void flecs_query_iter_run_ctx_init( ecs_iter_t *it, ecs_query_run_ctx_t *ctx) { ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); ctx->world = it->real_world; ctx->query = impl; ctx->it = it; ctx->vars = qit->vars; ctx->query_vars = qit->query_vars; ctx->written = qit->written; ctx->op_ctx = qit->op_ctx; ctx->qit = qit; } void flecs_query_iter_constrain( ecs_iter_t *it) { ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); ecs_assert(ctx.written != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_query_impl_t *query = ctx.query; const ecs_query_t *q = &query->pub; ecs_flags64_t it_written = it->constrained_vars; ctx.written[0] = it_written; if (it_written && ctx.query->src_vars) { /* If variables were constrained, check if there are any table * variables that have a constrained entity variable. */ ecs_var_t *vars = ctx.vars; int32_t i, count = q->field_count; for (i = 0; i < count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; ecs_query_var_t *var = &query->vars[var_id]; if (!(it_written & (1ull << var_id)) || (var->kind == EcsVarTable) || (var->table_id == EcsVarNone)) { continue; } /* Initialize table variable with constrained entity variable */ ecs_var_t *tvar = &vars[var->table_id]; tvar->range = flecs_range_from_entity(vars[var_id].entity, &ctx); ctx.written[0] |= (1ull << var->table_id); /* Mark as written */ } } /* This function can be called multiple times when setting variables, so * reset flags before setting them. */ it->flags &= ~(EcsIterTrivialTestWildcard|EcsIterTrivialTest| EcsIterTrivialTestNoData|EcsIterTrivialCached| EcsIterTrivialSearchWildcard|EcsIterTrivialSearchNoData| EcsIterTrivialSearch); /* Figure out whether this query can utilize specialized iterator modes for * improved performance. */ ecs_flags32_t flags = q->flags; ecs_query_cache_t *cache = query->cache; bool no_data = !!((flags & EcsQueryNoData) || (it->flags & EcsIterNoData)); if (flags & EcsQueryIsTrivial) { if ((flags & EcsQueryMatchOnlySelf)) { if (it_written) { /* When we're testing against an entity or table, set the $this * variable in advance since it won't change later on. This * initializes it.count, it.entities and it.table. */ flecs_query_set_iter_this(it, &ctx); if (!cache) { if (flags & EcsQueryMatchWildcards) { it->flags |= EcsIterTrivialTestWildcard; } else { it->flags |= EcsIterTrivialTest; } } else if (flags & EcsQueryIsCacheable) { if (no_data) { it->flags |= EcsIterTrivialTestNoData|EcsIterTrivialCached; } else { it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; } } } else { if (!cache) { if (flags & EcsQueryMatchWildcards) { it->flags |= EcsIterTrivialSearchWildcard; } else if (no_data) { it->flags |= EcsIterTrivialSearchNoData; } else { it->flags |= EcsIterTrivialSearch; } } else if (flags & EcsQueryIsCacheable) { if (!cache->order_by_callback) { if (no_data) { it->flags |= EcsIterTrivialSearchNoData|EcsIterTrivialCached; } else { it->flags |= EcsIterTrivialSearch|EcsIterTrivialCached; } } } } /* If we're using a specialized iterator mode, make sure to * initialize static component ids. Usually this is the first * instruction of a query plan, but because we're not running the * query plan when using a specialized iterator mode, manually call * the operation on iterator init. */ flecs_query_setids(&query->ops[0], false, &ctx); } } } bool flecs_query_next_instanced( ecs_iter_t *it) { ecs_assert(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(it->next == ecs_query_next, ECS_INVALID_PARAMETER, NULL); ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_query_impl_t *impl = ECS_CONST_CAST(ecs_query_impl_t*, qit->query); ecs_query_run_ctx_t ctx; flecs_query_iter_run_ctx_init(it, &ctx); const ecs_query_op_t *ops = qit->ops; bool redo = it->flags & EcsIterIsValid; if (redo) { /* Change detection */ if (!(it->flags & EcsIterSkip)) { /* Mark table columns that are written to dirty */ flecs_query_mark_fields_dirty(impl, it); if (qit->prev) { if (ctx.query->pub.flags & EcsQueryHasMonitor) { /* If this query uses change detection, synchronize the * monitor for the iterated table with the query */ flecs_query_sync_match_monitor(impl, qit->prev); } } } } it->flags &= ~(EcsIterSkip); it->flags |= EcsIterIsValid; it->frame_offset += it->count; /* Specialized iterator modes. When a query doesn't use any advanced * features, it can call specialized iterator functions directly instead of * going through the dispatcher of the query engine. * The iterator mode is set during iterator initialization. Besides being * determined by the query, there are different modes for searching and * testing, where searching returns all matches for a query, whereas testing * tests a single table or table range against the query. */ if (it->flags & EcsIterTrivialCached) { /* Cached iterator modes */ if (it->flags & EcsIterTrivialSearch) { if (flecs_query_is_cache_data_search(&ctx)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialSearchNoData) { if (flecs_query_is_cache_search(&ctx)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialTest) { if (flecs_query_is_cache_data_test(&ctx, redo)) { goto yield; } } else if (it->flags & EcsIterTrivialTestNoData) { if (flecs_query_is_cache_test(&ctx, redo)) { goto yield; } } } else { /* Uncached iterator modes */ /* Uncached iterator functions accept a mask which marks the terms that * can be trivially iterated. If the entire query is trivial, create a * mask that marks all terms. */ int32_t fields = ctx.query->pub.term_count; ecs_flags64_t mask = (2llu << (fields - 1)) - 1; if (it->flags & EcsIterTrivialSearch) { ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; if (flecs_query_trivial_search(&ctx, op_ctx, redo, mask)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialSearchNoData) { ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; if (flecs_query_trivial_search_nodata(&ctx, op_ctx, redo, mask)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialSearchWildcard) { ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; if (flecs_query_trivial_search_w_wildcards(&ctx, op_ctx, redo, mask)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialTest) { if (flecs_query_trivial_test(&ctx, redo, mask)) { goto yield; } } else if (it->flags & EcsIterTrivialTestWildcard) { if (flecs_query_trivial_test_w_wildcards(&ctx, redo, mask)) { goto yield; } } else { /* Default iterator mode. This enters the query VM dispatch loop. */ if (flecs_query_run_until( redo, &ctx, ops, -1, qit->op, impl->op_count - 1)) { ecs_assert(ops[ctx.op_index].kind == EcsQueryYield, ECS_INTERNAL_ERROR, NULL); flecs_query_set_iter_this(it, &ctx); ecs_assert(it->count >= 0, ECS_INTERNAL_ERROR, NULL); qit->op = flecs_itolbl(ctx.op_index - 1); goto yield; } } } /* Done iterating */ flecs_query_mark_fixed_fields_dirty(impl, it); if (ctx.query->monitor) { flecs_query_update_fixed_monitor( ECS_CONST_CAST(ecs_query_impl_t*, ctx.query)); } ecs_iter_fini(it); return false; trivial_search_yield: it->table = ctx.vars[0].range.table; it->count = ecs_table_count(it->table); it->entities = flecs_table_entities_array(it->table); yield: return true; } 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, flecs_query_next_instanced(it)); error: return false; } static void flecs_query_iter_fini_ctx( ecs_iter_t *it, ecs_query_iter_t *qit) { const ecs_query_impl_t *query = flecs_query_impl(qit->query); int32_t i, count = query->op_count; ecs_query_op_t *ops = query->ops; ecs_query_op_ctx_t *ctx = qit->op_ctx; ecs_allocator_t *a = flecs_query_get_allocator(it); for (i = 0; i < count; i ++) { ecs_query_op_t *op = &ops[i]; switch(op->kind) { case EcsQueryTrav: flecs_query_trav_cache_fini(a, &ctx[i].is.trav.cache); break; case EcsQueryUp: case EcsQuerySelfUp: case EcsQueryUpId: case EcsQuerySelfUpId: case EcsQueryUnionEqUp: case EcsQueryUnionEqSelfUp: { ecs_trav_up_cache_t *cache = &ctx[i].is.up.cache; if (cache->dir == EcsTravDown) { flecs_query_down_cache_fini(a, cache); } else { flecs_query_up_cache_fini(cache); } break; } default: break; } } } static void flecs_query_iter_fini( ecs_iter_t *it) { ecs_query_iter_t *qit = &it->priv_.iter.query; ecs_assert(qit->query != NULL, ECS_INTERNAL_ERROR, NULL); flecs_poly_assert(qit->query, ecs_query_t); int32_t op_count = flecs_query_impl(qit->query)->op_count; int32_t var_count = flecs_query_impl(qit->query)->var_count; #ifdef FLECS_DEBUG if (it->flags & EcsIterProfile) { char *str = ecs_query_plan_w_profile(qit->query, it); printf("%s\n", str); ecs_os_free(str); } flecs_iter_free_n(qit->profile, ecs_query_op_profile_t, op_count); #endif flecs_query_iter_fini_ctx(it, qit); flecs_iter_free_n(qit->vars, ecs_var_t, var_count); flecs_iter_free_n(qit->written, ecs_write_flags_t, op_count); flecs_iter_free_n(qit->op_ctx, ecs_query_op_ctx_t, op_count); qit->vars = NULL; qit->written = NULL; qit->op_ctx = NULL; qit->query = NULL; } ecs_iter_t flecs_query_iter( const ecs_world_t *world, const ecs_query_t *q) { ecs_iter_t it = {0}; ecs_query_iter_t *qit = &it.priv_.iter.query; ecs_check(q != NULL, ECS_INVALID_PARAMETER, NULL); flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); int32_t i, var_count = impl->var_count, op_count = impl->op_count; it.world = ECS_CONST_CAST(ecs_world_t*, world); /* If world passed to iterator is the real world, but query was created from * a stage, stage takes precedence. */ if (flecs_poly_is(it.world, ecs_world_t) && flecs_poly_is(q->world, ecs_stage_t)) { it.world = ECS_CONST_CAST(ecs_world_t*, q->world); } it.real_world = q->real_world; ecs_assert(flecs_poly_is(it.real_world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_check(!(it.real_world->flags & EcsWorldMultiThreaded) || it.world != it.real_world, ECS_INVALID_PARAMETER, "create iterator for stage when world is in multithreaded mode"); it.query = q; it.system = q->entity; it.next = ecs_query_next; it.fini = flecs_query_iter_fini; it.field_count = q->field_count; it.sizes = q->sizes; it.set_fields = q->set_fields; it.up_fields = 0; flecs_query_apply_iter_flags(&it, q); flecs_iter_init(it.world, &it, flecs_iter_cache_ids | flecs_iter_cache_columns | flecs_iter_cache_sources | flecs_iter_cache_ptrs); qit->query = q; qit->query_vars = impl->vars; qit->ops = impl->ops; ecs_query_cache_t *cache = impl->cache; if (cache) { qit->node = cache->list.first; qit->last = cache->list.last; if (cache->order_by_callback && cache->list.info.table_count) { flecs_query_cache_sort_tables(it.real_world, impl); qit->node = ecs_vec_first(&cache->table_slices); qit->last = ecs_vec_last_t( &cache->table_slices, ecs_query_cache_table_match_t); } cache->prev_match_count = cache->match_count; } if (var_count) { qit->vars = flecs_iter_calloc_n(&it, ecs_var_t, var_count); } if (op_count) { qit->written = flecs_iter_calloc_n(&it, ecs_write_flags_t, op_count); qit->op_ctx = flecs_iter_calloc_n(&it, ecs_query_op_ctx_t, op_count); } #ifdef FLECS_DEBUG qit->profile = flecs_iter_calloc_n(&it, ecs_query_op_profile_t, op_count); #endif for (i = 1; i < var_count; i ++) { qit->vars[i].entity = EcsWildcard; } it.variables = qit->vars; it.variable_count = impl->pub.var_count; it.variable_names = impl->pub.vars; /* Set flags for unconstrained query iteration. Can be reinitialized when * variables are constrained on iterator. */ flecs_query_iter_constrain(&it); error: return it; } ecs_iter_t ecs_query_iter( const ecs_world_t *world, const ecs_query_t *q) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(q != NULL, ECS_INVALID_PARAMETER, NULL); if (!(q->flags & EcsQueryCacheYieldEmptyTables)) { ecs_run_aperiodic(q->real_world, EcsAperiodicEmptyTables); } /* Ok, only for stats */ ECS_CONST_CAST(ecs_query_t*, q)->eval_count ++; ecs_query_impl_t *impl = flecs_query_impl(q); ecs_query_cache_t *cache = impl->cache; if (cache) { /* If monitors changed, do query rematching */ ecs_flags32_t flags = q->flags; if (!(world->flags & EcsWorldReadonly) && flags & EcsQueryHasRefs) { flecs_eval_component_monitors(q->world); } } return flecs_query_iter(world, q); } /** * @file query/engine/eval_member.c * @brief Component member evaluation. */ static bool flecs_query_member_cmp( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, bool neq) { ecs_table_range_t range; if (op->other) { ecs_var_id_t table_var = flecs_itovar(op->other - 1); range = flecs_query_var_get_range(table_var, ctx); } else { range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); } ecs_table_t *table = range.table; if (!table) { return false; } ecs_query_membereq_ctx_t *op_ctx = flecs_op_ctx(ctx, membereq); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; if (!range.count) { range.count = ecs_table_count(range.table); } int32_t row, end = range.count; if (end) { end += range.offset; } else { end = ecs_table_count(range.table); } void *data; if (!redo) { row = op_ctx->each.row = range.offset; /* Get data ptr starting from offset 0 so we can use row to index */ range.offset = 0; /* Populate data field so we have the array we can compare the member * value against. */ void *old_data = it->ptrs[field_index]; flecs_query_populate_field_from_range( it, &range, field_index, it->columns[field_index]); data = op_ctx->data = it->ptrs[field_index]; /* Ensure we only write ptrs when we match data */ it->ptrs[field_index] = old_data; /* Member fields are of type ecs_entity_t */ #ifdef FLECS_META it->ids[field_index] = ecs_id(ecs_entity_t); #else it->ids[field_index] = 0; #endif } else { row = ++ op_ctx->each.row; if (op_ctx->each.row >= end) { return false; } data = op_ctx->data; } int32_t offset = (int32_t)op->first.entity; int32_t size = (int32_t)(op->first.entity >> 32); ecs_entity_t *entities = table->data.entities.array; ecs_entity_t e = 0; ecs_entity_t *val; ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); bool second_written = true; if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { uint64_t written = ctx->written[ctx->op_index]; second_written = written & (1ull << op->second.var); } if (second_written) { ecs_flags16_t second_flags = flecs_query_ref_flags( op->flags, EcsQuerySecond); ecs_entity_t second = flecs_get_ref_entity( &op->second, second_flags, ctx); do { e = entities[row]; val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); if (val[0] == second || second == EcsWildcard) { if (!neq) { goto match; } } else { if (neq) { goto match; } } row ++; } while (row < end); return false; } else { e = entities[row]; val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); flecs_query_var_set_entity(op, op->second.var, val[0], ctx); } match: if (op->other) { ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); flecs_query_var_set_entity(op, op->src.var, e, ctx); } it->ptrs[field_index] = val; op_ctx->each.row = row; return true; } bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_member_cmp(op, redo, ctx, false); } bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_member_cmp(op, redo, ctx, true); } /** * @file query/engine/eval_populate.c * @brief Populate data fields. */ void flecs_query_populate_field_from_range( ecs_iter_t *it, ecs_table_range_t *range, int8_t field_index, int32_t index) { ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(range->table != NULL, ECS_INTERNAL_ERROR, NULL); if (range->count && range->table->column_map) { int32_t column = range->table->column_map[index]; if (column != -1) { it->ptrs[field_index] = ECS_ELEM( range->table->data.columns[column].data.array, it->sizes[field_index], range->offset); } } } static void flecs_query_populate_field( ecs_iter_t *it, ecs_table_range_t *range, int8_t field_index, ecs_query_run_ctx_t *ctx) { int32_t index = it->columns[field_index]; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_field_is_set(it, field_index), ECS_INTERNAL_ERROR, NULL); ecs_entity_t src = it->sources[field_index]; if (!src) { flecs_query_populate_field_from_range(it, range, field_index, index); ECS_TERMSET_CLEAR(it->shared_fields, 1u << field_index); } else { ecs_record_t *r = flecs_entities_get(ctx->world, src); ecs_table_t *src_table = r->table; if (src_table->column_map) { ecs_assert(index < src_table->type.count, ECS_INTERNAL_ERROR, NULL); int32_t column = src_table->column_map[index]; if (column != -1) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); it->ptrs[field_index] = ecs_vec_get( &src_table->data.columns[column].data, it->sizes[field_index], ECS_RECORD_TO_ROW(r->row)); ECS_TERMSET_SET(it->shared_fields, 1u << field_index); } } } } bool flecs_query_populate( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; if (!redo) { ecs_iter_t *it = ctx->it; if (it->flags & EcsIterNoData) { return true; } const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; int8_t i, field_count = q->field_count; ecs_flags64_t data_fields = op->src.entity; /* Bitset with fields to set */ ecs_table_range_t *range = &ctx->vars[0].range; ecs_table_t *table = range->table; if (table && !range->count) { range->count = ecs_table_count(table); } /* Only populate fields that are set */ data_fields &= it->set_fields; for (i = 0; i < field_count; i ++) { if (!(data_fields & (1llu << i))) { continue; } flecs_query_populate_field(it, range, i, ctx); } return true; } else { return false; } } bool flecs_query_populate_self( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; if (!redo) { const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; int32_t i, field_count = q->field_count; ecs_flags64_t data_fields = op->src.entity; /* Bitset with fields to set */ ecs_iter_t *it = ctx->it; ecs_table_range_t *range = &ctx->vars[0].range; ecs_table_t *table = range->table; if (!table->column_map) { return true; } if (!ecs_table_count(table)) { return true; } /* Only populate fields that can be set */ data_fields &= it->set_fields; for (i = 0; i < field_count; i ++) { if (!(data_fields & (1llu << i))) { continue; } int32_t index = it->columns[i]; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[index]; if (column != -1) { it->ptrs[i] = ECS_ELEM( table->data.columns[column].data.array, it->sizes[i], range->offset); } } return true; } else { return false; } } bool flecs_query_populate_sparse( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; if (!redo) { ecs_iter_t *it = ctx->it; if (it->flags & EcsIterNoData) { return true; } ecs_world_t *world = ctx->world; const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; int8_t i, field_count = q->field_count; ecs_flags64_t data_fields = op->src.entity; /* Bitset with fields to set */ /* Only populate fields that are set */ data_fields &= it->set_fields; for (i = 0; i < field_count; i ++) { if (!(data_fields & (1llu << i))) { continue; } ecs_entity_t src = it->sources[i]; if (!src) { src = ctx->vars[0].entity; } ecs_id_record_t *idr = flecs_id_record_get(world, it->ids[i]); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); it->ptrs[i] = flecs_sparse_get_any(idr->sparse, 0, src); } return true; } else { return false; } } /** * @file query/engine/eval_pred.c * @brief Equality predicate evaluation. */ static const char* flecs_query_name_arg( const ecs_query_op_t *op, ecs_query_run_ctx_t *ctx) { int8_t term_index = op->term_index; const ecs_term_t *term = &ctx->query->pub.terms[term_index]; return term->second.name; } static bool flecs_query_compare_range( const ecs_table_range_t *l, const ecs_table_range_t *r) { if (l->table != r->table) { return false; } if (l->count) { int32_t l_end = l->offset + l->count; int32_t r_end = r->offset + r->count; if (r->offset < l->offset) { return false; } if (r_end > l_end) { return false; } } else { /* Entire table is matched */ } return true; } static bool flecs_query_pred_eq_w_range( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_table_range_t r) { if (redo) { return false; } uint64_t written = ctx->written[ctx->op_index]; ecs_var_id_t src_var = op->src.var; if (!(written & (1ull << src_var))) { /* left = unknown, right = known. Assign right-hand value to left */ ecs_var_id_t l = src_var; ctx->vars[l].range = r; if (r.count == 1) { ctx->vars[l].entity = ecs_vec_get_t(&r.table->data.entities, ecs_entity_t, r.offset)[0]; } return true; } else { ecs_table_range_t l = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!flecs_query_compare_range(&l, &r)) { return false; } ctx->vars[src_var].range.offset = r.offset; ctx->vars[src_var].range.count = r.count; return true; } } bool flecs_query_pred_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized eq operand"); ecs_table_range_t r = flecs_query_get_range( op, &op->second, EcsQuerySecond, ctx); return flecs_query_pred_eq_w_range(op, redo, ctx, r); } bool flecs_query_pred_eq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { const char *name = flecs_query_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return false; } ecs_table_range_t r = flecs_range_from_entity(e, ctx); return flecs_query_pred_eq_w_range(op, redo, ctx, r); } bool flecs_query_pred_neq_w_range( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_table_range_t r) { ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); ecs_var_id_t src_var = op->src.var; ecs_table_range_t l = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); /* If tables don't match, neq always returns once */ if (l.table != r.table) { return true && !redo; } int32_t l_offset; int32_t l_count; if (!redo) { /* Make sure we're working with the correct table count */ if (!l.count && l.table) { l.count = ecs_table_count(l.table); } l_offset = l.offset; l_count = l.count; /* Cache old value */ op_ctx->range = l; } else { l_offset = op_ctx->range.offset; l_count = op_ctx->range.count; } /* If the table matches, a Neq returns twice: once for the slice before the * excluded slice, once for the slice after the excluded slice. If the right * hand range starts & overlaps with the left hand range, there is only * one slice. */ ecs_var_t *var = &ctx->vars[src_var]; if (!redo && r.offset > l_offset) { int32_t end = r.offset; if (end > l_count) { end = l_count; } /* Return first slice */ var->range.table = l.table; var->range.offset = l_offset; var->range.count = end - l_offset; op_ctx->redo = false; return true; } else if (!op_ctx->redo) { int32_t l_end = op_ctx->range.offset + l_count; int32_t r_end = r.offset + r.count; if (l_end <= r_end) { /* If end of existing range falls inside the excluded range, there's * nothing more to return */ var->range = l; return false; } /* Return second slice */ var->range.table = l.table; var->range.offset = r_end; var->range.count = l_end - r_end; /* Flag so we know we're done the next redo */ op_ctx->redo = true; return true; } else { /* Restore previous value */ var->range = l; return false; } } static bool flecs_query_pred_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, bool is_neq) { ecs_query_eq_ctx_t *op_ctx = flecs_op_ctx(ctx, eq); uint64_t written = ctx->written[ctx->op_index]; ecs_assert(flecs_ref_is_written(op, &op->src, EcsQuerySrc, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized match operand"); (void)written; ecs_var_id_t src_var = op->src.var; const char *match = flecs_query_name_arg(op, ctx); ecs_table_range_t l; if (!redo) { l = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); if (!l.table) { return false; } if (!l.count) { l.count = ecs_table_count(l.table); } op_ctx->range = l; op_ctx->index = l.offset; op_ctx->name_col = flecs_ito(int16_t, ecs_table_get_type_index(ctx->world, l.table, ecs_pair(ecs_id(EcsIdentifier), EcsName))); if (op_ctx->name_col == -1) { return is_neq; } op_ctx->name_col = flecs_ito(int16_t, l.table->column_map[op_ctx->name_col]); ecs_assert(op_ctx->name_col != -1, ECS_INTERNAL_ERROR, NULL); } else { if (op_ctx->name_col == -1) { /* Table has no name */ return false; } l = op_ctx->range; } const EcsIdentifier *names = l.table->data.columns[op_ctx->name_col].data.array; int32_t count = l.offset + l.count, offset = -1; for (; op_ctx->index < count; op_ctx->index ++) { const char *name = names[op_ctx->index].value; bool result = strstr(name, match); if (is_neq) { result = !result; } if (!result) { if (offset != -1) { break; } } else { if (offset == -1) { offset = op_ctx->index; } } } if (offset == -1) { ctx->vars[src_var].range = op_ctx->range; return false; } ctx->vars[src_var].range.offset = offset; ctx->vars[src_var].range.count = (op_ctx->index - offset); return true; } bool flecs_query_pred_eq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_pred_match(op, redo, ctx, false); } bool flecs_query_pred_neq_match( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_pred_match(op, redo, ctx, true); } bool flecs_query_pred_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized neq operand"); ecs_table_range_t r = flecs_query_get_range( op, &op->second, EcsQuerySecond, ctx); return flecs_query_pred_neq_w_range(op, redo, ctx, r); } bool flecs_query_pred_neq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { const char *name = flecs_query_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return true && !redo; } ecs_table_range_t r = flecs_range_from_entity(e, ctx); return flecs_query_pred_neq_w_range(op, redo, ctx, r); } /** * @file query/engine/eval_toggle.c * @brief Bitset toggle evaluation. */ typedef struct { ecs_flags64_t mask; bool has_bitset; } flecs_query_row_mask_t; static flecs_query_row_mask_t flecs_query_get_row_mask( ecs_iter_t *it, ecs_table_t *table, int32_t block_index, ecs_flags64_t and_fields, ecs_flags64_t not_fields, ecs_query_toggle_ctx_t *op_ctx) { ecs_flags64_t mask = UINT64_MAX; int32_t i, field_count = it->field_count; ecs_flags64_t fields = and_fields | not_fields; bool has_bitset = false; for (i = 0; i < field_count; i ++) { uint64_t field_bit = 1llu << i; if (!(fields & field_bit)) { continue; } if (not_fields & field_bit) { it->set_fields &= (ecs_termset_t)~field_bit; } else if (and_fields & field_bit) { ecs_assert(it->set_fields & field_bit, ECS_INTERNAL_ERROR, NULL); } else { ecs_abort(ECS_INTERNAL_ERROR, NULL); } ecs_id_t id = it->ids[i]; ecs_bitset_t *bs = flecs_table_get_toggle(table, id); if (!bs) { if (not_fields & field_bit) { if (op_ctx->prev_set_fields & field_bit) { has_bitset = false; break; } } continue; } ecs_assert((64 * block_index) < bs->size, ECS_INTERNAL_ERROR, NULL); ecs_flags64_t block = bs->data[block_index]; if (not_fields & field_bit) { block = ~block; } mask &= block; has_bitset = true; } return (flecs_query_row_mask_t){ mask, has_bitset }; } static bool flecs_query_toggle_for_up( ecs_iter_t *it, ecs_flags64_t and_fields, ecs_flags64_t not_fields) { int32_t i, field_count = it->field_count; ecs_flags64_t fields = (and_fields | not_fields) & it->up_fields; for (i = 0; i < field_count; i ++) { uint64_t field_bit = 1llu << i; if (!(fields & field_bit)) { continue; } bool match = false; if ((it->set_fields & field_bit)) { ecs_entity_t src = it->sources[i]; ecs_assert(src != 0, ECS_INTERNAL_ERROR, NULL); match = ecs_is_enabled_id(it->world, src, it->ids[i]); } if (field_bit & not_fields) { match = !match; } if (!match) { return false; } } return true; } static bool flecs_query_toggle_cmp( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx, ecs_flags64_t and_fields, ecs_flags64_t not_fields) { ecs_iter_t *it = ctx->it; ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); ecs_table_t *table = range.table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if ((and_fields & op_ctx->prev_set_fields) != and_fields) { /* If not all fields matching and toggles are set, table can't match */ return false; } ecs_flags32_t up_fields = it->up_fields; if (!redo) { if (up_fields & (and_fields|not_fields)) { /* If there are toggle fields that were matched with query * traversal, evaluate those separately. */ if (!flecs_query_toggle_for_up(it, and_fields, not_fields)) { return false; } it->set_fields &= (ecs_termset_t)~(not_fields & up_fields); } } /* Shared fields are evaluated, can be ignored from now on */ // and_fields &= ~up_fields; not_fields &= ~up_fields; if (!(table->flags & EcsTableHasToggle)) { if (not_fields) { /* If any of the toggle fields with a not operator are for fields * that are set, without a bitset those fields can't match. */ return false; } else { /* If table doesn't have toggles but query matched toggleable * components, all entities match. */ if (!redo) { return true; } else { return false; } } } if (table && !range.count) { range.count = ecs_table_count(table); if (!range.count) { return false; } } int32_t i, j; int32_t first, last, block_index, cur; uint64_t block = 0; if (!redo) { op_ctx->range = range; cur = op_ctx->cur = range.offset; block_index = op_ctx->block_index = -1; first = range.offset; last = range.offset + range.count; } else { if (!op_ctx->has_bitset) { goto done; } last = op_ctx->range.offset + op_ctx->range.count; cur = op_ctx->cur; ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); if (cur == last) { goto done; } first = cur; block_index = op_ctx->block_index; block = op_ctx->block; } /* If end of last iteration is start of new block, compute new block */ int32_t new_block_index = cur / 64, row = first; if (new_block_index != block_index) { compute_block: block_index = op_ctx->block_index = new_block_index; flecs_query_row_mask_t row_mask = flecs_query_get_row_mask( it, table, block_index, and_fields, not_fields, op_ctx); /* If table doesn't have bitset columns, all columns match */ if (!(op_ctx->has_bitset = row_mask.has_bitset)) { if (!not_fields) { return true; } else { goto done; } } /* No enabled bits */ block = row_mask.mask; if (!block) { next_block: new_block_index ++; cur = new_block_index * 64; if (cur >= last) { /* No more rows */ goto done; } op_ctx->cur = cur; goto compute_block; } op_ctx->block = block; } /* Find first enabled bit (TODO: use faster bitmagic) */ int32_t first_bit = cur - (block_index * 64); int32_t last_bit = ECS_MIN(64, last - (block_index * 64)); ecs_assert(first_bit >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(first_bit < 64, ECS_INTERNAL_ERROR, NULL); ecs_assert(last_bit >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(last_bit <= 64, ECS_INTERNAL_ERROR, NULL); ecs_assert(last_bit >= first_bit, ECS_INTERNAL_ERROR, NULL); for (i = first_bit; i < last_bit; i ++) { uint64_t bit = (1ull << i); bool cond = 0 != (block & bit); if (cond) { /* Find last enabled bit */ for (j = i; j < last_bit; j ++) { bit = (1ull << j); cond = !(block & bit); if (cond) { break; } } row = i + (block_index * 64); cur = j + (block_index * 64); break; } } if (i == last_bit) { goto next_block; } ecs_assert(row >= first, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur <= last, ECS_INTERNAL_ERROR, NULL); ecs_assert(cur >= first, ECS_INTERNAL_ERROR, NULL); if (!(cur - row)) { goto done; } if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_var_narrow_range(op->src.var, table, row, cur - row, ctx); } op_ctx->cur = cur; return true; done: /* Restore range & set fields */ if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_var_narrow_range(op->src.var, table, op_ctx->range.offset, op_ctx->range.count, ctx); } it->set_fields = op_ctx->prev_set_fields; return false; } bool flecs_query_toggle( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); if (!redo) { op_ctx->prev_set_fields = it->set_fields; } ecs_flags64_t and_fields = op->first.entity; ecs_flags64_t not_fields = op->second.entity & op_ctx->prev_set_fields; return flecs_query_toggle_cmp( op, redo, ctx, and_fields, not_fields); } bool flecs_query_toggle_option( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { ecs_iter_t *it = ctx->it; ecs_query_toggle_ctx_t *op_ctx = flecs_op_ctx(ctx, toggle); if (!redo) { op_ctx->prev_set_fields = it->set_fields; op_ctx->optional_not = false; op_ctx->has_bitset = false; } repeat: {} ecs_flags64_t and_fields = 0, not_fields = 0; if (op_ctx->optional_not) { not_fields = op->first.entity & op_ctx->prev_set_fields; } else { and_fields = op->first.entity; } bool result = flecs_query_toggle_cmp( op, redo, ctx, and_fields, not_fields); if (!result) { if (!op_ctx->optional_not) { /* Run the not-branch of optional fields */ op_ctx->optional_not = true; it->set_fields = op_ctx->prev_set_fields; redo = false; goto repeat; } } return result; } /** * @file query/engine/eval_trav.c * @brief Transitive/reflexive relationship traversal. */ static bool flecs_query_trav_fixed_src_reflexive( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_table_range_t *range, ecs_entity_t trav, ecs_entity_t second) { ecs_table_t *table = range->table; ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = table->data.entities.array; int32_t count = range->count; if (!count) { count = ecs_table_count(table); } int32_t i = range->offset, end = i + count; for (; i < end; i ++) { if (entities[i] == second) { /* Even though table doesn't have the specific relationship * pair, the relationship is reflexive and the target entity * is stored in the table. */ break; } } if (i == end) { /* Table didn't contain target entity */ return false; } if (count > 1) { /* If the range contains more than one entity, set the range to * return only the entity matched by the reflexive property. */ ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[op->src.var]; ecs_table_range_t *var_range = &var->range; var_range->offset = i; var_range->count = 1; var->entity = entities[i]; } flecs_query_set_trav_match(op, -1, trav, second, ctx); return true; } static bool flecs_query_trav_unknown_src_reflexive( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_entity_t trav, ecs_entity_t second) { ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_id_t src_var = op->src.var; flecs_query_var_set_entity(op, src_var, second, ctx); flecs_query_var_get_table(src_var, ctx); ecs_table_t *table = ctx->vars[src_var].range.table; if (table) { if (flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { return false; } } flecs_query_set_trav_match(op, -1, trav, second, ctx); return true; } static bool flecs_query_trav_fixed_src_up_fixed_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; /* If everything's fixed, can only have a single result */ } ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; /* Check if table has transitive relationship by traversing upwards */ int32_t column = ecs_search_relation(ctx->world, table, 0, ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, NULL); if (column == -1) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); } else { return false; } } flecs_query_set_trav_match(op, column, trav, second, ctx); return true; } static bool flecs_query_trav_unknown_src_up_fixed_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { ecs_record_t *r_second = flecs_entities_get(ctx->world, second); bool traversable = r_second && r_second->row & EcsEntityIsTraversable; bool reflexive = op->match_flags & EcsTermReflexive; if (!traversable && !reflexive) { trav_ctx->cache.id = 0; /* If there's no record for the entity, it can't have a subtree so * forward operation to a regular select. */ return flecs_query_select(op, redo, ctx); } /* Entity is traversable, which means it could have a subtree */ flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->index = -1; if(flecs_query_trav_unknown_src_reflexive( op, ctx, trav, second)) { /* It's possible that we couldn't return the entity required for * reflexive matching, like when it's a prefab or disabled. */ return true; } } } else { if (!trav_ctx->cache.id) { /* No traversal cache, which means this is a regular select */ return flecs_query_select(op, redo, ctx); } } if (trav_ctx->index == -1) { redo = false; /* First result after handling reflexive relationship */ trav_ctx->index = 0; } /* Forward to select */ int32_t count = ecs_vec_count(&trav_ctx->cache.entities); ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); for (; trav_ctx->index < count; trav_ctx->index ++) { ecs_trav_elem_t *el = &elems[trav_ctx->index]; trav_ctx->and.idr = el->idr; /* prevents lookup by select */ if (flecs_query_select_w_id(op, redo, ctx, ecs_pair(trav, el->entity), (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { return true; } redo = false; } return false; } static bool flecs_query_trav_yield_reflexive_src( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_table_range_t *range, ecs_entity_t trav) { ecs_var_t *vars = ctx->vars; ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); int32_t offset = trav_ctx->offset, count = trav_ctx->count; bool src_is_var = op->flags & (EcsQueryIsVar << EcsQuerySrc); if (trav_ctx->index >= (offset + count)) { /* Restore previous offset, count */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; vars[src_var].range.offset = offset; vars[src_var].range.count = count; vars[src_var].entity = 0; } return false; } ecs_entity_t entity = ecs_vec_get_t( &range->table->data.entities, ecs_entity_t, trav_ctx->index)[0]; flecs_query_set_trav_match(op, -1, trav, entity, ctx); /* Hijack existing variable to return one result at a time */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; ecs_table_t *table = vars[src_var].range.table; ecs_assert(!table || table == ecs_get_table(ctx->world, entity), ECS_INTERNAL_ERROR, NULL); (void)table; vars[src_var].entity = entity; vars[src_var].range = flecs_range_from_entity(entity, ctx); } return true; } static bool flecs_query_trav_fixed_src_up_unknown_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { flecs_query_get_trav_up_cache(ctx, &trav_ctx->cache, trav, table); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->yield_reflexive = true; trav_ctx->index = range.offset; trav_ctx->offset = range.offset; trav_ctx->count = range.count ? range.count : ecs_table_count(table); } } else { trav_ctx->index ++; } if (trav_ctx->yield_reflexive) { if (flecs_query_trav_yield_reflexive_src(op, ctx, &range, trav)) { return true; } trav_ctx->yield_reflexive = false; trav_ctx->index = 0; } if (trav_ctx->index >= ecs_vec_count(&trav_ctx->cache.entities)) { return false; } ecs_trav_elem_t *el = ecs_vec_get_t( &trav_ctx->cache.entities, ecs_trav_elem_t, trav_ctx->index); flecs_query_set_trav_match(op, el->column, trav, el->entity, ctx); return true; } bool flecs_query_trav( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (!flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { /* This can't happen, src or second should have been resolved */ ecs_abort(ECS_INTERNAL_ERROR, "invalid instruction sequence: unconstrained traversal"); } else { return flecs_query_trav_unknown_src_up_fixed_second(op, redo, ctx); } } else { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { return flecs_query_trav_fixed_src_up_unknown_second(op, redo, ctx); } else { return flecs_query_trav_fixed_src_up_fixed_second(op, redo, ctx); } } } /** * @file query/engine/eval_union.c * @brief Union relationship evaluation. */ static bool flecs_query_union_with_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_entity_t rel, bool neq) { ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; ecs_table_range_t range; ecs_table_t *table; if (!redo) { range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); table = range.table; if (!range.count) { range.count = ecs_table_count(table); } op_ctx->range = range; op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); if (!op_ctx->idr) { return neq; } if (neq) { if (flecs_id_record_get_table(op_ctx->idr, table) != NULL) { /* If table has (R, Union) none match !(R, _) */ return false; } else { /* If table doesn't have (R, Union) all match !(R, _) */ return true; } } op_ctx->row = 0; } else { if (neq) { /* !(R, _) terms only can have a single result */ return false; } range = op_ctx->range; table = range.table; op_ctx->row ++; } next_row: if (op_ctx->row >= range.count) { /* Restore range */ if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_var_narrow_range(op->src.var, table, op_ctx->range.offset, op_ctx->range.count, ctx); } return false; } ecs_entity_t e = flecs_table_entities_array(range.table) [range.offset + op_ctx->row]; ecs_entity_t tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); if (!tgt) { op_ctx->row ++; goto next_row; } it->ids[field_index] = ecs_pair(rel, tgt); if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_var_narrow_range(op->src.var, table, range.offset + op_ctx->row, 1, ctx); } flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } static bool flecs_query_union_with_tgt( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_entity_t rel, ecs_entity_t tgt, bool neq) { ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; ecs_table_range_t range; ecs_table_t *table; if (!redo) { range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); table = range.table; if (!range.count) { range.count = ecs_table_count(table); } op_ctx->range = range; op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); if (!op_ctx->idr) { return false; } op_ctx->row = 0; } else { range = op_ctx->range; table = range.table; op_ctx->row ++; } next_row: if (op_ctx->row >= range.count) { /* Restore range */ if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_var_narrow_range(op->src.var, table, op_ctx->range.offset, op_ctx->range.count, ctx); } return false; } ecs_entity_t e = flecs_table_entities_array(range.table) [range.offset + op_ctx->row]; ecs_entity_t e_tgt = flecs_switch_get(op_ctx->idr->sparse, (uint32_t)e); bool match = e_tgt == tgt; if (neq) { match = !match; } if (!match) { op_ctx->row ++; goto next_row; } it->ids[field_index] = ecs_pair(rel, tgt); if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { flecs_query_var_narrow_range(op->src.var, table, range.offset + op_ctx->row, 1, ctx); } flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } bool flecs_query_union_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool neq) { ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t tgt = ecs_pair_second(ctx->world, id); if (tgt == EcsWildcard) { return flecs_query_union_with_wildcard(op, redo, ctx, rel, neq); } else { return flecs_query_union_with_tgt(op, redo, ctx, rel, tgt, neq); } } static bool flecs_query_union_select_tgt( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_entity_t rel, ecs_entity_t tgt) { ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; if (!redo) { op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); if (!op_ctx->idr) { return false; } op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, tgt); } else { op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); } if (!op_ctx->cur) { return false; } ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); flecs_query_var_set_range(op, op->src.var, range.table, range.offset, range.count, ctx); flecs_query_set_vars(op, it->ids[field_index], ctx); it->ids[field_index] = ecs_pair(rel, tgt); return true; } static bool flecs_query_union_select_wildcard( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_entity_t rel) { ecs_query_union_ctx_t *op_ctx = flecs_op_ctx(ctx, union_); ecs_iter_t *it = ctx->it; int8_t field_index = op->field_index; if (!redo) { op_ctx->idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); if (!op_ctx->idr) { return false; } op_ctx->tgt_iter = flecs_switch_targets(op_ctx->idr->sparse); op_ctx->tgt = 0; } next_tgt: if (!op_ctx->tgt) { if (!ecs_map_next(&op_ctx->tgt_iter)) { return false; } op_ctx->tgt = ecs_map_key(&op_ctx->tgt_iter); op_ctx->cur = 0; it->ids[field_index] = ecs_pair(rel, op_ctx->tgt); } if (!op_ctx->cur) { op_ctx->cur = flecs_switch_first(op_ctx->idr->sparse, op_ctx->tgt); } else { op_ctx->cur = flecs_switch_next(op_ctx->idr->sparse, (uint32_t)op_ctx->cur); } if (!op_ctx->cur) { op_ctx->tgt = 0; goto next_tgt; } ecs_table_range_t range = flecs_range_from_entity(op_ctx->cur, ctx); flecs_query_var_set_range(op, op->src.var, range.table, range.offset, range.count, ctx); flecs_query_set_vars(op, it->ids[field_index], ctx); return true; } bool flecs_query_union_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_id_t id = flecs_query_op_get_id(op, ctx); ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t tgt = ecs_pair_second(ctx->world, id); if (tgt == EcsWildcard) { return flecs_query_union_select_wildcard(op, redo, ctx, rel); } else { return flecs_query_union_select_tgt(op, redo, ctx, rel, tgt); } } bool flecs_query_union( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_union_with(op, redo, ctx, false); } else { return flecs_query_union_select(op, redo, ctx); } } bool flecs_query_union_neq( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (written & (1ull << op->src.var)) { return flecs_query_union_with(op, redo, ctx, true); } else { return false; } } static void flecs_query_union_set_shared( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_id_record_t *idr = op_ctx->idr_with; ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t rel = ECS_PAIR_FIRST(idr->id); idr = flecs_id_record_get(ctx->world, ecs_pair(rel, EcsUnion)); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr->sparse != NULL, ECS_INTERNAL_ERROR, NULL); int8_t field_index = op->field_index; ecs_iter_t *it = ctx->it; ecs_entity_t src = it->sources[field_index]; ecs_entity_t tgt = flecs_switch_get(idr->sparse, (uint32_t)src); it->ids[field_index] = ecs_pair(rel, tgt); } bool flecs_query_union_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { if (!redo) { if (!flecs_query_up_with(op, redo, ctx)) { return false; } flecs_query_union_set_shared(op, ctx); return true; } else { return false; } } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectUp, FlecsQueryUpSelectUnion); } } bool flecs_query_union_self_up( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { if (redo) { goto next_for_union; } next_for_self_up_with: if (!flecs_query_self_up_with(op, redo, ctx, false)) { return false; } int8_t field_index = op->field_index; ecs_iter_t *it = ctx->it; if (it->sources[field_index]) { flecs_query_union_set_shared(op, ctx); return true; } next_for_union: if (!flecs_query_union_with(op, redo, ctx, false)) { goto next_for_self_up_with; } return true; } else { return flecs_query_up_select(op, redo, ctx, FlecsQueryUpSelectSelfUp, FlecsQueryUpSelectUnion); } } /** * @file query/engine/eval_utils.c * @brief Query engine evaluation utilities. */ void flecs_query_set_iter_this( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx) { const ecs_var_t *var = &ctx->vars[0]; const ecs_table_range_t *range = &var->range; ecs_table_t *table = range->table; int32_t count = range->count; if (table) { if (!count) { count = ecs_table_count(table); } it->table = table; it->offset = range->offset; it->count = count; it->entities = ECS_ELEM_T( table->data.entities.array, ecs_entity_t, it->offset); } else if (count == 1) { it->count = 1; it->entities = &ctx->vars[0].entity; } } ecs_query_op_ctx_t* flecs_op_ctx_( const ecs_query_run_ctx_t *ctx) { return &ctx->op_ctx[ctx->op_index]; } #define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) void flecs_reset_source_set_flag( ecs_iter_t *it, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); } void flecs_set_source_set_flag( ecs_iter_t *it, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); ECS_TERMSET_SET(it->up_fields, 1u << field_index); } ecs_table_range_t flecs_range_from_entity( ecs_entity_t e, const ecs_query_run_ctx_t *ctx) { ecs_record_t *r = flecs_entities_get(ctx->world, e); if (!r) { return (ecs_table_range_t){ 0 }; } return (ecs_table_range_t){ .table = r->table, .offset = ECS_RECORD_TO_ROW(r->row), .count = 1 }; } ecs_table_range_t flecs_query_var_get_range( int32_t var_id, const ecs_query_run_ctx_t *ctx) { ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; ecs_table_t *table = var->range.table; if (table) { return var->range; } ecs_entity_t entity = var->entity; if (entity && entity != EcsWildcard) { var->range = flecs_range_from_entity(entity, ctx); return var->range; } return (ecs_table_range_t){ 0 }; } ecs_table_t* flecs_query_var_get_table( int32_t var_id, const ecs_query_run_ctx_t *ctx) { ecs_var_t *var = &ctx->vars[var_id]; ecs_table_t *table = var->range.table; if (table) { return table; } ecs_entity_t entity = var->entity; if (entity && entity != EcsWildcard) { var->range = flecs_range_from_entity(entity, ctx); return var->range.table; } return NULL; } ecs_table_t* flecs_query_get_table( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); if (flags & EcsQueryIsEntity) { return ecs_get_table(ctx->world, ref->entity); } else { return flecs_query_var_get_table(ref->var, ctx); } } ecs_table_range_t flecs_query_get_range( const ecs_query_op_t *op, const ecs_query_ref_t *ref, ecs_flags16_t ref_kind, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags = flecs_query_ref_flags(op->flags, ref_kind); if (flags & EcsQueryIsEntity) { ecs_assert(!(flags & EcsQueryIsVar), ECS_INTERNAL_ERROR, NULL); return flecs_range_from_entity(ref->entity, ctx); } else { ecs_var_t *var = &ctx->vars[ref->var]; if (var->range.table) { return ctx->vars[ref->var].range; } else if (var->entity) { return flecs_range_from_entity(var->entity, ctx); } } return (ecs_table_range_t){0}; } ecs_entity_t flecs_query_var_get_entity( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; ecs_entity_t entity = var->entity; if (entity) { return entity; } ecs_assert(var->range.count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = var->range.table; ecs_entity_t *entities = table->data.entities.array; var->entity = entities[var->range.offset]; return var->entity; } void flecs_query_var_reset( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { ctx->vars[var_id].entity = EcsWildcard; ctx->vars[var_id].range.table = NULL; } void flecs_query_var_set_range( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx) { (void)op; ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_query_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->entity = 0; var->range = (ecs_table_range_t){ .table = table, .offset = offset, .count = count }; } void flecs_query_var_narrow_range( ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx) { ecs_var_t *var = &ctx->vars[var_id]; var->entity = 0; var->range = (ecs_table_range_t){ .table = table, .offset = offset, .count = count }; ecs_assert(var_id < ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); if (ctx->query_vars[var_id].kind != EcsVarTable) { ecs_assert(count == 1, ECS_INTERNAL_ERROR, NULL); var->entity = flecs_table_entities_array(table)[offset]; } } void flecs_query_var_set_entity( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_entity_t entity, const ecs_query_run_ctx_t *ctx) { (void)op; ecs_assert(var_id < (ecs_var_id_t)ctx->query->var_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_query_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->range.table = NULL; var->entity = entity; } void flecs_query_set_vars( const ecs_query_op_t *op, ecs_id_t id, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); if (flags_1st & EcsQueryIsVar) { ecs_var_id_t var = op->first.var; if (op->written & (1ull << var)) { if (ECS_IS_PAIR(id)) { flecs_query_var_set_entity( op, var, ecs_get_alive(ctx->world, ECS_PAIR_FIRST(id)), ctx); } else { flecs_query_var_set_entity(op, var, id, ctx); } } } if (flags_2nd & EcsQueryIsVar) { ecs_var_id_t var = op->second.var; if (op->written & (1ull << var)) { flecs_query_var_set_entity( op, var, ecs_get_alive(ctx->world, ECS_PAIR_SECOND(id)), ctx); } } } ecs_table_range_t flecs_get_ref_range( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx) { if (flag & EcsQueryIsEntity) { return flecs_range_from_entity(ref->entity, ctx); } else if (flag & EcsQueryIsVar) { return flecs_query_var_get_range(ref->var, ctx); } return (ecs_table_range_t){0}; } ecs_entity_t flecs_get_ref_entity( const ecs_query_ref_t *ref, ecs_flags16_t flag, const ecs_query_run_ctx_t *ctx) { if (flag & EcsQueryIsEntity) { return ref->entity; } else if (flag & EcsQueryIsVar) { return flecs_query_var_get_entity(ref->var, ctx); } return 0; } ecs_id_t flecs_query_op_get_id_w_written( const ecs_query_op_t *op, uint64_t written, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t flags_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t flags_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_entity_t first = 0, second = 0; if (flags_1st) { if (flecs_ref_is_written(op, &op->first, EcsQueryFirst, written)) { first = flecs_get_ref_entity(&op->first, flags_1st, ctx); } else if (flags_1st & EcsQueryIsVar) { first = EcsWildcard; } } if (flags_2nd) { if (flecs_ref_is_written(op, &op->second, EcsQuerySecond, written)) { second = flecs_get_ref_entity(&op->second, flags_2nd, ctx); } else if (flags_2nd & EcsQueryIsVar) { second = EcsWildcard; } } if (flags_2nd & (EcsQueryIsVar | EcsQueryIsEntity)) { return ecs_pair(first, second); } else { return flecs_entities_get_alive(ctx->world, first); } } ecs_id_t flecs_query_op_get_id( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; return flecs_query_op_get_id_w_written(op, written, ctx); } int16_t flecs_query_next_column( ecs_table_t *table, ecs_id_t id, int32_t column) { if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { column = column + 1; } else { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); column = ecs_search_offset(NULL, table, column + 1, id, NULL); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); } return flecs_ito(int16_t, column); } void flecs_query_it_set_column( ecs_iter_t *it, int32_t field_index, int32_t column) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); it->columns[field_index] = column; } ecs_id_t flecs_query_it_set_id( ecs_iter_t *it, ecs_table_t *table, int32_t field_index, int32_t column) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); return it->ids[field_index] = table->type.array[column]; } void flecs_query_set_match( const ecs_query_op_t *op, ecs_table_t *table, int32_t column, const ecs_query_run_ctx_t *ctx) { ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); int32_t field_index = op->field_index; if (field_index == -1) { return; } ecs_iter_t *it = ctx->it; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(column < table->type.count, ECS_INTERNAL_ERROR, NULL); flecs_query_it_set_column(it, field_index, column); ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, column); flecs_query_set_vars(op, matched, ctx); } void flecs_query_set_trav_match( const ecs_query_op_t *op, int32_t column, ecs_entity_t trav, ecs_entity_t second, const ecs_query_run_ctx_t *ctx) { int32_t field_index = op->field_index; if (field_index == -1) { return; } ecs_iter_t *it = ctx->it; ecs_id_t matched = ecs_pair(trav, second); it->ids[op->field_index] = matched; if (column != -1) { flecs_query_it_set_column(it, op->field_index, column); } flecs_query_set_vars(op, matched, ctx); } bool flecs_query_table_filter( ecs_table_t *table, ecs_query_lbl_t other, ecs_flags32_t filter_mask) { uint32_t filter = flecs_ito(uint32_t, other); return (table->flags & filter_mask & filter) != 0; } /** * @file query/engine/trav_cache.c * @brief Cache that stores the result of graph traversal. */ static void flecs_query_build_down_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(trav, entity)); if (!idr) { return; } ecs_trav_elem_t *elem = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); elem->entity = entity; elem->idr = idr; ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } int32_t i, count = ecs_table_count(table); ecs_entity_t *entities = table->data.entities.array; for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->row & EcsEntityIsTraversable) { flecs_query_build_down_cache( world, a, ctx, cache, trav, entities[i]); } } } } } static void flecs_query_build_up_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table, const ecs_table_record_t *tr, int32_t root_column) { ecs_id_t *ids = table->type.array; int32_t i = tr->index, end = i + tr->count; bool is_root = root_column == -1; for (; i < end; i ++) { ecs_entity_t second = ecs_pair_second(world, ids[i]); if (is_root) { root_column = i; } ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); el->entity = second; el->column = root_column; el->idr = NULL; ecs_record_t *r = flecs_entities_get_any(world, second); if (r->table) { ecs_table_record_t *r_tr = flecs_id_record_get_table( cache->idr, r->table); if (!r_tr) { return; } flecs_query_build_up_cache(world, a, ctx, cache, trav, r->table, r_tr, root_column); } } } void flecs_query_trav_cache_fini( ecs_allocator_t *a, ecs_trav_cache_t *cache) { ecs_vec_fini_t(a, &cache->entities, ecs_trav_elem_t); } void flecs_query_get_trav_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_entity_t entity) { if (cache->id != ecs_pair(trav, entity) || cache->up) { ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); flecs_query_build_down_cache(world, a, ctx, cache, trav, entity); cache->id = ecs_pair(trav, entity); cache->up = false; } } void flecs_query_get_trav_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_id_record_t *idr = cache->idr; if (!idr || idr->id != ecs_pair(trav, EcsWildcard)) { idr = cache->idr = flecs_id_record_get(world, ecs_pair(trav, EcsWildcard)); if (!idr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } } ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); return; } ecs_id_t id = table->type.array[tr->index]; if (cache->id != id || !cache->up) { ecs_vec_reset_t(a, &cache->entities, ecs_trav_elem_t); flecs_query_build_up_cache(world, a, ctx, cache, trav, table, tr, -1); cache->id = id; cache->up = true; } } /** * @file query/engine/trav_down_cache.c * @brief Compile query term. */ static void flecs_trav_entity_down_isa( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_with, bool self, bool empty); static ecs_trav_down_t* flecs_trav_entity_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_trav, ecs_id_record_t *idr_with, bool self, bool empty); static ecs_trav_down_t* flecs_trav_down_ensure( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t entity) { ecs_trav_down_t **trav = ecs_map_ensure_ref( &cache->src, ecs_trav_down_t, entity); if (!trav[0]) { trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_down_t); ecs_vec_init_t(NULL, &trav[0]->elems, ecs_trav_down_elem_t, 0); } return trav[0]; } static ecs_trav_down_t* flecs_trav_table_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, const ecs_table_t *table, ecs_id_record_t *idr_with, bool self, bool empty) { ecs_assert(table->id != 0, ECS_INTERNAL_ERROR, NULL); if (!table->_->traversable_count) { return dst; } ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = ecs_vec_first(&table->data.entities); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t entity = entities[i]; ecs_record_t *record = flecs_entities_get(world, entity); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, entity)); if (!idr_trav) { continue; } flecs_trav_entity_down(world, a, cache, dst, trav, entity, idr_trav, idr_with, self, empty); } } return dst; } static void flecs_trav_entity_down_isa( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_with, bool self, bool empty) { if (trav == EcsIsA || !world->idr_isa_wildcard) { return; } ecs_id_record_t *idr_isa = flecs_id_record_get( world, ecs_pair(EcsIsA, entity)); if (!idr_isa) { return; } ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr_isa->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (!table->_->traversable_count) { continue; } ecs_entity_t *entities = ecs_vec_first(&table->data.entities); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *record = flecs_entities_get(world, e); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); if (idr_trav) { flecs_trav_entity_down(world, a, cache, dst, trav, e, idr_trav, idr_with, self, empty); } } } } } } static ecs_trav_down_t* flecs_trav_entity_down( ecs_world_t *world, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, ecs_trav_down_t *dst, ecs_entity_t trav, ecs_entity_t entity, ecs_id_record_t *idr_trav, ecs_id_record_t *idr_with, bool self, bool empty) { ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); flecs_trav_entity_down_isa( world, a, cache, dst, trav, entity, idr_with, self, empty); int32_t first = ecs_vec_count(&dst->elems); ecs_table_cache_iter_t it; bool result; if (empty) { result = flecs_table_cache_all_iter(&idr_trav->cache, &it); } else { result = flecs_table_cache_iter(&idr_trav->cache, &it); } if (result) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = tr->hdr.table; bool leaf = false; if (flecs_id_record_get_table(idr_with, table) != NULL) { if (self) { continue; } leaf = true; } /* If record is not the first instance of (trav, *), don't add it * to the cache. */ int32_t index = tr->index; if (index) { ecs_id_t id = table->type.array[index - 1]; if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == trav) { int32_t col = ecs_search_relation(world, table, 0, idr_with->id, trav, EcsUp, NULL, NULL, &tr); ecs_assert(col >= 0, ECS_INTERNAL_ERROR, NULL); if (col != index) { /* First relationship through which the id is * reachable is not the current one, so skip. */ continue; } } } ecs_trav_down_elem_t *elem = ecs_vec_append_t( a, &dst->elems, ecs_trav_down_elem_t); elem->table = table; elem->leaf = leaf; } } /* Breadth first walk */ int32_t t, last = ecs_vec_count(&dst->elems); for (t = first; t < last; t ++) { ecs_trav_down_elem_t *elem = ecs_vec_get_t( &dst->elems, ecs_trav_down_elem_t, t); if (!elem->leaf) { flecs_trav_table_down(world, a, cache, dst, trav, elem->table, idr_with, self, empty); } } return dst; } ecs_trav_down_t* flecs_query_get_down_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_entity_t trav, ecs_entity_t e, ecs_id_record_t *idr_with, bool self, bool empty) { ecs_world_t *world = ctx->it->real_world; ecs_assert(cache->dir != EcsTravUp, ECS_INTERNAL_ERROR, NULL); cache->dir = EcsTravDown; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_map_init_if(&cache->src, a); ecs_trav_down_t *result = flecs_trav_down_ensure(ctx, cache, e); if (result->ready) { return result; } ecs_id_record_t *idr_trav = flecs_id_record_get(world, ecs_pair(trav, e)); if (!idr_trav) { if (trav != EcsIsA) { flecs_trav_entity_down_isa( world, a, cache, result, trav, e, idr_with, self, empty); } result->ready = true; return result; } ecs_vec_init_t(a, &result->elems, ecs_trav_down_elem_t, 0); flecs_trav_entity_down( world, a, cache, result, trav, e, idr_trav, idr_with, self, empty); result->ready = true; return result; } void flecs_query_down_cache_fini( ecs_allocator_t *a, ecs_trav_up_cache_t *cache) { ecs_map_iter_t it = ecs_map_iter(&cache->src); while (ecs_map_next(&it)) { ecs_trav_down_t *t = ecs_map_ptr(&it); ecs_vec_fini_t(a, &t->elems, ecs_trav_down_elem_t); } ecs_map_fini(&cache->src); } /** * @file query/engine/trav_up_cache.c * @brief Compile query term. */ static ecs_trav_up_t* flecs_trav_up_ensure( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, uint64_t table_id) { ecs_trav_up_t **trav = ecs_map_ensure_ref( &cache->src, ecs_trav_up_t, table_id); if (!trav[0]) { trav[0] = flecs_iter_calloc_t(ctx->it, ecs_trav_up_t); } return trav[0]; } static int32_t flecs_trav_type_search( ecs_trav_up_t *up, const ecs_table_t *table, ecs_id_record_t *idr_with, ecs_type_t *type) { ecs_table_record_t *tr = ecs_table_cache_get(&idr_with->cache, table); if (tr) { up->id = type->array[tr->index]; return up->column = tr->index; } return -1; } static int32_t flecs_trav_type_offset_search( ecs_trav_up_t *up, int32_t offset, ecs_id_t with, ecs_type_t *type) { ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); while (offset < type->count) { ecs_id_t type_id = type->array[offset ++]; if (ecs_id_match(type_id, with)) { up->id = type_id; return up->column = offset - 1; } } return -1; } static ecs_trav_up_t* flecs_trav_table_up( const ecs_query_run_ctx_t *ctx, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, const ecs_world_t *world, ecs_entity_t src, ecs_id_t with, ecs_id_t rel, ecs_id_record_t *idr_with, ecs_id_record_t *idr_trav) { ecs_trav_up_t *up = flecs_trav_up_ensure(ctx, cache, src); if (up->ready) { return up; } ecs_record_t *src_record = flecs_entities_get_any(world, src); ecs_table_t *table = src_record->table; if (!table) { goto not_found; } ecs_type_t type = table->type; if (flecs_trav_type_search(up, table, idr_with, &type) >= 0) { up->src = src; goto found; } ecs_flags32_t flags = table->flags; if ((flags & EcsTableHasPairs) && rel) { bool is_a = idr_trav == world->idr_isa_wildcard; if (is_a) { if (!(flags & EcsTableHasIsA)) { goto not_found; } if (!flecs_type_can_inherit_id(world, table, idr_with, with)) { goto not_found; } } ecs_trav_up_t up_pair = {0}; int32_t r_column = flecs_trav_type_search( &up_pair, table, idr_trav, &type); while (r_column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, tgt, with, rel, idr_with, idr_trav); if (up_parent->column != -1) { up->src = up_parent->src; up->column = up_parent->column; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, r_column + 1, rel, &type); } if (!is_a) { idr_trav = world->idr_isa_wildcard; r_column = flecs_trav_type_search( &up_pair, table, idr_trav, &type); while (r_column != -1) { ecs_entity_t tgt = ECS_PAIR_SECOND(up_pair.id); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_trav_up_t *up_parent = flecs_trav_table_up(ctx, a, cache, world, tgt, with, rel, idr_with, idr_trav); if (up_parent->column != -1) { up->src = up_parent->src; up->column = up_parent->column; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, r_column + 1, rel, &type); } } } not_found: up->column = -1; found: up->ready = true; return up; } ecs_trav_up_t* flecs_query_get_up_cache( const ecs_query_run_ctx_t *ctx, ecs_trav_up_cache_t *cache, ecs_table_t *table, ecs_id_t with, ecs_entity_t trav, ecs_id_record_t *idr_with, ecs_id_record_t *idr_trav) { if (cache->with && cache->with != with) { flecs_query_up_cache_fini(cache); } ecs_world_t *world = ctx->it->real_world; ecs_allocator_t *a = flecs_query_get_allocator(ctx->it); ecs_map_init_if(&cache->src, a); ecs_assert(cache->dir != EcsTravDown, ECS_INTERNAL_ERROR, NULL); cache->dir = EcsTravUp; cache->with = with; ecs_assert(idr_with != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(idr_trav != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ecs_table_cache_get(&idr_trav->cache, table); if (!tr) { return NULL; /* Table doesn't have the relationship */ } int32_t i = tr->index, end = i + tr->count; for (; i < end; i ++) { ecs_id_t id = table->type.array[i]; ecs_entity_t tgt = ECS_PAIR_SECOND(id); ecs_trav_up_t *result = flecs_trav_table_up(ctx, a, cache, world, tgt, with, ecs_pair(trav, EcsWildcard), idr_with, idr_trav); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); if (result->src != 0) { return result; } } return NULL; } void flecs_query_up_cache_fini( ecs_trav_up_cache_t *cache) { ecs_map_fini(&cache->src); } /** * @file query/engine/trivial_iter.c * @brief Iterator for trivial queries. */ static bool flecs_query_trivial_search_init( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, const ecs_query_t *query, bool redo, ecs_flags64_t term_set) { if (!redo) { /* Find first trivial term*/ int32_t t; for (t = 0; t < query->term_count; t ++) { if (term_set & (1llu << t)) { break; } } ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); const ecs_term_t *term = &query->terms[t]; ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); if (!idr) { return false; } if (query->flags & EcsQueryMatchEmptyTables) { if (!flecs_table_cache_all_iter(&idr->cache, &op_ctx->it)){ return false; } } else { if (!flecs_table_cache_iter(&idr->cache, &op_ctx->it)) { return false; } } /* Find next term to evaluate once */ for (; t < query->term_count; t ++) { if (term_set & (1llu << t)) { break; } } op_ctx->first_to_eval = t; } return true; } bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t term_set) { const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; int32_t t, term_count = query->pub.term_count; if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { return false; } do { const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { continue; } int32_t first_term = op_ctx->first_to_eval; for (t = first_term; t < term_count; t ++) { ecs_flags64_t term_bit = (1llu << t); if (!(term_set & term_bit)) { continue; } const ecs_term_t *term = &terms[t]; ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); if (!idr) { return false; } const ecs_table_record_t *tr_with = flecs_id_record_get_table( idr, table); if (!tr_with) { break; } it->columns[term->field_index] = tr_with->index; if (!(term->flags_ & EcsTermNoData)) { if (tr_with->column != -1) { it->ptrs[term->field_index] = ecs_vec_first( &table->data.columns[tr_with->column].data); } } } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; it->columns[first_term] = tr->index; if (!(terms[first_term].flags_ & EcsTermNoData)) { if (tr->column != -1) { it->ptrs[first_term] = ecs_vec_first( &table->data.columns[tr->column].data); } } break; } } while (true); return true; } bool flecs_query_trivial_search_w_wildcards( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t term_set) { bool result = flecs_query_trivial_search( ctx, op_ctx, redo, term_set); if (result) { ecs_iter_t *it = ctx->it; ecs_table_t *table = ctx->vars[0].range.table; const ecs_query_impl_t *query = ctx->query; int32_t t, term_count = query->pub.term_count; for (t = 0; t < term_count; t ++) { if (term_set & (1llu << t)) { int32_t column = it->columns[t]; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); it->ids[t] = table->type.array[column]; } } } return result; } bool flecs_query_trivial_search_nodata( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t term_set) { const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; int32_t t, term_count = query->pub.term_count; if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, term_set)) { return false; } do { const ecs_table_record_t *tr = flecs_table_cache_next( &op_ctx->it, ecs_table_record_t); if (!tr) { return false; } ecs_table_t *table = tr->hdr.table; if (table->flags & (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)) { continue; } for (t = op_ctx->first_to_eval; t < term_count; t ++) { if (!(term_set & (1llu << t))) { continue; } const ecs_term_t *term = &terms[t]; ecs_id_record_t *idr = flecs_id_record_get(ctx->world, term->id); if (!idr) { break; } const ecs_table_record_t *tr_with = flecs_id_record_get_table( idr, table); if (!tr_with) { break; } it->columns[term->field_index] = tr_with->index; } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; it->columns[0] = tr->index; break; } } while (true); return true; } bool flecs_query_trivial_test( const ecs_query_run_ctx_t *ctx, bool redo, ecs_flags64_t term_set) { if (redo) { return false; } else { const ecs_query_impl_t *impl = ctx->query; const ecs_query_t *q = &impl->pub; const ecs_term_t *terms = q->terms; ecs_iter_t *it = ctx->it; int32_t t, term_count = impl->pub.term_count; ecs_table_t *table = it->table; ecs_assert(table != NULL, ECS_INVALID_OPERATION, "the variable set on the iterator is missing a table"); for (t = 0; t < term_count; t ++) { if (!(term_set & (1llu << t))) { continue; } const ecs_term_t *term = &terms[t]; ecs_id_record_t *idr = flecs_id_record_get(q->world, term->id); if (!idr) { return false; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return false; } it->columns[term->field_index] = tr->index; if (it->count && tr->column != -1) { it->ptrs[term->field_index] = ecs_vec_get( &table->data.columns[tr->column].data, it->sizes[term->field_index], it->offset); } } it->entities = flecs_table_entities_array(table); if (it->entities) { it->entities = &it->entities[it->offset]; } return true; } } bool flecs_query_trivial_test_w_wildcards( const ecs_query_run_ctx_t *ctx, bool redo, ecs_flags64_t term_set) { const ecs_query_impl_t *query = ctx->query; int32_t t, term_count = query->pub.term_count; bool result = flecs_query_trivial_test(ctx, redo, term_set); if (result) { ecs_iter_t *it = ctx->it; ecs_table_t *table = ctx->vars[0].range.table; for (t = 0; t < term_count; t ++) { if (term_set & (1llu << t)) { int32_t column = it->columns[t]; ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); it->ids[t] = table->type.array[column]; } } } return result; }